@tileflow/core 0.1.0-alpha.13 → 0.1.0-alpha.16

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
@@ -115,7 +115,11 @@ var themePresets = {
115
115
  var defaultTypography = {
116
116
  font: "Noto Sans",
117
117
  fontFamily: "Noto Sans",
118
- weight: "regular"
118
+ weight: "regular",
119
+ places: { font: "Noto Sans", fontFamily: "Noto Sans", weight: "regular" },
120
+ roads: { font: "Noto Sans", fontFamily: "Noto Sans", weight: "regular" },
121
+ water: { font: "Noto Sans", fontFamily: "Noto Sans", weight: "regular" },
122
+ poi: { font: "Noto Sans", fontFamily: "Noto Sans", weight: "regular" }
119
123
  };
120
124
  var fontWeightLabels = {
121
125
  regular: "Regular",
@@ -219,6 +223,10 @@ function resolveColors(overrides = {}, defaults = defaultColors, moduleOverrides
219
223
  const buildingDefaults = {
220
224
  extrusion: mix(base.building, base.text, 0.08),
221
225
  fill: base.building,
226
+ highRise: base.building,
227
+ highRiseOutline: mix(base.building, base.text, 0.2),
228
+ lowRise: mix(base.building, base.land, 0.55),
229
+ lowRiseOutline: mix(base.building, base.land, 0.2),
222
230
  outline: mix(base.building, base.text, 0.2)
223
231
  };
224
232
  const boundaryDefaults = {
@@ -250,15 +258,35 @@ function resolveColors(overrides = {}, defaults = defaultColors, moduleOverrides
250
258
  };
251
259
  }
252
260
  function resolveTypography(overrides = {}, defaults = defaultTypography) {
253
- const font = overrides.font ?? overrides.fontFamily ?? defaults.font;
261
+ const base = resolveTypographyStyle(overrides, defaults);
262
+ return {
263
+ ...base,
264
+ places: resolveTypographyDomain(overrides.places, overrides, defaults.places, base),
265
+ roads: resolveTypographyDomain(overrides.roads, overrides, defaults.roads, base),
266
+ water: resolveTypographyDomain(overrides.water, overrides, defaults.water, base),
267
+ poi: resolveTypographyDomain(overrides.poi, overrides, defaults.poi, base)
268
+ };
269
+ }
270
+ function textFont(typography, domain) {
271
+ const style = domain ? typography[domain] : typography;
272
+ return [`${style.font} ${fontWeightLabels[style.weight]}`];
273
+ }
274
+ function resolveTypographyStyle(overrides, defaults) {
275
+ const font = overrides?.font ?? overrides?.fontFamily ?? defaults.font;
254
276
  return {
255
277
  font,
256
278
  fontFamily: font,
257
- weight: overrides.weight ?? defaults.weight
279
+ weight: overrides?.weight ?? defaults.weight
258
280
  };
259
281
  }
260
- function textFont(typography) {
261
- return [`${typography.font} ${fontWeightLabels[typography.weight]}`];
282
+ function resolveTypographyDomain(domainOverrides, globalOverrides, defaults, resolvedGlobal) {
283
+ const globalFontOverridden = Boolean(globalOverrides.font || globalOverrides.fontFamily);
284
+ const font = domainOverrides?.font ?? domainOverrides?.fontFamily ?? (globalFontOverridden ? resolvedGlobal.font : defaults.font);
285
+ return {
286
+ font,
287
+ fontFamily: font,
288
+ weight: domainOverrides?.weight ?? globalOverrides.weight ?? defaults.weight
289
+ };
262
290
  }
263
291
  function resolveLegacyTheme(theme) {
264
292
  const preset = themePresets[theme];
@@ -342,13 +370,17 @@ function isLegacyTheme(value) {
342
370
  function resolveThemeTypography(theme) {
343
371
  const font = theme.typography?.font ?? theme.typography?.fontFamily ?? theme.fonts?.labels ?? theme.fonts?.body;
344
372
  return {
373
+ ...theme.typography?.places ? { places: theme.typography.places } : {},
374
+ ...theme.typography?.poi ? { poi: theme.typography.poi } : {},
375
+ ...theme.typography?.roads ? { roads: theme.typography.roads } : {},
376
+ ...theme.typography?.water ? { water: theme.typography.water } : {},
345
377
  ...theme.typography?.weight ? { weight: theme.typography.weight } : {},
346
378
  ...font ? { font } : {}
347
379
  };
348
380
  }
349
381
  function hasTypographyConfig(theme) {
350
382
  return Boolean(
351
- theme.typography?.font || theme.typography?.fontFamily || theme.typography?.weight || theme.fonts?.labels || theme.fonts?.body
383
+ theme.typography?.font || theme.typography?.fontFamily || theme.typography?.weight || theme.typography?.places || theme.typography?.poi || theme.typography?.roads || theme.typography?.water || theme.fonts?.labels || theme.fonts?.body
352
384
  );
353
385
  }
354
386
  function normalizeColorAliases(colors) {
@@ -512,858 +544,1070 @@ function rgbToHex(r, g, b) {
512
544
  return `#${[r, g, b].map((value) => value.toString(16).padStart(2, "0")).join("")}`;
513
545
  }
514
546
 
515
- // src/modules/labels/index.ts
516
- var moduleLabelDefaults = {
517
- language: "auto",
518
- places: "major",
519
- roads: "major",
520
- water: "major"
547
+ // src/modules/roads/index.ts
548
+ var moduleRoadDefaults = {
549
+ detail: "streets",
550
+ extras: { ferry: false, paths: false, rail: false },
551
+ hierarchy: "clear",
552
+ oneWayMarkers: false,
553
+ outline: "subtle",
554
+ weight: "regular"
555
+ };
556
+ var defaultRoadWidthScale = {
557
+ motorway: 1,
558
+ trunk: 1,
559
+ primary: 1,
560
+ secondary: 1,
561
+ tertiary: 1,
562
+ minor: 1,
563
+ service: 1,
564
+ track: 1,
565
+ path: 1
521
566
  };
522
- var majorPlaceClasses = ["continent", "country", "state", "city", "town"];
523
567
  var highwayRoadClasses = ["motorway", "trunk"];
524
568
  var arterialRoadClasses = ["primary", "secondary", "tertiary"];
525
569
  var majorRoadClasses = [...highwayRoadClasses, ...arterialRoadClasses];
526
- var majorRoadClassSet = new Set(majorRoadClasses);
527
570
  var streetRoadClasses = ["minor"];
528
571
  var serviceRoadClasses = ["service", "track"];
529
- var majorWaterClasses = ["ocean", "sea", "lake"];
530
- var majorWaterwayClasses = ["river"];
531
- function labels(options = {}) {
572
+ var majorRoadClassSet = new Set(majorRoadClasses);
573
+ function roadClassesForDetail(detail) {
574
+ if (detail === "none") return [];
575
+ return [
576
+ ...detail === "highways" ? highwayRoadClasses : majorRoadClasses,
577
+ ...detail === "streets" || detail === "all" ? streetRoadClasses : [],
578
+ ...detail === "all" ? serviceRoadClasses : []
579
+ ];
580
+ }
581
+ function isMajorRoadClass(roadClass) {
582
+ return majorRoadClassSet.has(roadClass);
583
+ }
584
+ function roads(options = {}) {
532
585
  return {
533
- type: "labels",
534
- ...options.language ? { language: options.language } : {},
535
- ...options.places ? { places: options.places } : {},
536
- ...options.roads ? { roads: options.roads } : {},
537
- ...options.water ? { water: options.water } : {}
586
+ type: "roads",
587
+ ...options.detail ? { detail: options.detail } : {},
588
+ ...options.extras ? { extras: { ...options.extras } } : {},
589
+ ...options.hierarchy ? { hierarchy: options.hierarchy } : {},
590
+ ...options.oneWayMarkers !== void 0 ? { oneWayMarkers: options.oneWayMarkers } : {},
591
+ ...options.outline ? { outline: options.outline } : {},
592
+ ...options.weight ? { weight: options.weight } : {},
593
+ ...options.widthScale ? { widthScale: { ...options.widthScale } } : {}
538
594
  };
539
595
  }
540
- function resolveLabels(legacyLabels, moduleConfig, fallback) {
541
- const fallbackMode = legacyLabels ?? fallback;
542
- const defaults = moduleConfig ? moduleLabelDefaults : labelDefaultsFromLegacyMode(fallbackMode);
596
+ function resolveRoads(legacyRoads, moduleConfig, fallback) {
597
+ const fallbackMode = legacyRoads ?? fallback;
598
+ const defaults = moduleConfig ? moduleRoadDefaults : roadDefaultsFromLegacyMode(fallbackMode);
543
599
  const resolved = {
544
- language: moduleConfig?.language ?? defaults.language,
545
- places: moduleConfig?.places ?? defaults.places,
546
- roads: moduleConfig?.roads ?? defaults.roads,
547
- water: moduleConfig?.water ?? defaults.water
600
+ detail: moduleConfig?.detail ?? defaults.detail,
601
+ extras: {
602
+ ferry: moduleConfig?.extras?.ferry ?? defaults.extras.ferry,
603
+ paths: moduleConfig?.extras?.paths ?? defaults.extras.paths,
604
+ rail: moduleConfig?.extras?.rail ?? defaults.extras.rail
605
+ },
606
+ hierarchy: moduleConfig?.hierarchy ?? defaults.hierarchy,
607
+ oneWayMarkers: moduleConfig?.oneWayMarkers ?? defaults.oneWayMarkers,
608
+ outline: moduleConfig?.outline ?? defaults.outline,
609
+ weight: moduleConfig?.weight ?? defaults.weight,
610
+ ...moduleConfig?.widthScale ? { widthScale: { ...defaultRoadWidthScale, ...moduleConfig.widthScale } } : {}
548
611
  };
549
612
  return {
550
613
  ...resolved,
551
- mode: moduleConfig ? legacyModeFromResolvedLabels(resolved) : fallbackMode
614
+ mode: legacyModeFromResolvedRoads(resolved)
552
615
  };
553
616
  }
554
- function labelLayers(source, sourceLayers, colors, labelsConfig, roadsConfig, typography) {
555
- return [
556
- ...placeLabelLayers(source, sourceLayers.place, colors, labelsConfig, typography),
557
- ...roadLabelLayers(
558
- source,
559
- sourceLayers.roadName,
560
- colors,
561
- labelsConfig,
562
- roadsConfig,
563
- typography
564
- ),
565
- ...waterLabelLayers(
566
- source,
567
- sourceLayers.waterName,
568
- sourceLayers.waterway,
569
- colors,
570
- labelsConfig,
571
- typography
572
- )
573
- ];
617
+ function roadStyleMetrics(options) {
618
+ const hierarchy = {
619
+ subtle: {
620
+ majorOpacity: 0.92,
621
+ majorWidthScale: 1,
622
+ minorOpacity: 0.82,
623
+ minorWidthScale: 0.94
624
+ },
625
+ clear: {
626
+ majorOpacity: 0.92,
627
+ majorWidthScale: 1,
628
+ minorOpacity: 0.68,
629
+ minorWidthScale: 0.84
630
+ },
631
+ strong: {
632
+ majorOpacity: 0.92,
633
+ majorWidthScale: 1,
634
+ minorOpacity: 0.52,
635
+ minorWidthScale: 0.7
636
+ }
637
+ }[options.hierarchy];
638
+ const weightScale = {
639
+ thin: 0.78,
640
+ regular: 1,
641
+ bold: 1.24
642
+ }[options.weight];
643
+ const outlineOpacity = {
644
+ none: 0,
645
+ subtle: 0.18,
646
+ strong: 0.34
647
+ }[options.outline];
648
+ return {
649
+ ...hierarchy,
650
+ outlineOpacity,
651
+ weightScale
652
+ };
574
653
  }
575
- function placeLabelLayers(source, sourceLayer, colors, labelsConfig, typography) {
576
- if (labelsConfig.places === "none") return [];
577
- return [
578
- {
579
- id: "place-labels",
580
- type: "symbol",
654
+ function roadLayers(source, sourceLayer, colors, options, typography) {
655
+ if (options.detail === "none") {
656
+ return [];
657
+ }
658
+ const metrics = roadStyleMetrics(options);
659
+ const roadClasses = roadClassesForDetail(options.detail);
660
+ const selectedMajorRoadClasses = roadClasses.filter(isMajorRoadClass);
661
+ const showsStreetRoads = roadClasses.includes("minor");
662
+ const showsServiceRoads = roadClasses.includes("service");
663
+ const transportClasses = [
664
+ ...roadClasses,
665
+ ...options.extras.paths ? ["path"] : [],
666
+ ...options.extras.rail ? ["rail", "transit"] : [],
667
+ ...options.extras.ferry ? ["ferry"] : []
668
+ ];
669
+ const roadClassColor = roadClassColorExpression(colors);
670
+ const nonTunnelRoadFilter = ["!=", ["get", "brunnel"], "tunnel"];
671
+ const majorFilter = classFilter(selectedMajorRoadClasses);
672
+ const streetFilter = classFilter(streetRoadClasses);
673
+ const serviceFilter = classFilter(serviceRoadClasses);
674
+ const roadFilter = classFilter(roadClasses);
675
+ const transportFilter = classFilter(transportClasses);
676
+ const layers = [];
677
+ if (options.outline !== "none") {
678
+ layers.push({
679
+ id: "roads-tunnels-casing",
680
+ type: "line",
581
681
  source,
582
682
  "source-layer": sourceLayer,
583
- minzoom: 0,
584
- filter: labelsConfig.places === "major" ? ["all", ["has", "name"], classFilter(majorPlaceClasses)] : ["has", "name"],
585
- layout: {
586
- "text-field": labelTextField(labelsConfig.language),
587
- "text-font": textFont(typography),
588
- "text-size": ["interpolate", ["linear"], ["zoom"], 3, 10, 8, 13, 14, 17],
589
- "text-letter-spacing": 0,
590
- "text-padding": 4
591
- },
683
+ minzoom: 10,
684
+ filter: ["all", ["==", ["get", "brunnel"], "tunnel"], transportFilter],
592
685
  paint: {
593
- "text-color": colors.labels.primary,
594
- "text-halo-color": colors.labels.halo,
595
- "text-halo-width": 1.2
686
+ "line-color": colors.roads.tunnel,
687
+ "line-dasharray": [0.6, 0.8],
688
+ "line-opacity": roadOpacityExpression(
689
+ metrics.outlineOpacity * 0.85,
690
+ metrics.outlineOpacity * 0.85
691
+ ),
692
+ "line-width": roadWidthExpression(
693
+ [
694
+ [10, 0.8],
695
+ [14, 2.2],
696
+ [16, 4.8]
697
+ ],
698
+ metrics,
699
+ options.widthScale
700
+ )
596
701
  }
702
+ });
703
+ }
704
+ layers.push({
705
+ id: "roads-tunnels",
706
+ type: "line",
707
+ source,
708
+ "source-layer": sourceLayer,
709
+ minzoom: 10,
710
+ filter: ["all", ["==", ["get", "brunnel"], "tunnel"], transportFilter],
711
+ paint: {
712
+ "line-color": roadClassColor,
713
+ "line-dasharray": [0.6, 0.8],
714
+ "line-opacity": roadOpacityExpression(
715
+ metrics.majorOpacity * 0.28,
716
+ metrics.minorOpacity * 0.28
717
+ ),
718
+ "line-width": roadWidthExpression(
719
+ [
720
+ [10, 0.25],
721
+ [14, 1],
722
+ [16, 2.4]
723
+ ],
724
+ metrics,
725
+ options.widthScale
726
+ )
597
727
  }
598
- ];
599
- }
600
- function roadLabelLayers(source, sourceLayer, colors, labelsConfig, roadsConfig, typography) {
601
- const classes = visibleRoadLabelClasses(labelsConfig.roads, roadsConfig);
602
- const majorClasses = classes.filter((roadClass) => majorRoadClassSet.has(roadClass));
603
- const minorClasses = classes.filter((roadClass) => !majorRoadClassSet.has(roadClass));
604
- if (classes.length === 0) return [];
605
- const layers = [];
606
- const sharedLayout = {
607
- "symbol-placement": "line",
608
- "text-field": labelTextField(labelsConfig.language),
609
- "text-font": textFont(typography),
610
- "text-rotation-alignment": "map",
611
- "text-size": ["interpolate", ["linear"], ["zoom"], 11, 11, 14, 13, 17, 15]
612
- };
613
- const sharedPaint = {
614
- "text-color": colors.labels.road,
615
- "text-halo-color": colors.labels.halo,
616
- "text-halo-width": 1
617
- };
618
- if (majorClasses.length > 0) {
619
- const namedMajorClasses = majorClasses.filter((roadClass) => roadClass !== "motorway");
728
+ });
729
+ if (options.outline !== "none") {
620
730
  layers.push({
621
- id: "road-shields",
622
- type: "symbol",
731
+ id: "roads-casing",
732
+ type: "line",
623
733
  source,
624
734
  "source-layer": sourceLayer,
625
- minzoom: 8,
626
- filter: ["all", ["has", "ref"], classFilter(majorClasses)],
627
- layout: {
628
- "symbol-placement": "line",
629
- "symbol-spacing": 240,
630
- "text-field": ["get", "ref"],
631
- "text-font": textFont(typography),
632
- "text-rotation-alignment": "viewport",
633
- "text-size": 10
634
- },
635
- paint: sharedPaint
735
+ minzoom: 5,
736
+ filter: ["all", nonTunnelRoadFilter, roadFilter],
737
+ paint: {
738
+ "line-color": colors.roads.casing,
739
+ "line-opacity": roadOpacityExpression(metrics.outlineOpacity, metrics.outlineOpacity),
740
+ "line-width": roadWidthExpression(
741
+ [
742
+ [6, 0.7],
743
+ [12, 1.8],
744
+ [16, 5.4]
745
+ ],
746
+ metrics,
747
+ options.widthScale
748
+ )
749
+ }
636
750
  });
637
- if (namedMajorClasses.length > 0) {
638
- layers.unshift({
639
- id: "road-labels-major",
640
- type: "symbol",
641
- source,
642
- "source-layer": sourceLayer,
643
- minzoom: 10,
644
- filter: ["all", ["has", "name"], classFilter(namedMajorClasses)],
645
- layout: sharedLayout,
646
- paint: sharedPaint
647
- });
648
- }
649
751
  }
650
- if (minorClasses.length > 0) {
752
+ if (options.extras.ferry) {
651
753
  layers.push({
652
- id: "road-labels-minor",
653
- type: "symbol",
754
+ id: "roads-ferry",
755
+ type: "line",
654
756
  source,
655
757
  "source-layer": sourceLayer,
656
- minzoom: 14,
657
- filter: ["all", ["has", "name"], classFilter(minorClasses)],
658
- layout: sharedLayout,
659
- paint: sharedPaint
758
+ minzoom: 5,
759
+ filter: ["all", nonTunnelRoadFilter, classFilter(["ferry"])],
760
+ paint: {
761
+ "line-color": colors.roads.ferry,
762
+ "line-dasharray": [1.2, 1.1],
763
+ "line-opacity": metrics.majorOpacity * 0.8,
764
+ "line-width": zoomWidth(
765
+ [
766
+ [5, 0.25],
767
+ [12, 0.8],
768
+ [16, 2.6]
769
+ ],
770
+ metrics.weightScale
771
+ )
772
+ }
660
773
  });
661
774
  }
662
- return layers;
663
- }
664
- function waterLabelLayers(source, waterNameSourceLayer, waterwaySourceLayer, colors, labelsConfig, typography) {
665
- if (labelsConfig.water === "none") return [];
666
- const waterFilter = labelsConfig.water === "major" ? ["all", ["has", "name"], classFilter(majorWaterClasses)] : ["has", "name"];
667
- const waterwayFilter = labelsConfig.water === "major" ? ["all", ["has", "name"], classFilter(majorWaterwayClasses)] : ["has", "name"];
668
- const paint = {
669
- "text-color": colors.labels.water,
670
- "text-halo-color": colors.labels.halo,
671
- "text-halo-width": 1
672
- };
673
- return [
674
- {
675
- id: "water-labels",
676
- type: "symbol",
775
+ if (options.extras.paths) {
776
+ layers.push({
777
+ id: "roads-paths",
778
+ type: "line",
677
779
  source,
678
- "source-layer": waterNameSourceLayer,
679
- minzoom: 3,
680
- filter: ["all", ["==", ["geometry-type"], "Point"], waterFilter],
681
- layout: {
682
- "text-field": labelTextField(labelsConfig.language),
683
- "text-font": textFont(typography),
684
- "text-size": ["interpolate", ["linear"], ["zoom"], 3, 11, 8, 14]
685
- },
686
- paint
687
- },
780
+ "source-layer": sourceLayer,
781
+ minzoom: 10,
782
+ filter: ["all", nonTunnelRoadFilter, classFilter(["path"])],
783
+ paint: {
784
+ "line-color": colors.roads.path,
785
+ "line-dasharray": [0.7, 0.7],
786
+ "line-opacity": metrics.minorOpacity * 0.77,
787
+ "line-width": zoomWidth(
788
+ [
789
+ [10, 0.25],
790
+ [14, 0.8],
791
+ [16, 2.2]
792
+ ],
793
+ metrics.weightScale * metrics.minorWidthScale * (options.widthScale?.path ?? 1)
794
+ )
795
+ }
796
+ });
797
+ }
798
+ if (options.extras.rail) {
799
+ layers.push({
800
+ id: "roads-rail",
801
+ type: "line",
802
+ source,
803
+ "source-layer": sourceLayer,
804
+ minzoom: 7,
805
+ filter: ["all", nonTunnelRoadFilter, classFilter(["rail", "transit"])],
806
+ paint: {
807
+ "line-color": colors.roads.rail,
808
+ "line-opacity": metrics.minorOpacity * 0.65,
809
+ "line-width": zoomWidth(
810
+ [
811
+ [7, 0.35],
812
+ [12, 0.9],
813
+ [16, 2.4]
814
+ ],
815
+ metrics.weightScale * metrics.minorWidthScale
816
+ )
817
+ }
818
+ });
819
+ }
820
+ if (showsServiceRoads) {
821
+ layers.push({
822
+ id: "roads-service",
823
+ type: "line",
824
+ source,
825
+ "source-layer": sourceLayer,
826
+ minzoom: 14,
827
+ filter: ["all", nonTunnelRoadFilter, serviceFilter],
828
+ paint: {
829
+ "line-color": roadClassColor,
830
+ "line-opacity": metrics.minorOpacity,
831
+ "line-width": zoomWidth(
832
+ [
833
+ [14, 0.6],
834
+ [16, 2.2]
835
+ ],
836
+ metrics.weightScale * metrics.minorWidthScale,
837
+ options.widthScale
838
+ )
839
+ }
840
+ });
841
+ }
842
+ if (showsStreetRoads) {
843
+ layers.push({
844
+ id: "roads-minor",
845
+ type: "line",
846
+ source,
847
+ "source-layer": sourceLayer,
848
+ minzoom: 12,
849
+ filter: ["all", nonTunnelRoadFilter, streetFilter],
850
+ paint: {
851
+ "line-color": roadClassColor,
852
+ "line-opacity": metrics.minorOpacity,
853
+ "line-width": zoomWidth(
854
+ [
855
+ [12, 0.8],
856
+ [16, 3]
857
+ ],
858
+ metrics.weightScale * metrics.minorWidthScale,
859
+ options.widthScale
860
+ )
861
+ }
862
+ });
863
+ }
864
+ layers.push(
688
865
  {
689
- id: "water-line-labels",
690
- type: "symbol",
866
+ id: "roads-major",
867
+ type: "line",
691
868
  source,
692
- "source-layer": waterNameSourceLayer,
869
+ "source-layer": sourceLayer,
693
870
  minzoom: 5,
694
- filter: ["all", ["==", ["geometry-type"], "LineString"], waterFilter],
695
- layout: {
696
- "symbol-placement": "line",
697
- "text-field": labelTextField(labelsConfig.language),
698
- "text-font": textFont(typography),
699
- "text-size": 13
700
- },
701
- paint
871
+ filter: ["all", nonTunnelRoadFilter, majorFilter],
872
+ paint: {
873
+ "line-color": roadClassColor,
874
+ "line-opacity": metrics.majorOpacity,
875
+ "line-width": zoomWidth(
876
+ [
877
+ [5, 0.35],
878
+ [12, 1.2],
879
+ [16, 4]
880
+ ],
881
+ metrics.weightScale * metrics.majorWidthScale,
882
+ options.widthScale
883
+ )
884
+ }
702
885
  },
703
886
  {
704
- id: "waterway-labels",
705
- type: "symbol",
887
+ id: "roads-bridges",
888
+ type: "line",
706
889
  source,
707
- "source-layer": waterwaySourceLayer,
890
+ "source-layer": sourceLayer,
708
891
  minzoom: 10,
709
- filter: ["all", ["==", ["geometry-type"], "LineString"], waterwayFilter],
710
- layout: {
711
- "symbol-placement": "line",
712
- "symbol-spacing": 320,
713
- "text-field": labelTextField(labelsConfig.language),
714
- "text-font": textFont(typography),
715
- "text-size": 12
716
- },
717
- paint
892
+ filter: ["all", ["==", ["get", "brunnel"], "bridge"], transportFilter],
893
+ paint: {
894
+ "line-color": colors.roads.bridge,
895
+ "line-opacity": roadOpacityExpression(metrics.majorOpacity, metrics.minorOpacity),
896
+ "line-width": roadWidthExpression(
897
+ [
898
+ [10, 0.45],
899
+ [14, 1.6],
900
+ [16, 4.4]
901
+ ],
902
+ metrics,
903
+ options.widthScale
904
+ )
905
+ }
718
906
  }
719
- ];
720
- }
721
- function visibleRoadLabelClasses(detail, roadsConfig) {
722
- if (detail === "none" || roadsConfig.detail === "none") return [];
723
- const visibleRoadClasses = roadsConfig.detail === "highways" ? [...highwayRoadClasses] : [
724
- ...majorRoadClasses,
725
- ...roadsConfig.detail === "streets" || roadsConfig.detail === "all" ? streetRoadClasses : [],
726
- ...roadsConfig.detail === "all" ? serviceRoadClasses : [],
727
- ...roadsConfig.extras.paths ? ["path"] : []
728
- ];
729
- const requestedClasses = detail === "major" ? [...majorRoadClasses] : [...majorRoadClasses, ...streetRoadClasses, ...serviceRoadClasses, "path"];
730
- const visible = new Set(visibleRoadClasses);
731
- return requestedClasses.filter((roadClass) => visible.has(roadClass));
732
- }
733
- function labelDefaultsFromLegacyMode(mode) {
734
- if (mode === "none") {
735
- return { language: "auto", places: "none", roads: "none", water: "none" };
736
- }
737
- if (mode === "essential") {
738
- return { language: "auto", places: "major", roads: "major", water: "major" };
907
+ );
908
+ if (options.oneWayMarkers) {
909
+ layers.push(oneWayMarkerLayer(source, sourceLayer, colors, typography));
739
910
  }
740
- return { language: "auto", places: "all", roads: "all", water: "all" };
911
+ return layers;
741
912
  }
742
- function legacyModeFromResolvedLabels(options) {
743
- if (options.places === "none" && options.roads === "none" && options.water === "none") {
744
- return "none";
913
+ function roadDefaultsFromLegacyMode(mode) {
914
+ if (mode === "hidden") {
915
+ return {
916
+ detail: "none",
917
+ extras: { ferry: false, paths: false, rail: false },
918
+ hierarchy: "clear",
919
+ oneWayMarkers: false,
920
+ outline: "subtle",
921
+ weight: "regular"
922
+ };
745
923
  }
746
- if (options.places === "major" && options.roads === "major" && options.water === "major") {
747
- return "essential";
924
+ if (mode === "soft") {
925
+ return {
926
+ detail: "streets",
927
+ extras: { ferry: false, paths: false, rail: false },
928
+ hierarchy: "subtle",
929
+ oneWayMarkers: false,
930
+ outline: "subtle",
931
+ weight: "thin"
932
+ };
748
933
  }
749
- return "balanced";
934
+ if (mode === "detailed") {
935
+ return {
936
+ detail: "all",
937
+ extras: { ferry: true, paths: true, rail: true },
938
+ hierarchy: "clear",
939
+ oneWayMarkers: false,
940
+ outline: "strong",
941
+ weight: "regular"
942
+ };
943
+ }
944
+ return {
945
+ detail: "streets",
946
+ extras: { ferry: true, paths: true, rail: true },
947
+ hierarchy: "clear",
948
+ oneWayMarkers: false,
949
+ outline: "subtle",
950
+ weight: "regular"
951
+ };
952
+ }
953
+ function legacyModeFromResolvedRoads(options) {
954
+ if (options.detail === "none") return "hidden";
955
+ if (options.hierarchy === "subtle") return "soft";
956
+ if (options.hierarchy === "strong" || options.detail === "all") return "detailed";
957
+ return "standard";
750
958
  }
751
959
  function classFilter(classes) {
752
960
  return ["match", ["get", "class"], classes, true, false];
753
961
  }
754
- function labelTextField(language) {
755
- if (language === "local") {
756
- return ["coalesce", ["get", "name"], ["get", "name:latin"], ["get", "name:en"]];
757
- }
758
- if (language === "auto") {
759
- return ["coalesce", ["get", "name:latin"], ["get", "name"], ["get", "name:en"]];
760
- }
761
- return ["coalesce", ["get", `name:${language}`], ["get", "name:latin"], ["get", "name"]];
962
+ function roadOpacityExpression(majorOpacity, minorOpacity) {
963
+ return [
964
+ "step",
965
+ ["zoom"],
966
+ roadOpacityAtZoom(majorOpacity, minorOpacity, 0, 0),
967
+ 12,
968
+ roadOpacityAtZoom(majorOpacity, minorOpacity, minorOpacity, 0),
969
+ 14,
970
+ roadOpacityAtZoom(majorOpacity, minorOpacity, minorOpacity, minorOpacity)
971
+ ];
762
972
  }
763
-
764
- // src/modules/poi/index.ts
765
- var defaultPoiMinZoom = {
766
- none: 24,
767
- minimal: 15,
768
- balanced: 13,
769
- full: 11
770
- };
771
- function poi(options = {}) {
772
- return {
773
- type: "poi",
774
- ...options.categories ? { categories: options.categories } : {},
775
- ...options.icons !== void 0 ? { icons: options.icons } : {},
776
- ...options.labels ? { labels: options.labels } : {},
777
- ...options.minZoom !== void 0 ? { minZoom: options.minZoom } : {},
778
- ...options.preset ? { preset: options.preset } : {}
779
- };
780
- }
781
- function resolvePoi(legacyPoi, moduleConfig, fallback) {
782
- const mode = moduleConfig?.preset ?? legacyPoi ?? fallback;
783
- return {
784
- categories: moduleConfig?.categories,
785
- icons: moduleConfig?.icons ?? (mode === "none" ? false : "essential"),
786
- labels: moduleConfig?.labels ?? poiLabelsFromMode(mode),
787
- minZoom: moduleConfig?.minZoom ?? defaultPoiMinZoom[mode],
788
- mode
789
- };
790
- }
791
- function poiLayers(source, sourceLayer, colors, poiConfig, typography, customIconSpriteId2, customIconMapping) {
792
- if (poiConfig.mode === "none") {
793
- return [];
794
- }
795
- const layout = {
796
- "text-field": labelTextField("auto"),
797
- "text-font": textFont(typography),
798
- "text-size": ["interpolate", ["linear"], ["zoom"], poiConfig.minZoom, 10, 17, 12],
799
- "text-letter-spacing": 0,
800
- "text-padding": 2
801
- };
802
- if (poiConfig.labels === "none") {
803
- delete layout["text-field"];
804
- }
805
- if (poiConfig.icons) {
806
- layout["icon-image"] = poiIconImageExpression(customIconSpriteId2, customIconMapping);
807
- layout["icon-optional"] = true;
808
- layout["icon-padding"] = 2;
809
- layout["icon-size"] = ["interpolate", ["linear"], ["zoom"], poiConfig.minZoom, 0.85, 17, 1];
810
- layout["text-anchor"] = "top";
811
- layout["text-offset"] = [0, 0.75];
812
- }
973
+ function roadOpacityAtZoom(majorOpacity, minorOpacity, streetOpacity, serviceOpacity) {
813
974
  return [
814
- {
815
- id: "poi-labels",
816
- type: "symbol",
817
- source,
818
- "source-layer": sourceLayer,
819
- minzoom: poiConfig.minZoom,
820
- ...poiConfig.categories ? { filter: poiCategoryFilter(poiConfig.categories) } : {},
821
- layout,
822
- paint: {
823
- "text-color": colors.poi.label,
824
- "text-halo-color": colors.poi.halo ?? alpha(colors.background, 0.78),
825
- "text-halo-width": 1
826
- }
827
- }
975
+ "case",
976
+ classFilter(majorRoadClasses),
977
+ majorOpacity,
978
+ classFilter(streetRoadClasses),
979
+ streetOpacity,
980
+ classFilter(serviceRoadClasses),
981
+ serviceOpacity,
982
+ minorOpacity
828
983
  ];
829
984
  }
830
- function poiLabelsFromMode(mode) {
831
- if (mode === "none") return "none";
832
- if (mode === "minimal") return "minimal";
833
- if (mode === "full") return "full";
834
- return "balanced";
985
+ function roadWidthExpression(stops, metrics, widthScale) {
986
+ return [
987
+ "interpolate",
988
+ ["linear"],
989
+ ["zoom"],
990
+ ...stops.flatMap(([zoom, width]) => [
991
+ zoom,
992
+ widthScale ? [
993
+ "*",
994
+ [
995
+ "case",
996
+ classFilter(majorRoadClasses),
997
+ width * metrics.weightScale * metrics.majorWidthScale,
998
+ width * metrics.weightScale * metrics.minorWidthScale
999
+ ],
1000
+ roadClassWidthScaleExpression(widthScale)
1001
+ ] : [
1002
+ "case",
1003
+ classFilter(majorRoadClasses),
1004
+ width * metrics.weightScale * metrics.majorWidthScale,
1005
+ width * metrics.weightScale * metrics.minorWidthScale
1006
+ ]
1007
+ ])
1008
+ ];
835
1009
  }
836
- function poiCategoryFilter(categories) {
837
- return ["in", "class", ...categories.flatMap((category) => poiCategoryClasses(category))];
1010
+ function zoomWidth(stops, scale, widthScale) {
1011
+ return [
1012
+ "interpolate",
1013
+ ["linear"],
1014
+ ["zoom"],
1015
+ ...stops.flatMap(([zoom, width]) => [
1016
+ zoom,
1017
+ widthScale ? ["*", width * scale, roadClassWidthScaleExpression(widthScale)] : width * scale
1018
+ ])
1019
+ ];
838
1020
  }
839
- function poiIconImageExpression(customIconSpriteId2, customIconMapping = {}) {
840
- const matchParts = [];
841
- for (const [className, iconName] of Object.entries(poiClassIconNames)) {
842
- matchParts.push(
843
- className,
844
- poiIconImageFallback(iconName, customIconSpriteId2, customIconMapping)
845
- );
846
- }
1021
+ function roadClassWidthScaleExpression(widthScale) {
847
1022
  return [
848
1023
  "match",
849
1024
  ["get", "class"],
850
- ...matchParts,
851
- poiIconImageFallback("default", customIconSpriteId2, customIconMapping)
1025
+ ...Object.entries(widthScale).flatMap(([roadClass, scale]) => [roadClass, scale]),
1026
+ 1
852
1027
  ];
853
1028
  }
854
- function poiCategoryClasses(category) {
855
- return poiCategoryClassNames[category] ?? [category];
1029
+ function oneWayMarkerLayer(source, sourceLayer, colors, typography) {
1030
+ return {
1031
+ id: "road-oneway-markers",
1032
+ type: "symbol",
1033
+ source,
1034
+ "source-layer": sourceLayer,
1035
+ minzoom: 15,
1036
+ filter: [
1037
+ "any",
1038
+ ["==", ["get", "oneway"], 1],
1039
+ ["==", ["get", "oneway"], "1"],
1040
+ ["==", ["get", "oneway"], true]
1041
+ ],
1042
+ layout: {
1043
+ "symbol-placement": "line",
1044
+ "symbol-spacing": 88,
1045
+ "text-field": "\u203A",
1046
+ ...typography ? { "text-font": textFont(typography, "roads") } : {},
1047
+ "text-keep-upright": false,
1048
+ "text-rotation-alignment": "map",
1049
+ "text-size": 15
1050
+ },
1051
+ paint: {
1052
+ "text-color": colors.labels.road,
1053
+ "text-halo-color": colors.labels.halo,
1054
+ "text-halo-width": 0.5
1055
+ }
1056
+ };
856
1057
  }
857
- function poiIconImageFallback(iconName, customIconSpriteId2, customIconMapping) {
858
- const osmBrightIcon = `${osmBrightIconNameForSemanticIcon(iconName)}_11`;
859
- const customIconName = customIconMapping[iconName] ?? iconName;
860
- if (!customIconSpriteId2) {
861
- return ["coalesce", ["image", customIconName], ["image", osmBrightIcon]];
862
- }
1058
+ function roadClassColorExpression(colors) {
863
1059
  return [
864
- "coalesce",
865
- ["image", `${customIconSpriteId2}:${customIconName}`],
866
- ["image", osmBrightIcon]
1060
+ "match",
1061
+ ["get", "class"],
1062
+ "motorway",
1063
+ colors.roads.motorway,
1064
+ "trunk",
1065
+ colors.roads.trunk,
1066
+ "primary",
1067
+ colors.roads.primary,
1068
+ "secondary",
1069
+ colors.roads.secondary,
1070
+ "rail",
1071
+ colors.roads.rail,
1072
+ "transit",
1073
+ colors.roads.rail,
1074
+ "ferry",
1075
+ colors.roads.ferry,
1076
+ "path",
1077
+ colors.roads.path,
1078
+ "track",
1079
+ colors.roads.path,
1080
+ colors.roads.minor
867
1081
  ];
868
1082
  }
869
- function osmBrightIconNameForSemanticIcon(iconName) {
870
- return semanticToOsmBrightIconName[iconName] ?? "marker";
871
- }
872
- var poiCategoryClassNames = {
873
- coffee: ["cafe"],
874
- culture: ["museum", "gallery", "theatre", "cinema", "library", "attraction"],
875
- education: ["school", "college", "university"],
876
- food: ["restaurant", "fast_food", "bar", "pub", "biergarten"],
877
- health: ["hospital", "pharmacy", "doctors", "dentist"],
878
- lodging: ["hotel", "motel", "hostel", "guest_house"],
879
- services: ["bank", "post", "toilet", "police", "fire_station", "town_hall"],
880
- shopping: ["shop", "mall", "supermarket", "marketplace"],
881
- transit: ["railway", "station", "bus", "bus_stop", "subway", "tram"]
882
- };
883
- var poiClassIconNames = {
884
- attraction: "culture",
885
- bank: "services",
886
- bar: "food",
887
- biergarten: "food",
888
- bus: "transit",
889
- bus_stop: "transit",
890
- cafe: "coffee",
891
- cinema: "culture",
892
- college: "education",
893
- dentist: "health",
894
- doctors: "health",
895
- fast_food: "food",
896
- fire_station: "services",
897
- gallery: "culture",
898
- guest_house: "lodging",
899
- hospital: "health",
900
- hostel: "lodging",
901
- hotel: "lodging",
902
- library: "culture",
903
- mall: "shopping",
904
- marketplace: "shopping",
905
- motel: "lodging",
906
- museum: "culture",
907
- pharmacy: "health",
908
- police: "services",
909
- post: "services",
910
- pub: "food",
911
- railway: "transit",
912
- restaurant: "food",
913
- school: "education",
914
- shop: "shopping",
915
- station: "transit",
916
- subway: "transit",
917
- supermarket: "shopping",
918
- theatre: "culture",
919
- toilet: "services",
920
- town_hall: "services",
921
- tram: "transit",
922
- university: "education"
923
- };
924
- var semanticToOsmBrightIconName = {
925
- coffee: "cafe",
926
- culture: "museum",
927
- default: "marker",
928
- education: "school",
929
- food: "restaurant",
930
- health: "hospital",
931
- lodging: "lodging",
932
- services: "marker",
933
- shopping: "shop",
934
- transit: "railway"
935
- };
936
1083
 
937
- // src/modules/roads/index.ts
938
- var moduleRoadDefaults = {
939
- detail: "streets",
940
- extras: { ferry: false, paths: false, rail: false },
941
- hierarchy: "clear",
942
- outline: "subtle",
943
- weight: "regular"
1084
+ // src/modules/labels/index.ts
1085
+ var moduleLabelDefaults = {
1086
+ language: "auto",
1087
+ places: "major",
1088
+ roads: "major",
1089
+ water: "major"
944
1090
  };
945
- var highwayRoadClasses2 = ["motorway", "trunk"];
946
- var arterialRoadClasses2 = ["primary", "secondary", "tertiary"];
947
- var majorRoadClasses2 = [...highwayRoadClasses2, ...arterialRoadClasses2];
948
- var streetRoadClasses2 = ["minor"];
949
- var serviceRoadClasses2 = ["service", "track"];
950
- function roads(options = {}) {
1091
+ var majorPlaceClasses = ["continent", "country", "state", "city", "town"];
1092
+ var majorWaterClasses = ["ocean", "sea", "lake"];
1093
+ var majorWaterwayClasses = ["river"];
1094
+ function labels(options = {}) {
951
1095
  return {
952
- type: "roads",
953
- ...options.detail ? { detail: options.detail } : {},
954
- ...options.extras ? { extras: { ...options.extras } } : {},
955
- ...options.hierarchy ? { hierarchy: options.hierarchy } : {},
956
- ...options.outline ? { outline: options.outline } : {},
957
- ...options.weight ? { weight: options.weight } : {}
1096
+ type: "labels",
1097
+ ...options.language ? { language: options.language } : {},
1098
+ ...options.places ? { places: options.places } : {},
1099
+ ...options.roadClasses ? { roadClasses: [...options.roadClasses] } : {},
1100
+ ...options.roads ? { roads: options.roads } : {},
1101
+ ...options.water ? { water: options.water } : {}
958
1102
  };
959
1103
  }
960
- function resolveRoads(legacyRoads, moduleConfig, fallback) {
961
- const fallbackMode = legacyRoads ?? fallback;
962
- const defaults = moduleConfig ? moduleRoadDefaults : roadDefaultsFromLegacyMode(fallbackMode);
1104
+ function resolveLabels(legacyLabels, moduleConfig, fallback) {
1105
+ const fallbackMode = legacyLabels ?? fallback;
1106
+ const defaults = moduleConfig ? moduleLabelDefaults : labelDefaultsFromLegacyMode(fallbackMode);
963
1107
  const resolved = {
964
- detail: moduleConfig?.detail ?? defaults.detail,
965
- extras: {
966
- ferry: moduleConfig?.extras?.ferry ?? defaults.extras.ferry,
967
- paths: moduleConfig?.extras?.paths ?? defaults.extras.paths,
968
- rail: moduleConfig?.extras?.rail ?? defaults.extras.rail
969
- },
970
- hierarchy: moduleConfig?.hierarchy ?? defaults.hierarchy,
971
- outline: moduleConfig?.outline ?? defaults.outline,
972
- weight: moduleConfig?.weight ?? defaults.weight
1108
+ language: moduleConfig?.language ?? defaults.language,
1109
+ places: moduleConfig?.places ?? defaults.places,
1110
+ ...moduleConfig?.roadClasses ? { roadClasses: moduleConfig.roadClasses } : {},
1111
+ roads: moduleConfig?.roads ?? defaults.roads,
1112
+ water: moduleConfig?.water ?? defaults.water
973
1113
  };
974
1114
  return {
975
1115
  ...resolved,
976
- mode: legacyModeFromResolvedRoads(resolved)
977
- };
978
- }
979
- function roadStyleMetrics(options) {
980
- const hierarchy = {
981
- subtle: {
982
- majorOpacity: 0.92,
983
- majorWidthScale: 1,
984
- minorOpacity: 0.82,
985
- minorWidthScale: 0.94
986
- },
987
- clear: {
988
- majorOpacity: 0.92,
989
- majorWidthScale: 1,
990
- minorOpacity: 0.68,
991
- minorWidthScale: 0.84
992
- },
993
- strong: {
994
- majorOpacity: 0.92,
995
- majorWidthScale: 1,
996
- minorOpacity: 0.52,
997
- minorWidthScale: 0.7
998
- }
999
- }[options.hierarchy];
1000
- const weightScale = {
1001
- thin: 0.78,
1002
- regular: 1,
1003
- bold: 1.24
1004
- }[options.weight];
1005
- const outlineOpacity = {
1006
- none: 0,
1007
- subtle: 0.18,
1008
- strong: 0.34
1009
- }[options.outline];
1010
- return {
1011
- ...hierarchy,
1012
- outlineOpacity,
1013
- weightScale
1116
+ mode: moduleConfig ? legacyModeFromResolvedLabels(resolved) : fallbackMode
1014
1117
  };
1015
1118
  }
1016
- function roadLayers(source, sourceLayer, colors, options) {
1017
- if (options.detail === "none") {
1018
- return [];
1019
- }
1020
- const metrics = roadStyleMetrics(options);
1021
- const selectedMajorRoadClasses = options.detail === "highways" ? [...highwayRoadClasses2] : [...majorRoadClasses2];
1022
- const showsStreetRoads = options.detail === "streets" || options.detail === "all";
1023
- const showsServiceRoads = options.detail === "all";
1024
- const roadClasses = [
1025
- ...selectedMajorRoadClasses,
1026
- ...showsStreetRoads ? streetRoadClasses2 : [],
1027
- ...showsServiceRoads ? serviceRoadClasses2 : []
1028
- ];
1029
- const transportClasses = [
1030
- ...roadClasses,
1031
- ...options.extras.paths ? ["path"] : [],
1032
- ...options.extras.rail ? ["rail", "transit"] : [],
1033
- ...options.extras.ferry ? ["ferry"] : []
1034
- ];
1035
- const roadClassColor = roadClassColorExpression(colors);
1036
- const nonTunnelRoadFilter = ["!=", ["get", "brunnel"], "tunnel"];
1037
- const majorFilter = classFilter2(selectedMajorRoadClasses);
1038
- const streetFilter = classFilter2(streetRoadClasses2);
1039
- const serviceFilter = classFilter2(serviceRoadClasses2);
1040
- const roadFilter = classFilter2(roadClasses);
1041
- const transportFilter = classFilter2(transportClasses);
1042
- const layers = [];
1043
- if (options.outline !== "none") {
1044
- layers.push({
1045
- id: "roads-tunnels-casing",
1046
- type: "line",
1047
- source,
1048
- "source-layer": sourceLayer,
1049
- minzoom: 10,
1050
- filter: ["all", ["==", ["get", "brunnel"], "tunnel"], transportFilter],
1051
- paint: {
1052
- "line-color": colors.roads.tunnel,
1053
- "line-dasharray": [0.6, 0.8],
1054
- "line-opacity": roadOpacityExpression(
1055
- metrics.outlineOpacity * 0.85,
1056
- metrics.outlineOpacity * 0.85
1057
- ),
1058
- "line-width": roadWidthExpression(
1059
- [
1060
- [10, 0.8],
1061
- [14, 2.2],
1062
- [16, 4.8]
1063
- ],
1064
- metrics
1065
- )
1066
- }
1067
- });
1068
- }
1069
- layers.push({
1070
- id: "roads-tunnels",
1071
- type: "line",
1072
- source,
1073
- "source-layer": sourceLayer,
1074
- minzoom: 10,
1075
- filter: ["all", ["==", ["get", "brunnel"], "tunnel"], transportFilter],
1076
- paint: {
1077
- "line-color": roadClassColor,
1078
- "line-dasharray": [0.6, 0.8],
1079
- "line-opacity": roadOpacityExpression(
1080
- metrics.majorOpacity * 0.28,
1081
- metrics.minorOpacity * 0.28
1082
- ),
1083
- "line-width": roadWidthExpression(
1084
- [
1085
- [10, 0.25],
1086
- [14, 1],
1087
- [16, 2.4]
1088
- ],
1089
- metrics
1090
- )
1091
- }
1092
- });
1093
- if (options.outline !== "none") {
1094
- layers.push({
1095
- id: "roads-casing",
1096
- type: "line",
1119
+ function labelLayers(source, sourceLayers, colors, labelsConfig, roadsConfig, typography) {
1120
+ return [
1121
+ ...placeLabelLayers(source, sourceLayers.place, colors, labelsConfig, typography),
1122
+ ...roadLabelLayers(
1097
1123
  source,
1098
- "source-layer": sourceLayer,
1099
- minzoom: 5,
1100
- filter: ["all", nonTunnelRoadFilter, roadFilter],
1101
- paint: {
1102
- "line-color": colors.roads.casing,
1103
- "line-opacity": roadOpacityExpression(metrics.outlineOpacity, metrics.outlineOpacity),
1104
- "line-width": roadWidthExpression(
1105
- [
1106
- [6, 0.7],
1107
- [12, 1.8],
1108
- [16, 5.4]
1109
- ],
1110
- metrics
1111
- )
1112
- }
1113
- });
1114
- }
1115
- if (options.extras.ferry) {
1116
- layers.push({
1117
- id: "roads-ferry",
1118
- type: "line",
1124
+ sourceLayers.roadName,
1125
+ colors,
1126
+ labelsConfig,
1127
+ roadsConfig,
1128
+ typography
1129
+ ),
1130
+ ...waterLabelLayers(
1119
1131
  source,
1120
- "source-layer": sourceLayer,
1121
- minzoom: 5,
1122
- filter: ["all", nonTunnelRoadFilter, classFilter2(["ferry"])],
1123
- paint: {
1124
- "line-color": colors.roads.ferry,
1125
- "line-dasharray": [1.2, 1.1],
1126
- "line-opacity": metrics.majorOpacity * 0.8,
1127
- "line-width": zoomWidth(
1128
- [
1129
- [5, 0.25],
1130
- [12, 0.8],
1131
- [16, 2.6]
1132
- ],
1133
- metrics.weightScale
1134
- )
1135
- }
1136
- });
1137
- }
1138
- if (options.extras.paths) {
1139
- layers.push({
1140
- id: "roads-paths",
1141
- type: "line",
1132
+ sourceLayers.waterName,
1133
+ sourceLayers.waterway,
1134
+ colors,
1135
+ labelsConfig,
1136
+ typography
1137
+ )
1138
+ ];
1139
+ }
1140
+ function placeLabelLayers(source, sourceLayer, colors, labelsConfig, typography) {
1141
+ if (labelsConfig.places === "none") return [];
1142
+ return [
1143
+ {
1144
+ id: "place-labels",
1145
+ type: "symbol",
1142
1146
  source,
1143
1147
  "source-layer": sourceLayer,
1144
- minzoom: 10,
1145
- filter: ["all", nonTunnelRoadFilter, classFilter2(["path"])],
1148
+ minzoom: 0,
1149
+ filter: labelsConfig.places === "major" ? ["all", ["has", "name"], classFilter2(majorPlaceClasses)] : ["has", "name"],
1150
+ layout: {
1151
+ "text-field": labelTextField(labelsConfig.language),
1152
+ "text-font": textFont(typography, "places"),
1153
+ "text-size": ["interpolate", ["linear"], ["zoom"], 3, 10, 8, 13, 14, 17],
1154
+ "text-letter-spacing": 0,
1155
+ "text-padding": 4
1156
+ },
1146
1157
  paint: {
1147
- "line-color": colors.roads.path,
1148
- "line-dasharray": [0.7, 0.7],
1149
- "line-opacity": metrics.minorOpacity * 0.77,
1150
- "line-width": zoomWidth(
1151
- [
1152
- [10, 0.25],
1153
- [14, 0.8],
1154
- [16, 2.2]
1155
- ],
1156
- metrics.weightScale * metrics.minorWidthScale
1157
- )
1158
+ "text-color": colors.labels.primary,
1159
+ "text-halo-color": colors.labels.halo,
1160
+ "text-halo-width": 1.2
1158
1161
  }
1159
- });
1160
- }
1161
- if (options.extras.rail) {
1162
+ }
1163
+ ];
1164
+ }
1165
+ function roadLabelLayers(source, sourceLayer, colors, labelsConfig, roadsConfig, typography) {
1166
+ const classes = visibleRoadLabelClasses(
1167
+ labelsConfig.roads,
1168
+ roadsConfig,
1169
+ labelsConfig.roadClasses
1170
+ );
1171
+ const majorClasses = classes.filter(isMajorRoadClass);
1172
+ const minorClasses = classes.filter((roadClass) => !isMajorRoadClass(roadClass));
1173
+ if (classes.length === 0) return [];
1174
+ const layers = [];
1175
+ const sharedLayout = {
1176
+ "symbol-placement": "line",
1177
+ "text-field": labelTextField(labelsConfig.language),
1178
+ "text-font": textFont(typography, "roads"),
1179
+ "text-rotation-alignment": "map",
1180
+ "text-size": ["interpolate", ["linear"], ["zoom"], 11, 11, 14, 13, 17, 15]
1181
+ };
1182
+ const sharedPaint = {
1183
+ "text-color": colors.labels.road,
1184
+ "text-halo-color": colors.labels.halo,
1185
+ "text-halo-width": 1
1186
+ };
1187
+ if (majorClasses.length > 0) {
1188
+ const namedMajorClasses = majorClasses.filter((roadClass) => roadClass !== "motorway");
1162
1189
  layers.push({
1163
- id: "roads-rail",
1164
- type: "line",
1190
+ id: "road-shields",
1191
+ type: "symbol",
1165
1192
  source,
1166
1193
  "source-layer": sourceLayer,
1167
- minzoom: 7,
1168
- filter: ["all", nonTunnelRoadFilter, classFilter2(["rail", "transit"])],
1169
- paint: {
1170
- "line-color": colors.roads.rail,
1171
- "line-opacity": metrics.minorOpacity * 0.65,
1172
- "line-width": zoomWidth(
1173
- [
1174
- [7, 0.35],
1175
- [12, 0.9],
1176
- [16, 2.4]
1177
- ],
1178
- metrics.weightScale * metrics.minorWidthScale
1179
- )
1180
- }
1194
+ minzoom: 8,
1195
+ filter: ["all", ["has", "ref"], classFilter2(majorClasses)],
1196
+ layout: {
1197
+ "symbol-placement": "line",
1198
+ "symbol-spacing": 240,
1199
+ "text-field": ["get", "ref"],
1200
+ "text-font": textFont(typography, "roads"),
1201
+ "text-rotation-alignment": "viewport",
1202
+ "text-size": 10
1203
+ },
1204
+ paint: sharedPaint
1181
1205
  });
1206
+ if (namedMajorClasses.length > 0) {
1207
+ layers.unshift({
1208
+ id: "road-labels-major",
1209
+ type: "symbol",
1210
+ source,
1211
+ "source-layer": sourceLayer,
1212
+ minzoom: 10,
1213
+ filter: ["all", ["has", "name"], classFilter2(namedMajorClasses)],
1214
+ layout: sharedLayout,
1215
+ paint: sharedPaint
1216
+ });
1217
+ }
1182
1218
  }
1183
- if (showsServiceRoads) {
1219
+ if (minorClasses.length > 0) {
1184
1220
  layers.push({
1185
- id: "roads-service",
1186
- type: "line",
1221
+ id: "road-labels-minor",
1222
+ type: "symbol",
1187
1223
  source,
1188
1224
  "source-layer": sourceLayer,
1189
1225
  minzoom: 14,
1190
- filter: ["all", nonTunnelRoadFilter, serviceFilter],
1191
- paint: {
1192
- "line-color": roadClassColor,
1193
- "line-opacity": metrics.minorOpacity,
1194
- "line-width": zoomWidth(
1195
- [
1196
- [14, 0.6],
1197
- [16, 2.2]
1198
- ],
1199
- metrics.weightScale * metrics.minorWidthScale
1200
- )
1201
- }
1202
- });
1203
- }
1204
- if (showsStreetRoads) {
1205
- layers.push({
1206
- id: "roads-minor",
1207
- type: "line",
1208
- source,
1209
- "source-layer": sourceLayer,
1210
- minzoom: 12,
1211
- filter: ["all", nonTunnelRoadFilter, streetFilter],
1212
- paint: {
1213
- "line-color": roadClassColor,
1214
- "line-opacity": metrics.minorOpacity,
1215
- "line-width": zoomWidth(
1216
- [
1217
- [12, 0.8],
1218
- [16, 3]
1219
- ],
1220
- metrics.weightScale * metrics.minorWidthScale
1221
- )
1222
- }
1226
+ filter: ["all", ["has", "name"], classFilter2(minorClasses)],
1227
+ layout: sharedLayout,
1228
+ paint: sharedPaint
1223
1229
  });
1224
1230
  }
1225
- layers.push(
1231
+ return layers;
1232
+ }
1233
+ function waterLabelLayers(source, waterNameSourceLayer, waterwaySourceLayer, colors, labelsConfig, typography) {
1234
+ if (labelsConfig.water === "none") return [];
1235
+ const waterFilter = labelsConfig.water === "major" ? ["all", ["has", "name"], classFilter2(majorWaterClasses)] : ["has", "name"];
1236
+ const waterwayFilter = labelsConfig.water === "major" ? ["all", ["has", "name"], classFilter2(majorWaterwayClasses)] : ["has", "name"];
1237
+ const paint = {
1238
+ "text-color": colors.labels.water,
1239
+ "text-halo-color": colors.labels.halo,
1240
+ "text-halo-width": 1
1241
+ };
1242
+ return [
1226
1243
  {
1227
- id: "roads-major",
1228
- type: "line",
1244
+ id: "water-labels",
1245
+ type: "symbol",
1229
1246
  source,
1230
- "source-layer": sourceLayer,
1231
- minzoom: 5,
1232
- filter: ["all", nonTunnelRoadFilter, majorFilter],
1233
- paint: {
1234
- "line-color": roadClassColor,
1235
- "line-opacity": metrics.majorOpacity,
1236
- "line-width": zoomWidth(
1237
- [
1238
- [5, 0.35],
1239
- [12, 1.2],
1240
- [16, 4]
1241
- ],
1242
- metrics.weightScale * metrics.majorWidthScale
1243
- )
1244
- }
1247
+ "source-layer": waterNameSourceLayer,
1248
+ minzoom: 3,
1249
+ filter: ["all", ["==", ["geometry-type"], "Point"], waterFilter],
1250
+ layout: {
1251
+ "text-field": labelTextField(labelsConfig.language),
1252
+ "text-font": textFont(typography, "water"),
1253
+ "text-size": ["interpolate", ["linear"], ["zoom"], 3, 11, 8, 14]
1254
+ },
1255
+ paint
1245
1256
  },
1246
1257
  {
1247
- id: "roads-bridges",
1248
- type: "line",
1258
+ id: "water-line-labels",
1259
+ type: "symbol",
1249
1260
  source,
1250
- "source-layer": sourceLayer,
1261
+ "source-layer": waterNameSourceLayer,
1262
+ minzoom: 5,
1263
+ filter: ["all", ["==", ["geometry-type"], "LineString"], waterFilter],
1264
+ layout: {
1265
+ "symbol-placement": "line",
1266
+ "text-field": labelTextField(labelsConfig.language),
1267
+ "text-font": textFont(typography, "water"),
1268
+ "text-size": 13
1269
+ },
1270
+ paint
1271
+ },
1272
+ {
1273
+ id: "waterway-labels",
1274
+ type: "symbol",
1275
+ source,
1276
+ "source-layer": waterwaySourceLayer,
1251
1277
  minzoom: 10,
1252
- filter: ["all", ["==", ["get", "brunnel"], "bridge"], transportFilter],
1253
- paint: {
1254
- "line-color": colors.roads.bridge,
1255
- "line-opacity": roadOpacityExpression(metrics.majorOpacity, metrics.minorOpacity),
1256
- "line-width": roadWidthExpression(
1257
- [
1258
- [10, 0.45],
1259
- [14, 1.6],
1260
- [16, 4.4]
1261
- ],
1262
- metrics
1263
- )
1264
- }
1278
+ filter: ["all", ["==", ["geometry-type"], "LineString"], waterwayFilter],
1279
+ layout: {
1280
+ "symbol-placement": "line",
1281
+ "symbol-spacing": 320,
1282
+ "text-field": labelTextField(labelsConfig.language),
1283
+ "text-font": textFont(typography, "water"),
1284
+ "text-size": 12
1285
+ },
1286
+ paint
1265
1287
  }
1288
+ ];
1289
+ }
1290
+ function visibleRoadLabelClasses(detail, roadsConfig, eligibleClasses) {
1291
+ if (detail === "none" || roadsConfig.detail === "none") return [];
1292
+ const visibleRoadClasses = [
1293
+ ...roadClassesForDetail(roadsConfig.detail),
1294
+ ...roadsConfig.extras.paths ? ["path"] : []
1295
+ ];
1296
+ const requestedClasses = roadLabelClasses(detail);
1297
+ const visible = new Set(visibleRoadClasses);
1298
+ const eligible = eligibleClasses ? new Set(eligibleClasses) : void 0;
1299
+ return requestedClasses.filter(
1300
+ (roadClass) => visible.has(roadClass) && (!eligible || eligible.has(roadClass))
1266
1301
  );
1267
- return layers;
1268
1302
  }
1269
- function roadDefaultsFromLegacyMode(mode) {
1270
- if (mode === "hidden") {
1271
- return {
1272
- detail: "none",
1273
- extras: { ferry: false, paths: false, rail: false },
1274
- hierarchy: "clear",
1275
- outline: "subtle",
1276
- weight: "regular"
1277
- };
1303
+ function roadLabelClasses(detail) {
1304
+ return [...roadClassesForDetail(detail), ...detail === "all" ? ["path"] : []];
1305
+ }
1306
+ function labelDefaultsFromLegacyMode(mode) {
1307
+ if (mode === "none") {
1308
+ return { language: "auto", places: "none", roads: "none", water: "none" };
1278
1309
  }
1279
- if (mode === "soft") {
1280
- return {
1281
- detail: "streets",
1282
- extras: { ferry: false, paths: false, rail: false },
1283
- hierarchy: "subtle",
1284
- outline: "subtle",
1285
- weight: "thin"
1286
- };
1310
+ if (mode === "essential") {
1311
+ return { language: "auto", places: "major", roads: "major", water: "major" };
1287
1312
  }
1288
- if (mode === "detailed") {
1289
- return {
1290
- detail: "all",
1291
- extras: { ferry: true, paths: true, rail: true },
1292
- hierarchy: "clear",
1293
- outline: "strong",
1294
- weight: "regular"
1295
- };
1313
+ return { language: "auto", places: "all", roads: "all", water: "all" };
1314
+ }
1315
+ function legacyModeFromResolvedLabels(options) {
1316
+ if (options.places === "none" && options.roads === "none" && options.water === "none") {
1317
+ return "none";
1318
+ }
1319
+ if (options.places === "major" && options.roads === "major" && options.water === "major") {
1320
+ return "essential";
1321
+ }
1322
+ return "balanced";
1323
+ }
1324
+ function classFilter2(classes) {
1325
+ return ["match", ["get", "class"], classes, true, false];
1326
+ }
1327
+ function labelTextField(language) {
1328
+ if (language === "local") {
1329
+ return ["coalesce", ["get", "name"], ["get", "name:latin"], ["get", "name:en"]];
1330
+ }
1331
+ if (language === "auto") {
1332
+ return ["coalesce", ["get", "name:latin"], ["get", "name"], ["get", "name:en"]];
1296
1333
  }
1334
+ return ["coalesce", ["get", `name:${language}`], ["get", "name:latin"], ["get", "name"]];
1335
+ }
1336
+
1337
+ // src/modules/poi/index.ts
1338
+ var defaultPoiMinZoom = {
1339
+ none: 24,
1340
+ minimal: 15,
1341
+ balanced: 13,
1342
+ full: 11
1343
+ };
1344
+ function poi(options = {}) {
1297
1345
  return {
1298
- detail: "streets",
1299
- extras: { ferry: true, paths: true, rail: true },
1300
- hierarchy: "clear",
1301
- outline: "subtle",
1302
- weight: "regular"
1346
+ type: "poi",
1347
+ ...options.categories ? { categories: options.categories } : {},
1348
+ ...options.classMapping ? { classMapping: { ...options.classMapping } } : {},
1349
+ ...options.color ? { color: options.color } : {},
1350
+ ...options.density ? { density: options.density } : {},
1351
+ ...options.icons !== void 0 ? { icons: options.icons } : {},
1352
+ ...options.labels ? { labels: options.labels } : {},
1353
+ ...options.minZoom !== void 0 ? { minZoom: options.minZoom } : {},
1354
+ ...options.placement ? { placement: { ...options.placement } } : {},
1355
+ ...options.preset ? { preset: options.preset } : {}
1303
1356
  };
1304
1357
  }
1305
- function legacyModeFromResolvedRoads(options) {
1306
- if (options.detail === "none") return "hidden";
1307
- if (options.hierarchy === "subtle") return "soft";
1308
- if (options.hierarchy === "strong" || options.detail === "all") return "detailed";
1309
- return "standard";
1358
+ function resolvePoi(legacyPoi, moduleConfig, fallback) {
1359
+ const mode = moduleConfig?.preset ?? legacyPoi ?? fallback;
1360
+ const density = moduleConfig?.density;
1361
+ return {
1362
+ ...moduleConfig?.categories ? { categories: moduleConfig.categories } : {},
1363
+ classMapping: resolvePoiClassMapping(moduleConfig?.classMapping),
1364
+ color: moduleConfig?.color ?? "uniform",
1365
+ ...density ? { density } : {},
1366
+ icons: moduleConfig?.icons ?? (mode === "none" ? false : "essential"),
1367
+ labels: moduleConfig?.labels ?? poiLabelsFromMode(mode),
1368
+ minZoom: moduleConfig?.minZoom ?? defaultPoiMinZoom[mode] + (density === void 0 ? 0 : poiDensityZoomOffset(density)),
1369
+ mode,
1370
+ placement: {
1371
+ coupleIconAndLabel: moduleConfig?.placement?.coupleIconAndLabel ?? false,
1372
+ iconPadding: moduleConfig?.placement?.iconPadding ?? 2,
1373
+ textPadding: moduleConfig?.placement?.textPadding ?? 2
1374
+ }
1375
+ };
1310
1376
  }
1311
- function classFilter2(classes) {
1312
- return ["match", ["get", "class"], classes, true, false];
1377
+ function poiLayers(source, sourceLayer, colors, poiConfig, typography, customIconSpriteId2, customIconMapping) {
1378
+ if (poiConfig.mode === "none") {
1379
+ return [];
1380
+ }
1381
+ const layout = {
1382
+ "text-field": labelTextField("auto"),
1383
+ "text-font": textFont(typography, "poi"),
1384
+ "text-size": ["interpolate", ["linear"], ["zoom"], poiConfig.minZoom, 10, 17, 12],
1385
+ "text-letter-spacing": 0,
1386
+ "text-padding": poiConfig.placement.textPadding
1387
+ };
1388
+ if (poiConfig.density) {
1389
+ layout["symbol-sort-key"] = poiPriorityExpression(poiConfig.classMapping);
1390
+ }
1391
+ if (poiConfig.labels === "none") {
1392
+ delete layout["text-field"];
1393
+ }
1394
+ if (poiConfig.icons) {
1395
+ layout["icon-image"] = poiIconImageExpression(
1396
+ customIconSpriteId2,
1397
+ customIconMapping,
1398
+ poiConfig.classMapping
1399
+ );
1400
+ layout["icon-optional"] = !poiConfig.placement.coupleIconAndLabel;
1401
+ layout["icon-padding"] = poiConfig.placement.iconPadding;
1402
+ layout["icon-size"] = ["interpolate", ["linear"], ["zoom"], poiConfig.minZoom, 0.85, 17, 1];
1403
+ layout["text-anchor"] = "top";
1404
+ layout["text-offset"] = [0, 0.75];
1405
+ if (poiConfig.placement.coupleIconAndLabel) layout["text-optional"] = false;
1406
+ }
1407
+ return [
1408
+ {
1409
+ id: "poi-labels",
1410
+ type: "symbol",
1411
+ source,
1412
+ "source-layer": sourceLayer,
1413
+ minzoom: poiConfig.minZoom,
1414
+ ...poiFilter(poiConfig) ? { filter: poiFilter(poiConfig) } : {},
1415
+ layout,
1416
+ paint: {
1417
+ "text-color": poiConfig.color === "category" ? poiCategoryColorExpression(colors, poiConfig.classMapping) : colors.poi.label,
1418
+ "text-halo-color": colors.poi.halo ?? alpha(colors.background, 0.78),
1419
+ "text-halo-width": 1
1420
+ }
1421
+ }
1422
+ ];
1313
1423
  }
1314
- function roadOpacityExpression(majorOpacity, minorOpacity) {
1424
+ function poiLabelsFromMode(mode) {
1425
+ if (mode === "none") return "none";
1426
+ if (mode === "minimal") return "minimal";
1427
+ if (mode === "full") return "full";
1428
+ return "balanced";
1429
+ }
1430
+ function poiCategoryFilter(categories, classMapping = poiCategoryClassNames) {
1431
+ const classes = categories.flatMap((category) => poiCategoryClasses(category, classMapping));
1432
+ return ["any", ["in", "class", ...classes], ["in", "subclass", ...classes]];
1433
+ }
1434
+ function poiIconImageExpression(customIconSpriteId2, customIconMapping = {}, classMapping = poiCategoryClassNames) {
1435
+ const matchParts = Object.keys(classMapping).flatMap((category) => [
1436
+ category,
1437
+ poiIconImageFallback(category, customIconSpriteId2, customIconMapping)
1438
+ ]);
1315
1439
  return [
1316
- "case",
1317
- classFilter2(majorRoadClasses2),
1318
- majorOpacity,
1319
- classFilter2(streetRoadClasses2),
1320
- ["step", ["zoom"], 0, 12, minorOpacity],
1321
- classFilter2(serviceRoadClasses2),
1322
- ["step", ["zoom"], 0, 14, minorOpacity],
1323
- minorOpacity
1440
+ "match",
1441
+ poiCategoryExpression("default", classMapping),
1442
+ ...matchParts,
1443
+ poiIconImageFallback("default", customIconSpriteId2, customIconMapping)
1324
1444
  ];
1325
1445
  }
1326
- function roadWidthExpression(stops, metrics) {
1446
+ function poiCategoryExpression(fallback = "", classMapping = poiCategoryClassNames) {
1447
+ const categoryMatches = poiCategoryMatchParts(classMapping);
1327
1448
  return [
1328
- "case",
1329
- classFilter2(majorRoadClasses2),
1330
- zoomWidth(stops, metrics.weightScale * metrics.majorWidthScale),
1331
- zoomWidth(stops, metrics.weightScale * metrics.minorWidthScale)
1449
+ "match",
1450
+ ["get", "subclass"],
1451
+ ...categoryMatches,
1452
+ fallbackCategoryExpression(fallback, classMapping)
1453
+ ];
1454
+ }
1455
+ function fallbackCategoryExpression(fallback, classMapping) {
1456
+ const categoryMatches = poiCategoryMatchParts(classMapping);
1457
+ return ["match", ["get", "class"], ...categoryMatches, fallback];
1458
+ }
1459
+ function poiCategoryMatchParts(classMapping) {
1460
+ return Object.entries(classMapping).flatMap(([category, classes]) => [classes, category]);
1461
+ }
1462
+ function poiCategoryClasses(category, classMapping = poiCategoryClassNames) {
1463
+ return [...classMapping[category] ?? [category]];
1464
+ }
1465
+ function poiPriorityExpression(classMapping = poiCategoryClassNames) {
1466
+ const categoryPenalty = [
1467
+ "match",
1468
+ poiCategoryExpression("default", classMapping),
1469
+ "transit",
1470
+ 0,
1471
+ "culture",
1472
+ 1,
1473
+ "health",
1474
+ 2,
1475
+ "education",
1476
+ 3,
1477
+ "lodging",
1478
+ 4,
1479
+ "food",
1480
+ 5,
1481
+ "coffee",
1482
+ 6,
1483
+ "services",
1484
+ 7,
1485
+ "shopping",
1486
+ 8,
1487
+ 9
1332
1488
  ];
1489
+ return ["+", ["*", categoryPenalty, 1e3], ["to-number", ["get", "rank"], 999]];
1333
1490
  }
1334
- function zoomWidth(stops, scale) {
1491
+ function poiDensityFilter(density, classMapping = poiCategoryClassNames) {
1492
+ if (density === "dense") return void 0;
1493
+ const cutoffs = poiDensityRankCutoffs[density];
1335
1494
  return [
1336
- "interpolate",
1337
- ["linear"],
1338
- ["zoom"],
1339
- ...stops.flatMap(([zoom, width]) => [zoom, width * scale])
1495
+ "any",
1496
+ ...Object.keys(classMapping).map((category) => [
1497
+ "all",
1498
+ poiCategoryFilter([category], classMapping),
1499
+ ["any", ["!has", "rank"], ["<=", "rank", cutoffs[category] ?? cutoffs.default]]
1500
+ ])
1340
1501
  ];
1341
1502
  }
1342
- function roadClassColorExpression(colors) {
1503
+ function poiCategoryColorExpression(colors, classMapping = poiCategoryClassNames) {
1343
1504
  return [
1344
1505
  "match",
1345
- ["get", "class"],
1346
- "motorway",
1347
- colors.roads.motorway,
1348
- "trunk",
1349
- colors.roads.trunk,
1350
- "primary",
1351
- colors.roads.primary,
1352
- "secondary",
1353
- colors.roads.secondary,
1354
- "rail",
1355
- colors.roads.rail,
1356
- "transit",
1357
- colors.roads.rail,
1358
- "ferry",
1359
- colors.roads.ferry,
1360
- "path",
1361
- colors.roads.path,
1362
- "track",
1363
- colors.roads.path,
1364
- colors.roads.minor
1506
+ poiCategoryExpression("default", classMapping),
1507
+ ...Object.keys(classMapping).flatMap((category) => [
1508
+ category,
1509
+ colors.poi[category] ?? colors.poi.label
1510
+ ]),
1511
+ colors.poi.label
1512
+ ];
1513
+ }
1514
+ function resolvePoiClassMapping(overrides) {
1515
+ const categories = /* @__PURE__ */ new Set([
1516
+ ...Object.keys(poiCategoryClassNames),
1517
+ ...Object.keys(overrides ?? {})
1518
+ ]);
1519
+ return Object.fromEntries(
1520
+ [...categories].map((category) => [
1521
+ category,
1522
+ [.../* @__PURE__ */ new Set([...poiCategoryClassNames[category] ?? [], ...overrides?.[category] ?? []])]
1523
+ ])
1524
+ );
1525
+ }
1526
+ function poiDensityZoomOffset(density) {
1527
+ if (density === "sparse") return 1;
1528
+ if (density === "dense") return -1;
1529
+ return 0;
1530
+ }
1531
+ function poiFilter(poiConfig) {
1532
+ const categoryFilter = poiConfig.categories ? poiCategoryFilter(poiConfig.categories, poiConfig.classMapping) : void 0;
1533
+ const densityFilter = poiConfig.density ? poiDensityFilter(poiConfig.density, poiConfig.classMapping) : void 0;
1534
+ if (categoryFilter && densityFilter) return ["all", categoryFilter, densityFilter];
1535
+ return categoryFilter ?? densityFilter;
1536
+ }
1537
+ function poiIconImageFallback(iconName, customIconSpriteId2, customIconMapping) {
1538
+ const osmBrightIcon = `${osmBrightIconNameForSemanticIcon(iconName)}_11`;
1539
+ const customIconName = customIconMapping[iconName] ?? iconName;
1540
+ if (!customIconSpriteId2) {
1541
+ return ["coalesce", ["image", customIconName], ["image", osmBrightIcon]];
1542
+ }
1543
+ return [
1544
+ "coalesce",
1545
+ ["image", `${customIconSpriteId2}:${customIconName}`],
1546
+ ["image", osmBrightIcon]
1365
1547
  ];
1366
1548
  }
1549
+ function osmBrightIconNameForSemanticIcon(iconName) {
1550
+ return semanticToOsmBrightIconName[iconName] ?? "marker";
1551
+ }
1552
+ var poiCategoryClassNames = {
1553
+ coffee: ["cafe"],
1554
+ culture: [
1555
+ "museum",
1556
+ "gallery",
1557
+ "art_gallery",
1558
+ "theatre",
1559
+ "cinema",
1560
+ "library",
1561
+ "attraction",
1562
+ "garden",
1563
+ "monument"
1564
+ ],
1565
+ education: ["school", "college", "university"],
1566
+ food: ["restaurant", "fast_food", "bar", "pub", "biergarten"],
1567
+ health: ["hospital", "pharmacy", "doctors", "dentist"],
1568
+ lodging: ["lodging", "hotel", "motel", "hostel", "guest_house"],
1569
+ services: ["bank", "post", "toilet", "police", "fire_station", "town_hall"],
1570
+ shopping: ["shop", "mall", "supermarket", "marketplace"],
1571
+ transit: ["railway", "station", "bus", "bus_stop", "subway", "tram"]
1572
+ };
1573
+ var poiDensityRankCutoffs = {
1574
+ sparse: {
1575
+ coffee: 4,
1576
+ culture: 10,
1577
+ education: 8,
1578
+ food: 6,
1579
+ health: 8,
1580
+ lodging: 8,
1581
+ services: 6,
1582
+ shopping: 4,
1583
+ transit: 14,
1584
+ default: 6
1585
+ },
1586
+ balanced: {
1587
+ coffee: 10,
1588
+ culture: 18,
1589
+ education: 14,
1590
+ food: 14,
1591
+ health: 14,
1592
+ lodging: 16,
1593
+ services: 12,
1594
+ shopping: 10,
1595
+ transit: 24,
1596
+ default: 12
1597
+ }
1598
+ };
1599
+ var semanticToOsmBrightIconName = {
1600
+ coffee: "cafe",
1601
+ culture: "museum",
1602
+ default: "marker",
1603
+ education: "school",
1604
+ food: "restaurant",
1605
+ health: "hospital",
1606
+ lodging: "lodging",
1607
+ services: "marker",
1608
+ shopping: "shop",
1609
+ transit: "railway"
1610
+ };
1367
1611
 
1368
1612
  // src/basemaps/osm/layers.ts
1369
1613
  function openMapTilesLayers(source, sourceLayers, colors, options) {
@@ -1375,7 +1619,8 @@ function openMapTilesLayers(source, sourceLayers, colors, options) {
1375
1619
  "background-color": colors.background
1376
1620
  }
1377
1621
  },
1378
- fillLayer("landuse", source, sourceLayers.landuse, colors.landuse.residential, 0.42),
1622
+ semanticLanduseLayer(source, sourceLayers.landuse, colors),
1623
+ semanticLandcoverLayer(source, sourceLayers.landcover, colors),
1379
1624
  fillLayer(
1380
1625
  "parks",
1381
1626
  source,
@@ -1384,8 +1629,15 @@ function openMapTilesLayers(source, sourceLayers, colors, options) {
1384
1629
  parkOpacity(options.density)
1385
1630
  ),
1386
1631
  fillLayer("water", source, sourceLayers.water, colors.hydro.water, 1),
1387
- ...buildingLayers(source, sourceLayers.building, colors, options.density, options.buildings),
1388
- ...roadLayers(source, sourceLayers.road, colors, options.roads),
1632
+ ...buildingLayers(
1633
+ source,
1634
+ sourceLayers.building,
1635
+ colors,
1636
+ options.density,
1637
+ options.buildings,
1638
+ options.buildingStyle
1639
+ ),
1640
+ ...roadLayers(source, sourceLayers.road, colors, options.roads, options.typography),
1389
1641
  ...boundaryLayers(source, sourceLayers.boundary, colors),
1390
1642
  ...labelLayers(source, sourceLayers, colors, options.labels, options.roads, options.typography),
1391
1643
  ...poiLayers(
@@ -1399,6 +1651,61 @@ function openMapTilesLayers(source, sourceLayers, colors, options) {
1399
1651
  )
1400
1652
  ];
1401
1653
  }
1654
+ function semanticLanduseLayer(source, sourceLayer, colors) {
1655
+ return {
1656
+ id: "landuse",
1657
+ type: "fill",
1658
+ source,
1659
+ "source-layer": sourceLayer,
1660
+ paint: {
1661
+ "fill-color": [
1662
+ "match",
1663
+ ["get", "class"],
1664
+ ["commercial", "retail"],
1665
+ colors.landuse.commercial,
1666
+ ["industrial", "railway"],
1667
+ colors.landuse.industrial,
1668
+ ["school", "university", "hospital", "civic"],
1669
+ colors.landuse.civic,
1670
+ "cemetery",
1671
+ colors.landuse.cemetery,
1672
+ colors.landuse.residential
1673
+ ],
1674
+ "fill-opacity": 0.62
1675
+ }
1676
+ };
1677
+ }
1678
+ function semanticLandcoverLayer(source, sourceLayer, colors) {
1679
+ return {
1680
+ id: "landcover",
1681
+ type: "fill",
1682
+ source,
1683
+ "source-layer": sourceLayer,
1684
+ filter: [
1685
+ "match",
1686
+ ["get", "class"],
1687
+ ["grass", "wood", "forest", "scrub", "farmland", "sand", "beach", "ice", "glacier"],
1688
+ true,
1689
+ false
1690
+ ],
1691
+ paint: {
1692
+ "fill-color": [
1693
+ "match",
1694
+ ["get", "class"],
1695
+ ["wood", "forest"],
1696
+ colors.landcover.wood,
1697
+ ["scrub", "farmland"],
1698
+ colors.landcover.protected,
1699
+ ["sand", "beach"],
1700
+ colors.landcover.sand,
1701
+ ["ice", "glacier"],
1702
+ colors.landcover.ice,
1703
+ colors.landcover.grass
1704
+ ],
1705
+ "fill-opacity": 0.88
1706
+ }
1707
+ };
1708
+ }
1402
1709
  function fillLayer(id, source, sourceLayer, color, opacity) {
1403
1710
  return {
1404
1711
  id,
@@ -1411,7 +1718,7 @@ function fillLayer(id, source, sourceLayer, color, opacity) {
1411
1718
  }
1412
1719
  };
1413
1720
  }
1414
- function buildingLayers(source, sourceLayer, colors, density, buildings) {
1721
+ function buildingLayers(source, sourceLayer, colors, density, buildings, buildingStyle) {
1415
1722
  if (buildings === "hidden") {
1416
1723
  return [];
1417
1724
  }
@@ -1478,8 +1785,12 @@ function buildingLayers(source, sourceLayer, colors, density, buildings) {
1478
1785
  "source-layer": sourceLayer,
1479
1786
  minzoom: 13,
1480
1787
  paint: {
1481
- "fill-color": colors.buildings.fill,
1482
- "fill-opacity": density === "dense" ? 0.62 : 0.5
1788
+ "fill-color": buildingStyle ? buildingHeightColorExpression(
1789
+ buildingStyle.heightThreshold ?? 12,
1790
+ colors.buildings.lowRise,
1791
+ colors.buildings.highRise
1792
+ ) : colors.buildings.fill,
1793
+ "fill-opacity": buildingStyle?.fillOpacity ?? (density === "dense" ? 0.62 : 0.5)
1483
1794
  }
1484
1795
  },
1485
1796
  {
@@ -1489,13 +1800,33 @@ function buildingLayers(source, sourceLayer, colors, density, buildings) {
1489
1800
  "source-layer": sourceLayer,
1490
1801
  minzoom: 15,
1491
1802
  paint: {
1492
- "line-color": colors.buildings.outline,
1493
- "line-opacity": 0.24,
1494
- "line-width": 0.6
1803
+ "line-color": buildingStyle ? buildingHeightColorExpression(
1804
+ buildingStyle.heightThreshold ?? 12,
1805
+ colors.buildings.lowRiseOutline,
1806
+ colors.buildings.highRiseOutline
1807
+ ) : colors.buildings.outline,
1808
+ "line-opacity": buildingStyle?.outlineOpacity ?? 0.24,
1809
+ "line-width": buildingStyle?.outlineWidth ?? 0.6
1495
1810
  }
1496
1811
  }
1497
1812
  ];
1498
1813
  }
1814
+ function buildingHeightColorExpression(threshold, lowRise, highRise) {
1815
+ return [
1816
+ "case",
1817
+ [
1818
+ ">=",
1819
+ [
1820
+ "to-number",
1821
+ ["coalesce", ["get", "render_height"], ["get", "height"], ["get", "levels"]],
1822
+ 0
1823
+ ],
1824
+ threshold
1825
+ ],
1826
+ highRise,
1827
+ lowRise
1828
+ ];
1829
+ }
1499
1830
  function boundaryLayers(source, sourceLayer, colors) {
1500
1831
  return [
1501
1832
  {
@@ -1545,12 +1876,15 @@ function applyLayerOverride(layers, layerId, override) {
1545
1876
  return insertLayer(layers, layer, override.before);
1546
1877
  }
1547
1878
  const previousLayer = layers[index];
1879
+ const layout = mergeRecords(previousLayer.layout, override.layout);
1880
+ const paint = mergeRecords(previousLayer.paint, override.paint);
1881
+ const metadata = mergeRecords(previousLayer.metadata, override.metadata);
1548
1882
  const patchedLayer = stripControlFields({
1549
1883
  ...previousLayer,
1550
1884
  ...override,
1551
- layout: mergeRecords(previousLayer.layout, override.layout),
1552
- paint: mergeRecords(previousLayer.paint, override.paint),
1553
- metadata: mergeRecords(previousLayer.metadata, override.metadata)
1885
+ ...layout ? { layout } : {},
1886
+ ...paint ? { paint } : {},
1887
+ ...metadata ? { metadata } : {}
1554
1888
  });
1555
1889
  const nextLayers = [...layers];
1556
1890
  nextLayers[index] = patchedLayer;
@@ -1577,28 +1911,206 @@ function mergeRecords(previous, next) {
1577
1911
  }
1578
1912
  function stripControlFields(layer) {
1579
1913
  const { before, ...styleLayer } = layer;
1580
- return styleLayer;
1914
+ return Object.fromEntries(Object.entries(styleLayer).filter(([, value]) => value !== void 0));
1581
1915
  }
1582
1916
  function isRecord2(value) {
1583
1917
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
1584
1918
  }
1585
1919
 
1586
1920
  // src/schema.ts
1921
+ import { z as z2 } from "zod";
1922
+
1923
+ // src/capture-scene.ts
1587
1924
  import { z } from "zod";
1925
+ var tileflowCaptureSceneSchemaVersion = 1;
1926
+ var tileflowCaptureSceneLimits = Object.freeze({
1927
+ applicationPathLength: 2048,
1928
+ boundsPadding: { maximum: 1024, minimum: 0 },
1929
+ captureIdLength: 64,
1930
+ maximumPhysicalPixels: 16777216,
1931
+ pitch: { maximum: 85, minimum: 0 },
1932
+ selectorLength: 256,
1933
+ viewport: { maximum: 4096, minimum: 64 },
1934
+ zoom: { maximum: 24, minimum: 0 }
1935
+ });
1936
+ var finiteLongitudeSchema = z.number().finite().min(-180).max(180);
1937
+ var finiteLatitudeSchema = z.number().finite().min(-90).max(90);
1938
+ var bearingSchema = z.number().finite().min(-180).max(180).optional();
1939
+ var pitchSchema = z.number().finite().min(tileflowCaptureSceneLimits.pitch.minimum).max(tileflowCaptureSceneLimits.pitch.maximum).optional();
1940
+ var tileflowCaptureCenterCameraSchema = z.object({
1941
+ type: z.literal("center"),
1942
+ center: z.tuple([finiteLongitudeSchema, finiteLatitudeSchema]),
1943
+ zoom: z.number().finite().min(tileflowCaptureSceneLimits.zoom.minimum).max(tileflowCaptureSceneLimits.zoom.maximum),
1944
+ bearing: bearingSchema,
1945
+ pitch: pitchSchema
1946
+ }).strict();
1947
+ var tileflowCaptureBoundsCameraSchema = z.object({
1948
+ type: z.literal("bounds"),
1949
+ bounds: z.tuple([
1950
+ finiteLongitudeSchema,
1951
+ finiteLatitudeSchema,
1952
+ finiteLongitudeSchema,
1953
+ finiteLatitudeSchema
1954
+ ]),
1955
+ padding: z.number().finite().min(tileflowCaptureSceneLimits.boundsPadding.minimum).max(tileflowCaptureSceneLimits.boundsPadding.maximum).optional(),
1956
+ bearing: bearingSchema,
1957
+ pitch: pitchSchema
1958
+ }).strict().superRefine((camera, context) => {
1959
+ const [west, south, east, north] = camera.bounds;
1960
+ if (south >= north) {
1961
+ context.addIssue({
1962
+ code: "custom",
1963
+ message: "Expected south latitude to be less than north latitude",
1964
+ path: ["bounds"]
1965
+ });
1966
+ }
1967
+ if (west === east) {
1968
+ context.addIssue({
1969
+ code: "custom",
1970
+ message: "Expected west and east longitude to define a non-empty span",
1971
+ path: ["bounds"]
1972
+ });
1973
+ }
1974
+ });
1975
+ var tileflowCaptureCameraSchema = z.discriminatedUnion("type", [
1976
+ tileflowCaptureCenterCameraSchema,
1977
+ tileflowCaptureBoundsCameraSchema
1978
+ ]);
1979
+ var tileflowCaptureViewportSchema = z.object({
1980
+ width: z.number().int().min(tileflowCaptureSceneLimits.viewport.minimum).max(tileflowCaptureSceneLimits.viewport.maximum),
1981
+ height: z.number().int().min(tileflowCaptureSceneLimits.viewport.minimum).max(tileflowCaptureSceneLimits.viewport.maximum),
1982
+ dpr: z.union([z.literal(1), z.literal(2)]).optional()
1983
+ }).strict().superRefine((viewport, context) => {
1984
+ const dpr = viewport.dpr ?? 1;
1985
+ const physicalPixels = viewport.width * viewport.height * dpr * dpr;
1986
+ if (physicalPixels > tileflowCaptureSceneLimits.maximumPhysicalPixels) {
1987
+ context.addIssue({
1988
+ code: "custom",
1989
+ message: `Expected at most ${tileflowCaptureSceneLimits.maximumPhysicalPixels} physical pixels`,
1990
+ path: ["dpr"]
1991
+ });
1992
+ }
1993
+ });
1994
+ var tileflowCaptureIdSchema = z.string().trim().min(1).max(tileflowCaptureSceneLimits.captureIdLength).regex(/^[A-Za-z0-9_-]+$/, {
1995
+ message: "Expected letters, numbers, underscores, or hyphens"
1996
+ }).refine((name) => name !== "__proto__", {
1997
+ message: "Expected an identifier that cannot mutate an object prototype"
1998
+ });
1999
+ var tileflowCaptureSceneNameSchema = tileflowCaptureIdSchema.refine(
2000
+ (name) => !/^(?:AUX|CON|NUL|PRN|COM[1-9]|LPT[1-9])$/i.test(name),
2001
+ "Expected a portable scene name that is not a reserved filename or prototype key"
2002
+ );
2003
+ var applicationPathSchema = z.string().min(1).max(tileflowCaptureSceneLimits.applicationPathLength).refine(isPortableApplicationPath, {
2004
+ message: "Expected a root-relative application path without an origin, credentials, fragment, backslash, or control character"
2005
+ });
2006
+ var tileflowCaptureMapTargetSchema = z.object({ kind: z.literal("map") }).strict();
2007
+ var tileflowCaptureApplicationTargetSchema = z.object({
2008
+ kind: z.literal("application"),
2009
+ path: applicationPathSchema,
2010
+ captureId: tileflowCaptureIdSchema.optional(),
2011
+ selector: z.string().trim().min(1).max(tileflowCaptureSceneLimits.selectorLength).optional(),
2012
+ frame: z.enum(["map", "viewport"]).optional()
2013
+ }).strict().superRefine((target, context) => {
2014
+ if (target.captureId && target.selector) {
2015
+ context.addIssue({
2016
+ code: "custom",
2017
+ message: "Expected either captureId or selector, not both",
2018
+ path: ["selector"]
2019
+ });
2020
+ }
2021
+ });
2022
+ var tileflowCaptureTargetSchema = z.discriminatedUnion("kind", [
2023
+ tileflowCaptureMapTargetSchema,
2024
+ tileflowCaptureApplicationTargetSchema
2025
+ ]);
2026
+ var tileflowCaptureSceneSchema = z.object({
2027
+ map: tileflowCaptureIdSchema,
2028
+ camera: tileflowCaptureCameraSchema,
2029
+ viewport: tileflowCaptureViewportSchema,
2030
+ target: tileflowCaptureTargetSchema.optional()
2031
+ }).strict();
2032
+ function normalizeTileflowCaptureScene(input) {
2033
+ const scene = tileflowCaptureSceneSchema.parse(input);
2034
+ const camera = scene.camera.type === "center" ? {
2035
+ type: scene.camera.type,
2036
+ center: scene.camera.center,
2037
+ zoom: scene.camera.zoom,
2038
+ bearing: scene.camera.bearing ?? 0,
2039
+ pitch: scene.camera.pitch ?? 0
2040
+ } : {
2041
+ type: scene.camera.type,
2042
+ bounds: scene.camera.bounds,
2043
+ padding: scene.camera.padding ?? 0,
2044
+ bearing: scene.camera.bearing ?? 0,
2045
+ pitch: scene.camera.pitch ?? 0
2046
+ };
2047
+ const target = scene.target ?? { kind: "map" };
2048
+ return {
2049
+ map: scene.map,
2050
+ camera,
2051
+ viewport: { ...scene.viewport, dpr: scene.viewport.dpr ?? 1 },
2052
+ target: target.kind === "map" ? target : {
2053
+ kind: target.kind,
2054
+ path: target.path,
2055
+ ...target.captureId ? { captureId: target.captureId } : {},
2056
+ ...target.selector ? { selector: target.selector } : {},
2057
+ frame: target.frame ?? "map"
2058
+ }
2059
+ };
2060
+ }
2061
+ function normalizeTileflowCaptureId(value) {
2062
+ return value === void 0 ? void 0 : tileflowCaptureIdSchema.parse(value);
2063
+ }
2064
+ function isPortableApplicationPath(value) {
2065
+ if (value !== value.trim() || !value.startsWith("/") || value.startsWith("//") || value.includes("#") || value.includes("\\") || hasControlCharacter(value)) {
2066
+ return false;
2067
+ }
2068
+ try {
2069
+ const base = new URL("http://tileflow.local");
2070
+ const resolved = new URL(value, base);
2071
+ return resolved.origin === base.origin && resolved.username === "" && resolved.password === "" && resolved.pathname.startsWith("/");
2072
+ } catch {
2073
+ return false;
2074
+ }
2075
+ }
2076
+ function hasControlCharacter(value) {
2077
+ for (let index = 0; index < value.length; index += 1) {
2078
+ const code = value.charCodeAt(index);
2079
+ if (code <= 31 || code === 127) return true;
2080
+ }
2081
+ return false;
2082
+ }
2083
+
2084
+ // src/schema.ts
1588
2085
  var legacyThemeNames = /* @__PURE__ */ new Set(["standard", "light", "dark", "minimal", "osm-bright-2"]);
1589
- var identifierSchema = z.string().trim().regex(/^[A-Za-z0-9_-]+$/, {
2086
+ var prototypeSafeInputSchema = z2.unknown().superRefine((value, context) => {
2087
+ const unsafe = findUnsafeObjectStructure(value);
2088
+ if (unsafe) {
2089
+ context.addIssue({
2090
+ code: "custom",
2091
+ message: unsafe.reason === "key" ? "Expected an object without prototype-mutating keys" : "Expected plain configuration objects without inherited properties",
2092
+ path: unsafe.path
2093
+ });
2094
+ }
2095
+ });
2096
+ var identifierSchema = z2.string().trim().regex(/^[A-Za-z0-9_-]+$/, {
1590
2097
  message: "Expected letters, numbers, underscores, or hyphens"
2098
+ }).refine((value) => value !== "__proto__", {
2099
+ message: "Expected an identifier that cannot mutate an object prototype"
2100
+ });
2101
+ var sourceVersionSchema = z2.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/, {
2102
+ message: "Expected a portable source version"
1591
2103
  });
1592
- var hexColorSchema = z.string().regex(/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/, {
2104
+ var hexColorSchema = z2.string().regex(/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/, {
1593
2105
  message: "Expected a hex color like #F6F7F3 or #F6F7F3FF"
1594
2106
  });
1595
2107
  function colorGroupSchema(keys) {
1596
2108
  const shape = Object.fromEntries(
1597
2109
  keys.map((key) => [key, hexColorSchema.optional()])
1598
2110
  );
1599
- return z.object(shape).strict();
2111
+ return z2.object(shape).strict();
1600
2112
  }
1601
- var colorConfigSchema = z.object({
2113
+ var colorConfigSchema = z2.object({
1602
2114
  accent: hexColorSchema.optional(),
1603
2115
  background: hexColorSchema.optional(),
1604
2116
  boundary: hexColorSchema.optional(),
@@ -1616,7 +2128,15 @@ var colorConfigSchema = z.object({
1616
2128
  textMuted: hexColorSchema.optional(),
1617
2129
  water: hexColorSchema.optional(),
1618
2130
  boundaries: colorGroupSchema(["admin", "disputed", "major", "maritime"]).optional(),
1619
- buildings: colorGroupSchema(["extrusion", "fill", "outline"]).optional(),
2131
+ buildings: colorGroupSchema([
2132
+ "extrusion",
2133
+ "fill",
2134
+ "highRise",
2135
+ "highRiseOutline",
2136
+ "lowRise",
2137
+ "lowRiseOutline",
2138
+ "outline"
2139
+ ]).optional(),
1620
2140
  hydro: colorGroupSchema(["ferry", "label", "water", "waterway"]).optional(),
1621
2141
  labels: colorGroupSchema([
1622
2142
  "country",
@@ -1665,9 +2185,17 @@ var colorConfigSchema = z.object({
1665
2185
  "tunnel"
1666
2186
  ]).optional()
1667
2187
  }).strict();
1668
- var themeModulesSchema = z.object({
2188
+ var themeModulesSchema = z2.object({
1669
2189
  boundaries: colorGroupSchema(["admin", "disputed", "major", "maritime"]).optional(),
1670
- buildings: colorGroupSchema(["extrusion", "fill", "outline"]).optional(),
2190
+ buildings: colorGroupSchema([
2191
+ "extrusion",
2192
+ "fill",
2193
+ "highRise",
2194
+ "highRiseOutline",
2195
+ "lowRise",
2196
+ "lowRiseOutline",
2197
+ "outline"
2198
+ ]).optional(),
1671
2199
  hydro: colorGroupSchema(["ferry", "label", "water", "waterway"]).optional(),
1672
2200
  labels: colorGroupSchema([
1673
2201
  "country",
@@ -1716,169 +2244,228 @@ var themeModulesSchema = z.object({
1716
2244
  "tunnel"
1717
2245
  ]).optional()
1718
2246
  }).strict();
1719
- var styleLayerOverrideSchema = z.object({
1720
- before: z.string().min(1).optional(),
1721
- filter: z.unknown().optional(),
1722
- layout: z.record(z.string(), z.unknown()).optional(),
1723
- maxzoom: z.number().finite().min(0).max(24).optional(),
1724
- metadata: z.record(z.string(), z.unknown()).optional(),
1725
- minzoom: z.number().finite().min(0).max(24).optional(),
1726
- paint: z.record(z.string(), z.unknown()).optional(),
1727
- source: z.string().min(1).optional(),
1728
- "source-layer": z.string().min(1).optional(),
1729
- type: z.string().min(1).optional()
1730
- }).catchall(z.unknown());
1731
- var typographySchema = z.object({
1732
- font: z.string().trim().min(1).optional(),
1733
- fontFamily: z.string().trim().min(1).optional(),
1734
- weight: z.enum(["regular", "medium", "semibold", "bold"]).optional()
2247
+ var styleLayerOverrideSchema = z2.object({
2248
+ before: z2.string().min(1).optional(),
2249
+ filter: z2.unknown().optional(),
2250
+ layout: z2.record(z2.string(), z2.unknown()).optional(),
2251
+ maxzoom: z2.number().finite().min(0).max(24).optional(),
2252
+ metadata: z2.record(z2.string(), z2.unknown()).optional(),
2253
+ minzoom: z2.number().finite().min(0).max(24).optional(),
2254
+ paint: z2.record(z2.string(), z2.unknown()).optional(),
2255
+ source: z2.string().min(1).optional(),
2256
+ "source-layer": z2.string().min(1).optional(),
2257
+ type: z2.string().min(1).optional()
2258
+ }).catchall(z2.unknown());
2259
+ var typographyStyleSchema = z2.object({
2260
+ font: z2.string().trim().min(1).optional(),
2261
+ fontFamily: z2.string().trim().min(1).optional(),
2262
+ weight: z2.enum(["regular", "medium", "semibold", "bold"]).optional()
1735
2263
  }).strict();
1736
- var fontsSchema = z.object({
1737
- body: z.string().trim().min(1).optional(),
1738
- labels: z.string().trim().min(1).optional()
2264
+ var typographySchema = typographyStyleSchema.extend({
2265
+ places: typographyStyleSchema.optional(),
2266
+ poi: typographyStyleSchema.optional(),
2267
+ roads: typographyStyleSchema.optional(),
2268
+ water: typographyStyleSchema.optional()
2269
+ });
2270
+ var fontsSchema = z2.object({
2271
+ body: z2.string().trim().min(1).optional(),
2272
+ labels: z2.string().trim().min(1).optional()
1739
2273
  }).strict();
1740
- var tileflowThemeSchema = z.object({
2274
+ var tileflowThemeSchema = z2.object({
1741
2275
  colors: colorConfigSchema.optional(),
1742
- extends: z.string().trim().min(1).optional(),
2276
+ extends: z2.string().trim().min(1).optional(),
1743
2277
  fonts: fontsSchema.optional(),
1744
- layers: z.record(z.string(), styleLayerOverrideSchema).optional(),
1745
- mode: z.enum(["light", "dark"]).optional(),
2278
+ layers: z2.record(z2.string(), styleLayerOverrideSchema).optional(),
2279
+ mode: z2.enum(["light", "dark"]).optional(),
1746
2280
  modules: themeModulesSchema.optional(),
1747
2281
  typography: typographySchema.optional()
1748
2282
  }).strict();
1749
2283
  var themeConfigSchema = tileflowThemeSchema;
1750
- var sourceLayersSchema = z.object({
1751
- landuse: z.string().optional(),
1752
- park: z.string().optional(),
1753
- water: z.string().optional(),
1754
- waterName: z.string().optional(),
1755
- waterway: z.string().optional(),
1756
- road: z.string().optional(),
1757
- roadName: z.string().optional(),
1758
- building: z.string().optional(),
1759
- boundary: z.string().optional(),
1760
- place: z.string().optional(),
1761
- poi: z.string().optional()
2284
+ var sourceLayersSchema = z2.object({
2285
+ landcover: z2.string().optional(),
2286
+ landuse: z2.string().optional(),
2287
+ park: z2.string().optional(),
2288
+ water: z2.string().optional(),
2289
+ waterName: z2.string().optional(),
2290
+ waterway: z2.string().optional(),
2291
+ road: z2.string().optional(),
2292
+ roadName: z2.string().optional(),
2293
+ building: z2.string().optional(),
2294
+ boundary: z2.string().optional(),
2295
+ place: z2.string().optional(),
2296
+ poi: z2.string().optional()
1762
2297
  }).strict();
1763
- var basemapSchema = z.object({
1764
- type: z.literal("osm"),
1765
- attribution: z.string().optional(),
1766
- glyphs: z.string().min(1).optional(),
1767
- sourceId: z.string().min(1).optional(),
2298
+ var basemapSchema = z2.object({
2299
+ type: z2.literal("osm"),
2300
+ attribution: z2.string().optional(),
2301
+ glyphs: z2.string().min(1).optional(),
2302
+ sourceId: z2.string().min(1).optional(),
1768
2303
  sourceLayers: sourceLayersSchema.optional(),
1769
- sprite: z.string().min(1).optional(),
1770
- tileset: z.string().trim().min(1).optional(),
1771
- url: z.url().optional()
2304
+ sprite: z2.string().min(1).optional(),
2305
+ tileset: z2.string().trim().min(1).optional(),
2306
+ url: z2.url().optional(),
2307
+ version: sourceVersionSchema.optional()
1772
2308
  }).strict();
1773
- var roadsModuleSchema = z.object({
1774
- type: z.literal("roads"),
1775
- detail: z.enum(["none", "highways", "major", "streets", "all"]).optional(),
1776
- extras: z.object({
1777
- ferry: z.boolean().optional(),
1778
- paths: z.boolean().optional(),
1779
- rail: z.boolean().optional()
2309
+ var roadDetailSchema = z2.enum(["none", "highways", "major", "streets", "all"]);
2310
+ var roadClassSchema = z2.enum([
2311
+ "motorway",
2312
+ "trunk",
2313
+ "primary",
2314
+ "secondary",
2315
+ "tertiary",
2316
+ "minor",
2317
+ "service",
2318
+ "track",
2319
+ "path"
2320
+ ]);
2321
+ var roadsModuleSchema = z2.object({
2322
+ type: z2.literal("roads"),
2323
+ detail: roadDetailSchema.optional(),
2324
+ extras: z2.object({
2325
+ ferry: z2.boolean().optional(),
2326
+ paths: z2.boolean().optional(),
2327
+ rail: z2.boolean().optional()
1780
2328
  }).strict().optional(),
1781
- hierarchy: z.enum(["subtle", "clear", "strong"]).optional(),
1782
- outline: z.enum(["none", "subtle", "strong"]).optional(),
1783
- weight: z.enum(["thin", "regular", "bold"]).optional()
2329
+ hierarchy: z2.enum(["subtle", "clear", "strong"]).optional(),
2330
+ oneWayMarkers: z2.boolean().optional(),
2331
+ outline: z2.enum(["none", "subtle", "strong"]).optional(),
2332
+ weight: z2.enum(["thin", "regular", "bold"]).optional(),
2333
+ widthScale: z2.partialRecord(roadClassSchema, z2.number().finite().min(0.1).max(8)).optional()
1784
2334
  }).strict();
1785
- var labelDetailSchema = z.enum(["none", "major", "all"]);
1786
- var labelsModuleSchema = z.object({
1787
- type: z.literal("labels"),
1788
- language: z.string().trim().min(1).optional(),
2335
+ var labelDetailSchema = z2.enum(["none", "major", "all"]);
2336
+ var labelsModuleSchema = z2.object({
2337
+ type: z2.literal("labels"),
2338
+ language: z2.string().trim().min(1).optional(),
1789
2339
  places: labelDetailSchema.optional(),
1790
- roads: labelDetailSchema.optional(),
2340
+ roadClasses: z2.array(roadClassSchema).min(1).max(9).optional(),
2341
+ roads: roadDetailSchema.optional(),
1791
2342
  water: labelDetailSchema.optional()
1792
2343
  }).strict();
1793
- var poiModuleSchema = z.object({
1794
- type: z.literal("poi"),
1795
- categories: z.array(identifierSchema).optional(),
1796
- icons: z.union([z.boolean(), z.enum(["essential", "full"])]).optional(),
1797
- labels: z.enum(["none", "minimal", "balanced", "full"]).optional(),
1798
- minZoom: z.number().finite().min(0).max(24).optional(),
1799
- preset: z.enum(["none", "minimal", "balanced", "full"]).optional()
2344
+ var poiModuleSchema = z2.object({
2345
+ type: z2.literal("poi"),
2346
+ categories: z2.array(identifierSchema).optional(),
2347
+ classMapping: z2.record(identifierSchema, z2.array(identifierSchema).min(1).max(64)).optional(),
2348
+ color: z2.enum(["uniform", "category"]).optional(),
2349
+ density: z2.enum(["sparse", "balanced", "dense"]).optional(),
2350
+ icons: z2.union([z2.boolean(), z2.enum(["essential", "full"])]).optional(),
2351
+ labels: z2.enum(["none", "minimal", "balanced", "full"]).optional(),
2352
+ minZoom: z2.number().finite().min(0).max(24).optional(),
2353
+ placement: z2.object({
2354
+ coupleIconAndLabel: z2.boolean().optional(),
2355
+ iconPadding: z2.number().finite().min(0).max(64).optional(),
2356
+ textPadding: z2.number().finite().min(0).max(64).optional()
2357
+ }).strict().optional(),
2358
+ preset: z2.enum(["none", "minimal", "balanced", "full"]).optional()
1800
2359
  }).strict();
1801
- var styleOverrideModuleSchema = z.object({
1802
- type: z.literal("styleOverride"),
1803
- layers: z.record(z.string(), styleLayerOverrideSchema).optional(),
1804
- removeLayers: z.array(z.string().min(1)).optional()
2360
+ var styleOverrideModuleSchema = z2.object({
2361
+ type: z2.literal("styleOverride"),
2362
+ layers: z2.record(z2.string(), styleLayerOverrideSchema).optional(),
2363
+ removeLayers: z2.array(z2.string().min(1)).optional()
1805
2364
  }).strict();
1806
- var moduleSchema = z.discriminatedUnion("type", [
2365
+ var moduleSchema = z2.discriminatedUnion("type", [
1807
2366
  labelsModuleSchema,
1808
2367
  poiModuleSchema,
1809
2368
  roadsModuleSchema,
1810
2369
  styleOverrideModuleSchema
1811
2370
  ]);
1812
- var iconSetSchema = z.union([
1813
- z.string().trim().min(1),
1814
- z.object({
1815
- extends: z.string().trim().min(1).optional(),
1816
- mapping: z.record(identifierSchema, z.string().trim().min(1)).optional(),
1817
- source: z.string().trim().min(1).optional(),
1818
- sprite: z.string().trim().min(1).optional()
2371
+ var iconSetSchema = z2.union([
2372
+ z2.string().trim().min(1),
2373
+ z2.object({
2374
+ extends: z2.string().trim().min(1).optional(),
2375
+ mapping: z2.record(identifierSchema, z2.string().trim().min(1)).optional(),
2376
+ source: z2.string().trim().min(1).optional(),
2377
+ sprite: z2.string().trim().min(1).optional()
1819
2378
  }).strict()
1820
2379
  ]);
1821
- var viewSchema = z.object({
1822
- bearing: z.number().finite().min(-180).max(180).optional(),
1823
- center: z.tuple([z.number().finite(), z.number().finite()]).refine(([lng, lat]) => lng >= -180 && lng <= 180 && lat >= -90 && lat <= 90, {
2380
+ var viewSchema = z2.object({
2381
+ bearing: z2.number().finite().min(-180).max(180).optional(),
2382
+ center: z2.tuple([z2.number().finite(), z2.number().finite()]).refine(([lng, lat]) => lng >= -180 && lng <= 180 && lat >= -90 && lat <= 90, {
1824
2383
  message: "Expected center as [lng, lat] within world bounds"
1825
2384
  }).optional(),
1826
- zoom: z.number().finite().min(0).max(24).optional()
2385
+ zoom: z2.number().finite().min(0).max(24).optional()
2386
+ }).strict();
2387
+ var buildingStyleSchema = z2.object({
2388
+ fillOpacity: z2.number().finite().min(0).max(1).optional(),
2389
+ heightThreshold: z2.number().finite().min(0).max(1e3).optional(),
2390
+ outlineOpacity: z2.number().finite().min(0).max(1).optional(),
2391
+ outlineWidth: z2.number().finite().min(0).max(16).optional()
1827
2392
  }).strict();
1828
- var terrainConfigSchema = z.object({
1829
- attribution: z.string().optional(),
1830
- encoding: z.enum(["mapbox", "terrarium"]).optional(),
1831
- exaggeration: z.number().min(0).max(10).optional(),
1832
- mode: z.enum(["none", "hillshade", "3d"]).optional(),
1833
- sourceId: z.string().min(1).optional(),
1834
- tileset: z.string().trim().min(1).optional(),
1835
- url: z.url().optional()
2393
+ var terrainConfigSchema = z2.object({
2394
+ attribution: z2.string().optional(),
2395
+ encoding: z2.enum(["mapbox", "terrarium"]).optional(),
2396
+ exaggeration: z2.number().min(0).max(10).optional(),
2397
+ mode: z2.enum(["none", "hillshade", "3d"]).optional(),
2398
+ sourceId: z2.string().min(1).optional(),
2399
+ tileset: z2.string().trim().min(1).optional(),
2400
+ url: z2.url().optional()
1836
2401
  }).strict();
1837
- var tilesSchema = z.object({
1838
- sourceId: z.string().min(1).optional(),
1839
- tileset: z.string().trim().min(1).optional(),
1840
- url: z.url().optional(),
1841
- attribution: z.string().optional(),
1842
- sourceLayers: sourceLayersSchema.optional()
2402
+ var tilesSchema = z2.object({
2403
+ sourceId: z2.string().min(1).optional(),
2404
+ tileset: z2.string().trim().min(1).optional(),
2405
+ url: z2.url().optional(),
2406
+ attribution: z2.string().optional(),
2407
+ sourceLayers: sourceLayersSchema.optional(),
2408
+ version: sourceVersionSchema.optional()
1843
2409
  }).strict();
1844
- var tileflowMapSchema = z.object({
2410
+ var tileflowMapSchema = z2.object({
1845
2411
  basemap: basemapSchema.optional(),
1846
- name: z.string().min(1).optional(),
1847
- tileset: z.string().trim().min(1).optional(),
1848
- theme: z.union([z.string().trim().min(1), themeConfigSchema]).optional(),
1849
- modules: z.array(moduleSchema).optional(),
1850
- layers: z.record(z.string(), styleLayerOverrideSchema).optional(),
2412
+ name: z2.string().min(1).optional(),
2413
+ tileset: z2.string().trim().min(1).optional(),
2414
+ theme: z2.union([z2.string().trim().min(1), themeConfigSchema]).optional(),
2415
+ modules: z2.array(moduleSchema).optional(),
2416
+ layers: z2.record(z2.string(), styleLayerOverrideSchema).optional(),
1851
2417
  colors: colorConfigSchema.optional(),
1852
2418
  typography: typographySchema.optional(),
1853
- density: z.enum(["clean", "balanced", "dense"]).optional(),
1854
- roads: z.enum(["hidden", "soft", "standard", "detailed"]).optional(),
1855
- labels: z.enum(["none", "essential", "balanced", "full"]).optional(),
1856
- poi: z.enum(["none", "minimal", "balanced", "full"]).optional(),
1857
- buildings: z.enum(["hidden", "flat", "3d"]).optional(),
1858
- terrain: z.union([z.enum(["none", "hillshade", "3d"]), terrainConfigSchema]).optional(),
1859
- renderer: z.enum(["auto", "osm-bright", "generated"]).optional(),
1860
- glyphs: z.string().min(1).optional(),
2419
+ density: z2.enum(["clean", "balanced", "dense"]).optional(),
2420
+ roads: z2.enum(["hidden", "soft", "standard", "detailed"]).optional(),
2421
+ labels: z2.enum(["none", "essential", "balanced", "full"]).optional(),
2422
+ poi: z2.enum(["none", "minimal", "balanced", "full"]).optional(),
2423
+ buildings: z2.enum(["hidden", "flat", "3d"]).optional(),
2424
+ buildingStyle: buildingStyleSchema.optional(),
2425
+ terrain: z2.union([z2.enum(["none", "hillshade", "3d"]), terrainConfigSchema]).optional(),
2426
+ renderer: z2.enum(["auto", "osm-bright", "generated"]).optional(),
2427
+ glyphs: z2.string().min(1).optional(),
1861
2428
  icons: iconSetSchema.optional(),
1862
- sprite: z.string().min(1).optional(),
2429
+ sprite: z2.string().min(1).optional(),
1863
2430
  tiles: tilesSchema.optional(),
1864
2431
  view: viewSchema.optional(),
1865
- allowedOrigins: z.array(z.string().trim().min(1).max(253)).max(20).optional()
1866
- }).strict();
1867
- var configSchema = tileflowMapSchema;
1868
- var tilesetConfigSchema = z.object({
1869
- id: z.string().trim().min(1).optional(),
1870
- name: z.string().trim().min(1).optional(),
1871
- attribution: z.string().optional(),
1872
- sourceLayers: sourceLayersSchema.optional()
2432
+ allowedOrigins: z2.array(z2.string().trim().min(1).max(253)).max(20).optional()
1873
2433
  }).strict();
1874
- var tileflowProjectSchema = z.object({
1875
- icons: z.record(identifierSchema, iconSetSchema).optional(),
1876
- themes: z.record(identifierSchema, themeConfigSchema).optional(),
1877
- tilesets: z.record(identifierSchema, tilesetConfigSchema).optional(),
1878
- maps: z.record(identifierSchema, configSchema).refine((maps) => {
1879
- return Object.keys(maps).length > 0;
1880
- }, "Expected at least one map")
2434
+ var configSchema = prototypeSafeInputSchema.pipe(tileflowMapSchema);
2435
+ var tilesetConfigSchema = z2.object({
2436
+ id: z2.string().trim().min(1).optional(),
2437
+ name: z2.string().trim().min(1).optional(),
2438
+ attribution: z2.string().optional(),
2439
+ sourceLayers: sourceLayersSchema.optional(),
2440
+ version: sourceVersionSchema.optional()
1881
2441
  }).strict();
2442
+ var captureScenesSchema = z2.record(tileflowCaptureSceneNameSchema, tileflowCaptureSceneSchema).superRefine((scenes, context) => {
2443
+ const namesByCaseFold = /* @__PURE__ */ new Map();
2444
+ for (const name of Object.keys(scenes).sort()) {
2445
+ const folded = name.toLowerCase();
2446
+ const existing = namesByCaseFold.get(folded);
2447
+ if (existing !== void 0) {
2448
+ context.addIssue({
2449
+ code: "custom",
2450
+ message: `Expected a portable scene name distinct from "${existing}" on case-insensitive filesystems`,
2451
+ path: [name]
2452
+ });
2453
+ } else {
2454
+ namesByCaseFold.set(folded, name);
2455
+ }
2456
+ }
2457
+ });
2458
+ var tileflowProjectSchema = prototypeSafeInputSchema.pipe(
2459
+ z2.object({
2460
+ icons: z2.record(identifierSchema, iconSetSchema).optional(),
2461
+ scenes: captureScenesSchema.optional(),
2462
+ themes: z2.record(identifierSchema, themeConfigSchema).optional(),
2463
+ tilesets: z2.record(identifierSchema, tilesetConfigSchema).optional(),
2464
+ maps: z2.record(identifierSchema, configSchema).refine((maps) => {
2465
+ return Object.keys(maps).length > 0;
2466
+ }, "Expected at least one map")
2467
+ }).strict()
2468
+ );
1882
2469
  var projectConfigSchema = tileflowProjectSchema;
1883
2470
  function parseTileflowMap(input) {
1884
2471
  const parsed = configSchema.parse(input);
@@ -1935,17 +2522,54 @@ function validateConfig(input) {
1935
2522
  var validateTileflowConfig = validateConfig;
1936
2523
  function isProjectConfigLike(input) {
1937
2524
  return Boolean(
1938
- input && typeof input === "object" && "maps" in input && typeof input.maps === "object"
2525
+ input && typeof input === "object" && Object.hasOwn(input, "maps") && typeof input.maps === "object"
1939
2526
  );
1940
2527
  }
2528
+ function findUnsafeObjectStructure(input) {
2529
+ if (!input || typeof input !== "object") return void 0;
2530
+ const pending = [{ path: [], value: input }];
2531
+ const visited = /* @__PURE__ */ new WeakSet();
2532
+ while (pending.length > 0) {
2533
+ const current = pending.pop();
2534
+ if (!current || visited.has(current.value)) continue;
2535
+ visited.add(current.value);
2536
+ if (!Array.isArray(current.value)) {
2537
+ const prototype = Object.getPrototypeOf(current.value);
2538
+ if (prototype !== Object.prototype && prototype !== null) {
2539
+ return { path: current.path, reason: "prototype" };
2540
+ }
2541
+ }
2542
+ for (const key of Object.keys(current.value)) {
2543
+ const path = [...current.path, Array.isArray(current.value) ? Number(key) : key];
2544
+ if (key === "__proto__") return { path, reason: "key" };
2545
+ const descriptor = Object.getOwnPropertyDescriptor(current.value, key);
2546
+ if (descriptor && "value" in descriptor && descriptor.value && typeof descriptor.value === "object") {
2547
+ pending.push({ path, value: descriptor.value });
2548
+ }
2549
+ }
2550
+ }
2551
+ return void 0;
2552
+ }
1941
2553
  function validateProjectThemeReferences(project) {
1942
2554
  const themeNames = new Set(Object.keys(project.themes ?? {}));
1943
2555
  const themeMessages = validateThemeExtends(project.themes, themeNames);
1944
2556
  const iconMessages = validateProjectIconReferences(project);
2557
+ const sceneMessages = validateProjectSceneReferences(project);
1945
2558
  const mapMessages = Object.entries(project.maps).flatMap(
1946
2559
  ([mapName, mapConfig]) => validateMapThemeReference(mapConfig, themeNames, `maps.${mapName}.theme`)
1947
2560
  );
1948
- return [...themeMessages, ...iconMessages, ...mapMessages];
2561
+ return [...themeMessages, ...iconMessages, ...sceneMessages, ...mapMessages];
2562
+ }
2563
+ function validateProjectSceneReferences(project) {
2564
+ return Object.entries(project.scenes ?? {}).flatMap(
2565
+ ([sceneName, scene]) => Object.hasOwn(project.maps, scene.map) ? [] : [
2566
+ {
2567
+ level: "error",
2568
+ path: `scenes.${sceneName}.map`,
2569
+ message: `Unknown Tileflow map "${scene.map}"`
2570
+ }
2571
+ ]
2572
+ );
1949
2573
  }
1950
2574
  function validateMapThemeReference(config, themeNames, path) {
1951
2575
  if (!config.theme) {
@@ -2015,7 +2639,7 @@ function validateProjectIconReferences(project) {
2015
2639
  const iconSets = project.icons ?? {};
2016
2640
  for (const [iconSetName, iconSet] of Object.entries(iconSets)) {
2017
2641
  const extendedName = iconSetReference(iconSet);
2018
- if (extendedName && !iconSets[extendedName]) {
2642
+ if (extendedName && !Object.hasOwn(iconSets, extendedName)) {
2019
2643
  messages.push({
2020
2644
  level: "error",
2021
2645
  path: iconSetExtendsPath(`icons.${iconSetName}`, iconSet),
@@ -2037,7 +2661,7 @@ function validateProjectIconReferences(project) {
2037
2661
  if (!isIconSetObject(mapConfig.icons) || !mapConfig.icons.extends) {
2038
2662
  continue;
2039
2663
  }
2040
- if (!iconSets[mapConfig.icons.extends]) {
2664
+ if (!Object.hasOwn(iconSets, mapConfig.icons.extends)) {
2041
2665
  messages.push({
2042
2666
  level: "error",
2043
2667
  path: `maps.${mapName}.icons.extends`,
@@ -2051,8 +2675,8 @@ function findIconSetCycle(iconSetName, iconSets, path) {
2051
2675
  if (path.includes(iconSetName)) {
2052
2676
  return [...path, iconSetName];
2053
2677
  }
2054
- const nextIconSetName = iconSetReference(iconSets?.[iconSetName]);
2055
- if (!nextIconSetName || !iconSets?.[nextIconSetName]) {
2678
+ const nextIconSetName = Object.hasOwn(iconSets ?? {}, iconSetName) ? iconSetReference(iconSets?.[iconSetName]) : void 0;
2679
+ if (!nextIconSetName || !Object.hasOwn(iconSets ?? {}, nextIconSetName)) {
2056
2680
  return void 0;
2057
2681
  }
2058
2682
  return findIconSetCycle(nextIconSetName, iconSets, [...path, iconSetName]);
@@ -2136,6 +2760,7 @@ function applyOsmBrightControls(layers, options) {
2136
2760
  return applyPoiLayerControls(
2137
2761
  layer,
2138
2762
  options.poi,
2763
+ options.colors,
2139
2764
  options.customIconSpriteId,
2140
2765
  options.customIconMapping
2141
2766
  );
@@ -2162,7 +2787,8 @@ function applyRoadLayerControls(layer, roads2) {
2162
2787
  const opacityScale = isCasing ? metrics.outlineOpacity : group === "highways" || group === "major" ? metrics.majorOpacity : group === "ferry" ? metrics.majorOpacity * 0.8 : group === "paths" ? metrics.minorOpacity * 0.77 : group === "rail" ? metrics.minorOpacity * 0.65 : metrics.minorOpacity;
2163
2788
  if (type === "line") {
2164
2789
  paint["line-opacity"] = scaleStyleValue(paint["line-opacity"], opacityScale, 1);
2165
- paint["line-width"] = scaleStyleValue(paint["line-width"], widthScale, 1);
2790
+ const scaledWidth = scaleStyleValue(paint["line-width"], widthScale, 1);
2791
+ paint["line-width"] = roads2.widthScale ? multiplyStyleValue(scaledWidth, roadClassWidthScaleExpression(roads2.widthScale)) : scaledWidth;
2166
2792
  nextLayer.paint = paint;
2167
2793
  }
2168
2794
  if (type === "fill") {
@@ -2183,6 +2809,22 @@ function applyRoadLayerControls(layer, roads2) {
2183
2809
  }
2184
2810
  return nextLayer;
2185
2811
  }
2812
+ function multiplyStyleValue(value, multiplier) {
2813
+ return ["*", legacyCameraFunctionExpression(value), multiplier];
2814
+ }
2815
+ function legacyCameraFunctionExpression(value) {
2816
+ if (!isRecord3(value) || !Array.isArray(value.stops)) return value;
2817
+ const stops = value.stops.filter(
2818
+ (stop) => Array.isArray(stop) && stop.length >= 2 && typeof stop[0] === "number"
2819
+ );
2820
+ if (stops.length === 0) return typeof value.default === "number" ? value.default : 1;
2821
+ return [
2822
+ "interpolate",
2823
+ ["exponential", typeof value.base === "number" ? value.base : 1],
2824
+ ["zoom"],
2825
+ ...stops.flatMap(([zoom, output]) => [zoom, output])
2826
+ ];
2827
+ }
2186
2828
  function isRoadLayerVisible(group, roads2) {
2187
2829
  if (roads2.detail === "none") return false;
2188
2830
  if (group === "paths") return roads2.extras.paths;
@@ -2241,10 +2883,10 @@ function applyLabelLayerControls(layer, labels2, roads2) {
2241
2883
  if ("text-field" in layout && !isHighwayShieldLayer(id)) {
2242
2884
  layout["text-field"] = labelTextField(labels2.language);
2243
2885
  }
2244
- if (group === "roads" && roads2) {
2245
- const allowedClasses = visibleRoadLabelClasses(detail, roads2).filter(
2246
- (roadClass) => roadLabelLayerClasses(id).includes(roadClass)
2247
- );
2886
+ if (group === "roads") {
2887
+ const allowedClasses = (roads2 ? visibleRoadLabelClasses(detail, roads2, labels2.roadClasses) : roadLabelClasses(detail).filter(
2888
+ (roadClass) => !labels2.roadClasses || labels2.roadClasses.includes(roadClass)
2889
+ )).filter((roadClass) => roadLabelLayerClasses(id).includes(roadClass));
2248
2890
  if (allowedClasses.length === 0) {
2249
2891
  return setLayerVisibility(layer, "none");
2250
2892
  }
@@ -2260,15 +2902,24 @@ function applyLabelLayerControls(layer, labels2, roads2) {
2260
2902
  nextLayer.layout = layout;
2261
2903
  return nextLayer;
2262
2904
  }
2263
- function applyPoiLayerControls(layer, poi2, customIconSpriteId2, customIconMapping) {
2905
+ function applyPoiLayerControls(layer, poi2, colors, customIconSpriteId2, customIconMapping) {
2264
2906
  if (poi2.mode === "none") {
2265
2907
  return setLayerVisibility(layer, "none");
2266
2908
  }
2267
2909
  const nextLayer = setLayerVisibility(layer, "visible");
2268
2910
  const layout = isRecord3(nextLayer.layout) ? { ...nextLayer.layout } : {};
2911
+ const paint = isRecord3(nextLayer.paint) ? { ...nextLayer.paint } : {};
2269
2912
  nextLayer.minzoom = Math.max(numberValue(nextLayer.minzoom) ?? 0, poi2.minZoom);
2270
2913
  if (poi2.categories && sourceLayerValue(nextLayer) === "poi") {
2271
- nextLayer.filter = combineFilters(nextLayer.filter, poiCategoryFilter(poi2.categories));
2914
+ nextLayer.filter = combineFilters(
2915
+ nextLayer.filter,
2916
+ poiCategoryFilter(poi2.categories, poi2.classMapping)
2917
+ );
2918
+ }
2919
+ if (poi2.density && sourceLayerValue(nextLayer) === "poi") {
2920
+ const densityFilter = poiDensityFilter(poi2.density, poi2.classMapping);
2921
+ if (densityFilter) nextLayer.filter = combineFilters(nextLayer.filter, densityFilter);
2922
+ layout["symbol-sort-key"] = poiPriorityExpression(poi2.classMapping);
2272
2923
  }
2273
2924
  if (poi2.labels === "none") {
2274
2925
  delete layout["text-field"];
@@ -2278,10 +2929,21 @@ function applyPoiLayerControls(layer, poi2, customIconSpriteId2, customIconMappi
2278
2929
  if (poi2.icons === false) {
2279
2930
  delete layout["icon-image"];
2280
2931
  } else if ("icon-image" in layout || customIconSpriteId2) {
2281
- layout["icon-image"] = poiIconImageExpression(customIconSpriteId2, customIconMapping);
2282
- layout["icon-optional"] = true;
2932
+ layout["icon-image"] = poiIconImageExpression(
2933
+ customIconSpriteId2,
2934
+ customIconMapping,
2935
+ poi2.classMapping
2936
+ );
2937
+ layout["icon-optional"] = !poi2.placement.coupleIconAndLabel;
2938
+ layout["icon-padding"] = poi2.placement.iconPadding;
2939
+ }
2940
+ layout["text-padding"] = poi2.placement.textPadding;
2941
+ if (poi2.placement.coupleIconAndLabel) layout["text-optional"] = false;
2942
+ if (poi2.color === "category" && "text-color" in paint) {
2943
+ paint["text-color"] = poiCategoryColorExpression(colors, poi2.classMapping);
2283
2944
  }
2284
2945
  nextLayer.layout = layout;
2946
+ nextLayer.paint = paint;
2285
2947
  return nextLayer;
2286
2948
  }
2287
2949
  function labelGroupDetail(labels2, group) {
@@ -4907,8 +5569,8 @@ var style_default = {
4907
5569
  };
4908
5570
 
4909
5571
  // src/templates/osm-bright/theme.ts
4910
- function applyOsmBrightTheme(layers, colors, theme, typography) {
4911
- if (!theme.custom && theme.name === "light") {
5572
+ function applyOsmBrightTheme(layers, colors, theme, typography, typographyOverridden = theme.typographyOverridden) {
5573
+ if (!theme.custom && theme.name === "light" && !typographyOverridden) {
4912
5574
  return layers;
4913
5575
  }
4914
5576
  return layers.map((layer) => {
@@ -4948,8 +5610,8 @@ function applyOsmBrightTheme(layers, colors, theme, typography) {
4948
5610
  paint["text-halo-color"] = colors.labels.halo;
4949
5611
  paintChanged = true;
4950
5612
  }
4951
- if (theme.typographyOverridden && "text-font" in layout) {
4952
- layout["text-font"] = textFont(typography);
5613
+ if (typographyOverridden && "text-font" in layout) {
5614
+ layout["text-font"] = textFont(typography, typographyDomain(id, sourceLayer));
4953
5615
  layoutChanged = true;
4954
5616
  }
4955
5617
  }
@@ -4962,6 +5624,16 @@ function applyOsmBrightTheme(layers, colors, theme, typography) {
4962
5624
  return nextLayer;
4963
5625
  });
4964
5626
  }
5627
+ function typographyDomain(id, sourceLayer) {
5628
+ if (sourceLayer === "poi" || id.startsWith("poi-")) return "poi";
5629
+ if (sourceLayer === "transportation_name" || id.includes("highway-name") || id.includes("highway-shield")) {
5630
+ return "roads";
5631
+ }
5632
+ if (sourceLayer === "water_name" || sourceLayer === "waterway" || id.includes("water-name")) {
5633
+ return "water";
5634
+ }
5635
+ return "places";
5636
+ }
4965
5637
  function osmBrightFillColor(id, sourceLayer, colors, theme) {
4966
5638
  if (sourceLayer === "water" || id.includes("water")) {
4967
5639
  return hasBaseColor(theme, ["water"]) || hasNestedModuleColor(theme, "hydro", "water") ? colors.hydro.water : void 0;
@@ -5181,16 +5853,22 @@ function createOsmBrightStyle(options) {
5181
5853
  layers,
5182
5854
  options.colors,
5183
5855
  options.theme,
5184
- options.typography
5856
+ options.typography,
5857
+ options.typographyOverridden
5185
5858
  );
5186
5859
  const controlledLayers = applyOsmBrightControls(themedLayers, {
5860
+ colors: options.colors,
5187
5861
  customIconMapping: options.customIconMapping,
5188
5862
  customIconSpriteId: options.customIconSpriteId,
5189
5863
  labels: options.labelsModule,
5190
5864
  poi: options.poiModule,
5191
5865
  roads: options.roadsModule
5192
5866
  });
5193
- const resolvedLayers = options.terrain ? withTerrainLayers(controlledLayers, options.terrain) : controlledLayers;
5867
+ const semanticLayers = options.roadsModule?.oneWayMarkers ? insertBeforeRoadLabels(
5868
+ controlledLayers,
5869
+ oneWayMarkerLayer(options.sourceId, "transportation", options.colors, options.typography)
5870
+ ) : controlledLayers;
5871
+ const resolvedLayers = options.terrain ? withTerrainLayers(semanticLayers, options.terrain) : semanticLayers;
5194
5872
  return {
5195
5873
  ...template,
5196
5874
  name: options.name,
@@ -5220,6 +5898,13 @@ function createOsmBrightStyle(options) {
5220
5898
  }
5221
5899
  };
5222
5900
  }
5901
+ function insertBeforeRoadLabels(layers, layer) {
5902
+ const index = layers.findIndex(
5903
+ (candidate) => candidate.type === "symbol" && candidate["source-layer"] === "transportation_name"
5904
+ );
5905
+ if (index < 0) return [...layers, layer];
5906
+ return [...layers.slice(0, index), layer, ...layers.slice(index)];
5907
+ }
5223
5908
  function cloneJson(value) {
5224
5909
  return JSON.parse(JSON.stringify(value));
5225
5910
  }
@@ -5234,7 +5919,9 @@ function remapOsmBrightSource(layer, sourceId) {
5234
5919
  }
5235
5920
 
5236
5921
  // src/compiler.ts
5922
+ var tileflowHostedNotoSansWeights = ["regular", "bold"];
5237
5923
  var defaultSourceLayers = {
5924
+ landcover: "landcover",
5238
5925
  landuse: "landuse",
5239
5926
  park: "park",
5240
5927
  water: "water",
@@ -5300,6 +5987,15 @@ function createStyle(input = {}, options = {}) {
5300
5987
  theme.preset.roads ?? defaultConfig.roads
5301
5988
  );
5302
5989
  const { mode: resolvedRoadMode, ...resolvedRoadModule } = resolvedRoads;
5990
+ const resolvedRoadModuleMetadata = {
5991
+ detail: resolvedRoadModule.detail,
5992
+ extras: resolvedRoadModule.extras,
5993
+ hierarchy: resolvedRoadModule.hierarchy,
5994
+ outline: resolvedRoadModule.outline,
5995
+ weight: resolvedRoadModule.weight,
5996
+ ...roadModule?.oneWayMarkers !== void 0 ? { oneWayMarkers: resolvedRoadModule.oneWayMarkers } : {},
5997
+ ...roadModule?.widthScale ? { widthScale: resolvedRoadModule.widthScale } : {}
5998
+ };
5303
5999
  const resolvedLabels = resolveLabels(
5304
6000
  config.labels,
5305
6001
  labelsModule,
@@ -5307,14 +6003,18 @@ function createStyle(input = {}, options = {}) {
5307
6003
  );
5308
6004
  const { mode: resolvedLabelsMode, ...resolvedLabelsModule } = resolvedLabels;
5309
6005
  const resolvedPoi = resolvePoi(config.poi, poiModule, theme.preset.poi ?? defaultConfig.poi);
6006
+ const { mode: poi2, ...resolvedPoiModule } = resolvedPoi;
5310
6007
  const labels2 = resolvedLabelsMode;
5311
- const poi2 = resolvedPoi.mode;
5312
6008
  const buildings = config.buildings ?? "flat";
5313
6009
  const colors = resolveColors(config.colors, theme.colors, {}, theme.modules);
5314
6010
  const sourceId = config.tiles?.sourceId ?? basemap.sourceId ?? "tileflow";
5315
6011
  const tileBaseUrl = normalizeBaseUrl(options.tileBaseUrl);
5316
6012
  const tileset = options.tileset ?? config.tileset ?? config.tiles?.tileset ?? basemap.tileset ?? defaultTileset;
5317
- const tilesUrl = config.tiles?.url ?? basemap.url ?? `${tileBaseUrl}/tiles/${tileset}/tiles.json`;
6013
+ const tilesetVersion = config.tiles?.version ?? basemap.version;
6014
+ const tilesUrl = withArchiveVersion(
6015
+ config.tiles?.url ?? basemap.url ?? `${tileBaseUrl}/tiles/${tileset}/tiles.json`,
6016
+ tilesetVersion
6017
+ );
5318
6018
  const typography = resolveTypography(config.typography, theme.typography);
5319
6019
  const terrain = resolveTerrain(config.terrain, tileBaseUrl);
5320
6020
  const rendererPreference = config.renderer ?? "auto";
@@ -5322,10 +6022,11 @@ function createStyle(input = {}, options = {}) {
5322
6022
  const useOsmBrightTemplate = rendererPreference === "osm-bright" || rendererPreference === "auto" && canUseOsmBrightTemplate;
5323
6023
  if (rendererPreference === "osm-bright" && !canUseOsmBrightTemplate) {
5324
6024
  throw new Error(
5325
- 'Tileflow renderer "osm-bright" is not compatible with this config. Use renderer "auto" or "generated", or remove map-level color overrides, typography, density, legacy roads/buildings, custom source layers, and unsupported modules.'
6025
+ 'Tileflow renderer "osm-bright" is not compatible with this config. Use renderer "auto" or "generated", or remove map-level color overrides, density, legacy roads/buildings, custom source layers, and unsupported modules.'
5326
6026
  );
5327
6027
  }
5328
6028
  const glyphs = config.glyphs ?? basemap.glyphs ?? `${tileBaseUrl}/fonts/{fontstack}/{range}.pbf`;
6029
+ validateHostedTypography(typography, glyphs, tileBaseUrl);
5329
6030
  const iconSet = resolveIconSet(config.icons, options.iconSets);
5330
6031
  const iconSprite = iconSet?.sprite;
5331
6032
  const basemapSprite = basemap.sprite ?? (basemap.type === "osm" ? `${tileBaseUrl}/sprites/osm-bright/sprite` : void 0);
@@ -5338,32 +6039,38 @@ function createStyle(input = {}, options = {}) {
5338
6039
  "tileflow:themeMode": theme.mode,
5339
6040
  "tileflow:density": density,
5340
6041
  "tileflow:roads": resolvedRoadMode,
5341
- "tileflow:roadsModule": roadModule ? resolvedRoadModule : void 0,
6042
+ ...roadModule ? { "tileflow:roadsModule": resolvedRoadModuleMetadata } : {},
5342
6043
  "tileflow:labels": labels2,
5343
- "tileflow:labelsModule": labelsModule ? resolvedLabelsModule : void 0,
6044
+ ...labelsModule ? { "tileflow:labelsModule": resolvedLabelsModule } : {},
5344
6045
  "tileflow:poi": poi2,
5345
- "tileflow:poiModule": poiModule,
5346
- "tileflow:icons": iconSet ? {
5347
- customSpriteId: customIconSpriteName,
5348
- mapping: iconSet.mapping,
5349
- sprite: iconSprite
5350
- } : void 0,
6046
+ ...poiModule ? { "tileflow:poiModule": resolvedPoiModule } : {},
6047
+ ...iconSet ? {
6048
+ "tileflow:icons": {
6049
+ ...customIconSpriteName ? { customSpriteId: customIconSpriteName } : {},
6050
+ ...iconSet.mapping ? { mapping: iconSet.mapping } : {},
6051
+ ...iconSprite ? { sprite: iconSprite } : {}
6052
+ }
6053
+ } : {},
5351
6054
  "tileflow:buildings": buildings,
6055
+ ...config.buildingStyle ? { "tileflow:buildingStyle": config.buildingStyle } : {},
5352
6056
  "tileflow:modules": config.modules?.map((moduleConfig) => moduleConfig.type) ?? [],
5353
6057
  "tileflow:themeModules": Object.keys(theme.moduleOverrides),
5354
- "tileflow:terrain": terrain ? {
5355
- encoding: terrain.source.encoding,
5356
- mode: terrain.mode,
5357
- sourceId: terrain.sourceId
5358
- } : void 0,
6058
+ ...terrain ? {
6059
+ "tileflow:terrain": {
6060
+ encoding: terrain.source.encoding,
6061
+ mode: terrain.mode,
6062
+ sourceId: terrain.sourceId
6063
+ }
6064
+ } : {},
5359
6065
  "tileflow:renderer": useOsmBrightTemplate ? "osm-bright" : "generated",
5360
6066
  "tileflow:rendererPreference": rendererPreference,
5361
- "tileflow:typography": typography,
6067
+ "tileflow:typography": typographyMetadata(typography),
5362
6068
  "tileflow:colors": colors,
5363
6069
  "tileflow:tileBaseUrl": tileBaseUrl,
5364
6070
  "tileflow:tileset": tileset,
6071
+ ...tilesetVersion ? { "tileflow:tilesetVersion": tilesetVersion } : {},
5365
6072
  "tileflow:tiles": "tileflow",
5366
- "tileflow:view": config.view
6073
+ ...config.view ? { "tileflow:view": config.view } : {}
5367
6074
  };
5368
6075
  if (useOsmBrightTemplate) {
5369
6076
  const style2 = createOsmBrightStyle({
@@ -5378,6 +6085,7 @@ function createStyle(input = {}, options = {}) {
5378
6085
  theme,
5379
6086
  tilesUrl,
5380
6087
  typography,
6088
+ typographyOverridden: Boolean(config.typography) || theme.typographyOverridden,
5381
6089
  labelsModule: resolvedLabels,
5382
6090
  poi: poi2,
5383
6091
  poiModule: resolvedPoi,
@@ -5400,6 +6108,7 @@ function createStyle(input = {}, options = {}) {
5400
6108
  );
5401
6109
  const layers = openMapTilesLayers(sourceId, sourceLayers, colors, {
5402
6110
  buildings,
6111
+ buildingStyle: config.buildingStyle,
5403
6112
  density,
5404
6113
  labels: resolvedLabels,
5405
6114
  poi: resolvedPoi,
@@ -5437,7 +6146,7 @@ function createStyle(input = {}, options = {}) {
5437
6146
  return style;
5438
6147
  }
5439
6148
  function createStyleFromProject(projectConfig, mapName, options = {}) {
5440
- const config = projectConfig.maps[mapName];
6149
+ const config = Object.hasOwn(projectConfig.maps, mapName) ? projectConfig.maps[mapName] : void 0;
5441
6150
  if (!config) {
5442
6151
  throw new Error(`Unknown Tileflow map: ${mapName}`);
5443
6152
  }
@@ -5456,11 +6165,13 @@ function createStyleFromProject(projectConfig, mapName, options = {}) {
5456
6165
  ...baseBasemap,
5457
6166
  tileset: resolvedTileset,
5458
6167
  attribution: baseBasemap.attribution ?? tilesetConfig?.attribution,
6168
+ version: baseBasemap.version ?? tilesetConfig?.version,
5459
6169
  ...resolvedBasemapSourceLayers ? { sourceLayers: resolvedBasemapSourceLayers } : {}
5460
6170
  },
5461
6171
  tiles: {
5462
6172
  ...tileOptions,
5463
6173
  tileset: resolvedTileset,
6174
+ version: tileOptions.version ?? baseBasemap.version ?? tilesetConfig?.version,
5464
6175
  ...resolvedTileSourceLayers ? { sourceLayers: resolvedTileSourceLayers } : {}
5465
6176
  }
5466
6177
  },
@@ -5499,7 +6210,7 @@ function resolveSourceLayers(mapLayers, tilesetLayers) {
5499
6210
  }
5500
6211
  function isOsmBrightTemplateCompatible(config, basemap, theme) {
5501
6212
  const templateTheme = theme.custom || theme.name === "light" || theme.name === "dark";
5502
- return basemap.type === "osm" && templateTheme && !hasEffectiveColorOverrides(config.colors) && !config.typography && !config.density && !config.roads && !config.buildings && !config.modules?.some(
6213
+ return basemap.type === "osm" && templateTheme && !hasEffectiveColorOverrides(config.colors) && !config.density && !config.roads && !config.buildings && !config.buildingStyle && !config.modules?.some(
5503
6214
  (moduleConfig) => moduleConfig.type !== "styleOverride" && moduleConfig.type !== "labels" && moduleConfig.type !== "poi" && moduleConfig.type !== "roads"
5504
6215
  ) && !basemap.sourceLayers && !config.tiles?.sourceLayers;
5505
6216
  }
@@ -5517,6 +6228,23 @@ function hasEffectiveColorOverrides(colors) {
5517
6228
  return typeof value === "string";
5518
6229
  });
5519
6230
  }
6231
+ function typographyMetadata(typography) {
6232
+ const base = {
6233
+ font: typography.font,
6234
+ fontFamily: typography.fontFamily,
6235
+ weight: typography.weight
6236
+ };
6237
+ return {
6238
+ ...base,
6239
+ ...sameTypographyStyle(typography.places, base) ? {} : { places: typography.places },
6240
+ ...sameTypographyStyle(typography.roads, base) ? {} : { roads: typography.roads },
6241
+ ...sameTypographyStyle(typography.water, base) ? {} : { water: typography.water },
6242
+ ...sameTypographyStyle(typography.poi, base) ? {} : { poi: typography.poi }
6243
+ };
6244
+ }
6245
+ function sameTypographyStyle(left, right) {
6246
+ return left.font === right.font && left.fontFamily === right.fontFamily && left.weight === right.weight;
6247
+ }
5520
6248
  function isRecord5(value) {
5521
6249
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
5522
6250
  }
@@ -5529,6 +6257,12 @@ function normalizeStyleBaseUrl(value) {
5529
6257
  function normalizeUrl(value) {
5530
6258
  return value.replace(/\/+$/, "");
5531
6259
  }
6260
+ function withArchiveVersion(value, version) {
6261
+ if (!version) return value;
6262
+ const url = new URL(value);
6263
+ url.searchParams.set("archiveVersion", version);
6264
+ return url.toString();
6265
+ }
5532
6266
  function resolveIconSet(icons, iconSets, path = []) {
5533
6267
  if (!icons) {
5534
6268
  return void 0;
@@ -5571,12 +6305,455 @@ function resolveSprite(basemapSprite, iconSprite) {
5571
6305
  function isSpriteReference(value) {
5572
6306
  return value.startsWith("http://") || value.startsWith("https://") || value.startsWith("/") || value.startsWith("data:");
5573
6307
  }
6308
+ function validateHostedTypography(typography, glyphs, tileBaseUrl) {
6309
+ const hostedGlyphs = `${tileBaseUrl}/fonts/{fontstack}/{range}.pbf`;
6310
+ if (glyphs !== hostedGlyphs) return;
6311
+ for (const [domain, style] of [
6312
+ ["default", typography],
6313
+ ["places", typography.places],
6314
+ ["roads", typography.roads],
6315
+ ["water", typography.water],
6316
+ ["poi", typography.poi]
6317
+ ]) {
6318
+ if (style.font === "Noto Sans" && !tileflowHostedNotoSansWeights.includes(style.weight)) {
6319
+ throw new Error(
6320
+ `Tileflow-hosted Noto Sans does not provide the ${style.weight} weight for ${domain} typography. Use regular or bold, or configure a custom glyphs URL that provides this font stack.`
6321
+ );
6322
+ }
6323
+ }
6324
+ }
5574
6325
 
5575
6326
  // src/config.ts
5576
6327
  function defineTileflow(config) {
5577
6328
  return config;
5578
6329
  }
5579
6330
 
6331
+ // src/icon-package.ts
6332
+ import { z as z3 } from "zod";
6333
+ var tileflowIconPackageFileNames = [
6334
+ "sprite.json",
6335
+ "sprite.png",
6336
+ "sprite@2x.json",
6337
+ "sprite@2x.png"
6338
+ ];
6339
+ var tileflowHostedIconIdPattern = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
6340
+ var tileflowIconPackageLimits = {
6341
+ decodeConcurrency: 4,
6342
+ maxAtlasDimension: 2048,
6343
+ maxDecodedPixelsPerIcon: 4194304,
6344
+ maxGeneratedFileBytes: 4 * 1024 * 1024,
6345
+ maxGeneratedPackageBytes: 8 * 1024 * 1024,
6346
+ maxIconCount: 256,
6347
+ maxSourceBytes: 32 * 1024 * 1024,
6348
+ maxSourceFileBytes: 1024 * 1024
6349
+ };
6350
+ var tileflowHostedAlphaCompatibility = {
6351
+ iconPackages: {
6352
+ maxGeneratedBytesPerProject: 64 * 1024 * 1024,
6353
+ maxRetainedPerProject: 24
6354
+ },
6355
+ maxMapsPerDeploy: 1
6356
+ };
6357
+ var sha256Schema = z3.string().regex(/^[a-f0-9]{64}$/);
6358
+ var tileflowIconPackageContentHashSchema = sha256Schema;
6359
+ var tileflowHostedIconIdSchema = z3.string().regex(tileflowHostedIconIdPattern);
6360
+ var tileflowRenderedIconManifestEntrySchema = z3.object({
6361
+ name: z3.string().min(1).max(256),
6362
+ pixelSha256: z3.object({
6363
+ oneX: sha256Schema,
6364
+ twoX: sha256Schema
6365
+ }).strict()
6366
+ }).strict();
6367
+ var tileflowIconPackageLabelSchema = z3.string().superRefine((value, context) => {
6368
+ if (value !== value.trim() || value.length === 0) {
6369
+ context.addIssue({
6370
+ code: "custom",
6371
+ message: "Icon package labels must be non-empty and have no surrounding whitespace"
6372
+ });
6373
+ }
6374
+ if (Array.from(value).length > 64) {
6375
+ context.addIssue({
6376
+ code: "custom",
6377
+ message: "Icon package labels must contain at most 64 Unicode characters"
6378
+ });
6379
+ }
6380
+ if (!isWellFormedUnicode(value) || containsControlCharacter(value)) {
6381
+ context.addIssue({
6382
+ code: "custom",
6383
+ message: "Icon package labels must contain printable, well-formed Unicode"
6384
+ });
6385
+ }
6386
+ });
6387
+ var spriteDimensionsSchema = z3.object({
6388
+ height: z3.number().int().positive().max(tileflowIconPackageLimits.maxAtlasDimension),
6389
+ pixelRatio: z3.union([z3.literal(1), z3.literal(2)]),
6390
+ width: z3.number().int().positive().max(tileflowIconPackageLimits.maxAtlasDimension)
6391
+ }).strict();
6392
+ function iconPackageFileSchema(name, contentType) {
6393
+ return z3.object({
6394
+ byteLength: z3.number().int().nonnegative().max(tileflowIconPackageLimits.maxGeneratedFileBytes),
6395
+ contentType: z3.literal(contentType),
6396
+ name: z3.literal(name),
6397
+ sha256: sha256Schema
6398
+ }).strict();
6399
+ }
6400
+ var tileflowIconPackageManifestSchema = z3.object({
6401
+ files: z3.tuple([
6402
+ iconPackageFileSchema("sprite.json", "application/json"),
6403
+ iconPackageFileSchema("sprite.png", "image/png"),
6404
+ iconPackageFileSchema("sprite@2x.json", "application/json"),
6405
+ iconPackageFileSchema("sprite@2x.png", "image/png")
6406
+ ]),
6407
+ format: z3.literal("tileflow-icon-package-v1"),
6408
+ iconNames: z3.array(z3.string().min(1).max(256)).min(1).max(tileflowIconPackageLimits.maxIconCount),
6409
+ renderedIcons: z3.array(tileflowRenderedIconManifestEntrySchema).min(1).max(tileflowIconPackageLimits.maxIconCount),
6410
+ sprites: z3.object({
6411
+ oneX: spriteDimensionsSchema.extend({ pixelRatio: z3.literal(1) }),
6412
+ twoX: spriteDimensionsSchema.extend({ pixelRatio: z3.literal(2) })
6413
+ }).strict()
6414
+ }).strict().superRefine((manifest, context) => {
6415
+ const sortedNames = [...manifest.iconNames].sort(compareCodeUnits);
6416
+ if (sortedNames.some((name, index) => name !== manifest.iconNames[index]) || new Set(manifest.iconNames).size !== manifest.iconNames.length) {
6417
+ context.addIssue({
6418
+ code: "custom",
6419
+ message: "Icon names must be unique and sorted by Unicode code unit",
6420
+ path: ["iconNames"]
6421
+ });
6422
+ }
6423
+ if (manifest.renderedIcons.length !== manifest.iconNames.length || manifest.renderedIcons.some((entry, index) => entry.name !== manifest.iconNames[index])) {
6424
+ context.addIssue({
6425
+ code: "custom",
6426
+ message: "Rendered icon entries must exactly match iconNames in sorted order",
6427
+ path: ["renderedIcons"]
6428
+ });
6429
+ }
6430
+ const totalBytes = manifest.files.reduce((total, file) => total + file.byteLength, 0);
6431
+ if (totalBytes > tileflowIconPackageLimits.maxGeneratedPackageBytes) {
6432
+ context.addIssue({
6433
+ code: "custom",
6434
+ message: `Generated icon package exceeds ${tileflowIconPackageLimits.maxGeneratedPackageBytes} bytes`,
6435
+ path: ["files"]
6436
+ });
6437
+ }
6438
+ if (manifest.sprites.twoX.width !== manifest.sprites.oneX.width * 2 || manifest.sprites.twoX.height !== manifest.sprites.oneX.height * 2) {
6439
+ context.addIssue({
6440
+ code: "custom",
6441
+ message: "The @2x sprite dimensions must be exactly twice the 1x dimensions",
6442
+ path: ["sprites", "twoX"]
6443
+ });
6444
+ }
6445
+ });
6446
+ function compareCodeUnits(left, right) {
6447
+ if (left < right) {
6448
+ return -1;
6449
+ }
6450
+ if (left > right) {
6451
+ return 1;
6452
+ }
6453
+ return 0;
6454
+ }
6455
+ function serializeCanonicalJson(value) {
6456
+ return JSON.stringify(sortJsonValue(value));
6457
+ }
6458
+ function serializeTileflowIconPackageManifest(input) {
6459
+ const manifest = tileflowIconPackageManifestSchema.parse(input);
6460
+ return serializeCanonicalJson(manifest);
6461
+ }
6462
+ async function sha256Hex(input) {
6463
+ const bytes = typeof input === "string" ? new TextEncoder().encode(input) : input;
6464
+ const digestInput = new Uint8Array(bytes.byteLength);
6465
+ digestInput.set(bytes);
6466
+ const digest = await crypto.subtle.digest("SHA-256", digestInput.buffer);
6467
+ return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
6468
+ }
6469
+ async function hashTileflowIconPackageManifest(input) {
6470
+ return sha256Hex(serializeTileflowIconPackageManifest(input));
6471
+ }
6472
+ async function hashTileflowRenderedIconPixels(input) {
6473
+ if (!Number.isSafeInteger(input.width) || !Number.isSafeInteger(input.height) || input.width <= 0 || input.height <= 0 || input.width > tileflowIconPackageLimits.maxAtlasDimension || input.height > tileflowIconPackageLimits.maxAtlasDimension) {
6474
+ throw new Error("Rendered icon dimensions are invalid");
6475
+ }
6476
+ if (input.pixelRatio !== 1 && input.pixelRatio !== 2) {
6477
+ throw new Error("Rendered icon pixel ratio must be 1 or 2");
6478
+ }
6479
+ const pixelCount = input.width * input.height;
6480
+ const expectedBytes = pixelCount * 4;
6481
+ if (pixelCount > tileflowIconPackageLimits.maxDecodedPixelsPerIcon || input.rgba.byteLength !== expectedBytes) {
6482
+ throw new Error(`Rendered icon RGBA must contain exactly ${expectedBytes} bytes`);
6483
+ }
6484
+ const normalized = new Uint8Array(input.rgba);
6485
+ for (let offset = 0; offset < normalized.byteLength; offset += 4) {
6486
+ if (normalized[offset + 3] === 0) {
6487
+ normalized[offset] = 0;
6488
+ normalized[offset + 1] = 0;
6489
+ normalized[offset + 2] = 0;
6490
+ }
6491
+ }
6492
+ const domain = new TextEncoder().encode("tileflow-rendered-icon-pixels-v1\0");
6493
+ const frame = new Uint8Array(domain.byteLength + 9 + normalized.byteLength);
6494
+ frame.set(domain);
6495
+ frame[domain.byteLength] = input.pixelRatio;
6496
+ const view = new DataView(frame.buffer, frame.byteOffset, frame.byteLength);
6497
+ view.setUint32(domain.byteLength + 1, input.width);
6498
+ view.setUint32(domain.byteLength + 5, input.height);
6499
+ frame.set(normalized, domain.byteLength + 9);
6500
+ return sha256Hex(frame);
6501
+ }
6502
+ function sortJsonValue(value) {
6503
+ if (Array.isArray(value)) {
6504
+ return value.map(sortJsonValue);
6505
+ }
6506
+ if (value !== null && typeof value === "object") {
6507
+ return Object.fromEntries(
6508
+ Object.entries(value).sort(([left], [right]) => compareCodeUnits(left, right)).map(([key, child]) => [key, sortJsonValue(child)])
6509
+ );
6510
+ }
6511
+ return value;
6512
+ }
6513
+ function isWellFormedUnicode(value) {
6514
+ for (let index = 0; index < value.length; index += 1) {
6515
+ const code = value.charCodeAt(index);
6516
+ if (code >= 55296 && code <= 56319) {
6517
+ const next = value.charCodeAt(index + 1);
6518
+ if (next < 56320 || next > 57343) {
6519
+ return false;
6520
+ }
6521
+ index += 1;
6522
+ continue;
6523
+ }
6524
+ if (code >= 56320 && code <= 57343) {
6525
+ return false;
6526
+ }
6527
+ }
6528
+ return true;
6529
+ }
6530
+ function containsControlCharacter(value) {
6531
+ return Array.from(value).some((character) => {
6532
+ const codePoint = character.codePointAt(0) ?? 0;
6533
+ return codePoint < 32 || codePoint >= 127 && codePoint <= 159;
6534
+ });
6535
+ }
6536
+
6537
+ // src/icon-diff.ts
6538
+ function diffTileflowIconPackageManifests(before, after) {
6539
+ const beforeIcons = new Map(
6540
+ (before?.renderedIcons ?? []).map((entry) => [entry.name, entry.pixelSha256])
6541
+ );
6542
+ const afterIcons = new Map(
6543
+ (after?.renderedIcons ?? []).map((entry) => [entry.name, entry.pixelSha256])
6544
+ );
6545
+ const names = [.../* @__PURE__ */ new Set([...beforeIcons.keys(), ...afterIcons.keys()])].sort(compareCodeUnits);
6546
+ const added = [];
6547
+ const modified = [];
6548
+ const removed = [];
6549
+ let unchangedCount = 0;
6550
+ for (const name of names) {
6551
+ const previous = beforeIcons.get(name);
6552
+ const proposed = afterIcons.get(name);
6553
+ if (!previous) {
6554
+ added.push(name);
6555
+ } else if (!proposed) {
6556
+ removed.push(name);
6557
+ } else if (previous.oneX !== proposed.oneX || previous.twoX !== proposed.twoX) {
6558
+ modified.push(name);
6559
+ } else {
6560
+ unchangedCount += 1;
6561
+ }
6562
+ }
6563
+ return {
6564
+ added,
6565
+ afterBytes: packageBytes(after),
6566
+ beforeBytes: packageBytes(before),
6567
+ modified,
6568
+ removed,
6569
+ unchangedCount
6570
+ };
6571
+ }
6572
+ function diffTileflowIconMappings(before, after) {
6573
+ const previous = before ?? {};
6574
+ const proposed = after ?? {};
6575
+ const keys = [.../* @__PURE__ */ new Set([...Object.keys(previous), ...Object.keys(proposed)])].sort(
6576
+ compareCodeUnits
6577
+ );
6578
+ const result = { added: [], changed: [], removed: [] };
6579
+ for (const key of keys) {
6580
+ const beforeTarget = previous[key];
6581
+ const afterTarget = proposed[key];
6582
+ if (beforeTarget === void 0 && afterTarget !== void 0) {
6583
+ result.added.push({ after: afterTarget, key });
6584
+ } else if (beforeTarget !== void 0 && afterTarget === void 0) {
6585
+ result.removed.push({ before: beforeTarget, key });
6586
+ } else if (beforeTarget !== afterTarget) {
6587
+ result.changed.push({ after: afterTarget, before: beforeTarget, key });
6588
+ }
6589
+ }
6590
+ return result;
6591
+ }
6592
+ function inspectTileflowIconReferences(project, mapName, availableIconNames) {
6593
+ const map = Object.hasOwn(project.maps, mapName) ? project.maps[mapName] : void 0;
6594
+ if (!map) {
6595
+ throw new Error(`Unknown Tileflow map: ${mapName}`);
6596
+ }
6597
+ const available = new Set(availableIconNames);
6598
+ const dangling = [];
6599
+ const unanalyzable = [];
6600
+ const mapping = resolveMappingReferences(project, mapName, map.icons);
6601
+ for (const reference of mapping.values()) {
6602
+ if (!available.has(reference.iconName)) {
6603
+ dangling.push({ ...reference, kind: "mapping" });
6604
+ }
6605
+ }
6606
+ const layers = [];
6607
+ collectThemeLayers(project, mapName, map.theme, layers);
6608
+ if (map.layers) {
6609
+ layers.push({ layers: map.layers, path: `maps.${mapName}.layers` });
6610
+ }
6611
+ for (const [index, moduleConfig] of (map.modules ?? []).entries()) {
6612
+ if (moduleConfig.type === "styleOverride" && moduleConfig.layers) {
6613
+ layers.push({
6614
+ layers: moduleConfig.layers,
6615
+ path: `maps.${mapName}.modules.${index}.layers`
6616
+ });
6617
+ }
6618
+ }
6619
+ for (const layerGroup of layers) {
6620
+ for (const [layerName, layer] of Object.entries(layerGroup.layers).sort(
6621
+ ([left], [right]) => compareCodeUnits(left, right)
6622
+ )) {
6623
+ if (!layer.layout || !Object.prototype.hasOwnProperty.call(layer.layout, "icon-image")) {
6624
+ continue;
6625
+ }
6626
+ const path = `${layerGroup.path}.${layerName}.layout.icon-image`;
6627
+ const inspection = inspectIconImageValue(layer.layout["icon-image"]);
6628
+ for (const iconName of inspection.managedIconNames) {
6629
+ if (!available.has(iconName)) {
6630
+ dangling.push({ iconName, kind: "style-override-literal", path });
6631
+ }
6632
+ }
6633
+ if (!inspection.complete) {
6634
+ unanalyzable.push({ kind: "style-override-expression", path });
6635
+ }
6636
+ }
6637
+ }
6638
+ const uniqueDangling = uniqueBy(
6639
+ dangling,
6640
+ (item) => `${item.kind}\0${item.path}\0${item.iconName}`
6641
+ ).sort(compareReferences);
6642
+ const uniqueUnanalyzable = uniqueBy(unanalyzable, (item) => `${item.kind}\0${item.path}`).sort(
6643
+ (left, right) => compareCodeUnits(left.path, right.path)
6644
+ );
6645
+ return {
6646
+ analysisComplete: uniqueUnanalyzable.length === 0,
6647
+ dangling: uniqueDangling,
6648
+ unanalyzable: uniqueUnanalyzable
6649
+ };
6650
+ }
6651
+ function resolveTileflowIconMapping(project, mapName) {
6652
+ const map = Object.hasOwn(project.maps, mapName) ? project.maps[mapName] : void 0;
6653
+ if (!map) {
6654
+ throw new Error(`Unknown Tileflow map: ${mapName}`);
6655
+ }
6656
+ return Object.fromEntries(
6657
+ [...resolveMappingReferences(project, mapName, map.icons).entries()].sort(([left], [right]) => compareCodeUnits(left, right)).map(([key, reference]) => [key, reference.iconName])
6658
+ );
6659
+ }
6660
+ function packageBytes(manifest) {
6661
+ return manifest?.files.reduce((total, file) => total + file.byteLength, 0) ?? 0;
6662
+ }
6663
+ function resolveMappingReferences(project, mapName, icons) {
6664
+ const result = /* @__PURE__ */ new Map();
6665
+ function apply(value, path, rootPath) {
6666
+ if (!value) {
6667
+ return;
6668
+ }
6669
+ if (typeof value === "string") {
6670
+ const referenced = Object.hasOwn(project.icons ?? {}, value) ? project.icons?.[value] : void 0;
6671
+ if (!referenced) {
6672
+ return;
6673
+ }
6674
+ if (rootPath.includes(value)) {
6675
+ throw new Error(`Circular Tileflow icon set extends: ${[...rootPath, value].join(" -> ")}`);
6676
+ }
6677
+ apply(referenced, `icons.${value}`, [...rootPath, value]);
6678
+ return;
6679
+ }
6680
+ if (value.extends) {
6681
+ apply(value.extends, path, rootPath);
6682
+ }
6683
+ for (const [key, iconName] of Object.entries(value.mapping ?? {}).sort(
6684
+ ([left], [right]) => compareCodeUnits(left, right)
6685
+ )) {
6686
+ result.set(key, { iconName, path: `${path}.mapping.${key}` });
6687
+ }
6688
+ }
6689
+ apply(icons, `maps.${mapName}.icons`, []);
6690
+ return result;
6691
+ }
6692
+ function collectThemeLayers(project, mapName, theme, result) {
6693
+ function apply(value, path, rootPath) {
6694
+ if (!value) {
6695
+ return;
6696
+ }
6697
+ if (typeof value === "string") {
6698
+ const custom = Object.hasOwn(project.themes ?? {}, value) ? project.themes?.[value] : void 0;
6699
+ if (!custom) {
6700
+ return;
6701
+ }
6702
+ if (rootPath.includes(value)) {
6703
+ throw new Error(`Circular Tileflow theme extends: ${[...rootPath, value].join(" -> ")}`);
6704
+ }
6705
+ apply(custom, `themes.${value}`, [...rootPath, value]);
6706
+ return;
6707
+ }
6708
+ if (value.extends) {
6709
+ apply(value.extends, path, rootPath);
6710
+ }
6711
+ if (value.layers) {
6712
+ result.push({ layers: value.layers, path: `${path}.layers` });
6713
+ }
6714
+ }
6715
+ apply(theme, `maps.${mapName}.theme`, []);
6716
+ }
6717
+ function inspectIconImageValue(value) {
6718
+ if (typeof value === "string") {
6719
+ const managed = managedIconName(value);
6720
+ return { complete: true, managedIconNames: managed ? [managed] : [] };
6721
+ }
6722
+ if (!Array.isArray(value)) {
6723
+ return { complete: false, managedIconNames: [] };
6724
+ }
6725
+ const managedIconNames = /* @__PURE__ */ new Set();
6726
+ collectManagedImageLiterals(value, managedIconNames);
6727
+ const isSingleLiteralImage = value.length === 2 && (value[0] === "image" || value[0] === "literal") && typeof value[1] === "string";
6728
+ return {
6729
+ complete: isSingleLiteralImage,
6730
+ managedIconNames: [...managedIconNames].sort(compareCodeUnits)
6731
+ };
6732
+ }
6733
+ function collectManagedImageLiterals(value, result) {
6734
+ if (!Array.isArray(value)) {
6735
+ return;
6736
+ }
6737
+ if ((value[0] === "image" || value[0] === "literal") && typeof value[1] === "string") {
6738
+ const managed = managedIconName(value[1]);
6739
+ if (managed) {
6740
+ result.add(managed);
6741
+ }
6742
+ }
6743
+ for (const child of value) {
6744
+ collectManagedImageLiterals(child, result);
6745
+ }
6746
+ }
6747
+ function managedIconName(value) {
6748
+ return value.startsWith("tileflow:") && value.length > "tileflow:".length ? value.slice("tileflow:".length) : null;
6749
+ }
6750
+ function uniqueBy(values, key) {
6751
+ return [...new Map(values.map((value) => [key(value), value])).values()];
6752
+ }
6753
+ function compareReferences(left, right) {
6754
+ return compareCodeUnits(left.path, right.path) || compareCodeUnits(left.kind, right.kind) || compareCodeUnits(left.iconName, right.iconName);
6755
+ }
6756
+
5580
6757
  // src/runtime.ts
5581
6758
  var defaultTileflowManifestUrl = "/tileflow/manifest.json";
5582
6759
  var defaultTileflowStyleBaseUrl = "http://localhost:3333";
@@ -5855,6 +7032,7 @@ function getTileflowLocationOrigin() {
5855
7032
  return window.location.origin;
5856
7033
  }
5857
7034
  export {
7035
+ compareCodeUnits,
5858
7036
  configSchema,
5859
7037
  createManifest,
5860
7038
  createStyle,
@@ -5863,14 +7041,21 @@ export {
5863
7041
  defaultTileflowManifestUrl,
5864
7042
  defaultTileflowStyleBaseUrl,
5865
7043
  defineTileflow,
7044
+ diffTileflowIconMappings,
7045
+ diffTileflowIconPackageManifests,
5866
7046
  getDefaultColors2 as getDefaultColors,
5867
7047
  getDefaultTileflowStyleBaseUrl,
5868
7048
  getTileflowAnalyticsApiUrl,
7049
+ hashTileflowIconPackageManifest,
7050
+ hashTileflowRenderedIconPixels,
5869
7051
  inferTileflowAnalyticsFromStyleUrl,
7052
+ inspectTileflowIconReferences,
5870
7053
  isTileflowLocalDevHost,
5871
7054
  labels,
5872
7055
  loadTileflowManifest,
5873
7056
  mergeTileflowAnalytics,
7057
+ normalizeTileflowCaptureId,
7058
+ normalizeTileflowCaptureScene,
5874
7059
  normalizeTileflowStaticImageSize,
5875
7060
  normalizeTileflowUrl,
5876
7061
  osm,
@@ -5878,17 +7063,43 @@ export {
5878
7063
  parseTileflowProject,
5879
7064
  poi,
5880
7065
  resolveTileflowAnalyticsRequestUrl,
7066
+ resolveTileflowIconMapping,
5881
7067
  resolveTileflowManifestMap,
5882
7068
  resolveTileflowMapMode,
5883
7069
  resolveTileflowRuntimeStyle,
5884
7070
  resolveTileflowStaticImageUrl,
5885
7071
  resolveTileflowStyleUrl,
5886
7072
  roads,
7073
+ serializeCanonicalJson,
7074
+ serializeTileflowIconPackageManifest,
7075
+ sha256Hex,
5887
7076
  shouldLoadTileflowManifest,
5888
7077
  startTileflowSession,
5889
7078
  styleOverride,
7079
+ tileflowCaptureApplicationTargetSchema,
7080
+ tileflowCaptureBoundsCameraSchema,
7081
+ tileflowCaptureCameraSchema,
7082
+ tileflowCaptureCenterCameraSchema,
7083
+ tileflowCaptureIdSchema,
7084
+ tileflowCaptureMapTargetSchema,
7085
+ tileflowCaptureSceneLimits,
7086
+ tileflowCaptureSceneNameSchema,
7087
+ tileflowCaptureSceneSchema,
7088
+ tileflowCaptureSceneSchemaVersion,
7089
+ tileflowCaptureTargetSchema,
7090
+ tileflowCaptureViewportSchema,
7091
+ tileflowHostedAlphaCompatibility,
7092
+ tileflowHostedIconIdPattern,
7093
+ tileflowHostedIconIdSchema,
7094
+ tileflowHostedNotoSansWeights,
7095
+ tileflowIconPackageContentHashSchema,
7096
+ tileflowIconPackageFileNames,
7097
+ tileflowIconPackageLabelSchema,
7098
+ tileflowIconPackageLimits,
7099
+ tileflowIconPackageManifestSchema,
5890
7100
  tileflowMapSchema,
5891
7101
  tileflowProjectSchema,
7102
+ tileflowRenderedIconManifestEntrySchema,
5892
7103
  tileflowThemeSchema,
5893
7104
  validateConfig,
5894
7105
  validateTileflowConfig