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

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
@@ -512,858 +512,861 @@ function rgbToHex(r, g, b) {
512
512
  return `#${[r, g, b].map((value) => value.toString(16).padStart(2, "0")).join("")}`;
513
513
  }
514
514
 
515
- // src/modules/labels/index.ts
516
- var moduleLabelDefaults = {
517
- language: "auto",
518
- places: "major",
519
- roads: "major",
520
- water: "major"
515
+ // src/modules/roads/index.ts
516
+ var moduleRoadDefaults = {
517
+ detail: "streets",
518
+ extras: { ferry: false, paths: false, rail: false },
519
+ hierarchy: "clear",
520
+ outline: "subtle",
521
+ weight: "regular"
521
522
  };
522
- var majorPlaceClasses = ["continent", "country", "state", "city", "town"];
523
523
  var highwayRoadClasses = ["motorway", "trunk"];
524
524
  var arterialRoadClasses = ["primary", "secondary", "tertiary"];
525
525
  var majorRoadClasses = [...highwayRoadClasses, ...arterialRoadClasses];
526
- var majorRoadClassSet = new Set(majorRoadClasses);
527
526
  var streetRoadClasses = ["minor"];
528
527
  var serviceRoadClasses = ["service", "track"];
529
- var majorWaterClasses = ["ocean", "sea", "lake"];
530
- var majorWaterwayClasses = ["river"];
531
- function labels(options = {}) {
528
+ var majorRoadClassSet = new Set(majorRoadClasses);
529
+ function roadClassesForDetail(detail) {
530
+ if (detail === "none") return [];
531
+ return [
532
+ ...detail === "highways" ? highwayRoadClasses : majorRoadClasses,
533
+ ...detail === "streets" || detail === "all" ? streetRoadClasses : [],
534
+ ...detail === "all" ? serviceRoadClasses : []
535
+ ];
536
+ }
537
+ function isMajorRoadClass(roadClass) {
538
+ return majorRoadClassSet.has(roadClass);
539
+ }
540
+ function roads(options = {}) {
532
541
  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 } : {}
542
+ type: "roads",
543
+ ...options.detail ? { detail: options.detail } : {},
544
+ ...options.extras ? { extras: { ...options.extras } } : {},
545
+ ...options.hierarchy ? { hierarchy: options.hierarchy } : {},
546
+ ...options.outline ? { outline: options.outline } : {},
547
+ ...options.weight ? { weight: options.weight } : {}
538
548
  };
539
549
  }
540
- function resolveLabels(legacyLabels, moduleConfig, fallback) {
541
- const fallbackMode = legacyLabels ?? fallback;
542
- const defaults = moduleConfig ? moduleLabelDefaults : labelDefaultsFromLegacyMode(fallbackMode);
550
+ function resolveRoads(legacyRoads, moduleConfig, fallback) {
551
+ const fallbackMode = legacyRoads ?? fallback;
552
+ const defaults = moduleConfig ? moduleRoadDefaults : roadDefaultsFromLegacyMode(fallbackMode);
543
553
  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
554
+ detail: moduleConfig?.detail ?? defaults.detail,
555
+ extras: {
556
+ ferry: moduleConfig?.extras?.ferry ?? defaults.extras.ferry,
557
+ paths: moduleConfig?.extras?.paths ?? defaults.extras.paths,
558
+ rail: moduleConfig?.extras?.rail ?? defaults.extras.rail
559
+ },
560
+ hierarchy: moduleConfig?.hierarchy ?? defaults.hierarchy,
561
+ outline: moduleConfig?.outline ?? defaults.outline,
562
+ weight: moduleConfig?.weight ?? defaults.weight
548
563
  };
549
564
  return {
550
565
  ...resolved,
551
- mode: moduleConfig ? legacyModeFromResolvedLabels(resolved) : fallbackMode
566
+ mode: legacyModeFromResolvedRoads(resolved)
552
567
  };
553
568
  }
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
- ];
569
+ function roadStyleMetrics(options) {
570
+ const hierarchy = {
571
+ subtle: {
572
+ majorOpacity: 0.92,
573
+ majorWidthScale: 1,
574
+ minorOpacity: 0.82,
575
+ minorWidthScale: 0.94
576
+ },
577
+ clear: {
578
+ majorOpacity: 0.92,
579
+ majorWidthScale: 1,
580
+ minorOpacity: 0.68,
581
+ minorWidthScale: 0.84
582
+ },
583
+ strong: {
584
+ majorOpacity: 0.92,
585
+ majorWidthScale: 1,
586
+ minorOpacity: 0.52,
587
+ minorWidthScale: 0.7
588
+ }
589
+ }[options.hierarchy];
590
+ const weightScale = {
591
+ thin: 0.78,
592
+ regular: 1,
593
+ bold: 1.24
594
+ }[options.weight];
595
+ const outlineOpacity = {
596
+ none: 0,
597
+ subtle: 0.18,
598
+ strong: 0.34
599
+ }[options.outline];
600
+ return {
601
+ ...hierarchy,
602
+ outlineOpacity,
603
+ weightScale
604
+ };
574
605
  }
575
- function placeLabelLayers(source, sourceLayer, colors, labelsConfig, typography) {
576
- if (labelsConfig.places === "none") return [];
577
- return [
578
- {
579
- id: "place-labels",
580
- type: "symbol",
606
+ function roadLayers(source, sourceLayer, colors, options) {
607
+ if (options.detail === "none") {
608
+ return [];
609
+ }
610
+ const metrics = roadStyleMetrics(options);
611
+ const roadClasses = roadClassesForDetail(options.detail);
612
+ const selectedMajorRoadClasses = roadClasses.filter(isMajorRoadClass);
613
+ const showsStreetRoads = roadClasses.includes("minor");
614
+ const showsServiceRoads = roadClasses.includes("service");
615
+ const transportClasses = [
616
+ ...roadClasses,
617
+ ...options.extras.paths ? ["path"] : [],
618
+ ...options.extras.rail ? ["rail", "transit"] : [],
619
+ ...options.extras.ferry ? ["ferry"] : []
620
+ ];
621
+ const roadClassColor = roadClassColorExpression(colors);
622
+ const nonTunnelRoadFilter = ["!=", ["get", "brunnel"], "tunnel"];
623
+ const majorFilter = classFilter(selectedMajorRoadClasses);
624
+ const streetFilter = classFilter(streetRoadClasses);
625
+ const serviceFilter = classFilter(serviceRoadClasses);
626
+ const roadFilter = classFilter(roadClasses);
627
+ const transportFilter = classFilter(transportClasses);
628
+ const layers = [];
629
+ if (options.outline !== "none") {
630
+ layers.push({
631
+ id: "roads-tunnels-casing",
632
+ type: "line",
581
633
  source,
582
634
  "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
- },
635
+ minzoom: 10,
636
+ filter: ["all", ["==", ["get", "brunnel"], "tunnel"], transportFilter],
592
637
  paint: {
593
- "text-color": colors.labels.primary,
594
- "text-halo-color": colors.labels.halo,
595
- "text-halo-width": 1.2
638
+ "line-color": colors.roads.tunnel,
639
+ "line-dasharray": [0.6, 0.8],
640
+ "line-opacity": roadOpacityExpression(
641
+ metrics.outlineOpacity * 0.85,
642
+ metrics.outlineOpacity * 0.85
643
+ ),
644
+ "line-width": roadWidthExpression(
645
+ [
646
+ [10, 0.8],
647
+ [14, 2.2],
648
+ [16, 4.8]
649
+ ],
650
+ metrics
651
+ )
596
652
  }
653
+ });
654
+ }
655
+ layers.push({
656
+ id: "roads-tunnels",
657
+ type: "line",
658
+ source,
659
+ "source-layer": sourceLayer,
660
+ minzoom: 10,
661
+ filter: ["all", ["==", ["get", "brunnel"], "tunnel"], transportFilter],
662
+ paint: {
663
+ "line-color": roadClassColor,
664
+ "line-dasharray": [0.6, 0.8],
665
+ "line-opacity": roadOpacityExpression(
666
+ metrics.majorOpacity * 0.28,
667
+ metrics.minorOpacity * 0.28
668
+ ),
669
+ "line-width": roadWidthExpression(
670
+ [
671
+ [10, 0.25],
672
+ [14, 1],
673
+ [16, 2.4]
674
+ ],
675
+ metrics
676
+ )
597
677
  }
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");
678
+ });
679
+ if (options.outline !== "none") {
620
680
  layers.push({
621
- id: "road-shields",
622
- type: "symbol",
681
+ id: "roads-casing",
682
+ type: "line",
623
683
  source,
624
684
  "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
685
+ minzoom: 5,
686
+ filter: ["all", nonTunnelRoadFilter, roadFilter],
687
+ paint: {
688
+ "line-color": colors.roads.casing,
689
+ "line-opacity": roadOpacityExpression(metrics.outlineOpacity, metrics.outlineOpacity),
690
+ "line-width": roadWidthExpression(
691
+ [
692
+ [6, 0.7],
693
+ [12, 1.8],
694
+ [16, 5.4]
695
+ ],
696
+ metrics
697
+ )
698
+ }
636
699
  });
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
700
  }
650
- if (minorClasses.length > 0) {
701
+ if (options.extras.ferry) {
651
702
  layers.push({
652
- id: "road-labels-minor",
653
- type: "symbol",
703
+ id: "roads-ferry",
704
+ type: "line",
654
705
  source,
655
706
  "source-layer": sourceLayer,
656
- minzoom: 14,
657
- filter: ["all", ["has", "name"], classFilter(minorClasses)],
658
- layout: sharedLayout,
659
- paint: sharedPaint
707
+ minzoom: 5,
708
+ filter: ["all", nonTunnelRoadFilter, classFilter(["ferry"])],
709
+ paint: {
710
+ "line-color": colors.roads.ferry,
711
+ "line-dasharray": [1.2, 1.1],
712
+ "line-opacity": metrics.majorOpacity * 0.8,
713
+ "line-width": zoomWidth(
714
+ [
715
+ [5, 0.25],
716
+ [12, 0.8],
717
+ [16, 2.6]
718
+ ],
719
+ metrics.weightScale
720
+ )
721
+ }
660
722
  });
661
723
  }
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",
724
+ if (options.extras.paths) {
725
+ layers.push({
726
+ id: "roads-paths",
727
+ type: "line",
677
728
  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
- },
729
+ "source-layer": sourceLayer,
730
+ minzoom: 10,
731
+ filter: ["all", nonTunnelRoadFilter, classFilter(["path"])],
732
+ paint: {
733
+ "line-color": colors.roads.path,
734
+ "line-dasharray": [0.7, 0.7],
735
+ "line-opacity": metrics.minorOpacity * 0.77,
736
+ "line-width": zoomWidth(
737
+ [
738
+ [10, 0.25],
739
+ [14, 0.8],
740
+ [16, 2.2]
741
+ ],
742
+ metrics.weightScale * metrics.minorWidthScale
743
+ )
744
+ }
745
+ });
746
+ }
747
+ if (options.extras.rail) {
748
+ layers.push({
749
+ id: "roads-rail",
750
+ type: "line",
751
+ source,
752
+ "source-layer": sourceLayer,
753
+ minzoom: 7,
754
+ filter: ["all", nonTunnelRoadFilter, classFilter(["rail", "transit"])],
755
+ paint: {
756
+ "line-color": colors.roads.rail,
757
+ "line-opacity": metrics.minorOpacity * 0.65,
758
+ "line-width": zoomWidth(
759
+ [
760
+ [7, 0.35],
761
+ [12, 0.9],
762
+ [16, 2.4]
763
+ ],
764
+ metrics.weightScale * metrics.minorWidthScale
765
+ )
766
+ }
767
+ });
768
+ }
769
+ if (showsServiceRoads) {
770
+ layers.push({
771
+ id: "roads-service",
772
+ type: "line",
773
+ source,
774
+ "source-layer": sourceLayer,
775
+ minzoom: 14,
776
+ filter: ["all", nonTunnelRoadFilter, serviceFilter],
777
+ paint: {
778
+ "line-color": roadClassColor,
779
+ "line-opacity": metrics.minorOpacity,
780
+ "line-width": zoomWidth(
781
+ [
782
+ [14, 0.6],
783
+ [16, 2.2]
784
+ ],
785
+ metrics.weightScale * metrics.minorWidthScale
786
+ )
787
+ }
788
+ });
789
+ }
790
+ if (showsStreetRoads) {
791
+ layers.push({
792
+ id: "roads-minor",
793
+ type: "line",
794
+ source,
795
+ "source-layer": sourceLayer,
796
+ minzoom: 12,
797
+ filter: ["all", nonTunnelRoadFilter, streetFilter],
798
+ paint: {
799
+ "line-color": roadClassColor,
800
+ "line-opacity": metrics.minorOpacity,
801
+ "line-width": zoomWidth(
802
+ [
803
+ [12, 0.8],
804
+ [16, 3]
805
+ ],
806
+ metrics.weightScale * metrics.minorWidthScale
807
+ )
808
+ }
809
+ });
810
+ }
811
+ layers.push(
688
812
  {
689
- id: "water-line-labels",
690
- type: "symbol",
813
+ id: "roads-major",
814
+ type: "line",
691
815
  source,
692
- "source-layer": waterNameSourceLayer,
816
+ "source-layer": sourceLayer,
693
817
  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
818
+ filter: ["all", nonTunnelRoadFilter, majorFilter],
819
+ paint: {
820
+ "line-color": roadClassColor,
821
+ "line-opacity": metrics.majorOpacity,
822
+ "line-width": zoomWidth(
823
+ [
824
+ [5, 0.35],
825
+ [12, 1.2],
826
+ [16, 4]
827
+ ],
828
+ metrics.weightScale * metrics.majorWidthScale
829
+ )
830
+ }
702
831
  },
703
832
  {
704
- id: "waterway-labels",
705
- type: "symbol",
833
+ id: "roads-bridges",
834
+ type: "line",
706
835
  source,
707
- "source-layer": waterwaySourceLayer,
836
+ "source-layer": sourceLayer,
708
837
  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
838
+ filter: ["all", ["==", ["get", "brunnel"], "bridge"], transportFilter],
839
+ paint: {
840
+ "line-color": colors.roads.bridge,
841
+ "line-opacity": roadOpacityExpression(metrics.majorOpacity, metrics.minorOpacity),
842
+ "line-width": roadWidthExpression(
843
+ [
844
+ [10, 0.45],
845
+ [14, 1.6],
846
+ [16, 4.4]
847
+ ],
848
+ metrics
849
+ )
850
+ }
718
851
  }
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));
852
+ );
853
+ return layers;
732
854
  }
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" };
855
+ function roadDefaultsFromLegacyMode(mode) {
856
+ if (mode === "hidden") {
857
+ return {
858
+ detail: "none",
859
+ extras: { ferry: false, paths: false, rail: false },
860
+ hierarchy: "clear",
861
+ outline: "subtle",
862
+ weight: "regular"
863
+ };
739
864
  }
740
- return { language: "auto", places: "all", roads: "all", water: "all" };
741
- }
742
- function legacyModeFromResolvedLabels(options) {
743
- if (options.places === "none" && options.roads === "none" && options.water === "none") {
744
- return "none";
865
+ if (mode === "soft") {
866
+ return {
867
+ detail: "streets",
868
+ extras: { ferry: false, paths: false, rail: false },
869
+ hierarchy: "subtle",
870
+ outline: "subtle",
871
+ weight: "thin"
872
+ };
745
873
  }
746
- if (options.places === "major" && options.roads === "major" && options.water === "major") {
747
- return "essential";
874
+ if (mode === "detailed") {
875
+ return {
876
+ detail: "all",
877
+ extras: { ferry: true, paths: true, rail: true },
878
+ hierarchy: "clear",
879
+ outline: "strong",
880
+ weight: "regular"
881
+ };
748
882
  }
749
- return "balanced";
883
+ return {
884
+ detail: "streets",
885
+ extras: { ferry: true, paths: true, rail: true },
886
+ hierarchy: "clear",
887
+ outline: "subtle",
888
+ weight: "regular"
889
+ };
890
+ }
891
+ function legacyModeFromResolvedRoads(options) {
892
+ if (options.detail === "none") return "hidden";
893
+ if (options.hierarchy === "subtle") return "soft";
894
+ if (options.hierarchy === "strong" || options.detail === "all") return "detailed";
895
+ return "standard";
750
896
  }
751
897
  function classFilter(classes) {
752
898
  return ["match", ["get", "class"], classes, true, false];
753
899
  }
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"]];
900
+ function roadOpacityExpression(majorOpacity, minorOpacity) {
901
+ return [
902
+ "case",
903
+ classFilter(majorRoadClasses),
904
+ majorOpacity,
905
+ classFilter(streetRoadClasses),
906
+ ["step", ["zoom"], 0, 12, minorOpacity],
907
+ classFilter(serviceRoadClasses),
908
+ ["step", ["zoom"], 0, 14, minorOpacity],
909
+ minorOpacity
910
+ ];
911
+ }
912
+ function roadWidthExpression(stops, metrics) {
913
+ return [
914
+ "case",
915
+ classFilter(majorRoadClasses),
916
+ zoomWidth(stops, metrics.weightScale * metrics.majorWidthScale),
917
+ zoomWidth(stops, metrics.weightScale * metrics.minorWidthScale)
918
+ ];
919
+ }
920
+ function zoomWidth(stops, scale) {
921
+ return [
922
+ "interpolate",
923
+ ["linear"],
924
+ ["zoom"],
925
+ ...stops.flatMap(([zoom, width]) => [zoom, width * scale])
926
+ ];
927
+ }
928
+ function roadClassColorExpression(colors) {
929
+ return [
930
+ "match",
931
+ ["get", "class"],
932
+ "motorway",
933
+ colors.roads.motorway,
934
+ "trunk",
935
+ colors.roads.trunk,
936
+ "primary",
937
+ colors.roads.primary,
938
+ "secondary",
939
+ colors.roads.secondary,
940
+ "rail",
941
+ colors.roads.rail,
942
+ "transit",
943
+ colors.roads.rail,
944
+ "ferry",
945
+ colors.roads.ferry,
946
+ "path",
947
+ colors.roads.path,
948
+ "track",
949
+ colors.roads.path,
950
+ colors.roads.minor
951
+ ];
762
952
  }
763
953
 
764
- // src/modules/poi/index.ts
765
- var defaultPoiMinZoom = {
766
- none: 24,
767
- minimal: 15,
768
- balanced: 13,
769
- full: 11
954
+ // src/modules/labels/index.ts
955
+ var moduleLabelDefaults = {
956
+ language: "auto",
957
+ places: "major",
958
+ roads: "major",
959
+ water: "major"
770
960
  };
771
- function poi(options = {}) {
961
+ var majorPlaceClasses = ["continent", "country", "state", "city", "town"];
962
+ var majorWaterClasses = ["ocean", "sea", "lake"];
963
+ var majorWaterwayClasses = ["river"];
964
+ function labels(options = {}) {
772
965
  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 } : {}
966
+ type: "labels",
967
+ ...options.language ? { language: options.language } : {},
968
+ ...options.places ? { places: options.places } : {},
969
+ ...options.roads ? { roads: options.roads } : {},
970
+ ...options.water ? { water: options.water } : {}
779
971
  };
780
972
  }
781
- function resolvePoi(legacyPoi, moduleConfig, fallback) {
782
- const mode = moduleConfig?.preset ?? legacyPoi ?? fallback;
973
+ function resolveLabels(legacyLabels, moduleConfig, fallback) {
974
+ const fallbackMode = legacyLabels ?? fallback;
975
+ const defaults = moduleConfig ? moduleLabelDefaults : labelDefaultsFromLegacyMode(fallbackMode);
976
+ const resolved = {
977
+ language: moduleConfig?.language ?? defaults.language,
978
+ places: moduleConfig?.places ?? defaults.places,
979
+ roads: moduleConfig?.roads ?? defaults.roads,
980
+ water: moduleConfig?.water ?? defaults.water
981
+ };
783
982
  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
983
+ ...resolved,
984
+ mode: moduleConfig ? legacyModeFromResolvedLabels(resolved) : fallbackMode
789
985
  };
790
986
  }
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
- }
987
+ function labelLayers(source, sourceLayers, colors, labelsConfig, roadsConfig, typography) {
988
+ return [
989
+ ...placeLabelLayers(source, sourceLayers.place, colors, labelsConfig, typography),
990
+ ...roadLabelLayers(
991
+ source,
992
+ sourceLayers.roadName,
993
+ colors,
994
+ labelsConfig,
995
+ roadsConfig,
996
+ typography
997
+ ),
998
+ ...waterLabelLayers(
999
+ source,
1000
+ sourceLayers.waterName,
1001
+ sourceLayers.waterway,
1002
+ colors,
1003
+ labelsConfig,
1004
+ typography
1005
+ )
1006
+ ];
1007
+ }
1008
+ function placeLabelLayers(source, sourceLayer, colors, labelsConfig, typography) {
1009
+ if (labelsConfig.places === "none") return [];
813
1010
  return [
814
1011
  {
815
- id: "poi-labels",
1012
+ id: "place-labels",
816
1013
  type: "symbol",
817
1014
  source,
818
1015
  "source-layer": sourceLayer,
819
- minzoom: poiConfig.minZoom,
820
- ...poiConfig.categories ? { filter: poiCategoryFilter(poiConfig.categories) } : {},
821
- layout,
1016
+ minzoom: 0,
1017
+ filter: labelsConfig.places === "major" ? ["all", ["has", "name"], classFilter2(majorPlaceClasses)] : ["has", "name"],
1018
+ layout: {
1019
+ "text-field": labelTextField(labelsConfig.language),
1020
+ "text-font": textFont(typography),
1021
+ "text-size": ["interpolate", ["linear"], ["zoom"], 3, 10, 8, 13, 14, 17],
1022
+ "text-letter-spacing": 0,
1023
+ "text-padding": 4
1024
+ },
822
1025
  paint: {
823
- "text-color": colors.poi.label,
824
- "text-halo-color": colors.poi.halo ?? alpha(colors.background, 0.78),
825
- "text-halo-width": 1
1026
+ "text-color": colors.labels.primary,
1027
+ "text-halo-color": colors.labels.halo,
1028
+ "text-halo-width": 1.2
826
1029
  }
827
1030
  }
828
1031
  ];
829
1032
  }
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";
835
- }
836
- function poiCategoryFilter(categories) {
837
- return ["in", "class", ...categories.flatMap((category) => poiCategoryClasses(category))];
838
- }
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
- );
1033
+ function roadLabelLayers(source, sourceLayer, colors, labelsConfig, roadsConfig, typography) {
1034
+ const classes = visibleRoadLabelClasses(labelsConfig.roads, roadsConfig);
1035
+ const majorClasses = classes.filter(isMajorRoadClass);
1036
+ const minorClasses = classes.filter((roadClass) => !isMajorRoadClass(roadClass));
1037
+ if (classes.length === 0) return [];
1038
+ const layers = [];
1039
+ const sharedLayout = {
1040
+ "symbol-placement": "line",
1041
+ "text-field": labelTextField(labelsConfig.language),
1042
+ "text-font": textFont(typography),
1043
+ "text-rotation-alignment": "map",
1044
+ "text-size": ["interpolate", ["linear"], ["zoom"], 11, 11, 14, 13, 17, 15]
1045
+ };
1046
+ const sharedPaint = {
1047
+ "text-color": colors.labels.road,
1048
+ "text-halo-color": colors.labels.halo,
1049
+ "text-halo-width": 1
1050
+ };
1051
+ if (majorClasses.length > 0) {
1052
+ const namedMajorClasses = majorClasses.filter((roadClass) => roadClass !== "motorway");
1053
+ layers.push({
1054
+ id: "road-shields",
1055
+ type: "symbol",
1056
+ source,
1057
+ "source-layer": sourceLayer,
1058
+ minzoom: 8,
1059
+ filter: ["all", ["has", "ref"], classFilter2(majorClasses)],
1060
+ layout: {
1061
+ "symbol-placement": "line",
1062
+ "symbol-spacing": 240,
1063
+ "text-field": ["get", "ref"],
1064
+ "text-font": textFont(typography),
1065
+ "text-rotation-alignment": "viewport",
1066
+ "text-size": 10
1067
+ },
1068
+ paint: sharedPaint
1069
+ });
1070
+ if (namedMajorClasses.length > 0) {
1071
+ layers.unshift({
1072
+ id: "road-labels-major",
1073
+ type: "symbol",
1074
+ source,
1075
+ "source-layer": sourceLayer,
1076
+ minzoom: 10,
1077
+ filter: ["all", ["has", "name"], classFilter2(namedMajorClasses)],
1078
+ layout: sharedLayout,
1079
+ paint: sharedPaint
1080
+ });
1081
+ }
846
1082
  }
847
- return [
848
- "match",
849
- ["get", "class"],
850
- ...matchParts,
851
- poiIconImageFallback("default", customIconSpriteId2, customIconMapping)
852
- ];
853
- }
854
- function poiCategoryClasses(category) {
855
- return poiCategoryClassNames[category] ?? [category];
856
- }
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]];
1083
+ if (minorClasses.length > 0) {
1084
+ layers.push({
1085
+ id: "road-labels-minor",
1086
+ type: "symbol",
1087
+ source,
1088
+ "source-layer": sourceLayer,
1089
+ minzoom: 14,
1090
+ filter: ["all", ["has", "name"], classFilter2(minorClasses)],
1091
+ layout: sharedLayout,
1092
+ paint: sharedPaint
1093
+ });
862
1094
  }
863
- return [
864
- "coalesce",
865
- ["image", `${customIconSpriteId2}:${customIconName}`],
866
- ["image", osmBrightIcon]
867
- ];
868
- }
869
- function osmBrightIconNameForSemanticIcon(iconName) {
870
- return semanticToOsmBrightIconName[iconName] ?? "marker";
1095
+ return layers;
871
1096
  }
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
-
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"
944
- };
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 = {}) {
951
- 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 } : {}
958
- };
959
- }
960
- function resolveRoads(legacyRoads, moduleConfig, fallback) {
961
- const fallbackMode = legacyRoads ?? fallback;
962
- const defaults = moduleConfig ? moduleRoadDefaults : roadDefaultsFromLegacyMode(fallbackMode);
963
- 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
973
- };
974
- return {
975
- ...resolved,
976
- mode: legacyModeFromResolvedRoads(resolved)
1097
+ function waterLabelLayers(source, waterNameSourceLayer, waterwaySourceLayer, colors, labelsConfig, typography) {
1098
+ if (labelsConfig.water === "none") return [];
1099
+ const waterFilter = labelsConfig.water === "major" ? ["all", ["has", "name"], classFilter2(majorWaterClasses)] : ["has", "name"];
1100
+ const waterwayFilter = labelsConfig.water === "major" ? ["all", ["has", "name"], classFilter2(majorWaterwayClasses)] : ["has", "name"];
1101
+ const paint = {
1102
+ "text-color": colors.labels.water,
1103
+ "text-halo-color": colors.labels.halo,
1104
+ "text-halo-width": 1
977
1105
  };
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
1106
+ return [
1107
+ {
1108
+ id: "water-labels",
1109
+ type: "symbol",
1110
+ source,
1111
+ "source-layer": waterNameSourceLayer,
1112
+ minzoom: 3,
1113
+ filter: ["all", ["==", ["geometry-type"], "Point"], waterFilter],
1114
+ layout: {
1115
+ "text-field": labelTextField(labelsConfig.language),
1116
+ "text-font": textFont(typography),
1117
+ "text-size": ["interpolate", ["linear"], ["zoom"], 3, 11, 8, 14]
1118
+ },
1119
+ paint
986
1120
  },
987
- clear: {
988
- majorOpacity: 0.92,
989
- majorWidthScale: 1,
990
- minorOpacity: 0.68,
991
- minorWidthScale: 0.84
1121
+ {
1122
+ id: "water-line-labels",
1123
+ type: "symbol",
1124
+ source,
1125
+ "source-layer": waterNameSourceLayer,
1126
+ minzoom: 5,
1127
+ filter: ["all", ["==", ["geometry-type"], "LineString"], waterFilter],
1128
+ layout: {
1129
+ "symbol-placement": "line",
1130
+ "text-field": labelTextField(labelsConfig.language),
1131
+ "text-font": textFont(typography),
1132
+ "text-size": 13
1133
+ },
1134
+ paint
992
1135
  },
993
- strong: {
994
- majorOpacity: 0.92,
995
- majorWidthScale: 1,
996
- minorOpacity: 0.52,
997
- minorWidthScale: 0.7
1136
+ {
1137
+ id: "waterway-labels",
1138
+ type: "symbol",
1139
+ source,
1140
+ "source-layer": waterwaySourceLayer,
1141
+ minzoom: 10,
1142
+ filter: ["all", ["==", ["geometry-type"], "LineString"], waterwayFilter],
1143
+ layout: {
1144
+ "symbol-placement": "line",
1145
+ "symbol-spacing": 320,
1146
+ "text-field": labelTextField(labelsConfig.language),
1147
+ "text-font": textFont(typography),
1148
+ "text-size": 12
1149
+ },
1150
+ paint
998
1151
  }
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
1014
- };
1015
- }
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
1152
  ];
1029
- const transportClasses = [
1030
- ...roadClasses,
1031
- ...options.extras.paths ? ["path"] : [],
1032
- ...options.extras.rail ? ["rail", "transit"] : [],
1033
- ...options.extras.ferry ? ["ferry"] : []
1153
+ }
1154
+ function visibleRoadLabelClasses(detail, roadsConfig) {
1155
+ if (detail === "none" || roadsConfig.detail === "none") return [];
1156
+ const visibleRoadClasses = [
1157
+ ...roadClassesForDetail(roadsConfig.detail),
1158
+ ...roadsConfig.extras.paths ? ["path"] : []
1034
1159
  ];
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
- });
1160
+ const requestedClasses = roadLabelClasses(detail);
1161
+ const visible = new Set(visibleRoadClasses);
1162
+ return requestedClasses.filter((roadClass) => visible.has(roadClass));
1163
+ }
1164
+ function roadLabelClasses(detail) {
1165
+ return [...roadClassesForDetail(detail), ...detail === "all" ? ["path"] : []];
1166
+ }
1167
+ function labelDefaultsFromLegacyMode(mode) {
1168
+ if (mode === "none") {
1169
+ return { language: "auto", places: "none", roads: "none", water: "none" };
1068
1170
  }
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",
1097
- 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
- });
1171
+ if (mode === "essential") {
1172
+ return { language: "auto", places: "major", roads: "major", water: "major" };
1114
1173
  }
1115
- if (options.extras.ferry) {
1116
- layers.push({
1117
- id: "roads-ferry",
1118
- type: "line",
1119
- 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
- });
1174
+ return { language: "auto", places: "all", roads: "all", water: "all" };
1175
+ }
1176
+ function legacyModeFromResolvedLabels(options) {
1177
+ if (options.places === "none" && options.roads === "none" && options.water === "none") {
1178
+ return "none";
1137
1179
  }
1138
- if (options.extras.paths) {
1139
- layers.push({
1140
- id: "roads-paths",
1141
- type: "line",
1142
- source,
1143
- "source-layer": sourceLayer,
1144
- minzoom: 10,
1145
- filter: ["all", nonTunnelRoadFilter, classFilter2(["path"])],
1146
- 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
- }
1159
- });
1180
+ if (options.places === "major" && options.roads === "major" && options.water === "major") {
1181
+ return "essential";
1160
1182
  }
1161
- if (options.extras.rail) {
1162
- layers.push({
1163
- id: "roads-rail",
1164
- type: "line",
1165
- source,
1166
- "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
- }
1181
- });
1183
+ return "balanced";
1184
+ }
1185
+ function classFilter2(classes) {
1186
+ return ["match", ["get", "class"], classes, true, false];
1187
+ }
1188
+ function labelTextField(language) {
1189
+ if (language === "local") {
1190
+ return ["coalesce", ["get", "name"], ["get", "name:latin"], ["get", "name:en"]];
1182
1191
  }
1183
- if (showsServiceRoads) {
1184
- layers.push({
1185
- id: "roads-service",
1186
- type: "line",
1187
- source,
1188
- "source-layer": sourceLayer,
1189
- 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
- });
1192
+ if (language === "auto") {
1193
+ return ["coalesce", ["get", "name:latin"], ["get", "name"], ["get", "name:en"]];
1194
+ }
1195
+ return ["coalesce", ["get", `name:${language}`], ["get", "name:latin"], ["get", "name"]];
1196
+ }
1197
+
1198
+ // src/modules/poi/index.ts
1199
+ var defaultPoiMinZoom = {
1200
+ none: 24,
1201
+ minimal: 15,
1202
+ balanced: 13,
1203
+ full: 11
1204
+ };
1205
+ function poi(options = {}) {
1206
+ return {
1207
+ type: "poi",
1208
+ ...options.categories ? { categories: options.categories } : {},
1209
+ ...options.icons !== void 0 ? { icons: options.icons } : {},
1210
+ ...options.labels ? { labels: options.labels } : {},
1211
+ ...options.minZoom !== void 0 ? { minZoom: options.minZoom } : {},
1212
+ ...options.preset ? { preset: options.preset } : {}
1213
+ };
1214
+ }
1215
+ function resolvePoi(legacyPoi, moduleConfig, fallback) {
1216
+ const mode = moduleConfig?.preset ?? legacyPoi ?? fallback;
1217
+ return {
1218
+ categories: moduleConfig?.categories,
1219
+ icons: moduleConfig?.icons ?? (mode === "none" ? false : "essential"),
1220
+ labels: moduleConfig?.labels ?? poiLabelsFromMode(mode),
1221
+ minZoom: moduleConfig?.minZoom ?? defaultPoiMinZoom[mode],
1222
+ mode
1223
+ };
1224
+ }
1225
+ function poiLayers(source, sourceLayer, colors, poiConfig, typography, customIconSpriteId2, customIconMapping) {
1226
+ if (poiConfig.mode === "none") {
1227
+ return [];
1228
+ }
1229
+ const layout = {
1230
+ "text-field": labelTextField("auto"),
1231
+ "text-font": textFont(typography),
1232
+ "text-size": ["interpolate", ["linear"], ["zoom"], poiConfig.minZoom, 10, 17, 12],
1233
+ "text-letter-spacing": 0,
1234
+ "text-padding": 2
1235
+ };
1236
+ if (poiConfig.labels === "none") {
1237
+ delete layout["text-field"];
1203
1238
  }
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
- }
1223
- });
1239
+ if (poiConfig.icons) {
1240
+ layout["icon-image"] = poiIconImageExpression(customIconSpriteId2, customIconMapping);
1241
+ layout["icon-optional"] = true;
1242
+ layout["icon-padding"] = 2;
1243
+ layout["icon-size"] = ["interpolate", ["linear"], ["zoom"], poiConfig.minZoom, 0.85, 17, 1];
1244
+ layout["text-anchor"] = "top";
1245
+ layout["text-offset"] = [0, 0.75];
1224
1246
  }
1225
- layers.push(
1226
- {
1227
- id: "roads-major",
1228
- type: "line",
1229
- 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
- }
1245
- },
1247
+ return [
1246
1248
  {
1247
- id: "roads-bridges",
1248
- type: "line",
1249
+ id: "poi-labels",
1250
+ type: "symbol",
1249
1251
  source,
1250
1252
  "source-layer": sourceLayer,
1251
- minzoom: 10,
1252
- filter: ["all", ["==", ["get", "brunnel"], "bridge"], transportFilter],
1253
+ minzoom: poiConfig.minZoom,
1254
+ ...poiConfig.categories ? { filter: poiCategoryFilter(poiConfig.categories) } : {},
1255
+ layout,
1253
1256
  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
- )
1257
+ "text-color": colors.poi.label,
1258
+ "text-halo-color": colors.poi.halo ?? alpha(colors.background, 0.78),
1259
+ "text-halo-width": 1
1264
1260
  }
1265
1261
  }
1266
- );
1267
- return layers;
1268
- }
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
- };
1278
- }
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
- };
1287
- }
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
- };
1296
- }
1297
- return {
1298
- detail: "streets",
1299
- extras: { ferry: true, paths: true, rail: true },
1300
- hierarchy: "clear",
1301
- outline: "subtle",
1302
- weight: "regular"
1303
- };
1262
+ ];
1304
1263
  }
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";
1264
+ function poiLabelsFromMode(mode) {
1265
+ if (mode === "none") return "none";
1266
+ if (mode === "minimal") return "minimal";
1267
+ if (mode === "full") return "full";
1268
+ return "balanced";
1310
1269
  }
1311
- function classFilter2(classes) {
1312
- return ["match", ["get", "class"], classes, true, false];
1270
+ function poiCategoryFilter(categories) {
1271
+ return ["in", "class", ...categories.flatMap((category) => poiCategoryClasses(category))];
1313
1272
  }
1314
- function roadOpacityExpression(majorOpacity, minorOpacity) {
1273
+ function poiIconImageExpression(customIconSpriteId2, customIconMapping = {}) {
1274
+ const matchParts = [];
1275
+ for (const [className, iconName] of Object.entries(poiClassIconNames)) {
1276
+ matchParts.push(
1277
+ className,
1278
+ poiIconImageFallback(iconName, customIconSpriteId2, customIconMapping)
1279
+ );
1280
+ }
1315
1281
  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
1282
+ "match",
1283
+ ["get", "class"],
1284
+ ...matchParts,
1285
+ poiIconImageFallback("default", customIconSpriteId2, customIconMapping)
1324
1286
  ];
1325
1287
  }
1326
- function roadWidthExpression(stops, metrics) {
1327
- return [
1328
- "case",
1329
- classFilter2(majorRoadClasses2),
1330
- zoomWidth(stops, metrics.weightScale * metrics.majorWidthScale),
1331
- zoomWidth(stops, metrics.weightScale * metrics.minorWidthScale)
1332
- ];
1288
+ function poiCategoryClasses(category) {
1289
+ return poiCategoryClassNames[category] ?? [category];
1333
1290
  }
1334
- function zoomWidth(stops, scale) {
1291
+ function poiIconImageFallback(iconName, customIconSpriteId2, customIconMapping) {
1292
+ const osmBrightIcon = `${osmBrightIconNameForSemanticIcon(iconName)}_11`;
1293
+ const customIconName = customIconMapping[iconName] ?? iconName;
1294
+ if (!customIconSpriteId2) {
1295
+ return ["coalesce", ["image", customIconName], ["image", osmBrightIcon]];
1296
+ }
1335
1297
  return [
1336
- "interpolate",
1337
- ["linear"],
1338
- ["zoom"],
1339
- ...stops.flatMap(([zoom, width]) => [zoom, width * scale])
1298
+ "coalesce",
1299
+ ["image", `${customIconSpriteId2}:${customIconName}`],
1300
+ ["image", osmBrightIcon]
1340
1301
  ];
1341
1302
  }
1342
- function roadClassColorExpression(colors) {
1343
- return [
1344
- "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
1365
- ];
1303
+ function osmBrightIconNameForSemanticIcon(iconName) {
1304
+ return semanticToOsmBrightIconName[iconName] ?? "marker";
1366
1305
  }
1306
+ var poiCategoryClassNames = {
1307
+ coffee: ["cafe"],
1308
+ culture: ["museum", "gallery", "theatre", "cinema", "library", "attraction"],
1309
+ education: ["school", "college", "university"],
1310
+ food: ["restaurant", "fast_food", "bar", "pub", "biergarten"],
1311
+ health: ["hospital", "pharmacy", "doctors", "dentist"],
1312
+ lodging: ["hotel", "motel", "hostel", "guest_house"],
1313
+ services: ["bank", "post", "toilet", "police", "fire_station", "town_hall"],
1314
+ shopping: ["shop", "mall", "supermarket", "marketplace"],
1315
+ transit: ["railway", "station", "bus", "bus_stop", "subway", "tram"]
1316
+ };
1317
+ var poiClassIconNames = {
1318
+ attraction: "culture",
1319
+ bank: "services",
1320
+ bar: "food",
1321
+ biergarten: "food",
1322
+ bus: "transit",
1323
+ bus_stop: "transit",
1324
+ cafe: "coffee",
1325
+ cinema: "culture",
1326
+ college: "education",
1327
+ dentist: "health",
1328
+ doctors: "health",
1329
+ fast_food: "food",
1330
+ fire_station: "services",
1331
+ gallery: "culture",
1332
+ guest_house: "lodging",
1333
+ hospital: "health",
1334
+ hostel: "lodging",
1335
+ hotel: "lodging",
1336
+ library: "culture",
1337
+ mall: "shopping",
1338
+ marketplace: "shopping",
1339
+ motel: "lodging",
1340
+ museum: "culture",
1341
+ pharmacy: "health",
1342
+ police: "services",
1343
+ post: "services",
1344
+ pub: "food",
1345
+ railway: "transit",
1346
+ restaurant: "food",
1347
+ school: "education",
1348
+ shop: "shopping",
1349
+ station: "transit",
1350
+ subway: "transit",
1351
+ supermarket: "shopping",
1352
+ theatre: "culture",
1353
+ toilet: "services",
1354
+ town_hall: "services",
1355
+ tram: "transit",
1356
+ university: "education"
1357
+ };
1358
+ var semanticToOsmBrightIconName = {
1359
+ coffee: "cafe",
1360
+ culture: "museum",
1361
+ default: "marker",
1362
+ education: "school",
1363
+ food: "restaurant",
1364
+ health: "hospital",
1365
+ lodging: "lodging",
1366
+ services: "marker",
1367
+ shopping: "shop",
1368
+ transit: "railway"
1369
+ };
1367
1370
 
1368
1371
  // src/basemaps/osm/layers.ts
1369
1372
  function openMapTilesLayers(source, sourceLayers, colors, options) {
@@ -1770,9 +1773,10 @@ var basemapSchema = z.object({
1770
1773
  tileset: z.string().trim().min(1).optional(),
1771
1774
  url: z.url().optional()
1772
1775
  }).strict();
1776
+ var roadDetailSchema = z.enum(["none", "highways", "major", "streets", "all"]);
1773
1777
  var roadsModuleSchema = z.object({
1774
1778
  type: z.literal("roads"),
1775
- detail: z.enum(["none", "highways", "major", "streets", "all"]).optional(),
1779
+ detail: roadDetailSchema.optional(),
1776
1780
  extras: z.object({
1777
1781
  ferry: z.boolean().optional(),
1778
1782
  paths: z.boolean().optional(),
@@ -1787,7 +1791,7 @@ var labelsModuleSchema = z.object({
1787
1791
  type: z.literal("labels"),
1788
1792
  language: z.string().trim().min(1).optional(),
1789
1793
  places: labelDetailSchema.optional(),
1790
- roads: labelDetailSchema.optional(),
1794
+ roads: roadDetailSchema.optional(),
1791
1795
  water: labelDetailSchema.optional()
1792
1796
  }).strict();
1793
1797
  var poiModuleSchema = z.object({
@@ -2241,10 +2245,8 @@ function applyLabelLayerControls(layer, labels2, roads2) {
2241
2245
  if ("text-field" in layout && !isHighwayShieldLayer(id)) {
2242
2246
  layout["text-field"] = labelTextField(labels2.language);
2243
2247
  }
2244
- if (group === "roads" && roads2) {
2245
- const allowedClasses = visibleRoadLabelClasses(detail, roads2).filter(
2246
- (roadClass) => roadLabelLayerClasses(id).includes(roadClass)
2247
- );
2248
+ if (group === "roads") {
2249
+ const allowedClasses = (roads2 ? visibleRoadLabelClasses(detail, roads2) : roadLabelClasses(detail)).filter((roadClass) => roadLabelLayerClasses(id).includes(roadClass));
2248
2250
  if (allowedClasses.length === 0) {
2249
2251
  return setLayerVisibility(layer, "none");
2250
2252
  }