diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index acafe31..6ef7a91 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,148 +1,148 @@ + android:theme="@style/AppThemeDayNight.NoActionBar"> + android:theme="@style/AppThemeDayNight.NoActionBar"> \ No newline at end of file diff --git a/app/src/main/assets/versatiles_eclipse_modif.json b/app/src/main/assets/versatiles_eclipse_modif.json new file mode 100644 index 0000000..9aab152 --- /dev/null +++ b/app/src/main/assets/versatiles_eclipse_modif.json @@ -0,0 +1,6266 @@ +{ + "version": 8, + "name": "versatiles-eclipse", + "metadata": { + "license": "https://creativecommons.org/publicdomain/zero/1.0/", + "maputnik:renderer": "mlgljs" + }, + "sources": { + "versatiles-shortbread": { + "attribution": "© OpenStreetMap contributors", + "tiles": ["https://tiles.versatiles.org/tiles/osm/{z}/{x}/{y}"], + "type": "vector", + "scheme": "xyz", + "bounds": [-180, -85.0511287798066, 180, 85.0511287798066], + "minzoom": 0, + "maxzoom": 14 + } + }, + "sprite": [ + { + "id": "basics", + "url": "https://tiles.versatiles.org/assets/sprites/basics/sprites" + } + ], + "glyphs": "https://tiles.versatiles.org/assets/glyphs/{fontstack}/{range}.pbf", + "layers": [ + { + "id": "background", + "type": "background", + "paint": {"background-color": "hsl(33,48%,5%)"} + }, + { + "id": "water-ocean", + "type": "fill", + "source": "versatiles-shortbread", + "source-layer": "ocean", + "paint": {"fill-color": "hsl(205,69%,15%)"} + }, + { + "id": "land-glacier", + "type": "fill", + "source": "versatiles-shortbread", + "source-layer": "water_polygons", + "filter": ["all", ["==", "kind", "glacier"]], + "paint": {"fill-color": "hsl(0,0%,0%)"} + }, + { + "id": "land-commercial", + "type": "fill", + "source": "versatiles-shortbread", + "source-layer": "land", + "filter": ["all", ["in", "kind", "commercial", "retail"]], + "paint": { + "fill-color": "hsla(324,61%,8%,0.251)", + "fill-opacity": {"stops": [[10, 0], [11, 1]]} + } + }, + { + "id": "land-industrial", + "type": "fill", + "source": "versatiles-shortbread", + "source-layer": "land", + "filter": ["all", ["in", "kind", "industrial", "quarry", "railway"]], + "paint": { + "fill-color": "hsla(49,100%,12%,0.333)", + "fill-opacity": {"stops": [[10, 0], [11, 1]]} + } + }, + { + "id": "land-residential", + "type": "fill", + "source": "versatiles-shortbread", + "source-layer": "land", + "filter": ["all", ["in", "kind", "garages", "residential"]], + "paint": { + "fill-color": "hsla(33,18%,10%,0.2)", + "fill-opacity": {"stops": [[10, 0], [11, 1]]} + } + }, + { + "id": "land-agriculture", + "type": "fill", + "source": "versatiles-shortbread", + "source-layer": "land", + "filter": [ + "all", + [ + "in", + "kind", + "brownfield", + "farmland", + "farmyard", + "greenfield", + "greenhouse_horticulture", + "orchard", + "plant_nursery", + "vineyard" + ] + ], + "paint": { + "fill-color": "hsl(43,51%,12%)", + "fill-opacity": {"stops": [[10, 0], [11, 1]]} + } + }, + { + "id": "land-waste", + "type": "fill", + "source": "versatiles-shortbread", + "source-layer": "land", + "filter": ["all", ["in", "kind", "landfill"]], + "paint": { + "fill-color": "hsl(50,29%,20%)", + "fill-opacity": {"stops": [[10, 0], [11, 1]]} + } + }, + { + "id": "land-park", + "type": "fill", + "source": "versatiles-shortbread", + "source-layer": "land", + "filter": [ + "all", + ["in", "kind", "park", "village_green", "recreation_ground"] + ], + "paint": { + "fill-color": "hsl(60,41%,25%)", + "fill-opacity": {"stops": [[11, 0], [12, 1]]} + } + }, + { + "id": "land-garden", + "type": "fill", + "source": "versatiles-shortbread", + "source-layer": "land", + "filter": ["all", ["in", "kind", "allotments", "garden"]], + "paint": { + "fill-color": "hsl(60,41%,25%)", + "fill-opacity": {"stops": [[11, 0], [12, 1]]} + } + }, + { + "id": "land-burial", + "type": "fill", + "source": "versatiles-shortbread", + "source-layer": "land", + "filter": ["all", ["in", "kind", "cemetery", "grave_yard"]], + "paint": { + "fill-color": "hsl(54,22%,17%)", + "fill-opacity": {"stops": [[13, 0], [14, 1]]} + } + }, + { + "id": "land-leisure", + "type": "fill", + "source": "versatiles-shortbread", + "source-layer": "land", + "filter": [ + "all", + ["in", "kind", "miniature_golf", "playground", "golf_course"] + ], + "paint": {"fill-color": "hsl(84,29%,10%)"} + }, + { + "id": "land-rock", + "type": "fill", + "source": "versatiles-shortbread", + "source-layer": "land", + "filter": ["all", ["in", "kind", "bare_rock", "scree", "shingle"]], + "paint": {"fill-color": "hsl(192,9%,11%)"} + }, + { + "id": "land-forest", + "type": "fill", + "source": "versatiles-shortbread", + "source-layer": "land", + "filter": ["all", ["in", "kind", "forest"]], + "paint": { + "fill-color": "hsl(100,43%,53%)", + "fill-opacity": {"stops": [[7, 0], [8, 0.1]]} + } + }, + { + "id": "land-grass", + "type": "fill", + "source": "versatiles-shortbread", + "source-layer": "land", + "filter": [ + "all", + ["in", "kind", "grass", "grassland", "meadow", "wet_meadow"] + ], + "paint": { + "fill-color": "hsl(90,41%,15%)", + "fill-opacity": {"stops": [[11, 0], [12, 1]]} + } + }, + { + "id": "land-vegetation", + "type": "fill", + "source": "versatiles-shortbread", + "source-layer": "land", + "filter": ["all", ["in", "kind", "heath", "scrub"]], + "paint": { + "fill-color": "hsl(60,41%,25%)", + "fill-opacity": {"stops": [[11, 0], [12, 1]]} + } + }, + { + "id": "land-sand", + "type": "fill", + "source": "versatiles-shortbread", + "source-layer": "land", + "filter": ["all", ["in", "kind", "beach", "sand"]], + "paint": {"fill-color": "hsl(60,57%,5%)"} + }, + { + "id": "land-wetland", + "type": "fill", + "source": "versatiles-shortbread", + "source-layer": "land", + "filter": ["all", ["in", "kind", "bog", "marsh", "string_bog", "swamp"]], + "paint": {"fill-color": "hsl(145,28%,14%)"} + }, + { + "id": "water-river", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "water_lines", + "filter": [ + "all", + ["in", "kind", "river"], + ["!=", "tunnel", true], + ["!=", "bridge", true] + ], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "hsl(205,69%,15%)", + "line-width": { + "stops": [[9, 0], [10, 3], [15, 5], [17, 9], [18, 20], [20, 60]] + } + } + }, + { + "id": "water-canal", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "water_lines", + "filter": [ + "all", + ["in", "kind", "canal"], + ["!=", "tunnel", true], + ["!=", "bridge", true] + ], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "hsl(205,69%,15%)", + "line-width": { + "stops": [[9, 0], [10, 2], [15, 4], [17, 8], [18, 17], [20, 50]] + } + } + }, + { + "id": "water-stream", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "water_lines", + "filter": [ + "all", + ["in", "kind", "stream"], + ["!=", "tunnel", true], + ["!=", "bridge", true] + ], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "hsl(205,69%,15%)", + "line-width": { + "stops": [[13, 0], [14, 1], [15, 2], [17, 6], [18, 12], [20, 30]] + } + } + }, + { + "id": "water-ditch", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "water_lines", + "filter": [ + "all", + ["in", "kind", "ditch"], + ["!=", "tunnel", true], + ["!=", "bridge", true] + ], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "hsl(205,69%,15%)", + "line-width": {"stops": [[14, 0], [15, 1], [17, 4], [18, 8], [20, 20]]} + } + }, + { + "id": "water-area", + "type": "fill", + "source": "versatiles-shortbread", + "source-layer": "water_polygons", + "filter": ["==", "kind", "water"], + "paint": { + "fill-color": "hsl(205,69%,15%)", + "fill-opacity": {"stops": [[4, 0], [6, 1]]} + } + }, + { + "id": "water-area-river", + "type": "fill", + "source": "versatiles-shortbread", + "source-layer": "water_polygons", + "filter": ["==", "kind", "river"], + "paint": { + "fill-color": "hsl(205,69%,15%)", + "fill-opacity": {"stops": [[4, 0], [6, 1]]} + } + }, + { + "id": "water-area-small", + "type": "fill", + "source": "versatiles-shortbread", + "source-layer": "water_polygons", + "filter": ["in", "kind", "reservoir", "basin", "dock"], + "paint": { + "fill-color": "hsl(205,69%,15%)", + "fill-opacity": {"stops": [[4, 0], [6, 1]]} + } + }, + { + "id": "water-dam-area", + "type": "fill", + "source": "versatiles-shortbread", + "source-layer": "dam_polygons", + "filter": ["==", "kind", "dam"], + "paint": { + "fill-color": "hsl(33,48%,5%)", + "fill-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "water-dam", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "dam_lines", + "filter": ["==", "kind", "dam"], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": {"line-color": "hsl(205,69%,15%)"} + }, + { + "id": "water-pier-area", + "type": "fill", + "source": "versatiles-shortbread", + "source-layer": "pier_polygons", + "filter": ["in", "kind", "pier", "breakwater", "groyne"], + "paint": { + "fill-color": "hsl(33,48%,5%)", + "fill-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "water-pier", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "pier_lines", + "filter": ["in", "kind", "pier", "breakwater", "groyne"], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": {"line-color": "hsl(33,48%,5%)"} + }, + { + "id": "site-dangerarea", + "type": "fill", + "source": "versatiles-shortbread", + "source-layer": "sites", + "filter": ["in", "kind", "danger_area"], + "paint": { + "fill-color": "hsl(0,100%,50%)", + "fill-outline-color": "hsl(0,100%,50%)", + "fill-opacity": 0.3, + "fill-pattern": "basics:pattern-warning" + } + }, + { + "id": "site-university", + "type": "fill", + "source": "versatiles-shortbread", + "source-layer": "sites", + "filter": ["in", "kind", "university"], + "paint": {"fill-color": "hsl(60,100%,25%)", "fill-opacity": 0.1} + }, + { + "id": "site-college", + "type": "fill", + "source": "versatiles-shortbread", + "source-layer": "sites", + "filter": ["in", "kind", "college"], + "paint": {"fill-color": "hsl(60,100%,25%)", "fill-opacity": 0.1} + }, + { + "id": "site-school", + "type": "fill", + "source": "versatiles-shortbread", + "source-layer": "sites", + "filter": ["in", "kind", "school"], + "paint": {"fill-color": "hsl(60,100%,25%)", "fill-opacity": 0.1} + }, + { + "id": "site-hospital", + "type": "fill", + "source": "versatiles-shortbread", + "source-layer": "sites", + "filter": ["in", "kind", "hospital"], + "paint": {"fill-color": "hsl(0,100%,30%)", "fill-opacity": 0.1} + }, + { + "id": "site-prison", + "type": "fill", + "source": "versatiles-shortbread", + "source-layer": "sites", + "filter": ["in", "kind", "prison"], + "paint": { + "fill-color": "hsl(305,73%,3%)", + "fill-pattern": "basics:pattern-striped", + "fill-opacity": 0.1 + } + }, + { + "id": "site-parking", + "type": "fill", + "source": "versatiles-shortbread", + "source-layer": "sites", + "filter": ["in", "kind", "parking"], + "paint": {"fill-color": "hsl(24,11%,9%)"} + }, + { + "id": "site-bicycleparking", + "type": "fill", + "source": "versatiles-shortbread", + "source-layer": "sites", + "filter": ["in", "kind", "bicycle_parking"], + "paint": {"fill-color": "hsl(24,11%,9%)"} + }, + { + "id": "site-construction", + "type": "fill", + "source": "versatiles-shortbread", + "source-layer": "sites", + "filter": ["in", "kind", "construction"], + "paint": { + "fill-color": "hsl(0,0%,34%)", + "fill-pattern": "basics:pattern-hatched_thin", + "fill-opacity": 0.1 + } + }, + { + "id": "airport-area", + "type": "fill", + "source": "versatiles-shortbread", + "source-layer": "street_polygons", + "filter": ["in", "kind", "runway", "taxiway"], + "paint": {"fill-color": "hsl(0,0%,0%)", "fill-opacity": 0.5} + }, + { + "id": "airport-taxiway:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": ["==", "kind", "taxiway"], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "hsl(36,5%,20%)", + "line-width": { + "stops": [[13, 0], [14, 2], [15, 10], [16, 14], [18, 20], [20, 40]] + } + } + }, + { + "id": "airport-runway:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": ["==", "kind", "runway"], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "hsl(36,5%,20%)", + "line-width": { + "stops": [ + [11, 0], + [12, 6], + [13, 9], + [14, 16], + [15, 24], + [16, 40], + [17, 100], + [18, 160], + [20, 300] + ] + } + } + }, + { + "id": "airport-taxiway", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": ["==", "kind", "taxiway"], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "hsl(0,0%,0%)", + "line-width": { + "stops": [[13, 0], [14, 1], [15, 8], [16, 12], [18, 18], [20, 36]] + }, + "line-opacity": {"stops": [[13, 0], [14, 1]]} + } + }, + { + "id": "airport-runway", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": ["==", "kind", "runway"], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "hsl(0,0%,0%)", + "line-width": { + "stops": [ + [11, 0], + [12, 5], + [13, 8], + [14, 14], + [15, 22], + [16, 38], + [17, 98], + [18, 158], + [20, 298] + ] + }, + "line-opacity": {"stops": [[11, 0], [12, 1]]} + } + }, + { + "id": "building:outline", + "type": "fill", + "source": "versatiles-shortbread", + "source-layer": "buildings", + "paint": { + "fill-color": "hsl(30,11%,14%)", + "fill-opacity": {"stops": [[14, 0], [15, 1]]} + } + }, + { + "id": "building", + "type": "fill", + "source": "versatiles-shortbread", + "source-layer": "buildings", + "paint": { + "fill-color": "hsl(30,38%,8%)", + "fill-opacity": {"stops": [[14, 0], [15, 1]]}, + "fill-translate": [-2, -2] + } + }, + { + "id": "tunnel-street-pedestrian-zone", + "type": "fill", + "source": "versatiles-shortbread", + "source-layer": "street_polygons", + "filter": ["all", ["==", "tunnel", true], ["==", "kind", "pedestrian"]], + "paint": { + "fill-color": "rgb(8,8,8)", + "fill-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "tunnel-way-footway:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": ["all", ["==", "tunnel", true], ["in", "kind", "footway"]], + "layout": {"line-cap": "round"}, + "paint": { + "line-width": { + "stops": [[15, 0], [16, 5], [18, 7], [19, 12], [20, 22]] + }, + "line-color": "hsl(288,13%,14%)" + } + }, + { + "id": "tunnel-way-steps:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": ["all", ["==", "tunnel", true], ["in", "kind", "steps"]], + "layout": {"line-cap": "round"}, + "paint": { + "line-width": { + "stops": [[15, 0], [16, 5], [18, 7], [19, 12], [20, 22]] + }, + "line-color": "hsl(288,13%,14%)" + } + }, + { + "id": "tunnel-way-path:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": ["all", ["==", "tunnel", true], ["in", "kind", "path"]], + "layout": {"line-cap": "round"}, + "paint": { + "line-width": { + "stops": [[15, 0], [16, 5], [18, 7], [19, 12], [20, 22]] + }, + "line-color": "hsl(288,13%,14%)" + } + }, + { + "id": "tunnel-way-cycleway:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": ["all", ["==", "tunnel", true], ["in", "kind", "cycleway"]], + "layout": {"line-cap": "round"}, + "paint": { + "line-width": { + "stops": [[15, 0], [16, 5], [18, 7], [19, 12], [20, 22]] + }, + "line-color": "hsl(203,11%,13%)" + } + }, + { + "id": "tunnel-street-track:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "track"], ["==", "tunnel", true]], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "rgb(33,33,33)", + "line-width": { + "stops": [[14, 2], [16, 4], [18, 18], [19, 48], [20, 96]] + }, + "line-opacity": {"stops": [[14, 0], [15, 1]]} + } + }, + { + "id": "tunnel-street-pedestrian:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "pedestrian"], ["==", "tunnel", true]], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "rgb(33,33,33)", + "line-width": { + "stops": [[12, 2], [14, 3], [16, 6], [18, 26], [19, 64], [20, 128]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "tunnel-street-service:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "service"], ["==", "tunnel", true]], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "rgb(37,36,34)", + "line-width": { + "stops": [[14, 1], [16, 3], [18, 12], [19, 32], [20, 48]] + }, + "line-opacity": {"stops": [[15, 0], [16, 1]]} + } + }, + { + "id": "tunnel-street-livingstreet:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "living_street"], + ["==", "tunnel", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "rgb(33,33,33)", + "line-width": { + "stops": [[12, 2], [14, 3], [16, 6], [18, 26], [19, 64], [20, 128]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "tunnel-street-residential:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "residential"], ["==", "tunnel", true]], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "rgb(33,33,33)", + "line-width": { + "stops": [[12, 2], [14, 3], [16, 6], [18, 26], [19, 64], [20, 128]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "tunnel-street-unclassified:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "unclassified"], ["==", "tunnel", true]], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "rgb(33,33,33)", + "line-width": { + "stops": [[12, 2], [14, 3], [16, 6], [18, 26], [19, 64], [20, 128]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "tunnel-street-busway:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "busway"], ["==", "tunnel", true]], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "rgb(37,36,34)", + "line-width": { + "stops": [[14, 1], [16, 3], [18, 12], [19, 32], [20, 48]] + }, + "line-opacity": {"stops": [[15, 0], [16, 1]]} + } + }, + { + "id": "tunnel-street-busguideway:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "bus_guideway"], ["==", "tunnel", true]], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "rgb(37,36,34)", + "line-width": { + "stops": [[14, 1], [16, 3], [18, 12], [19, 32], [20, 48]] + }, + "line-opacity": {"stops": [[15, 0], [16, 1]]} + } + }, + { + "id": "tunnel-street-tertiary-link:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "tertiary"], + ["==", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "rgb(33,33,33)", + "line-width": { + "stops": [[12, 2], [14, 3], [16, 6], [18, 26], [19, 64], [20, 128]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "tunnel-street-secondary-link:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 13, + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "secondary"], + ["==", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "rgb(129,71,21)", + "line-dasharray": [1, 0.3], + "line-width": {"stops": [[12, 2], [14, 3], [16, 7], [18, 14], [20, 40]]} + } + }, + { + "id": "tunnel-street-primary-link:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 13, + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "primary"], + ["==", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "rgb(129,71,21)", + "line-dasharray": [1, 0.3], + "line-width": {"stops": [[12, 2], [14, 3], [16, 7], [18, 14], [20, 40]]} + } + }, + { + "id": "tunnel-street-trunk-link:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 13, + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "trunk"], + ["==", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "rgb(129,71,21)", + "line-dasharray": [1, 0.3], + "line-width": {"stops": [[12, 2], [14, 3], [16, 7], [18, 14], [20, 40]]} + } + }, + { + "id": "tunnel-street-motorway-link:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 12, + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "motorway"], + ["==", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "rgb(129,71,21)", + "line-dasharray": [1, 0.3], + "line-width": {"stops": [[12, 2], [14, 3], [16, 7], [18, 14], [20, 40]]} + } + }, + { + "id": "tunnel-street-tertiary:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "tertiary"], + ["!=", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "rgb(33,33,33)", + "line-width": { + "stops": [[12, 2], [14, 3], [16, 6], [18, 26], [19, 64], [20, 128]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "tunnel-street-secondary:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "secondary"], + ["!=", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "rgb(129,71,21)", + "line-dasharray": [1, 0.3], + "line-width": { + "stops": [[11, 2], [14, 5], [16, 8], [18, 30], [19, 68], [20, 138]] + }, + "line-opacity": {"stops": [[11, 0], [12, 1]]} + } + }, + { + "id": "tunnel-street-primary:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "primary"], + ["!=", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "rgb(129,71,21)", + "line-dasharray": [1, 0.3], + "line-width": { + "stops": [ + [8, 0], + [9, 1], + [10, 4], + [14, 6], + [16, 12], + [18, 36], + [19, 74], + [20, 144] + ] + } + } + }, + { + "id": "tunnel-street-trunk:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "trunk"], + ["!=", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "rgb(129,71,21)", + "line-dasharray": [1, 0.3], + "line-width": { + "stops": [ + [6, 0], + [7, 2], + [10, 4], + [14, 6], + [16, 12], + [18, 36], + [19, 74], + [20, 144] + ] + } + } + }, + { + "id": "tunnel-street-motorway:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "motorway"], + ["!=", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "rgb(129,71,21)", + "line-dasharray": [1, 0.3], + "line-width": { + "stops": [ + [5, 0], + [6, 2], + [10, 5], + [14, 5], + [16, 14], + [18, 38], + [19, 84], + [20, 168] + ] + } + } + }, + { + "id": "tunnel-way-footway", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": ["all", ["==", "tunnel", true], ["in", "kind", "footway"]], + "layout": {"line-cap": "round"}, + "paint": { + "line-width": { + "stops": [[15, 0], [16, 4], [18, 6], [19, 10], [20, 20]] + }, + "line-color": "hsl(288,33%,6%)", + "line-dasharray": [1, 0.2] + } + }, + { + "id": "tunnel-way-steps", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": ["all", ["==", "tunnel", true], ["in", "kind", "steps"]], + "layout": {"line-cap": "round"}, + "paint": { + "line-width": { + "stops": [[15, 0], [16, 4], [18, 6], [19, 10], [20, 20]] + }, + "line-color": "hsl(288,33%,6%)", + "line-dasharray": [1, 0.2] + } + }, + { + "id": "tunnel-way-path", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": ["all", ["==", "tunnel", true], ["in", "kind", "path"]], + "layout": {"line-cap": "round"}, + "paint": { + "line-width": { + "stops": [[15, 0], [16, 4], [18, 6], [19, 10], [20, 20]] + }, + "line-color": "hsl(288,33%,6%)", + "line-dasharray": [1, 0.2] + } + }, + { + "id": "tunnel-way-cycleway", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": ["all", ["==", "tunnel", true], ["in", "kind", "cycleway"]], + "layout": {"line-cap": "round"}, + "paint": { + "line-width": { + "stops": [[15, 0], [16, 4], [18, 6], [19, 10], [20, 20]] + }, + "line-color": "hsl(203,30%,5%)", + "line-dasharray": [1, 0.2] + } + }, + { + "id": "tunnel-street-track", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "track"], ["==", "tunnel", true]], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "rgb(8,8,8)", + "line-width": { + "stops": [[14, 1], [16, 3], [18, 16], [19, 44], [20, 88]] + }, + "line-opacity": {"stops": [[14, 0], [15, 1]]} + } + }, + { + "id": "tunnel-street-pedestrian", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "pedestrian"], ["==", "tunnel", true]], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "rgb(8,8,8)", + "line-width": { + "stops": [[12, 1], [14, 2], [16, 5], [18, 24], [19, 60], [20, 120]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "tunnel-street-service", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "service"], ["==", "tunnel", true]], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "rgb(8,8,8)", + "line-width": { + "stops": [[14, 1], [16, 2], [18, 10], [19, 28], [20, 40]] + }, + "line-opacity": {"stops": [[15, 0], [16, 1]]} + } + }, + { + "id": "tunnel-street-livingstreet", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "living_street"], + ["==", "tunnel", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "rgb(8,8,8)", + "line-width": { + "stops": [[12, 1], [14, 2], [16, 5], [18, 24], [19, 60], [20, 120]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "tunnel-street-residential", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "residential"], ["==", "tunnel", true]], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "rgb(8,8,8)", + "line-width": { + "stops": [[12, 1], [14, 2], [16, 5], [18, 24], [19, 60], [20, 120]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "tunnel-street-unclassified", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "unclassified"], ["==", "tunnel", true]], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "rgb(8,8,8)", + "line-width": { + "stops": [[12, 1], [14, 2], [16, 5], [18, 24], [19, 60], [20, 120]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "tunnel-street-busway", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "busway"], ["==", "tunnel", true]], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "rgb(8,8,8)", + "line-width": { + "stops": [[14, 1], [16, 2], [18, 10], [19, 28], [20, 40]] + }, + "line-opacity": {"stops": [[15, 0], [16, 1]]} + } + }, + { + "id": "tunnel-street-busguideway", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "bus_guideway"], ["==", "tunnel", true]], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "rgb(8,8,8)", + "line-width": { + "stops": [[14, 1], [16, 2], [18, 10], [19, 28], [20, 40]] + }, + "line-opacity": {"stops": [[15, 0], [16, 1]]} + } + }, + { + "id": "tunnel-street-track-bicycle", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "track"], + ["==", "bicycle", "designated"], + ["==", "tunnel", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": {"line-color": "rgb(8,8,8)"} + }, + { + "id": "tunnel-street-pedestrian-bicycle", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "pedestrian"], + ["==", "bicycle", "designated"], + ["==", "tunnel", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(203,100%,3%)", + "line-width": { + "stops": [[12, 1], [14, 2], [16, 5], [18, 24], [19, 60], [20, 120]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "tunnel-street-service-bicycle", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "service"], + ["==", "bicycle", "designated"], + ["==", "tunnel", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": {"line-color": "rgb(8,8,8)"} + }, + { + "id": "tunnel-street-livingstreet-bicycle", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "living_street"], + ["==", "bicycle", "designated"], + ["==", "tunnel", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(203,100%,3%)", + "line-width": { + "stops": [[12, 1], [14, 2], [16, 5], [18, 24], [19, 60], [20, 120]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "tunnel-street-residential-bicycle", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "residential"], + ["==", "bicycle", "designated"], + ["==", "tunnel", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(203,100%,3%)", + "line-width": { + "stops": [[12, 1], [14, 2], [16, 5], [18, 24], [19, 60], [20, 120]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "tunnel-street-unclassified-bicycle", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "unclassified"], + ["==", "bicycle", "designated"], + ["==", "tunnel", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(203,100%,3%)", + "line-width": { + "stops": [[12, 1], [14, 2], [16, 5], [18, 24], [19, 60], [20, 120]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "tunnel-street-tertiary-link", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "tertiary"], + ["==", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "rgb(8,8,8)", + "line-width": { + "stops": [[12, 1], [14, 2], [16, 5], [18, 24], [19, 60], [20, 120]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "tunnel-street-secondary-link", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 13, + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "secondary"], + ["==", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "rgb(77,61,0)", + "line-width": {"stops": [[12, 1], [14, 2], [16, 5], [18, 12], [20, 38]]} + } + }, + { + "id": "tunnel-street-primary-link", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 13, + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "primary"], + ["==", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "rgb(77,61,0)", + "line-width": {"stops": [[12, 1], [14, 2], [16, 5], [18, 12], [20, 38]]} + } + }, + { + "id": "tunnel-street-trunk-link", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 13, + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "trunk"], + ["==", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "rgb(77,61,0)", + "line-width": {"stops": [[12, 1], [14, 2], [16, 5], [18, 12], [20, 38]]} + } + }, + { + "id": "tunnel-street-motorway-link", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 12, + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "motorway"], + ["==", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "rgb(107,61,0)", + "line-width": {"stops": [[12, 1], [14, 2], [16, 5], [18, 12], [20, 38]]} + } + }, + { + "id": "tunnel-street-tertiary", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "tertiary"], + ["!=", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "rgb(8,8,8)", + "line-width": { + "stops": [[12, 1], [14, 2], [16, 5], [18, 24], [19, 60], [20, 120]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "tunnel-street-secondary", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "secondary"], + ["!=", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "rgb(77,61,0)", + "line-width": { + "stops": [[11, 1], [14, 4], [16, 6], [18, 28], [19, 64], [20, 130]] + }, + "line-opacity": {"stops": [[11, 0], [12, 1]]} + } + }, + { + "id": "tunnel-street-primary", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "primary"], + ["!=", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "rgb(77,61,0)", + "line-width": { + "stops": [ + [8, 0], + [9, 2], + [10, 3], + [14, 5], + [16, 10], + [18, 34], + [19, 70], + [20, 140] + ] + }, + "line-opacity": {"stops": [[8, 0], [9, 1]]} + } + }, + { + "id": "tunnel-street-trunk", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "trunk"], + ["!=", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "rgb(77,61,0)", + "line-width": { + "stops": [ + [6, 0], + [7, 1], + [10, 3], + [14, 5], + [16, 10], + [18, 34], + [19, 70], + [20, 140] + ] + }, + "line-opacity": {"stops": [[6, 0], [7, 1]]} + } + }, + { + "id": "tunnel-street-motorway", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "motorway"], + ["!=", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "rgb(107,61,0)", + "line-width": { + "stops": [ + [5, 0], + [6, 1], + [10, 4], + [14, 4], + [16, 12], + [18, 36], + [19, 80], + [20, 160] + ] + }, + "line-opacity": {"stops": [[5, 0], [6, 1]]} + } + }, + { + "id": "tunnel-transport-tram:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": [ + "all", + ["in", "kind", "tram"], + ["!has", "service"], + ["==", "tunnel", true] + ], + "paint": { + "line-color": "hsl(208,14%,27%)", + "line-width": {"stops": [[15, 0], [16, 5], [18, 7], [20, 20]]}, + "line-dasharray": [0.1, 0.5] + } + }, + { + "id": "tunnel-transport-narrowgauge:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": [ + "all", + ["in", "kind", "narrow_gauge"], + ["!has", "service"], + ["==", "tunnel", true] + ], + "paint": { + "line-color": "hsl(208,14%,27%)", + "line-width": {"stops": [[15, 0], [16, 5], [18, 7], [20, 20]]}, + "line-dasharray": [0.1, 0.5] + } + }, + { + "id": "tunnel-transport-subway:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["in", "kind", "subway"], + ["!has", "service"], + ["==", "tunnel", true] + ], + "paint": { + "line-color": "hsl(207,23%,28%)", + "line-width": { + "stops": [ + [11, 0], + [12, 1], + [15, 3], + [16, 3], + [18, 6], + [19, 8], + [20, 10] + ] + }, + "line-opacity": {"stops": [[11, 0], [12, 0.5]]} + } + }, + { + "id": "tunnel-transport-lightrail:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 8, + "filter": [ + "all", + ["in", "kind", "light_rail"], + ["!has", "service"], + ["==", "tunnel", true] + ], + "paint": { + "line-color": "hsl(208,14%,27%)", + "line-width": {"stops": [[8, 1], [13, 1], [15, 1], [20, 14]]}, + "line-opacity": {"stops": [[11, 0], [12, 0.5]]} + } + }, + { + "id": "tunnel-transport-lightrail-service:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 14, + "filter": [ + "all", + ["in", "kind", "light_rail"], + ["has", "service"], + ["==", "tunnel", true] + ], + "paint": { + "line-color": "hsl(208,14%,27%)", + "line-width": {"stops": [[14, 0], [15, 1], [16, 1], [20, 14]]} + } + }, + { + "id": "tunnel-transport-rail:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 8, + "filter": [ + "all", + ["in", "kind", "rail"], + ["!has", "service"], + ["==", "tunnel", true] + ], + "paint": { + "line-color": "hsl(208,14%,27%)", + "line-width": {"stops": [[8, 1], [13, 1], [15, 1], [20, 14]]}, + "line-opacity": {"stops": [[8, 0], [9, 0.3]]} + } + }, + { + "id": "tunnel-transport-rail-service:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 14, + "filter": [ + "all", + ["in", "kind", "rail"], + ["has", "service"], + ["==", "tunnel", true] + ], + "paint": { + "line-color": "hsl(208,14%,27%)", + "line-width": {"stops": [[14, 0], [15, 1], [16, 1], [20, 14]]} + } + }, + { + "id": "tunnel-transport-monorail:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": ["all", ["in", "kind", "monorail"], ["==", "tunnel", true]], + "paint": { + "line-color": "hsl(208,14%,27%)", + "line-width": {"stops": [[15, 0], [16, 5], [18, 7], [20, 20]]}, + "line-dasharray": [0.1, 0.5] + } + }, + { + "id": "tunnel-transport-funicular:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": ["all", ["in", "kind", "funicular"], ["==", "tunnel", true]], + "paint": { + "line-color": "hsl(208,14%,27%)", + "line-width": {"stops": [[15, 0], [16, 5], [18, 7], [20, 20]]}, + "line-dasharray": [0.1, 0.5] + } + }, + { + "id": "tunnel-transport-tram", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 13, + "filter": [ + "all", + ["in", "kind", "tram"], + ["!has", "service"], + ["==", "tunnel", true] + ], + "paint": { + "line-width": {"stops": [[13, 0], [16, 1], [17, 2], [18, 3], [20, 5]]}, + "line-color": "hsl(208,14%,27%)" + } + }, + { + "id": "tunnel-transport-narrowgauge", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 13, + "filter": [ + "all", + ["in", "kind", "narrow_gauge"], + ["!has", "service"], + ["==", "tunnel", true] + ], + "paint": { + "line-width": {"stops": [[13, 0], [16, 1], [17, 2], [18, 3], [20, 5]]}, + "line-color": "hsl(208,14%,27%)" + } + }, + { + "id": "tunnel-transport-subway", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["in", "kind", "subway"], + ["!has", "service"], + ["==", "tunnel", true] + ], + "paint": { + "line-color": "rgb(42,55,67)", + "line-width": { + "stops": [ + [11, 0], + [12, 1], + [15, 2], + [16, 2], + [18, 5], + [19, 6], + [20, 8] + ] + }, + "line-dasharray": [2, 2], + "line-opacity": {"stops": [[14, 0], [15, 1]]} + } + }, + { + "id": "tunnel-transport-lightrail", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 14, + "filter": [ + "all", + ["in", "kind", "light_rail"], + ["!has", "service"], + ["==", "tunnel", true] + ], + "paint": { + "line-color": "rgb(44,52,59)", + "line-width": {"stops": [[14, 0], [15, 1], [20, 10]]}, + "line-dasharray": [2, 2], + "line-opacity": {"stops": [[14, 0], [15, 1]]} + } + }, + { + "id": "tunnel-transport-lightrail-service", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": [ + "all", + ["in", "kind", "light_rail"], + ["has", "service"], + ["==", "tunnel", true] + ], + "paint": { + "line-color": "rgb(44,52,59)", + "line-width": {"stops": [[15, 0], [16, 1], [20, 10]]}, + "line-dasharray": [2, 2] + } + }, + { + "id": "tunnel-transport-rail", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 14, + "filter": [ + "all", + ["in", "kind", "rail"], + ["!has", "service"], + ["==", "tunnel", true] + ], + "paint": { + "line-color": "rgb(44,52,59)", + "line-width": {"stops": [[14, 0], [15, 1], [20, 10]]}, + "line-dasharray": [2, 2], + "line-opacity": {"stops": [[14, 0], [15, 0.3]]} + } + }, + { + "id": "tunnel-transport-rail-service", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": [ + "all", + ["in", "kind", "rail"], + ["has", "service"], + ["==", "tunnel", true] + ], + "paint": { + "line-color": "rgb(44,52,59)", + "line-width": {"stops": [[15, 0], [16, 1], [20, 10]]}, + "line-dasharray": [2, 2] + } + }, + { + "id": "tunnel-transport-monorail", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 13, + "filter": ["all", ["in", "kind", "monorail"], ["==", "tunnel", true]], + "paint": { + "line-width": {"stops": [[13, 0], [16, 1], [17, 2], [18, 3], [20, 5]]}, + "line-color": "hsl(208,14%,27%)" + } + }, + { + "id": "tunnel-transport-funicular", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 13, + "filter": ["all", ["in", "kind", "funicular"], ["==", "tunnel", true]], + "paint": { + "line-width": {"stops": [[13, 0], [16, 1], [17, 2], [18, 3], [20, 5]]}, + "line-color": "hsl(208,14%,27%)" + } + }, + { + "id": "bridge", + "type": "fill", + "source": "versatiles-shortbread", + "source-layer": "bridges", + "paint": { + "fill-color": "rgb(22,17,11)", + "fill-antialias": true, + "fill-opacity": 0.8 + } + }, + { + "id": "street-pedestrian-zone", + "type": "fill", + "source": "versatiles-shortbread", + "source-layer": "street_polygons", + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["==", "kind", "pedestrian"] + ], + "paint": { + "fill-color": "rgba(16,0,20,0.25)", + "fill-opacity": {"stops": [[12, 0], [13, 1], [14, 0], [15, 1]]} + } + }, + { + "id": "way-footway:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "footway"] + ], + "layout": {"line-cap": "round"}, + "paint": { + "line-width": { + "stops": [[15, 0], [16, 5], [18, 7], [19, 12], [20, 22]] + }, + "line-color": "rgb(40,26,44)" + } + }, + { + "id": "way-steps:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "steps"] + ], + "layout": {"line-cap": "round"}, + "paint": { + "line-width": { + "stops": [[15, 0], [16, 5], [18, 7], [19, 12], [20, 22]] + }, + "line-color": "rgb(40,26,44)" + } + }, + { + "id": "way-path:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "path"] + ], + "layout": {"line-cap": "round"}, + "paint": { + "line-width": { + "stops": [[15, 0], [16, 5], [18, 7], [19, 12], [20, 22]] + }, + "line-color": "rgb(40,26,44)" + } + }, + { + "id": "way-cycleway:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "cycleway"] + ], + "layout": {"line-cap": "round"}, + "paint": { + "line-width": { + "stops": [[15, 0], [16, 5], [18, 7], [19, 12], [20, 22]] + }, + "line-color": "rgb(26,34,40)" + } + }, + { + "id": "street-track:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "track"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(36,5%,20%)", + "line-width": { + "stops": [[14, 2], [16, 4], [18, 18], [19, 48], [20, 96]] + }, + "line-opacity": {"stops": [[14, 0], [15, 1]]} + } + }, + { + "id": "street-pedestrian:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "pedestrian"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(36,5%,20%)", + "line-width": { + "stops": [[12, 2], [14, 3], [16, 6], [18, 26], [19, 64], [20, 128]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "street-service:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "service"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "rgb(37,36,34)", + "line-width": { + "stops": [[14, 1], [16, 3], [18, 12], [19, 32], [20, 48]] + }, + "line-opacity": {"stops": [[15, 0], [16, 1]]} + } + }, + { + "id": "street-livingstreet:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "living_street"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(36,5%,20%)", + "line-width": { + "stops": [[12, 2], [14, 3], [16, 6], [18, 26], [19, 64], [20, 128]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "street-residential:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "residential"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(36,5%,20%)", + "line-width": { + "stops": [[12, 2], [14, 3], [16, 6], [18, 26], [19, 64], [20, 128]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "street-unclassified:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "unclassified"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(36,5%,20%)", + "line-width": { + "stops": [[12, 2], [14, 3], [16, 6], [18, 26], [19, 64], [20, 128]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "street-busway:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "busway"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "rgb(37,36,34)", + "line-width": { + "stops": [[14, 1], [16, 3], [18, 12], [19, 32], [20, 48]] + }, + "line-opacity": {"stops": [[15, 0], [16, 1]]} + } + }, + { + "id": "street-busguideway:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "bus_guideway"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "rgb(37,36,34)", + "line-width": { + "stops": [[14, 1], [16, 3], [18, 12], [19, 32], [20, 48]] + }, + "line-opacity": {"stops": [[15, 0], [16, 1]]} + } + }, + { + "id": "street-tertiary-link:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "tertiary"], + ["==", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(36,5%,20%)", + "line-width": { + "stops": [[12, 2], [14, 3], [16, 6], [18, 26], [19, 64], [20, 128]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "street-secondary-link:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 13, + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "secondary"], + ["==", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(28,72%,31%)", + "line-width": {"stops": [[12, 2], [14, 3], [16, 7], [18, 14], [20, 40]]} + } + }, + { + "id": "street-primary-link:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 13, + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "primary"], + ["==", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(28,72%,31%)", + "line-width": {"stops": [[12, 2], [14, 3], [16, 7], [18, 14], [20, 40]]} + } + }, + { + "id": "street-trunk-link:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 13, + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "trunk"], + ["==", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(28,72%,31%)", + "line-width": {"stops": [[12, 2], [14, 3], [16, 7], [18, 14], [20, 40]]} + } + }, + { + "id": "street-motorway-link:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 12, + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "motorway"], + ["==", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(28,72%,31%)", + "line-width": {"stops": [[12, 2], [14, 3], [16, 7], [18, 14], [20, 40]]} + } + }, + { + "id": "street-tertiary:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "tertiary"], + ["!=", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(36,5%,20%)", + "line-width": { + "stops": [[12, 2], [14, 3], [16, 6], [18, 26], [19, 64], [20, 128]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "street-secondary:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "secondary"], + ["!=", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(28,72%,31%)", + "line-width": { + "stops": [[11, 2], [14, 5], [16, 8], [18, 30], [19, 68], [20, 138]] + }, + "line-opacity": {"stops": [[11, 0], [12, 1]]} + } + }, + { + "id": "street-primary:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "primary"], + ["!=", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(28,72%,31%)", + "line-width": { + "stops": [ + [8, 0], + [9, 1], + [10, 4], + [14, 6], + [16, 12], + [18, 36], + [19, 74], + [20, 144] + ] + } + } + }, + { + "id": "street-trunk:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "trunk"], + ["!=", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(28,72%,31%)", + "line-width": { + "stops": [ + [6, 0], + [7, 2], + [10, 4], + [14, 6], + [16, 12], + [18, 36], + [19, 74], + [20, 144] + ] + } + } + }, + { + "id": "street-motorway:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "motorway"], + ["!=", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(28,72%,31%)", + "line-width": { + "stops": [ + [5, 0], + [6, 2], + [10, 5], + [14, 5], + [16, 14], + [18, 38], + [19, 84], + [20, 168] + ] + } + } + }, + { + "id": "way-footway", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "footway"] + ], + "layout": {"line-cap": "round"}, + "paint": { + "line-width": { + "stops": [[15, 0], [16, 4], [18, 6], [19, 10], [20, 20]] + }, + "line-color": "rgb(16,0,20)" + } + }, + { + "id": "way-steps", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "steps"] + ], + "layout": {"line-cap": "round"}, + "paint": { + "line-width": { + "stops": [[15, 0], [16, 4], [18, 6], [19, 10], [20, 20]] + }, + "line-color": "rgb(16,0,20)" + } + }, + { + "id": "way-path", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "path"] + ], + "layout": {"line-cap": "round"}, + "paint": { + "line-width": { + "stops": [[15, 0], [16, 4], [18, 6], [19, 10], [20, 20]] + }, + "line-color": "rgb(16,0,20)" + } + }, + { + "id": "way-cycleway", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "cycleway"] + ], + "layout": {"line-cap": "round"}, + "paint": { + "line-width": { + "stops": [[15, 0], [16, 4], [18, 6], [19, 10], [20, 20]] + }, + "line-color": "hsl(203,100%,3%)" + } + }, + { + "id": "street-track", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "track"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(0,0%,0%)", + "line-width": { + "stops": [[14, 1], [16, 3], [18, 16], [19, 44], [20, 88]] + }, + "line-opacity": {"stops": [[14, 0], [15, 1]]} + } + }, + { + "id": "street-pedestrian", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "pedestrian"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(288,100%,4%)", + "line-width": { + "stops": [[12, 1], [14, 2], [16, 5], [18, 24], [19, 60], [20, 120]] + }, + "line-opacity": {"stops": [[12, 0], [13, 0], [14, 1]]} + } + }, + { + "id": "street-service", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "service"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "rgb(8,8,8)", + "line-width": { + "stops": [[14, 1], [16, 2], [18, 10], [19, 28], [20, 40]] + }, + "line-opacity": {"stops": [[15, 0], [16, 1]]} + } + }, + { + "id": "street-livingstreet", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "living_street"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(0,0%,0%)", + "line-width": { + "stops": [[12, 1], [14, 2], [16, 5], [18, 24], [19, 60], [20, 120]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "street-residential", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "residential"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(0,0%,0%)", + "line-width": { + "stops": [[12, 1], [14, 2], [16, 5], [18, 24], [19, 60], [20, 120]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "street-unclassified", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "unclassified"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(0,0%,0%)", + "line-width": { + "stops": [[12, 1], [14, 2], [16, 5], [18, 24], [19, 60], [20, 120]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "street-busway", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "busway"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "rgb(8,8,8)", + "line-width": { + "stops": [[14, 1], [16, 2], [18, 10], [19, 28], [20, 40]] + }, + "line-opacity": {"stops": [[15, 0], [16, 1]]} + } + }, + { + "id": "street-busguideway", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "bus_guideway"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "rgb(8,8,8)", + "line-width": { + "stops": [[14, 1], [16, 2], [18, 10], [19, 28], [20, 40]] + }, + "line-opacity": {"stops": [[15, 0], [16, 1]]} + } + }, + { + "id": "street-track-bicycle", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "track"], + ["==", "bicycle", "designated"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": {"line-color": "hsl(0,0%,0%)"} + }, + { + "id": "street-pedestrian-bicycle", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "pedestrian"], + ["==", "bicycle", "designated"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(203,100%,3%)", + "line-width": { + "stops": [[12, 1], [14, 2], [16, 5], [18, 24], [19, 60], [20, 120]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "street-service-bicycle", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "service"], + ["==", "bicycle", "designated"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": {"line-color": "hsl(0,0%,0%)"} + }, + { + "id": "street-livingstreet-bicycle", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "living_street"], + ["==", "bicycle", "designated"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(203,100%,3%)", + "line-width": { + "stops": [[12, 1], [14, 2], [16, 5], [18, 24], [19, 60], [20, 120]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "street-residential-bicycle", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "residential"], + ["==", "bicycle", "designated"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(203,100%,3%)", + "line-width": { + "stops": [[12, 1], [14, 2], [16, 5], [18, 24], [19, 60], [20, 120]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "street-unclassified-bicycle", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "unclassified"], + ["==", "bicycle", "designated"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(203,100%,3%)", + "line-width": { + "stops": [[12, 1], [14, 2], [16, 5], [18, 24], [19, 60], [20, 120]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "street-tertiary-link", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "tertiary"], + ["==", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(0,0%,0%)", + "line-width": { + "stops": [[12, 1], [14, 2], [16, 5], [18, 24], [19, 60], [20, 120]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "street-secondary-link", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 13, + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "secondary"], + ["==", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(48,100%,17%)", + "line-width": {"stops": [[12, 1], [14, 2], [16, 5], [18, 12], [20, 38]]} + } + }, + { + "id": "street-primary-link", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 13, + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "primary"], + ["==", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(48,100%,17%)", + "line-width": {"stops": [[12, 1], [14, 2], [16, 5], [18, 12], [20, 38]]} + } + }, + { + "id": "street-trunk-link", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 13, + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "trunk"], + ["==", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(48,100%,17%)", + "line-width": {"stops": [[12, 1], [14, 2], [16, 5], [18, 12], [20, 38]]} + } + }, + { + "id": "street-motorway-link", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 12, + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "motorway"], + ["==", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(34,100%,23%)", + "line-width": {"stops": [[12, 1], [14, 2], [16, 5], [18, 12], [20, 38]]} + } + }, + { + "id": "street-tertiary", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "tertiary"], + ["!=", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(0,0%,0%)", + "line-width": { + "stops": [[12, 1], [14, 2], [16, 5], [18, 24], [19, 60], [20, 120]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "street-secondary", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "secondary"], + ["!=", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(48,100%,17%)", + "line-width": { + "stops": [[11, 1], [14, 4], [16, 6], [18, 28], [19, 64], [20, 130]] + }, + "line-opacity": {"stops": [[11, 0], [12, 1]]} + } + }, + { + "id": "street-primary", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "primary"], + ["!=", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(48,100%,17%)", + "line-width": { + "stops": [ + [8, 0], + [9, 2], + [10, 3], + [14, 5], + [16, 10], + [18, 34], + [19, 70], + [20, 140] + ] + }, + "line-opacity": {"stops": [[8, 0], [9, 1]]} + } + }, + { + "id": "street-trunk", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "trunk"], + ["!=", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(48,100%,17%)", + "line-width": { + "stops": [ + [6, 0], + [7, 1], + [10, 3], + [14, 5], + [16, 10], + [18, 34], + [19, 70], + [20, 140] + ] + }, + "line-opacity": {"stops": [[6, 0], [7, 1]]} + } + }, + { + "id": "street-motorway", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "motorway"], + ["!=", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(34,100%,23%)", + "line-width": { + "stops": [ + [5, 0], + [6, 1], + [10, 4], + [14, 4], + [16, 12], + [18, 36], + [19, 80], + [20, 160] + ] + }, + "line-opacity": {"stops": [[5, 0], [6, 1]]} + } + }, + { + "id": "transport-tram:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": [ + "all", + ["in", "kind", "tram"], + ["!has", "service"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "paint": { + "line-color": "hsl(208,14%,27%)", + "line-width": {"stops": [[15, 0], [16, 5], [18, 7], [20, 20]]}, + "line-dasharray": [0.1, 0.5] + } + }, + { + "id": "transport-narrowgauge:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": [ + "all", + ["in", "kind", "narrow_gauge"], + ["!has", "service"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "paint": { + "line-color": "hsl(208,14%,27%)", + "line-width": {"stops": [[15, 0], [16, 5], [18, 7], [20, 20]]}, + "line-dasharray": [0.1, 0.5] + } + }, + { + "id": "transport-subway:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["in", "kind", "subway"], + ["!has", "service"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "paint": { + "line-color": "hsl(207,23%,28%)", + "line-width": { + "stops": [ + [11, 0], + [12, 1], + [15, 3], + [16, 3], + [18, 6], + [19, 8], + [20, 10] + ] + }, + "line-opacity": {"stops": [[11, 0], [12, 1]]} + } + }, + { + "id": "transport-lightrail:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 8, + "filter": [ + "all", + ["in", "kind", "light_rail"], + ["!has", "service"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "paint": { + "line-color": "hsl(208,14%,27%)", + "line-width": {"stops": [[8, 1], [13, 1], [15, 1], [20, 14]]}, + "line-opacity": {"stops": [[11, 0], [12, 1]]} + } + }, + { + "id": "transport-lightrail-service:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 14, + "filter": [ + "all", + ["in", "kind", "light_rail"], + ["has", "service"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "paint": { + "line-color": "hsl(208,14%,27%)", + "line-width": {"stops": [[14, 0], [15, 1], [16, 1], [20, 14]]} + } + }, + { + "id": "transport-rail:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 8, + "filter": [ + "all", + ["in", "kind", "rail"], + ["!has", "service"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "paint": { + "line-color": "hsl(208,14%,27%)", + "line-width": {"stops": [[8, 1], [13, 1], [15, 1], [20, 14]]}, + "line-opacity": {"stops": [[8, 0], [9, 1]]} + } + }, + { + "id": "transport-rail-service:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 14, + "filter": [ + "all", + ["in", "kind", "rail"], + ["has", "service"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "paint": { + "line-color": "hsl(208,14%,27%)", + "line-width": {"stops": [[14, 0], [15, 1], [16, 1], [20, 14]]} + } + }, + { + "id": "transport-monorail:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": [ + "all", + ["in", "kind", "monorail"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "paint": { + "line-color": "hsl(208,14%,27%)", + "line-width": {"stops": [[15, 0], [16, 5], [18, 7], [20, 20]]}, + "line-dasharray": [0.1, 0.5] + } + }, + { + "id": "transport-funicular:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": [ + "all", + ["in", "kind", "funicular"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "paint": { + "line-color": "hsl(208,14%,27%)", + "line-width": {"stops": [[15, 0], [16, 5], [18, 7], [20, 20]]}, + "line-dasharray": [0.1, 0.5] + } + }, + { + "id": "transport-tram", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 13, + "filter": [ + "all", + ["in", "kind", "tram"], + ["!has", "service"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "paint": { + "line-width": {"stops": [[13, 0], [16, 1], [17, 2], [18, 3], [20, 5]]}, + "line-color": "hsl(208,14%,27%)" + } + }, + { + "id": "transport-narrowgauge", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 13, + "filter": [ + "all", + ["in", "kind", "narrow_gauge"], + ["!has", "service"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "paint": { + "line-width": {"stops": [[13, 0], [16, 1], [17, 2], [18, 3], [20, 5]]}, + "line-color": "hsl(208,14%,27%)" + } + }, + { + "id": "transport-subway", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["in", "kind", "subway"], + ["!has", "service"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "paint": { + "line-color": "rgb(42,55,67)", + "line-width": { + "stops": [ + [11, 0], + [12, 1], + [15, 2], + [16, 2], + [18, 5], + [19, 6], + [20, 8] + ] + }, + "line-dasharray": [2, 2], + "line-opacity": {"stops": [[14, 0], [15, 1]]} + } + }, + { + "id": "transport-lightrail", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 14, + "filter": [ + "all", + ["in", "kind", "light_rail"], + ["!has", "service"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "paint": { + "line-color": "rgb(44,52,59)", + "line-width": {"stops": [[14, 0], [15, 1], [20, 10]]}, + "line-dasharray": [2, 2], + "line-opacity": {"stops": [[14, 0], [15, 1]]} + } + }, + { + "id": "transport-lightrail-service", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": [ + "all", + ["in", "kind", "light_rail"], + ["has", "service"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "paint": { + "line-color": "rgb(44,52,59)", + "line-width": {"stops": [[15, 0], [16, 1], [20, 10]]}, + "line-dasharray": [2, 2] + } + }, + { + "id": "transport-rail", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 14, + "filter": [ + "all", + ["in", "kind", "rail"], + ["!has", "service"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "paint": { + "line-color": "rgb(44,52,59)", + "line-width": {"stops": [[14, 0], [15, 1], [20, 10]]}, + "line-dasharray": [2, 2], + "line-opacity": {"stops": [[14, 0], [15, 1]]} + } + }, + { + "id": "transport-rail-service", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": [ + "all", + ["in", "kind", "rail"], + ["has", "service"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "paint": { + "line-color": "rgb(44,52,59)", + "line-width": {"stops": [[15, 0], [16, 1], [20, 10]]}, + "line-dasharray": [2, 2] + } + }, + { + "id": "transport-monorail", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 13, + "filter": [ + "all", + ["in", "kind", "monorail"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "paint": { + "line-width": {"stops": [[13, 0], [16, 1], [17, 2], [18, 3], [20, 5]]}, + "line-color": "hsl(208,14%,27%)" + } + }, + { + "id": "transport-funicular", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 13, + "filter": [ + "all", + ["in", "kind", "funicular"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "paint": { + "line-width": {"stops": [[13, 0], [16, 1], [17, 2], [18, 3], [20, 5]]}, + "line-color": "hsl(208,14%,27%)" + } + }, + { + "id": "transport-ferry", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "ferries", + "minzoom": 10, + "paint": { + "line-color": "rgb(36,64,84)", + "line-width": {"stops": [[10, 1], [13, 2], [14, 3], [16, 4], [17, 6]]}, + "line-opacity": {"stops": [[10, 0], [11, 1]]}, + "line-dasharray": [1, 1] + } + }, + { + "id": "bridge-way-footway:bridge", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": ["all", ["==", "bridge", true], ["in", "kind", "footway"]], + "layout": {"line-cap": "butt", "line-join": "round"}, + "paint": { + "line-color": "rgb(22,17,11)", + "line-opacity": 0.5, + "line-width": { + "stops": [[15, 0], [16, 7], [18, 10], [19, 17], [20, 31]] + } + } + }, + { + "id": "bridge-way-steps:bridge", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": ["all", ["==", "bridge", true], ["in", "kind", "steps"]], + "layout": {"line-cap": "butt", "line-join": "round"}, + "paint": { + "line-color": "rgb(22,17,11)", + "line-opacity": 0.5, + "line-width": { + "stops": [[15, 0], [16, 7], [18, 10], [19, 17], [20, 31]] + } + } + }, + { + "id": "bridge-way-path:bridge", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": ["all", ["==", "bridge", true], ["in", "kind", "path"]], + "layout": {"line-cap": "butt", "line-join": "round"}, + "paint": { + "line-color": "rgb(22,17,11)", + "line-opacity": 0.5, + "line-width": { + "stops": [[15, 0], [16, 7], [18, 10], [19, 17], [20, 31]] + } + } + }, + { + "id": "bridge-way-cycleway:bridge", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": ["all", ["==", "bridge", true], ["in", "kind", "cycleway"]], + "layout": {"line-cap": "butt", "line-join": "round"}, + "paint": { + "line-color": "rgb(22,17,11)", + "line-opacity": 0.5, + "line-width": { + "stops": [[15, 0], [16, 7], [18, 10], [19, 17], [20, 31]] + } + } + }, + { + "id": "bridge-street-track:bridge", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "track"], ["==", "bridge", true]], + "layout": {"line-cap": "butt", "line-join": "round"}, + "paint": { + "line-color": "rgb(22,17,11)", + "line-opacity": {"stops": [[14, 0], [15, 1]]}, + "line-width": { + "stops": [[14, 3], [16, 6], [18, 25], [19, 67], [20, 134]] + } + } + }, + { + "id": "bridge-street-pedestrian:bridge", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "pedestrian"], ["==", "bridge", true]], + "layout": {"line-cap": "butt", "line-join": "round"}, + "paint": { + "line-color": "rgb(22,17,11)", + "line-opacity": {"stops": [[12, 0], [13, 1]]}, + "line-width": { + "stops": [[12, 3], [14, 4], [16, 8], [18, 36], [19, 90], [20, 179]] + } + } + }, + { + "id": "bridge-street-service:bridge", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "service"], ["==", "bridge", true]], + "layout": {"line-cap": "butt", "line-join": "round"}, + "paint": { + "line-color": "rgb(22,17,11)", + "line-opacity": {"stops": [[14, 0], [15, 1]]}, + "line-width": { + "stops": [[14, 3], [16, 6], [18, 25], [19, 67], [20, 134]] + } + } + }, + { + "id": "bridge-street-livingstreet:bridge", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "living_street"], + ["==", "bridge", true] + ], + "layout": {"line-cap": "butt", "line-join": "round"}, + "paint": { + "line-color": "rgb(22,17,11)", + "line-opacity": {"stops": [[12, 0], [13, 1]]}, + "line-width": { + "stops": [[12, 3], [14, 4], [16, 8], [18, 36], [19, 90], [20, 179]] + } + } + }, + { + "id": "bridge-street-residential:bridge", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "residential"], ["==", "bridge", true]], + "layout": {"line-cap": "butt", "line-join": "round"}, + "paint": { + "line-color": "rgb(22,17,11)", + "line-opacity": {"stops": [[12, 0], [13, 1]]}, + "line-width": { + "stops": [[12, 3], [14, 4], [16, 8], [18, 36], [19, 90], [20, 179]] + } + } + }, + { + "id": "bridge-street-unclassified:bridge", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "unclassified"], ["==", "bridge", true]], + "layout": {"line-cap": "butt", "line-join": "round"}, + "paint": { + "line-color": "rgb(22,17,11)", + "line-opacity": {"stops": [[12, 0], [13, 1]]}, + "line-width": { + "stops": [[12, 3], [14, 4], [16, 8], [18, 36], [19, 90], [20, 179]] + } + } + }, + { + "id": "bridge-street-busway:bridge", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "busway"], ["==", "bridge", true]], + "layout": {"line-cap": "butt", "line-join": "round"}, + "paint": {"line-color": "rgb(22,17,11)", "line-opacity": 0.5} + }, + { + "id": "bridge-street-busguideway:bridge", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "bus_guideway"], ["==", "bridge", true]], + "layout": {"line-cap": "butt", "line-join": "round"}, + "paint": {"line-color": "rgb(22,17,11)", "line-opacity": 0.5} + }, + { + "id": "bridge-street-tertiary-link:bridge", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "tertiary"], + ["==", "link", true] + ], + "layout": {"line-cap": "butt", "line-join": "round"}, + "paint": { + "line-color": "rgb(22,17,11)", + "line-opacity": {"stops": [[12, 0], [13, 1]]}, + "line-width": { + "stops": [[12, 3], [14, 4], [16, 8], [18, 36], [19, 90], [20, 179]] + } + } + }, + { + "id": "bridge-street-secondary-link:bridge", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 13, + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "secondary"], + ["==", "link", true] + ], + "layout": {"line-cap": "butt", "line-join": "round"}, + "paint": { + "line-color": "rgb(22,17,11)", + "line-opacity": 0.5, + "line-width": { + "stops": [[12, 3], [14, 4], [16, 10], [18, 20], [20, 56]] + } + } + }, + { + "id": "bridge-street-primary-link:bridge", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 13, + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "primary"], + ["==", "link", true] + ], + "layout": {"line-cap": "butt", "line-join": "round"}, + "paint": { + "line-color": "rgb(22,17,11)", + "line-opacity": 0.5, + "line-width": { + "stops": [[12, 3], [14, 4], [16, 10], [18, 20], [20, 56]] + } + } + }, + { + "id": "bridge-street-trunk-link:bridge", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 13, + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "trunk"], + ["==", "link", true] + ], + "layout": {"line-cap": "butt", "line-join": "round"}, + "paint": { + "line-color": "rgb(22,17,11)", + "line-opacity": 0.5, + "line-width": { + "stops": [[12, 3], [14, 4], [16, 10], [18, 20], [20, 56]] + } + } + }, + { + "id": "bridge-street-motorway-link:bridge", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 12, + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "motorway"], + ["==", "link", true] + ], + "layout": {"line-cap": "butt", "line-join": "round"}, + "paint": { + "line-color": "rgb(22,17,11)", + "line-opacity": 0.5, + "line-width": { + "stops": [[12, 3], [14, 4], [16, 10], [18, 20], [20, 56]] + } + } + }, + { + "id": "bridge-street-tertiary:bridge", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "tertiary"], + ["!=", "link", true] + ], + "layout": {"line-cap": "butt", "line-join": "round"}, + "paint": { + "line-color": "rgb(22,17,11)", + "line-opacity": {"stops": [[12, 0], [13, 1]]}, + "line-width": { + "stops": [[12, 3], [14, 4], [16, 8], [18, 36], [19, 90], [20, 179]] + } + } + }, + { + "id": "bridge-street-secondary:bridge", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "secondary"], + ["!=", "link", true] + ], + "layout": {"line-cap": "butt", "line-join": "round"}, + "paint": { + "line-color": "rgb(22,17,11)", + "line-opacity": {"stops": [[11, 0], [12, 1]]}, + "line-width": { + "stops": [[11, 3], [14, 7], [16, 11], [18, 42], [19, 95], [20, 193]] + } + } + }, + { + "id": "bridge-street-primary:bridge", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "primary"], + ["!=", "link", true] + ], + "layout": {"line-cap": "butt", "line-join": "round"}, + "paint": { + "line-color": "rgb(22,17,11)", + "line-opacity": 0.5, + "line-width": { + "stops": [ + [8, 0], + [9, 1], + [10, 6], + [14, 8], + [16, 17], + [18, 50], + [19, 104], + [20, 202] + ] + } + } + }, + { + "id": "bridge-street-trunk:bridge", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "trunk"], + ["!=", "link", true] + ], + "layout": {"line-cap": "butt", "line-join": "round"}, + "paint": { + "line-color": "rgb(22,17,11)", + "line-opacity": 0.5, + "line-width": { + "stops": [ + [7, 0], + [8, 3], + [10, 6], + [14, 8], + [16, 17], + [18, 50], + [19, 104], + [20, 202] + ] + } + } + }, + { + "id": "bridge-street-motorway:bridge", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "motorway"], + ["!=", "link", true] + ], + "layout": {"line-cap": "butt", "line-join": "round"}, + "paint": { + "line-color": "rgb(22,17,11)", + "line-opacity": 0.5, + "line-width": { + "stops": [ + [5, 0], + [6, 3], + [10, 7], + [14, 7], + [16, 20], + [18, 53], + [19, 118], + [20, 235] + ] + } + } + }, + { + "id": "bridge-street-pedestrian-zone", + "type": "fill", + "source": "versatiles-shortbread", + "source-layer": "street_polygons", + "filter": ["all", ["==", "bridge", true], ["==", "kind", "pedestrian"]], + "paint": { + "fill-color": "hsl(0,0%,0%)", + "fill-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "bridge-way-footway:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": ["all", ["==", "bridge", true], ["in", "kind", "footway"]], + "layout": {"line-cap": "butt"}, + "paint": { + "line-width": { + "stops": [[15, 0], [16, 5], [18, 7], [19, 12], [20, 22]] + }, + "line-color": "rgb(40,26,44)" + } + }, + { + "id": "bridge-way-steps:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": ["all", ["==", "bridge", true], ["in", "kind", "steps"]], + "layout": {"line-cap": "butt"}, + "paint": { + "line-width": { + "stops": [[15, 0], [16, 5], [18, 7], [19, 12], [20, 22]] + }, + "line-color": "rgb(40,26,44)" + } + }, + { + "id": "bridge-way-path:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": ["all", ["==", "bridge", true], ["in", "kind", "path"]], + "layout": {"line-cap": "butt"}, + "paint": { + "line-width": { + "stops": [[15, 0], [16, 5], [18, 7], [19, 12], [20, 22]] + }, + "line-color": "rgb(40,26,44)" + } + }, + { + "id": "bridge-way-cycleway:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": ["all", ["==", "bridge", true], ["in", "kind", "cycleway"]], + "layout": {"line-cap": "butt"}, + "paint": { + "line-width": { + "stops": [[15, 0], [16, 5], [18, 7], [19, 12], [20, 22]] + }, + "line-color": "rgb(26,34,40)" + } + }, + { + "id": "bridge-street-track:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "track"], ["==", "bridge", true]], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "rgb(38,38,38)", + "line-width": { + "stops": [[14, 2], [16, 4], [18, 18], [19, 48], [20, 96]] + }, + "line-opacity": {"stops": [[14, 0], [15, 1]]} + } + }, + { + "id": "bridge-street-pedestrian:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "pedestrian"], ["==", "bridge", true]], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "rgb(38,38,38)", + "line-width": { + "stops": [[12, 2], [14, 3], [16, 6], [18, 26], [19, 64], [20, 128]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "bridge-street-service:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "service"], ["==", "bridge", true]], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "rgb(37,36,34)", + "line-width": { + "stops": [[14, 1], [16, 3], [18, 12], [19, 32], [20, 48]] + }, + "line-opacity": {"stops": [[15, 0], [16, 1]]} + } + }, + { + "id": "bridge-street-livingstreet:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "living_street"], + ["==", "bridge", true] + ], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "rgb(38,38,38)", + "line-width": { + "stops": [[12, 2], [14, 3], [16, 6], [18, 26], [19, 64], [20, 128]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "bridge-street-residential:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "residential"], ["==", "bridge", true]], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "rgb(38,38,38)", + "line-width": { + "stops": [[12, 2], [14, 3], [16, 6], [18, 26], [19, 64], [20, 128]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "bridge-street-unclassified:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "unclassified"], ["==", "bridge", true]], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "rgb(38,38,38)", + "line-width": { + "stops": [[12, 2], [14, 3], [16, 6], [18, 26], [19, 64], [20, 128]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "bridge-street-busway:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "busway"], ["==", "bridge", true]], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "rgb(37,36,34)", + "line-width": { + "stops": [[14, 1], [16, 3], [18, 12], [19, 32], [20, 48]] + }, + "line-opacity": {"stops": [[15, 0], [16, 1]]} + } + }, + { + "id": "bridge-street-busguideway:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "bus_guideway"], ["==", "bridge", true]], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "rgb(37,36,34)", + "line-width": { + "stops": [[14, 1], [16, 3], [18, 12], [19, 32], [20, 48]] + }, + "line-opacity": {"stops": [[15, 0], [16, 1]]} + } + }, + { + "id": "bridge-street-tertiary-link:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "tertiary"], + ["==", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "rgb(38,38,38)", + "line-width": { + "stops": [[12, 2], [14, 3], [16, 6], [18, 26], [19, 64], [20, 128]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "bridge-street-secondary-link:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 13, + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "secondary"], + ["==", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "hsl(28,72%,31%)", + "line-width": {"stops": [[12, 2], [14, 3], [16, 7], [18, 14], [20, 40]]} + } + }, + { + "id": "bridge-street-primary-link:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 13, + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "primary"], + ["==", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "hsl(28,72%,31%)", + "line-width": {"stops": [[12, 2], [14, 3], [16, 7], [18, 14], [20, 40]]} + } + }, + { + "id": "bridge-street-trunk-link:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 13, + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "trunk"], + ["==", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "hsl(28,72%,31%)", + "line-width": {"stops": [[12, 2], [14, 3], [16, 7], [18, 14], [20, 40]]} + } + }, + { + "id": "bridge-street-motorway-link:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 12, + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "motorway"], + ["==", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "hsl(28,72%,31%)", + "line-width": {"stops": [[12, 2], [14, 3], [16, 7], [18, 14], [20, 40]]} + } + }, + { + "id": "bridge-street-tertiary:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "tertiary"], + ["!=", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "rgb(38,38,38)", + "line-width": { + "stops": [[12, 2], [14, 3], [16, 6], [18, 26], [19, 64], [20, 128]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "bridge-street-secondary:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "secondary"], + ["!=", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "hsl(28,72%,31%)", + "line-width": { + "stops": [[11, 2], [14, 5], [16, 8], [18, 30], [19, 68], [20, 138]] + }, + "line-opacity": {"stops": [[11, 0], [12, 1]]} + } + }, + { + "id": "bridge-street-primary:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "primary"], + ["!=", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "hsl(28,72%,31%)", + "line-width": { + "stops": [ + [8, 0], + [9, 1], + [10, 4], + [14, 6], + [16, 12], + [18, 36], + [19, 74], + [20, 144] + ] + } + } + }, + { + "id": "bridge-street-trunk:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "trunk"], + ["!=", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "hsl(28,72%,31%)", + "line-width": { + "stops": [ + [6, 0], + [7, 2], + [10, 4], + [14, 6], + [16, 12], + [18, 36], + [19, 74], + [20, 144] + ] + } + } + }, + { + "id": "bridge-street-motorway:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "motorway"], + ["!=", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "hsl(28,72%,31%)", + "line-width": { + "stops": [ + [5, 0], + [6, 2], + [10, 5], + [14, 5], + [16, 14], + [18, 38], + [19, 84], + [20, 168] + ] + } + } + }, + { + "id": "bridge-way-footway", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": ["all", ["==", "bridge", true], ["in", "kind", "footway"]], + "layout": {"line-cap": "butt"}, + "paint": { + "line-width": { + "stops": [[15, 0], [16, 4], [18, 6], [19, 10], [20, 20]] + }, + "line-color": "rgb(16,0,20)" + } + }, + { + "id": "bridge-way-steps", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": ["all", ["==", "bridge", true], ["in", "kind", "steps"]], + "layout": {"line-cap": "butt"}, + "paint": { + "line-width": { + "stops": [[15, 0], [16, 4], [18, 6], [19, 10], [20, 20]] + }, + "line-color": "rgb(16,0,20)" + } + }, + { + "id": "bridge-way-path", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": ["all", ["==", "bridge", true], ["in", "kind", "path"]], + "layout": {"line-cap": "butt"}, + "paint": { + "line-width": { + "stops": [[15, 0], [16, 4], [18, 6], [19, 10], [20, 20]] + }, + "line-color": "rgb(16,0,20)" + } + }, + { + "id": "bridge-way-cycleway", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": ["all", ["==", "bridge", true], ["in", "kind", "cycleway"]], + "layout": {"line-cap": "butt"}, + "paint": { + "line-width": { + "stops": [[15, 0], [16, 4], [18, 6], [19, 10], [20, 20]] + }, + "line-color": "hsl(203,100%,3%)" + } + }, + { + "id": "bridge-street-track", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "track"], ["==", "bridge", true]], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "hsl(0,0%,0%)", + "line-width": { + "stops": [[14, 1], [16, 3], [18, 16], [19, 44], [20, 88]] + }, + "line-opacity": {"stops": [[14, 0], [15, 1]]} + } + }, + { + "id": "bridge-street-pedestrian", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "pedestrian"], ["==", "bridge", true]], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "hsl(0,0%,0%)", + "line-width": { + "stops": [[12, 1], [14, 2], [16, 5], [18, 24], [19, 60], [20, 120]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "bridge-street-service", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "service"], ["==", "bridge", true]], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "rgb(8,8,8)", + "line-width": { + "stops": [[14, 1], [16, 2], [18, 10], [19, 28], [20, 40]] + }, + "line-opacity": {"stops": [[15, 0], [16, 1]]} + } + }, + { + "id": "bridge-street-livingstreet", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "living_street"], + ["==", "bridge", true] + ], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "hsl(0,0%,0%)", + "line-width": { + "stops": [[12, 1], [14, 2], [16, 5], [18, 24], [19, 60], [20, 120]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "bridge-street-residential", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "residential"], ["==", "bridge", true]], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "hsl(0,0%,0%)", + "line-width": { + "stops": [[12, 1], [14, 2], [16, 5], [18, 24], [19, 60], [20, 120]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "bridge-street-unclassified", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "unclassified"], ["==", "bridge", true]], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "hsl(0,0%,0%)", + "line-width": { + "stops": [[12, 1], [14, 2], [16, 5], [18, 24], [19, 60], [20, 120]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "bridge-street-busway", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "busway"], ["==", "bridge", true]], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "rgb(8,8,8)", + "line-width": { + "stops": [[14, 1], [16, 2], [18, 10], [19, 28], [20, 40]] + }, + "line-opacity": {"stops": [[15, 0], [16, 1]]} + } + }, + { + "id": "bridge-street-busguideway", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "bus_guideway"], ["==", "bridge", true]], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "rgb(8,8,8)", + "line-width": { + "stops": [[14, 1], [16, 2], [18, 10], [19, 28], [20, 40]] + }, + "line-opacity": {"stops": [[15, 0], [16, 1]]} + } + }, + { + "id": "bridge-street-track-bicycle", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "track"], + ["==", "bicycle", "designated"], + ["==", "bridge", true] + ], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": {"line-color": "hsl(0,0%,0%)"} + }, + { + "id": "bridge-street-pedestrian-bicycle", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "pedestrian"], + ["==", "bicycle", "designated"], + ["==", "bridge", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(203,100%,3%)", + "line-width": { + "stops": [[12, 1], [14, 2], [16, 5], [18, 24], [19, 60], [20, 120]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "bridge-street-service-bicycle", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "service"], + ["==", "bicycle", "designated"], + ["==", "bridge", true] + ], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": {"line-color": "hsl(0,0%,0%)"} + }, + { + "id": "bridge-street-livingstreet-bicycle", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "living_street"], + ["==", "bicycle", "designated"], + ["==", "bridge", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(203,100%,3%)", + "line-width": { + "stops": [[12, 1], [14, 2], [16, 5], [18, 24], [19, 60], [20, 120]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "bridge-street-residential-bicycle", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "residential"], + ["==", "bicycle", "designated"], + ["==", "bridge", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(203,100%,3%)", + "line-width": { + "stops": [[12, 1], [14, 2], [16, 5], [18, 24], [19, 60], [20, 120]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "bridge-street-unclassified-bicycle", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "unclassified"], + ["==", "bicycle", "designated"], + ["==", "bridge", true] + ], + "layout": {"line-join": "round", "line-cap": "round"}, + "paint": { + "line-color": "hsl(203,100%,3%)", + "line-width": { + "stops": [[12, 1], [14, 2], [16, 5], [18, 24], [19, 60], [20, 120]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "bridge-street-tertiary-link", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "tertiary"], + ["==", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "hsl(0,0%,0%)", + "line-width": { + "stops": [[12, 1], [14, 2], [16, 5], [18, 24], [19, 60], [20, 120]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "bridge-street-secondary-link", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 13, + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "secondary"], + ["==", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "hsl(48,100%,17%)", + "line-width": {"stops": [[12, 1], [14, 2], [16, 5], [18, 12], [20, 38]]} + } + }, + { + "id": "bridge-street-primary-link", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 13, + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "primary"], + ["==", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "hsl(48,100%,17%)", + "line-width": {"stops": [[12, 1], [14, 2], [16, 5], [18, 12], [20, 38]]} + } + }, + { + "id": "bridge-street-trunk-link", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 13, + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "trunk"], + ["==", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "hsl(48,100%,17%)", + "line-width": {"stops": [[12, 1], [14, 2], [16, 5], [18, 12], [20, 38]]} + } + }, + { + "id": "bridge-street-motorway-link", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 12, + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "motorway"], + ["==", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "hsl(34,100%,23%)", + "line-width": {"stops": [[12, 1], [14, 2], [16, 5], [18, 12], [20, 38]]} + } + }, + { + "id": "bridge-street-tertiary", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "tertiary"], + ["!=", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "hsl(0,0%,0%)", + "line-width": { + "stops": [[12, 1], [14, 2], [16, 5], [18, 24], [19, 60], [20, 120]] + }, + "line-opacity": {"stops": [[12, 0], [13, 1]]} + } + }, + { + "id": "bridge-street-secondary", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "secondary"], + ["!=", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "hsl(48,100%,17%)", + "line-width": { + "stops": [[11, 1], [14, 4], [16, 6], [18, 28], [19, 64], [20, 130]] + }, + "line-opacity": {"stops": [[11, 0], [12, 1]]} + } + }, + { + "id": "bridge-street-primary", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "primary"], + ["!=", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "hsl(48,100%,17%)", + "line-width": { + "stops": [ + [8, 0], + [9, 2], + [10, 3], + [14, 5], + [16, 10], + [18, 34], + [19, 70], + [20, 140] + ] + }, + "line-opacity": {"stops": [[8, 0], [9, 1]]} + } + }, + { + "id": "bridge-street-trunk", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "trunk"], + ["!=", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "hsl(48,100%,17%)", + "line-width": { + "stops": [ + [6, 0], + [7, 1], + [10, 3], + [14, 5], + [16, 10], + [18, 34], + [19, 70], + [20, 140] + ] + }, + "line-opacity": {"stops": [[6, 0], [7, 1]]} + } + }, + { + "id": "bridge-street-motorway", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "motorway"], + ["!=", "link", true] + ], + "layout": {"line-join": "round", "line-cap": "butt"}, + "paint": { + "line-color": "hsl(34,100%,23%)", + "line-width": { + "stops": [ + [5, 0], + [6, 1], + [10, 4], + [14, 4], + [16, 12], + [18, 36], + [19, 80], + [20, 160] + ] + }, + "line-opacity": {"stops": [[5, 0], [6, 1]]} + } + }, + { + "id": "bridge-transport-tram:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": [ + "all", + ["in", "kind", "tram"], + ["!has", "service"], + ["==", "bridge", true] + ], + "paint": { + "line-color": "hsl(208,14%,27%)", + "line-width": {"stops": [[15, 0], [16, 5], [18, 7], [20, 20]]}, + "line-dasharray": [0.1, 0.5] + } + }, + { + "id": "bridge-transport-narrowgauge:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": [ + "all", + ["in", "kind", "narrow_gauge"], + ["!has", "service"], + ["==", "bridge", true] + ], + "paint": { + "line-color": "hsl(208,14%,27%)", + "line-width": {"stops": [[15, 0], [16, 5], [18, 7], [20, 20]]}, + "line-dasharray": [0.1, 0.5] + } + }, + { + "id": "bridge-transport-subway:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["in", "kind", "subway"], + ["!has", "service"], + ["==", "bridge", true] + ], + "paint": { + "line-color": "hsl(207,23%,28%)", + "line-width": { + "stops": [ + [11, 0], + [12, 1], + [15, 3], + [16, 3], + [18, 6], + [19, 8], + [20, 10] + ] + }, + "line-opacity": {"stops": [[11, 0], [12, 1]]} + } + }, + { + "id": "bridge-transport-lightrail:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 8, + "filter": [ + "all", + ["in", "kind", "light_rail"], + ["!has", "service"], + ["==", "bridge", true] + ], + "paint": { + "line-color": "hsl(208,14%,27%)", + "line-width": {"stops": [[8, 1], [13, 1], [15, 1], [20, 14]]}, + "line-opacity": {"stops": [[11, 0], [12, 1]]} + } + }, + { + "id": "bridge-transport-lightrail-service:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 14, + "filter": [ + "all", + ["in", "kind", "light_rail"], + ["has", "service"], + ["==", "bridge", true] + ], + "paint": { + "line-color": "hsl(208,14%,27%)", + "line-width": {"stops": [[14, 0], [15, 1], [16, 1], [20, 14]]} + } + }, + { + "id": "bridge-transport-rail:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 8, + "filter": [ + "all", + ["in", "kind", "rail"], + ["!has", "service"], + ["==", "bridge", true] + ], + "paint": { + "line-color": "hsl(208,14%,27%)", + "line-width": {"stops": [[8, 1], [13, 1], [15, 1], [20, 14]]}, + "line-opacity": {"stops": [[8, 0], [9, 1]]} + } + }, + { + "id": "bridge-transport-rail-service:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 14, + "filter": [ + "all", + ["in", "kind", "rail"], + ["has", "service"], + ["==", "bridge", true] + ], + "paint": { + "line-color": "hsl(208,14%,27%)", + "line-width": {"stops": [[14, 0], [15, 1], [16, 1], [20, 14]]} + } + }, + { + "id": "bridge-transport-monorail:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": ["all", ["in", "kind", "monorail"], ["==", "bridge", true]], + "paint": { + "line-color": "hsl(208,14%,27%)", + "line-width": {"stops": [[15, 0], [16, 5], [18, 7], [20, 20]]}, + "line-dasharray": [0.1, 0.5] + } + }, + { + "id": "bridge-transport-funicular:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": ["all", ["in", "kind", "funicular"], ["==", "bridge", true]], + "paint": { + "line-color": "hsl(208,14%,27%)", + "line-width": {"stops": [[15, 0], [16, 5], [18, 7], [20, 20]]}, + "line-dasharray": [0.1, 0.5] + } + }, + { + "id": "bridge-transport-tram", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 13, + "filter": [ + "all", + ["in", "kind", "tram"], + ["!has", "service"], + ["==", "bridge", true] + ], + "paint": { + "line-width": {"stops": [[13, 0], [16, 1], [17, 2], [18, 3], [20, 5]]}, + "line-color": "hsl(208,14%,27%)" + } + }, + { + "id": "bridge-transport-narrowgauge", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 13, + "filter": [ + "all", + ["in", "kind", "narrow_gauge"], + ["!has", "service"], + ["==", "bridge", true] + ], + "paint": { + "line-width": {"stops": [[13, 0], [16, 1], [17, 2], [18, 3], [20, 5]]}, + "line-color": "hsl(208,14%,27%)" + } + }, + { + "id": "bridge-transport-subway", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "filter": [ + "all", + ["in", "kind", "subway"], + ["!has", "service"], + ["==", "bridge", true] + ], + "paint": { + "line-color": "rgb(42,55,67)", + "line-width": { + "stops": [ + [11, 0], + [12, 1], + [15, 2], + [16, 2], + [18, 5], + [19, 6], + [20, 8] + ] + }, + "line-dasharray": [2, 2], + "line-opacity": {"stops": [[14, 0], [15, 1]]} + } + }, + { + "id": "bridge-transport-lightrail", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 14, + "filter": [ + "all", + ["in", "kind", "light_rail"], + ["!has", "service"], + ["==", "bridge", true] + ], + "paint": { + "line-color": "rgb(44,52,59)", + "line-width": {"stops": [[14, 0], [15, 1], [20, 10]]}, + "line-dasharray": [2, 2], + "line-opacity": {"stops": [[14, 0], [15, 1]]} + } + }, + { + "id": "bridge-transport-lightrail-service", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": [ + "all", + ["in", "kind", "light_rail"], + ["has", "service"], + ["==", "bridge", true] + ], + "paint": { + "line-color": "rgb(44,52,59)", + "line-width": {"stops": [[15, 0], [16, 1], [20, 10]]}, + "line-dasharray": [2, 2] + } + }, + { + "id": "bridge-transport-rail", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 14, + "filter": [ + "all", + ["in", "kind", "rail"], + ["!has", "service"], + ["==", "bridge", true] + ], + "paint": { + "line-color": "rgb(44,52,59)", + "line-width": {"stops": [[14, 0], [15, 1], [20, 10]]}, + "line-dasharray": [2, 2], + "line-opacity": {"stops": [[14, 0], [15, 1]]} + } + }, + { + "id": "bridge-transport-rail-service", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 15, + "filter": [ + "all", + ["in", "kind", "rail"], + ["has", "service"], + ["==", "bridge", true] + ], + "paint": { + "line-color": "rgb(44,52,59)", + "line-width": {"stops": [[15, 0], [16, 1], [20, 10]]}, + "line-dasharray": [2, 2] + } + }, + { + "id": "bridge-transport-monorail", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 13, + "filter": ["all", ["in", "kind", "monorail"], ["==", "bridge", true]], + "paint": { + "line-width": {"stops": [[13, 0], [16, 1], [17, 2], [18, 3], [20, 5]]}, + "line-color": "hsl(208,14%,27%)" + } + }, + { + "id": "bridge-transport-funicular", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 13, + "filter": ["all", ["in", "kind", "funicular"], ["==", "bridge", true]], + "paint": { + "line-width": {"stops": [[13, 0], [16, 1], [17, 2], [18, 3], [20, 5]]}, + "line-color": "hsl(208,14%,27%)" + } + }, + { + "id": "poi-amenity", + "type": "symbol", + "source": "versatiles-shortbread", + "source-layer": "pois", + "minzoom": 16, + "filter": ["to-boolean", ["get", "amenity"]], + "layout": { + "icon-size": {"stops": [[16, 0.5], [19, 0.5], [20, 1]]}, + "symbol-placement": "point", + "icon-optional": true, + "text-font": ["noto_sans_regular"], + "icon-image": [ + "match", + ["get", "amenity"], + "arts_centre", + "basics:icon-art_gallery", + "atm", + "basics:icon-atm", + "bank", + "basics:icon-bank", + "bar", + "basics:icon-bar", + "bench", + "basics:icon-bench", + "bicycle_rental", + "basics:icon-bicycle_share", + "biergarten", + "basics:icon-beergarden", + "cafe", + "basics:icon-cafe", + "car_rental", + "basics:icon-car_rental", + "car_sharing", + "basics:icon-car_rental", + "car_wash", + "basics:icon-car_wash", + "cinema", + "basics:icon-cinema", + "college", + "basics:icon-college", + "community_centre", + "basics:icon-community", + "dentist", + "basics:icon-dentist", + "doctors", + "basics:icon-doctor", + "dog_park", + "basics:icon-dog_park", + "drinking_water", + "basics:icon-drinking_water", + "embassy", + "basics:icon-embassy", + "fast_food", + "basics:icon-fast_food", + "fire_station", + "basics:icon-fire_station", + "fountain", + "basics:icon-fountain", + "grave_yard", + "basics:icon-cemetery", + "hospital", + "basics:icon-hospital", + "hunting_stand", + "basics:icon-huntingstand", + "library", + "basics:icon-library", + "marketplace", + "basics:icon-marketplace", + "nightclub", + "basics:icon-nightclub", + "nursing_home", + "basics:icon-nursinghome", + "pharmacy", + "basics:icon-pharmacy", + "place_of_worship", + "basics:icon-place_of_worship", + "playground", + "basics:icon-playground", + "police", + "basics:icon-police", + "post_box", + "basics:icon-postbox", + "post_office", + "basics:icon-post", + "prison", + "basics:icon-prison", + "pub", + "basics:icon-beer", + "recycling", + "basics:icon-recycling", + "restaurant", + "basics:icon-restaurant", + "school", + "basics:icon-school", + "shelter", + "basics:icon-shelter", + "telephone", + "basics:icon-telephone", + "theatre", + "basics:icon-theatre", + "toilets", + "basics:icon-toilet", + "townhall", + "basics:icon-town_hall", + "vending_machine", + "basics:icon-vendingmachine", + "veterinary", + "basics:icon-veterinary", + "waste_basket", + "basics:icon-waste_basket", + "" + ] + }, + "paint": { + "icon-opacity": {"stops": [[16, 0], [17, 0.4]]}, + "text-opacity": {"stops": [[16, 0], [17, 0.4]]}, + "icon-color": "hsl(0,0%,67%)", + "text-color": "hsl(0,0%,67%)" + } + }, + { + "id": "poi-leisure", + "type": "symbol", + "source": "versatiles-shortbread", + "source-layer": "pois", + "minzoom": 16, + "filter": ["to-boolean", ["get", "leisure"]], + "layout": { + "icon-size": {"stops": [[16, 0.5], [19, 0.5], [20, 1]]}, + "symbol-placement": "point", + "icon-optional": true, + "text-font": ["noto_sans_regular"], + "icon-image": [ + "match", + ["get", "leisure"], + "golf_course", + "basics:icon-golf", + "ice_rink", + "basics:icon-icerink", + "pitch", + "basics:icon-pitch", + "stadium", + "basics:icon-stadium", + "swimming_pool", + "basics:icon-swimming", + "water_park", + "basics:icon-waterpark", + "basics:icon-sports" + ] + }, + "paint": { + "icon-opacity": {"stops": [[16, 0], [17, 0.4]]}, + "text-opacity": {"stops": [[16, 0], [17, 0.4]]}, + "icon-color": "hsl(0,0%,67%)", + "text-color": "hsl(0,0%,67%)" + } + }, + { + "id": "poi-tourism", + "type": "symbol", + "source": "versatiles-shortbread", + "source-layer": "pois", + "minzoom": 16, + "filter": ["to-boolean", ["get", "tourism"]], + "layout": { + "icon-size": {"stops": [[16, 0.5], [19, 0.5], [20, 1]]}, + "symbol-placement": "point", + "icon-optional": true, + "text-font": ["noto_sans_regular"], + "icon-image": [ + "match", + ["get", "tourism"], + "chalet", + "basics:icon-chalet", + "information", + "basics:transport-information", + "picnic_site", + "basics:icon-picnic_site", + "viewpoint", + "basics:icon-viewpoint", + "zoo", + "basics:icon-zoo", + "" + ] + }, + "paint": { + "icon-opacity": {"stops": [[16, 0], [17, 0.4]]}, + "text-opacity": {"stops": [[16, 0], [17, 0.4]]}, + "icon-color": "hsl(0,0%,67%)", + "text-color": "hsl(0,0%,67%)" + } + }, + { + "id": "poi-shop", + "type": "symbol", + "source": "versatiles-shortbread", + "source-layer": "pois", + "minzoom": 16, + "filter": ["to-boolean", ["get", "shop"]], + "layout": { + "icon-size": {"stops": [[16, 0.5], [19, 0.5], [20, 1]]}, + "symbol-placement": "point", + "icon-optional": true, + "text-font": ["noto_sans_regular"], + "icon-image": [ + "match", + ["get", "shop"], + "alcohol", + "basics:icon-alcohol_shop", + "bakery", + "basics:icon-bakery", + "beauty", + "basics:icon-beauty", + "beverages", + "basics:icon-beverages", + "books", + "basics:icon-books", + "butcher", + "basics:icon-butcher", + "chemist", + "basics:icon-chemist", + "clothes", + "basics:icon-clothes", + "doityourself", + "basics:icon-doityourself", + "dry_cleaning", + "basics:icon-drycleaning", + "florist", + "basics:icon-florist", + "furniture", + "basics:icon-furniture", + "garden_centre", + "basics:icon-garden_centre", + "general", + "basics:icon-shop", + "gift", + "basics:icon-gift", + "greengrocer", + "basics:icon-greengrocer", + "hairdresser", + "basics:icon-hairdresser", + "hardware", + "basics:icon-hardware", + "jewelry", + "basics:icon-jewelry_store", + "kiosk", + "basics:icon-kiosk", + "laundry", + "basics:icon-laundry", + "newsagent", + "basics:icon-newsagent", + "optican", + "basics:icon-optician", + "outdoor", + "basics:icon-outdoor", + "shoes", + "basics:icon-shoes", + "sports", + "basics:icon-sports", + "stationery", + "basics:icon-stationery", + "toys", + "basics:icon-toys", + "travel_agency", + "basics:icon-travel_agent", + "video", + "basics:icon-video", + "basics:icon-shop" + ] + }, + "paint": { + "icon-opacity": {"stops": [[16, 0], [17, 0.4]]}, + "text-opacity": {"stops": [[16, 0], [17, 0.4]]}, + "icon-color": "hsl(0,0%,67%)", + "text-color": "hsl(0,0%,67%)" + } + }, + { + "id": "poi-man_made", + "type": "symbol", + "source": "versatiles-shortbread", + "source-layer": "pois", + "minzoom": 16, + "filter": ["to-boolean", ["get", "man_made"]], + "layout": { + "icon-size": {"stops": [[16, 0.5], [19, 0.5], [20, 1]]}, + "symbol-placement": "point", + "icon-optional": true, + "text-font": ["noto_sans_regular"], + "icon-image": [ + "match", + ["get", "man_made"], + "lighthouse", + "basics:icon-lighthouse", + "surveillance", + "basics:icon-surveillance", + "tower", + "basics:icon-observation_tower", + "watermill", + "basics:icon-watermill", + "windmill", + "basics:icon-windmill", + "" + ] + }, + "paint": { + "icon-opacity": {"stops": [[16, 0], [17, 0.4]]}, + "text-opacity": {"stops": [[16, 0], [17, 0.4]]}, + "icon-color": "hsl(0,0%,67%)", + "text-color": "hsl(0,0%,67%)" + } + }, + { + "id": "poi-historic", + "type": "symbol", + "source": "versatiles-shortbread", + "source-layer": "pois", + "minzoom": 16, + "filter": ["to-boolean", ["get", "historic"]], + "layout": { + "icon-size": {"stops": [[16, 0.5], [19, 0.5], [20, 1]]}, + "symbol-placement": "point", + "icon-optional": true, + "text-font": ["noto_sans_regular"], + "icon-image": [ + "match", + ["get", "historic"], + "artwork", + "basics:icon-artwork", + "castle", + "basics:icon-castle", + "monument", + "basics:icon-monument", + "wayside_shrine", + "basics:icon-shrine", + "basics:icon-historic" + ] + }, + "paint": { + "icon-opacity": {"stops": [[16, 0], [17, 0.4]]}, + "text-opacity": {"stops": [[16, 0], [17, 0.4]]}, + "icon-color": "hsl(0,0%,67%)", + "text-color": "hsl(0,0%,67%)" + } + }, + { + "id": "poi-emergency", + "type": "symbol", + "source": "versatiles-shortbread", + "source-layer": "pois", + "minzoom": 16, + "filter": ["to-boolean", ["get", "emergency"]], + "layout": { + "icon-size": {"stops": [[16, 0.5], [19, 0.5], [20, 1]]}, + "symbol-placement": "point", + "icon-optional": true, + "text-font": ["noto_sans_regular"], + "icon-image": [ + "match", + ["get", "emergency"], + "defibrillator", + "basics:icon-defibrillator", + "fire_hydrant", + "basics:icon-hydrant", + "phone", + "basics:icon-emergency_phone", + "" + ] + }, + "paint": { + "icon-opacity": {"stops": [[16, 0], [17, 0.4]]}, + "text-opacity": {"stops": [[16, 0], [17, 0.4]]}, + "icon-color": "hsl(0,0%,67%)", + "text-color": "hsl(0,0%,67%)" + } + }, + { + "id": "poi-highway", + "type": "symbol", + "source": "versatiles-shortbread", + "source-layer": "pois", + "minzoom": 16, + "filter": ["to-boolean", ["get", "highway"]], + "layout": { + "icon-size": {"stops": [[16, 0.5], [19, 0.5], [20, 1]]}, + "symbol-placement": "point", + "icon-optional": true, + "text-font": ["noto_sans_regular"] + }, + "paint": { + "icon-opacity": {"stops": [[16, 0], [17, 0.4]]}, + "text-opacity": {"stops": [[16, 0], [17, 0.4]]}, + "icon-color": "hsl(0,0%,67%)", + "text-color": "hsl(0,0%,67%)" + } + }, + { + "id": "poi-office", + "type": "symbol", + "source": "versatiles-shortbread", + "source-layer": "pois", + "minzoom": 16, + "filter": ["to-boolean", ["get", "office"]], + "layout": { + "icon-size": {"stops": [[16, 0.5], [19, 0.5], [20, 1]]}, + "symbol-placement": "point", + "icon-optional": true, + "text-font": ["noto_sans_regular"] + }, + "paint": { + "icon-opacity": {"stops": [[16, 0], [17, 0.4]]}, + "text-opacity": {"stops": [[16, 0], [17, 0.4]]}, + "icon-color": "hsl(0,0%,67%)", + "text-color": "hsl(0,0%,67%)" + } + }, + { + "id": "boundary-country:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "boundaries", + "filter": [ + "all", + ["==", "admin_level", 2], + ["!=", "maritime", true], + ["!=", "disputed", true], + ["!=", "coastline", true] + ], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "rgb(16,11,6)", + "line-blur": 1, + "line-width": {"stops": [[2, 0], [3, 2], [10, 8]]}, + "line-opacity": 0.75 + } + }, + { + "id": "boundary-country-disputed:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "boundaries", + "filter": [ + "all", + ["==", "admin_level", 2], + ["==", "disputed", true], + ["!=", "maritime", true], + ["!=", "coastline", true] + ], + "paint": { + "line-width": {"stops": [[2, 0], [3, 2], [10, 8]]}, + "line-opacity": 0.75, + "line-color": "rgb(16,11,6)" + } + }, + { + "id": "boundary-state:outline", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "boundaries", + "filter": [ + "all", + ["==", "admin_level", 4], + ["!=", "maritime", true], + ["!=", "disputed", true], + ["!=", "coastline", true] + ], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "rgb(15,11,5)", + "line-blur": 1, + "line-width": {"stops": [[7, 0], [8, 2], [10, 4]]}, + "line-opacity": 0.75 + } + }, + { + "id": "boundary-country", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "boundaries", + "filter": [ + "all", + ["==", "admin_level", 2], + ["!=", "maritime", true], + ["!=", "disputed", true], + ["!=", "coastline", true] + ], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "hsl(240,24%,28%)", + "line-width": {"stops": [[2, 0], [3, 1], [10, 4]]} + } + }, + { + "id": "boundary-country-disputed", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "boundaries", + "filter": [ + "all", + ["==", "admin_level", 2], + ["==", "disputed", true], + ["!=", "maritime", true], + ["!=", "coastline", true] + ], + "layout": {"line-cap": "square"}, + "paint": { + "line-width": {"stops": [[2, 0], [3, 1], [10, 4]]}, + "line-color": "hsl(246,17%,23%)", + "line-dasharray": [2, 1] + } + }, + { + "id": "boundary-state", + "type": "line", + "source": "versatiles-shortbread", + "source-layer": "boundaries", + "filter": [ + "all", + ["==", "admin_level", 4], + ["!=", "maritime", true], + ["!=", "disputed", true], + ["!=", "coastline", true] + ], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "hsl(240,24%,28%)", + "line-width": {"stops": [[7, 0], [8, 1], [10, 2]]} + } + }, + { + "id": "label-address-housenumber", + "type": "symbol", + "source": "versatiles-shortbread", + "source-layer": "addresses", + "minzoom": 17, + "filter": ["has", "housenumber"], + "layout": { + "text-field": "{housenumber}", + "text-font": ["noto_sans_regular"], + "symbol-placement": "point", + "text-anchor": "center", + "text-size": {"stops": [[17, 8], [19, 10]]} + }, + "paint": { + "icon-color": "rgb(95,88,81)", + "text-color": "rgb(95,88,81)", + "text-halo-color": "rgb(23,17,10)", + "text-halo-width": 2, + "text-halo-blur": 1 + } + }, + { + "id": "label-motorway-shield", + "type": "symbol", + "source": "versatiles-shortbread", + "source-layer": "street_labels", + "minzoom": 14, + "filter": ["==", "kind", "motorway"], + "layout": { + "text-field": "{ref}", + "text-font": ["noto_sans_bold"], + "symbol-placement": "line", + "text-anchor": "center", + "text-size": {"stops": [[14, 10], [18, 12], [20, 16]]} + }, + "paint": { + "icon-color": "hsl(0,0%,0%)", + "text-color": "hsl(0,0%,0%)", + "text-halo-color": "hsl(34,100%,23%)", + "text-halo-width": 0.1, + "text-halo-blur": 1 + } + }, + { + "id": "label-street-pedestrian", + "type": "symbol", + "source": "versatiles-shortbread", + "source-layer": "street_labels", + "minzoom": 12, + "filter": ["==", "kind", "pedestrian"], + "layout": { + "text-field": [ + "coalesce", + ["get", "name"], + ["get", "name_en"], + ["get", "name_de"] + ], + "text-font": ["noto_sans_regular"], + "symbol-placement": "line", + "text-anchor": "center", + "text-size": {"stops": [[12, 10], [15, 13]]} + }, + "paint": { + "icon-color": "hsl(240,14%,77%)", + "text-color": "hsl(240,14%,77%)", + "text-halo-color": "hsla(0,0%,0%,0.8)", + "text-halo-width": 2, + "text-halo-blur": 1 + } + }, + { + "id": "label-street-livingstreet", + "type": "symbol", + "source": "versatiles-shortbread", + "source-layer": "street_labels", + "minzoom": 12, + "filter": ["==", "kind", "living_street"], + "layout": { + "text-field": [ + "coalesce", + ["get", "name"], + ["get", "name_en"], + ["get", "name_de"] + ], + "text-font": ["noto_sans_regular"], + "symbol-placement": "line", + "text-anchor": "center", + "text-size": {"stops": [[12, 10], [15, 13]]} + }, + "paint": { + "icon-color": "hsl(240,14%,77%)", + "text-color": "hsl(240,14%,77%)", + "text-halo-color": "hsla(0,0%,0%,0.8)", + "text-halo-width": 2, + "text-halo-blur": 1 + } + }, + { + "id": "label-street-residential", + "type": "symbol", + "source": "versatiles-shortbread", + "source-layer": "street_labels", + "minzoom": 12, + "filter": ["==", "kind", "residential"], + "layout": { + "text-field": [ + "coalesce", + ["get", "name"], + ["get", "name_en"], + ["get", "name_de"] + ], + "text-font": ["noto_sans_regular"], + "symbol-placement": "line", + "text-anchor": "center", + "text-size": {"stops": [[12, 10], [15, 13]]} + }, + "paint": { + "icon-color": "hsl(240,14%,77%)", + "text-color": "hsl(240,14%,77%)", + "text-halo-color": "hsla(0,0%,0%,0.8)", + "text-halo-width": 2, + "text-halo-blur": 1 + } + }, + { + "id": "label-street-unclassified", + "type": "symbol", + "source": "versatiles-shortbread", + "source-layer": "street_labels", + "minzoom": 12, + "filter": ["==", "kind", "unclassified"], + "layout": { + "text-field": [ + "coalesce", + ["get", "name"], + ["get", "name_en"], + ["get", "name_de"] + ], + "text-font": ["noto_sans_regular"], + "symbol-placement": "line", + "text-anchor": "center", + "text-size": {"stops": [[12, 10], [15, 13]]} + }, + "paint": { + "icon-color": "hsl(240,14%,77%)", + "text-color": "hsl(240,14%,77%)", + "text-halo-color": "hsla(0,0%,0%,0.8)", + "text-halo-width": 2, + "text-halo-blur": 1 + } + }, + { + "id": "label-street-tertiary", + "type": "symbol", + "source": "versatiles-shortbread", + "source-layer": "street_labels", + "minzoom": 12, + "filter": ["==", "kind", "tertiary"], + "layout": { + "text-field": [ + "coalesce", + ["get", "name"], + ["get", "name_en"], + ["get", "name_de"] + ], + "text-font": ["noto_sans_regular"], + "symbol-placement": "line", + "text-anchor": "center", + "text-size": {"stops": [[12, 10], [15, 13]]} + }, + "paint": { + "icon-color": "hsl(240,14%,77%)", + "text-color": "hsl(240,14%,77%)", + "text-halo-color": "hsla(0,0%,0%,0.8)", + "text-halo-width": 2, + "text-halo-blur": 1 + } + }, + { + "id": "label-street-secondary", + "type": "symbol", + "source": "versatiles-shortbread", + "source-layer": "street_labels", + "minzoom": 12, + "filter": ["==", "kind", "secondary"], + "layout": { + "text-field": [ + "coalesce", + ["get", "name"], + ["get", "name_en"], + ["get", "name_de"] + ], + "text-font": ["noto_sans_regular"], + "symbol-placement": "line", + "text-anchor": "center", + "text-size": {"stops": [[12, 10], [15, 13]]} + }, + "paint": { + "icon-color": "hsl(240,14%,77%)", + "text-color": "hsl(240,14%,77%)", + "text-halo-color": "hsla(0,0%,0%,0.8)", + "text-halo-width": 2, + "text-halo-blur": 1 + } + }, + { + "id": "label-street-primary", + "type": "symbol", + "source": "versatiles-shortbread", + "source-layer": "street_labels", + "minzoom": 12, + "filter": ["==", "kind", "primary"], + "layout": { + "text-field": [ + "coalesce", + ["get", "name"], + ["get", "name_en"], + ["get", "name_de"] + ], + "text-font": ["noto_sans_regular"], + "symbol-placement": "line", + "text-anchor": "center", + "text-size": {"stops": [[12, 10], [15, 13]]} + }, + "paint": { + "icon-color": "hsl(240,14%,77%)", + "text-color": "hsl(240,14%,77%)", + "text-halo-color": "hsla(0,0%,0%,0.8)", + "text-halo-width": 2, + "text-halo-blur": 1 + } + }, + { + "id": "label-street-trunk", + "type": "symbol", + "source": "versatiles-shortbread", + "source-layer": "street_labels", + "minzoom": 12, + "filter": ["==", "kind", "trunk"], + "layout": { + "text-field": [ + "coalesce", + ["get", "name"], + ["get", "name_en"], + ["get", "name_de"] + ], + "text-font": ["noto_sans_regular"], + "symbol-placement": "line", + "text-anchor": "center", + "text-size": {"stops": [[12, 10], [15, 13]]} + }, + "paint": { + "icon-color": "hsl(240,14%,77%)", + "text-color": "hsl(240,14%,77%)", + "text-halo-color": "hsla(0,0%,0%,0.8)", + "text-halo-width": 2, + "text-halo-blur": 1 + } + }, + { + "id": "label-street-track", + "type": "symbol", + "source": "versatiles-shortbread", + "source-layer": "street_labels", + "minzoom": 12, + "filter": ["==", "kind", "track"], + "layout": { + "text-field": [ + "coalesce", + ["get", "name"], + ["get", "name_en"], + ["get", "name_de"] + ], + "text-font": ["noto_sans_regular"], + "symbol-placement": "line", + "text-anchor": "center", + "text-size": {"stops": [[12, 10], [15, 13]]} + }, + "paint": { + "icon-color": "hsl(240,14%,77%)", + "text-color": "hsl(240,14%,77%)", + "text-halo-color": "hsla(0,0%,0%,0.8)", + "text-halo-width": 2, + "text-halo-blur": 1 + } + }, + { + "id": "label-place-neighbourhood", + "type": "symbol", + "source": "versatiles-shortbread", + "source-layer": "place_labels", + "minzoom": 14, + "filter": ["==", "kind", "neighbourhood"], + "layout": { + "text-field": [ + "coalesce", + ["get", "name"], + ["get", "name_en"], + ["get", "name_de"] + ], + "symbol-sort-key": ["-", ["to-number", ["get", "population"], 0]], + "text-font": ["noto_sans_regular"], + "text-size": {"stops": [[14, 12]]}, + "text-transform": "uppercase" + }, + "paint": { + "icon-color": "rgb(182,209,215)", + "text-color": "rgb(182,209,215)", + "text-halo-color": "hsla(0,0%,0%,0.8)", + "text-halo-width": 2, + "text-halo-blur": 1 + } + }, + { + "id": "label-place-quarter", + "type": "symbol", + "source": "versatiles-shortbread", + "source-layer": "place_labels", + "minzoom": 13, + "filter": ["==", "kind", "quarter"], + "layout": { + "text-field": [ + "coalesce", + ["get", "name"], + ["get", "name_en"], + ["get", "name_de"] + ], + "symbol-sort-key": ["-", ["to-number", ["get", "population"], 0]], + "text-font": ["noto_sans_regular"], + "text-size": {"stops": [[13, 13]]}, + "text-transform": "uppercase" + }, + "paint": { + "icon-color": "rgb(182,204,215)", + "text-color": "rgb(182,204,215)", + "text-halo-color": "hsla(0,0%,0%,0.8)", + "text-halo-width": 2, + "text-halo-blur": 1 + } + }, + { + "id": "label-place-suburb", + "type": "symbol", + "source": "versatiles-shortbread", + "source-layer": "place_labels", + "minzoom": 11, + "filter": ["==", "kind", "suburb"], + "layout": { + "text-field": [ + "coalesce", + ["get", "name"], + ["get", "name_en"], + ["get", "name_de"] + ], + "symbol-sort-key": ["-", ["to-number", ["get", "population"], 0]], + "text-font": ["noto_sans_regular"], + "text-size": {"stops": [[11, 11], [13, 14]]}, + "text-transform": "uppercase" + }, + "paint": { + "icon-color": "rgb(182,198,215)", + "text-color": "rgb(182,198,215)", + "text-halo-color": "hsla(0,0%,0%,0.8)", + "text-halo-width": 2, + "text-halo-blur": 1 + } + }, + { + "id": "label-place-hamlet", + "type": "symbol", + "source": "versatiles-shortbread", + "source-layer": "place_labels", + "minzoom": 13, + "filter": ["==", "kind", "hamlet"], + "layout": { + "text-field": [ + "coalesce", + ["get", "name"], + ["get", "name_en"], + ["get", "name_de"] + ], + "symbol-sort-key": ["-", ["to-number", ["get", "population"], 0]], + "text-font": ["noto_sans_regular"], + "text-size": {"stops": [[10, 11], [12, 14]]} + }, + "paint": { + "icon-color": "rgb(182,190,215)", + "text-color": "rgb(182,190,215)", + "text-halo-color": "hsla(0,0%,0%,0.8)", + "text-halo-width": 2, + "text-halo-blur": 1 + } + }, + { + "id": "label-place-village", + "type": "symbol", + "source": "versatiles-shortbread", + "source-layer": "place_labels", + "minzoom": 11, + "filter": ["==", "kind", "village"], + "layout": { + "text-field": [ + "coalesce", + ["get", "name"], + ["get", "name_en"], + ["get", "name_de"] + ], + "symbol-sort-key": ["-", ["to-number", ["get", "population"], 0]], + "text-font": ["noto_sans_regular"], + "text-size": {"stops": [[9, 11], [12, 14]]} + }, + "paint": { + "icon-color": "rgb(182,190,215)", + "text-color": "rgb(182,190,215)", + "text-halo-color": "hsla(0,0%,0%,0.8)", + "text-halo-width": 2, + "text-halo-blur": 1 + } + }, + { + "id": "label-place-town", + "type": "symbol", + "source": "versatiles-shortbread", + "source-layer": "place_labels", + "minzoom": 9, + "filter": ["==", "kind", "town"], + "layout": { + "text-field": [ + "coalesce", + ["get", "name"], + ["get", "name_en"], + ["get", "name_de"] + ], + "symbol-sort-key": ["-", ["to-number", ["get", "population"], 0]], + "text-font": ["noto_sans_regular"], + "text-size": {"stops": [[8, 11], [12, 14]]} + }, + "paint": { + "icon-color": "rgb(182,190,215)", + "text-color": "rgb(182,190,215)", + "text-halo-color": "hsla(0,0%,0%,0.8)", + "text-halo-width": 2, + "text-halo-blur": 1 + } + }, + { + "id": "label-boundary-state", + "type": "symbol", + "source": "versatiles-shortbread", + "source-layer": "boundary_labels", + "minzoom": 5, + "filter": ["in", "admin_level", 4, "4"], + "layout": { + "text-field": [ + "coalesce", + ["get", "name"], + ["get", "name_en"], + ["get", "name_de"] + ], + "text-font": ["noto_sans_regular"], + "text-transform": "uppercase", + "text-anchor": "top", + "text-offset": [0, 0.2], + "text-padding": 0, + "text-optional": true, + "text-size": {"stops": [[5, 8], [8, 12]]} + }, + "paint": { + "icon-color": "rgb(178,178,194)", + "text-color": "rgb(178,178,194)", + "text-halo-color": "hsla(0,0%,0%,0.8)", + "text-halo-width": 2, + "text-halo-blur": 1 + } + }, + { + "id": "label-place-city", + "type": "symbol", + "source": "versatiles-shortbread", + "source-layer": "place_labels", + "minzoom": 7, + "filter": ["==", "kind", "city"], + "layout": { + "text-field": [ + "coalesce", + ["get", "name"], + ["get", "name_en"], + ["get", "name_de"] + ], + "symbol-sort-key": ["-", ["to-number", ["get", "population"], 0]], + "text-font": ["noto_sans_regular"], + "text-size": {"stops": [[7, 11], [10, 14]]} + }, + "paint": { + "icon-color": "rgb(182,190,215)", + "text-color": "rgb(182,190,215)", + "text-halo-color": "hsla(0,0%,0%,0.8)", + "text-halo-width": 2, + "text-halo-blur": 1 + } + }, + { + "id": "label-place-statecapital", + "type": "symbol", + "source": "versatiles-shortbread", + "source-layer": "place_labels", + "minzoom": 6, + "filter": ["==", "kind", "state_capital"], + "layout": { + "text-field": [ + "coalesce", + ["get", "name"], + ["get", "name_en"], + ["get", "name_de"] + ], + "symbol-sort-key": ["-", ["to-number", ["get", "population"], 0]], + "text-font": ["noto_sans_regular"], + "text-size": {"stops": [[6, 11], [10, 15]]} + }, + "paint": { + "icon-color": "rgb(182,190,215)", + "text-color": "rgb(182,190,215)", + "text-halo-color": "hsla(0,0%,0%,0.8)", + "text-halo-width": 2, + "text-halo-blur": 1 + } + }, + { + "id": "label-place-capital", + "type": "symbol", + "source": "versatiles-shortbread", + "source-layer": "place_labels", + "minzoom": 5, + "filter": ["==", "kind", "capital"], + "layout": { + "text-field": [ + "coalesce", + ["get", "name"], + ["get", "name_en"], + ["get", "name_de"] + ], + "symbol-sort-key": ["-", ["to-number", ["get", "population"], 0]], + "text-font": ["noto_sans_regular"], + "text-size": {"stops": [[5, 12], [10, 16]]} + }, + "paint": { + "icon-color": "rgb(182,190,215)", + "text-color": "rgb(182,190,215)", + "text-halo-color": "hsla(0,0%,0%,0.8)", + "text-halo-width": 2, + "text-halo-blur": 1 + } + }, + { + "id": "label-boundary-country-small", + "type": "symbol", + "source": "versatiles-shortbread", + "source-layer": "boundary_labels", + "minzoom": 4, + "filter": [ + "all", + ["in", "admin_level", 2, "2"], + ["<=", "way_area", 10000000] + ], + "layout": { + "text-field": [ + "coalesce", + ["get", "name"], + ["get", "name_en"], + ["get", "name_de"] + ], + "text-font": ["noto_sans_regular"], + "text-transform": "uppercase", + "text-anchor": "top", + "text-offset": [0, 0.2], + "text-padding": 0, + "text-optional": true, + "text-size": {"stops": [[4, 8], [5, 11]]} + }, + "paint": { + "icon-color": "hsl(240,14%,77%)", + "text-color": "hsl(240,14%,77%)", + "text-halo-color": "hsla(0,0%,0%,0.8)", + "text-halo-width": 2, + "text-halo-blur": 1 + } + }, + { + "id": "label-boundary-country-medium", + "type": "symbol", + "source": "versatiles-shortbread", + "source-layer": "boundary_labels", + "minzoom": 3, + "filter": [ + "all", + ["in", "admin_level", 2, "2"], + ["<", "way_area", 90000000], + [">", "way_area", 10000000] + ], + "layout": { + "text-field": [ + "coalesce", + ["get", "name"], + ["get", "name_en"], + ["get", "name_de"] + ], + "text-font": ["noto_sans_regular"], + "text-transform": "uppercase", + "text-anchor": "top", + "text-offset": [0, 0.2], + "text-padding": 0, + "text-optional": true, + "text-size": {"stops": [[3, 8], [5, 12]]} + }, + "paint": { + "icon-color": "hsl(240,14%,77%)", + "text-color": "hsl(240,14%,77%)", + "text-halo-color": "hsla(0,0%,0%,0.8)", + "text-halo-width": 2, + "text-halo-blur": 1 + } + }, + { + "id": "label-boundary-country-large", + "type": "symbol", + "source": "versatiles-shortbread", + "source-layer": "boundary_labels", + "minzoom": 2, + "filter": [ + "all", + ["in", "admin_level", 2, "2"], + [">=", "way_area", 90000000] + ], + "layout": { + "text-field": [ + "coalesce", + ["get", "name"], + ["get", "name_en"], + ["get", "name_de"] + ], + "text-font": ["noto_sans_regular"], + "text-transform": "uppercase", + "text-anchor": "top", + "text-offset": [0, 0.2], + "text-padding": 0, + "text-optional": true, + "text-size": {"stops": [[2, 8], [5, 13]]} + }, + "paint": { + "icon-color": "hsl(240,14%,77%)", + "text-color": "hsl(240,14%,77%)", + "text-halo-color": "hsla(0,0%,0%,0.8)", + "text-halo-width": 2, + "text-halo-blur": 1 + } + }, + { + "id": "marking-oneway", + "type": "symbol", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 16, + "filter": [ + "all", + ["==", "oneway", true], + [ + "in", + "kind", + "trunk", + "primary", + "secondary", + "tertiary", + "unclassified", + "residential", + "living_street" + ] + ], + "layout": { + "symbol-placement": "line", + "symbol-spacing": 175, + "icon-rotate": 90, + "icon-rotation-alignment": "map", + "icon-padding": 5, + "symbol-avoid-edges": true, + "icon-image": "basics:marking-arrow", + "text-font": ["noto_sans_regular"] + }, + "paint": { + "icon-opacity": {"stops": [[16, 0], [17, 0.4], [20, 0.4]]}, + "text-opacity": {"stops": [[16, 0], [17, 0.4], [20, 0.4]]} + } + }, + { + "id": "marking-oneway-reverse", + "type": "symbol", + "source": "versatiles-shortbread", + "source-layer": "streets", + "minzoom": 16, + "filter": [ + "all", + ["==", "oneway_reverse", true], + [ + "in", + "kind", + "trunk", + "primary", + "secondary", + "tertiary", + "unclassified", + "residential", + "living_street" + ] + ], + "layout": { + "symbol-placement": "line", + "symbol-spacing": 75, + "icon-rotate": -90, + "icon-rotation-alignment": "map", + "icon-padding": 5, + "symbol-avoid-edges": true, + "icon-image": "basics:marking-arrow", + "text-font": ["noto_sans_regular"] + }, + "paint": { + "icon-opacity": {"stops": [[16, 0], [17, 0.4], [20, 0.4]]}, + "text-opacity": {"stops": [[16, 0], [17, 0.4], [20, 0.4]]} + } + }, + { + "id": "symbol-transit-station", + "type": "symbol", + "source": "versatiles-shortbread", + "source-layer": "public_transport", + "minzoom": 13, + "filter": [ + "all", + ["in", "kind", "station", "halt"], + ["!in", "station", "light_rail", "subway"] + ], + "layout": { + "text-field": [ + "coalesce", + ["get", "name"], + ["get", "name_en"], + ["get", "name_de"] + ], + "icon-size": {"stops": [[13, 0.5], [15, 1]]}, + "symbol-placement": "point", + "icon-keep-upright": true, + "text-font": ["noto_sans_regular"], + "text-size": 10, + "icon-anchor": "bottom", + "text-anchor": "top", + "icon-image": "basics:icon-rail" + }, + "paint": { + "icon-opacity": 0.7, + "icon-color": "hsl(270,4%,60%)", + "text-color": "hsl(270,4%,60%)", + "text-halo-color": "hsla(0,0%,0%,0.8)", + "text-halo-width": 2, + "text-halo-blur": 1 + } + }, + { + "id": "symbol-transit-airfield", + "type": "symbol", + "source": "versatiles-shortbread", + "source-layer": "public_transport", + "minzoom": 13, + "filter": ["all", ["==", "kind", "aerodrome"], ["!has", "iata"]], + "layout": { + "text-field": [ + "coalesce", + ["get", "name"], + ["get", "name_en"], + ["get", "name_de"] + ], + "icon-size": {"stops": [[13, 0.5], [15, 1]]}, + "symbol-placement": "point", + "icon-keep-upright": true, + "text-font": ["noto_sans_regular"], + "text-size": 10, + "icon-anchor": "bottom", + "text-anchor": "top", + "icon-image": "basics:icon-airfield" + }, + "paint": { + "icon-opacity": 0.7, + "icon-color": "hsl(270,4%,60%)", + "text-color": "hsl(270,4%,60%)", + "text-halo-color": "hsla(0,0%,0%,0.8)", + "text-halo-width": 2, + "text-halo-blur": 1 + } + }, + { + "id": "symbol-transit-airport", + "type": "symbol", + "source": "versatiles-shortbread", + "source-layer": "public_transport", + "minzoom": 12, + "filter": ["all", ["==", "kind", "aerodrome"], ["has", "iata"]], + "layout": { + "text-field": [ + "coalesce", + ["get", "name"], + ["get", "name_en"], + ["get", "name_de"] + ], + "icon-size": {"stops": [[12, 0.5], [14, 1]]}, + "symbol-placement": "point", + "icon-keep-upright": true, + "text-font": ["noto_sans_regular"], + "text-size": 10, + "icon-anchor": "bottom", + "text-anchor": "top", + "icon-image": "basics:icon-airport" + }, + "paint": { + "icon-opacity": 0.7, + "icon-color": "hsl(270,4%,60%)", + "text-color": "hsl(270,4%,60%)", + "text-halo-color": "hsla(0,0%,0%,0.8)", + "text-halo-width": 2, + "text-halo-blur": 1 + } + } + ], + "id": "80klscq" +} \ No newline at end of file diff --git a/app/src/main/java/it/reyboz/bustorino/ActivityIntro.kt b/app/src/main/java/it/reyboz/bustorino/ActivityIntro.kt index b65224f..032dc8f 100644 --- a/app/src/main/java/it/reyboz/bustorino/ActivityIntro.kt +++ b/app/src/main/java/it/reyboz/bustorino/ActivityIntro.kt @@ -1,143 +1,141 @@ package it.reyboz.bustorino import android.content.Intent import android.os.Build import android.os.Bundle import android.util.Log import android.view.View import android.widget.ImageButton import androidx.core.view.ViewCompat import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.widget.ViewPager2 import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayoutMediator import it.reyboz.bustorino.data.DBUpdateCheckWorker import it.reyboz.bustorino.data.PreferencesHolder import it.reyboz.bustorino.fragments.IntroFragment import it.reyboz.bustorino.middleware.GeneralActivity class ActivityIntro : GeneralActivity(), IntroFragment.IntroListener { private lateinit var viewPager : ViewPager2 private lateinit var btnForward: ImageButton private lateinit var btnBackward: ImageButton private lateinit var closeBottomButton: ImageButton private var restartMain = true override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_intro) - //check default settings and apply them - checkApplyDefaultSettingsValues() viewPager = findViewById(R.id.viewPager) btnBackward = findViewById(R.id.btnPrevious) btnForward = findViewById(R.id.btnNext) closeBottomButton = findViewById(R.id.btnCompactClose) val extras = intent.extras if(extras!=null){ restartMain = extras.getBoolean(RESTART_MAIN) } val adapter = IntroPagerAdapter(this) viewPager.adapter = adapter val tabLayout = findViewById(R.id.tab_layout) val tabLayoutMediator = TabLayoutMediator(tabLayout, viewPager) { tab, pos -> Log.d(DEBUG_TAG, "tabview on position $pos") } tabLayoutMediator.attach() btnForward.setOnClickListener { viewPager.setCurrentItem(viewPager.currentItem+1,true) } btnBackward.setOnClickListener { viewPager.setCurrentItem(viewPager.currentItem-1, true) } /*closeBottomButton.setOnClickListener { closeIntroduction() } */ viewPager.registerOnPageChangeCallback(object : OnPageChangeCallback() { override fun onPageSelected(position: Int) { if(position == 0){ btnBackward.visibility = View.INVISIBLE } else{ btnBackward.visibility = View.VISIBLE } if(position == NUM_ITEMS-1){ btnForward.visibility = View.INVISIBLE closeBottomButton.visibility = View.VISIBLE }else if(position == NUM_ITEMS-2){ if(closeBottomButton.visibility == View.VISIBLE) { closeBottomButton.visibility = View.INVISIBLE btnForward.visibility = View.VISIBLE } //btnForward.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.arrow_forward_white, null)) //btnForward.setBackgroundColor(ResourcesCompat.getColor(resources,R.attr.colorAccent, theme)) /*val GET THE COLOR VALUE OF THE THEMER colo = TypedValue() theme.resolveAttribute(R.attr.colorAccent,colo, true) btnForward.backgroundTintList //(colo.data) */ } } }) closeBottomButton.setOnClickListener { closeIntroduction() } ViewCompat.setOnApplyWindowInsetsListener( findViewById(R.id.theConstraintLayout), this.applyBottomAndBordersInsetsListener ) // Start the DB Update now DBUpdateCheckWorker.schedulePeriodicCheck(this, true) } /** * A simple pager adapter that represents 5 ScreenSlidePageFragment objects, in * sequence. */ private inner class IntroPagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) { override fun getItemCount(): Int = NUM_ITEMS override fun createFragment(position: Int): Fragment = IntroFragment.newInstance(position) } companion object{ const private val DEBUG_TAG = "BusTO-IntroActivity" const val RESTART_MAIN = "restartMainActivity" val NUM_ITEMS = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) 8 else 7 } override fun closeIntroduction() { if(restartMain) startActivity(Intent(this, ActivityPrincipal::class.java)) val pref = PreferencesHolder.getMainSharedPreferences(this) val editor = pref.edit() editor.putBoolean(PreferencesHolder.PREF_INTRO_ACTIVITY_RUN, true) //use commit so we don't "lose" info editor.commit() finish() } } \ No newline at end of file diff --git a/app/src/main/java/it/reyboz/bustorino/ActivityPrincipal.java b/app/src/main/java/it/reyboz/bustorino/ActivityPrincipal.java index fb88b9a..517627b 100644 --- a/app/src/main/java/it/reyboz/bustorino/ActivityPrincipal.java +++ b/app/src/main/java/it/reyboz/bustorino/ActivityPrincipal.java @@ -1,917 +1,918 @@ /* BusTO - Arrival times for Turin public transport. Copyright (C) 2021 Fabio Mazza This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package it.reyboz.bustorino; import android.Manifest; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.net.Uri; import android.os.Bundle; import android.util.Log; import android.view.*; import android.widget.Toast; import androidx.activity.OnBackPressedCallback; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBarDrawerToggle; +import androidx.appcompat.app.AppCompatDelegate; import androidx.appcompat.widget.Toolbar; import androidx.core.graphics.Insets; import androidx.core.view.*; import androidx.drawerlayout.widget.DrawerLayout; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; import androidx.lifecycle.ViewModelProvider; import androidx.preference.PreferenceManager; import androidx.work.WorkInfo; import com.google.android.material.navigation.NavigationView; import com.google.android.material.snackbar.Snackbar; import java.util.Arrays; import java.util.Map; +import java.util.Objects; import java.util.concurrent.LinkedBlockingDeque; import java.util.function.Consumer; import it.reyboz.bustorino.backend.Stop; import it.reyboz.bustorino.data.DBUpdateCheckWorker; import it.reyboz.bustorino.data.DBUpdateWorker; import it.reyboz.bustorino.data.PreferencesHolder; import it.reyboz.bustorino.fragments.*; import it.reyboz.bustorino.middleware.GeneralActivity; import it.reyboz.bustorino.viewmodels.ServiceAlertsViewModel; import static it.reyboz.bustorino.backend.utils.getBusStopIDFromUri; import static it.reyboz.bustorino.backend.utils.openIceweasel; public class ActivityPrincipal extends GeneralActivity implements FragmentListenerMain { private DrawerLayout mDrawer; private NavigationView mNavView; private ActionBarDrawerToggle drawerToggle; private final static String DEBUG_TAG="BusTO Act Principal"; private final static String TAG_FAVORITES="favorites_frag"; private Snackbar snackbar; + private ServiceAlertsViewModel serviceAlertsViewModel; //private FragmentKind showingFragmentKind; private final Map menuActions = Map.of( R.id.drawer_action_settings, () -> { Log.d("MAINBusTO", "Pressed button preferences"); startActivity(new Intent(this, ActivitySettings.class)); }, R.id.nav_favorites_item, () -> checkAndShowFavoritesFragment(getSupportFragmentManager(), true), R.id.nav_home, this::showHomeMainFragmentFromClick, R.id.nav_map_item, () -> requestMapFragment(true), R.id.nav_lines_item, () -> showLinesFragment(getSupportFragmentManager(), true, null), R.id.drawer_action_info, () -> startActivity(new Intent(this, ActivityAbout.class)), R.id.nav_nearby, this::openNearbyStopsFragment ); private long lastClosingAttempt = -1L; private final OnBackPressedCallback backPressedCallback = new OnBackPressedCallback(false) { @Override public void handleOnBackPressed() { boolean isResolved = activityCustomBackPressed(); Log.d(DEBUG_TAG, "backpress resolved: " + isResolved); if(!isResolved){ long currentTime = System.currentTimeMillis(); if(currentTime - lastClosingAttempt < 2000){ finish(); } else{ lastClosingAttempt = currentTime; Toast.makeText(getApplicationContext(),R.string.back_again_to_close,Toast.LENGTH_SHORT).show(); } } } }; + + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d(DEBUG_TAG, "onCreate, savedInstanceState is: "+savedInstanceState); setContentView(R.layout.activity_principal); serviceAlertsViewModel = new ViewModelProvider(this).get(ServiceAlertsViewModel.class); //Use LiveModel to sync fragment state - /*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - getWindow().setNavigationBarContrastEnforced(false); - } - */ //onBackPressed solution required from Android 16 backPressedCallback.setEnabled(true); this.getOnBackPressedDispatcher().addCallback(backPressedCallback); boolean showingArrivalsFromIntent = false; final Toolbar mToolbar = findViewById(R.id.default_toolbar); setSupportActionBar(mToolbar); if (getSupportActionBar()!=null) getSupportActionBar().setDisplayHomeAsUpEnabled(true); else Log.w(DEBUG_TAG, "NO ACTION BAR"); mToolbar.setOnMenuItemClickListener(new ToolbarItemClickListener(this)); mDrawer = findViewById(R.id.drawer_layout); drawerToggle = setupDrawerToggle(mToolbar); // Setup toggle to display hamburger icon with nice animation drawerToggle.setDrawerIndicatorEnabled(true); drawerToggle.syncState(); mDrawer.addDrawerListener(drawerToggle); mDrawer.addDrawerListener(new DrawerLayout.DrawerListener() { @Override public void onDrawerSlide(@NonNull View drawerView, float slideOffset) { } @Override public void onDrawerOpened(@NonNull View drawerView) { hideKeyboard(); } @Override public void onDrawerClosed(@NonNull View drawerView) { } @Override public void onDrawerStateChanged(int newState) { } }); mNavView = findViewById(R.id.nvView); setupDrawerContent(mNavView); /*View header = mNavView.getHeaderView(0); */ //mNavView.getMenu().findItem(R.id.versionFooter). /// LEGACY CODE //---------------------------- START INTENT CHECK QUEUE ------------------------------------ // Intercept calls from URL intent boolean tryedFromIntent = false; String busStopID = null; Uri data = getIntent().getData(); if (data != null) { busStopID = getBusStopIDFromUri(data); Log.d(DEBUG_TAG, "Opening Intent: busStopID: "+busStopID); tryedFromIntent = true; } // Intercept calls from other activities if (!tryedFromIntent) { Bundle b = getIntent().getExtras(); if (b != null) { busStopID = b.getString("bus-stop-ID"); /* * I'm not very sure if you are coming from an Intent. * Some launchers work in strange ways. */ tryedFromIntent = busStopID != null; } } //---------------------------- END INTENT CHECK QUEUE -------------------------------------- if (busStopID == null) { // Show keyboard if can't start from intent // JUST DON'T // showKeyboard(); // You haven't obtained anything... from an intent? if (tryedFromIntent) { // This shows a luser warning Toast.makeText(getApplicationContext(), R.string.insert_bus_stop_number_error, Toast.LENGTH_SHORT).show(); } } else { // If you are here an intent has worked successfully //setBusStopSearchByIDEditText(busStopID); //Log.d(DEBUG_TAG, "Requesting arrivals for stop "+busStopID+" from intent"); requestArrivalsForStopID(busStopID); //this shows the fragment, too showingArrivalsFromIntent = true; } //database check // DatabaseUpdate.requestDBUpdateWithWork(this, false, false); DBUpdateCheckWorker.Companion.schedulePeriodicCheck(this,false); /* Watch for database update */ DBUpdateWorker.getWorkInfoLiveData(this) .observe(this, workInfoList -> { // If there are no matching work info, do nothing if (workInfoList == null || workInfoList.isEmpty()) { return; } Log.d(DEBUG_TAG, "WorkerInfo: "+workInfoList); boolean showProgress = false; for (WorkInfo workInfo : workInfoList) { if (workInfo.getState() == WorkInfo.State.RUNNING) { showProgress = true; break; } } if (showProgress) { createDatabaseUpdateSnackbar(); } else { if(snackbar!=null) { snackbar.dismiss(); snackbar = null; } } }); // show the main fragment Fragment f = getSupportFragmentManager().findFragmentById(R.id.mainActContentFrame); Log.d(DEBUG_TAG, "OnCreate the fragment is "+f); String vl = PreferenceManager.getDefaultSharedPreferences(this).getString(SettingsFragment.PREF_KEY_STARTUP_SCREEN, ""); Log.d(DEBUG_TAG, "The default screen to open is: "+vl); if (showingArrivalsFromIntent){ //do nothing but exclude a case }else if (savedInstanceState==null) { var framan = getSupportFragmentManager(); //we are not restarting the activity from nothing switch (vl){ case "map" -> {requestMapFragment(false);} case "favorites" -> checkAndShowFavoritesFragment(framan, false); case "lines" -> showLinesFragment(framan, false, null); case "nearby" -> createShowMainFragment(framan, MainScreenFragment.makeArgsNearby(), false); default -> createShowMainFragment(framan, MainScreenFragment.makeArgsButtonsScreen(), false); } } //boolean onCreateComplete = true; - //last but not least, set the good default values - checkApplyDefaultSettingsValues(); + //default values are set in the BustoApp + //checkApplyDefaultSettingsValues(); // handle the device "insets" /* ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.rootRelativeLayout), (v, windowInsets) -> { Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); // Apply the insets as a margin to the view. This solution sets only the // bottom, left, and right dimensions, but you can apply whichever insets are // appropriate to your layout. You can also update the view padding if that's // more appropriate. ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) v.getLayoutParams(); mlp.leftMargin = insets.left; mlp.bottomMargin = insets.bottom; mlp.rightMargin = insets.right; v.setLayoutParams(mlp); //set for toolbar //mlp = (ViewGroup.MarginLayoutParams) mToolbar.getLayoutParams(); //mlp.topMargin = insets.top; //mToolbar.setLayoutParams(mlp); mToolbar.setPadding(0, insets.top, 0, 0); // Return CONSUMED if you don't want the window insets to keep passing // down to descendant views. return WindowInsetsCompat.CONSUMED; }); //to properly handle IME WindowInsetsControllerCompat insetsController = WindowCompat.getInsetsController(getWindow(), getWindow().getDecorView()); if (insetsController != null) { insetsController.setSystemBarsBehavior( WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE ); } */ // Toolbar: solo inset superiore (status bar) ViewCompat.setOnApplyWindowInsetsListener(mToolbar, (v, windowInsets) -> { Insets statusBar = windowInsets.getInsets(WindowInsetsCompat.Type.statusBars()); v.setPadding(0, statusBar.top, 0, 0); return windowInsets; // NON consumare: passa gli insets ai figli }); // Content frame: insets laterali e inferiori (navigation bar) // I fragment figli riceveranno gli insets e potranno gestirli a loro volta ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.mainActContentFrame), (v, windowInsets) -> { Insets systemBars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); // Solo left/right, bottom lo gestisce ogni fragment v.setPadding(systemBars.left, 0, systemBars.right, 0); return windowInsets; //WindowInsetsCompat.CONSUMED; // NON consumare: passa ai fragment }); //check if first run activity (IntroActivity) has been started once or not final SharedPreferences theShPr = getMainSharedPreferences(); boolean hasIntroRun = theShPr.getBoolean(PreferencesHolder.PREF_INTRO_ACTIVITY_RUN,false); if(!hasIntroRun){ startIntroductionActivity(); } serviceAlertsViewModel.getLastTimeRunningDownload().observe(this, (timeRunning) -> { if (timeRunning != null) { Log.d(DEBUG_TAG, "requested alerts download at time: "+timeRunning); } }); serviceAlertsViewModel.launchAlertsPeriodCheck(); } private ActionBarDrawerToggle setupDrawerToggle(Toolbar toolbar) { // NOTE: Make sure you pass in a valid toolbar reference. ActionBarDrawToggle() does not require it // and will not render the hamburger icon without it. return new ActionBarDrawerToggle(this, mDrawer, toolbar, R.string.drawer_open, R.string.drawer_close); } /** * Setup drawer actions * @param navigationView the navigation view on which to set the callbacks */ private void setupDrawerContent(NavigationView navigationView) { navigationView.setNavigationItemSelectedListener( menuItem -> { int menuId = menuItem.getItemId(); if( menuActions.containsKey(menuId)){ closeDrawerIfOpen(); var runnable = menuActions.get(menuId); if(runnable!=null) runnable.run(); return true; } else{ return false; } }); } private void closeDrawerIfOpen(){ if (mDrawer.isDrawerOpen(GravityCompat.START)) mDrawer.closeDrawer(GravityCompat.START); } // `onPostCreate` called when activity start-up is complete after `onStart()` // NOTE 1: Make sure to override the method with only a single `Bundle` argument // Note 2: Make sure you implement the correct `onPostCreate(Bundle savedInstanceState)` method. // There are 2 signatures and only `onPostCreate(Bundle state)` shows the hamburger icon. @Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); // Sync the toggle state after onRestoreInstanceState has occurred. drawerToggle.syncState(); } @Override public void onConfigurationChanged(@NonNull Configuration newConfig) { super.onConfigurationChanged(newConfig); // Pass any configuration change to the drawer toggles drawerToggle.onConfigurationChanged(newConfig); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.principal_menu, menu); MenuItem experimentsMenuItem = menu.findItem(R.id.action_experiments); SharedPreferences shPr = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); boolean exper_On = shPr.getBoolean(getString(R.string.pref_key_experimental), false); experimentsMenuItem.setVisible(exper_On); return super.onCreateOptionsMenu(menu); } //requesting permissions @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode==STORAGE_PERMISSION_REQ){ final String storagePerm = Manifest.permission.WRITE_EXTERNAL_STORAGE; if (permissionDoneRunnables.containsKey(storagePerm)) { Runnable toRun = permissionDoneRunnables.get(storagePerm); if (toRun != null) toRun.run(); permissionDoneRunnables.remove(storagePerm); } if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { Log.d(DEBUG_TAG, "Permissions check: " + Arrays.toString(permissions)); if (permissionDoneRunnables.containsKey(storagePerm)) { Runnable toRun = permissionDoneRunnables.get(storagePerm); if (toRun != null) toRun.run(); permissionDoneRunnables.remove(storagePerm); } } else { //permission denied showToastMessage(R.string.permission_storage_maps_msg, false); } } } @Override public boolean onOptionsItemSelected(@NonNull MenuItem item) { Log.d(DEBUG_TAG, "Item pressed"); if (item.getItemId() == android.R.id.home) { mDrawer.openDrawer(GravityCompat.START); return true; } if (drawerToggle.onOptionsItemSelected(item)) { return true; } return super.onOptionsItemSelected(item); } /*@Override public void onBackPressed() { if (!activityCustomBackPressed()) super.onBackPressed(); } */ private boolean activityCustomBackPressed(){ var mainFragManager = getSupportFragmentManager(); boolean resolved = mainFragManager.getBackStackEntryCount() > 0; Fragment shownFrag = mainFragManager.findFragmentById(R.id.mainActContentFrame); if (mDrawer.isDrawerOpen(GravityCompat.START)) { mDrawer.closeDrawer(GravityCompat.START); resolved = true; } else if (shownFrag instanceof MainScreenFragment mainFrag) { //the only case when we have to pop both stacks mainFrag.cancelReloadArrivalsIfNeeded(); var chManager = mainFrag.getChildFragmentManager(); if(chManager.getBackStackEntryCount() > 0){ chManager.popBackStack(); Log.d(DEBUG_TAG, "Child back stack popped"); resolved = true; } else{ Log.d(DEBUG_TAG, "No back stack on child fragment manager"); } boolean haveToPopMainFromFragment = mainFrag.needToPopMainStackOnBack(); //mainFrag.getChildFragmentManager().popBackStack(); if(haveToPopMainFromFragment){ // pops the stack mainFragManager.popBackStack(); } } else if (getSupportFragmentManager().getBackStackEntryCount() > 0) { mainFragManager.popBackStack(); var newFrag = mainFragManager.findFragmentById(R.id.mainActContentFrame); if(newFrag != null) { int backStackCount = newFrag.getChildFragmentManager().getBackStackEntryCount(); Log.d(DEBUG_TAG, "new fragment is "+newFrag.getClass().getSimpleName() +", count of the backstack: "+backStackCount); } Log.d(DEBUG_TAG, "Popping main backstack"); } else{ resolved = false; } return resolved; } /** * Create and show the SnackBar with the message * The fragment shown points to which view to attach the snackbar */ private void createDatabaseUpdateSnackbar() { View baseView = null; boolean showSnackbar = true; final Fragment frag = getSupportFragmentManager().findFragmentById(R.id.mainActContentFrame); if (frag instanceof ScreenBaseFragment){ baseView = ((ScreenBaseFragment) frag).getBaseViewForSnackBar(); showSnackbar = ((ScreenBaseFragment) frag).showSnackbarOnDBUpdate(); } if (baseView == null) baseView = findViewById(R.id.mainActContentFrame); //if (baseView == null) Log.e(DEBUG_TAG, "baseView null for default snackbar, probably exploding now"); if (baseView !=null && showSnackbar) { snackbar = Snackbar.make(baseView, R.string.database_update_msg_inapp, Snackbar.LENGTH_INDEFINITE); snackbar.setTextColor(getColor(android.R.color.white)); snackbar.setBackgroundTint(getColor(R.color.grey_800)); if (frag instanceof ScreenBaseFragment){ ((ScreenBaseFragment) frag).setSnackbarPropertiesBeforeShowing(snackbar); } snackbar.show(); } else{ Log.e(DEBUG_TAG, "Asked to show the snackbar but the baseView is null"); } } /* private void updateShowingFragmentKindInternal(@NonNull FragmentKind newKind){ if(BuildConfig.DEBUG) Log.d(DEBUG_TAG, "Updating fragment kind, new: "+newKind+", current: "+showingFragmentKind); boolean showingMainFragmentFromOther = false; if(showingFragmentKind == null){ showingFragmentKind = newKind; showingMainFragmentFromOther = false; } else if(newKind != showingFragmentKind) { showingMainFragmentFromOther = ( FragmentKind.getSuperKind(newKind) != FragmentKind.getSuperKind(showingFragmentKind)); showingFragmentKind = newKind; } } */ /** * Show the actual fragment by adding it to the backstack * @param fraMan the fragmentManager * @param fragment the fragment */ private static void showMainFragment(FragmentManager fraMan, MainScreenFragment fragment, boolean addToBackStack){ FragmentTransaction ft = fraMan.beginTransaction() .replace(R.id.mainActContentFrame, fragment, MainScreenFragment.FRAGMENT_TAG) .setReorderingAllowed(false) /*.setCustomAnimations( R.anim.slide_in, // enter R.anim.fade_out, // exit R.anim.fade_in, // popEnter R.anim.slide_out // popExit )*/ .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); if (addToBackStack) { ft.addToBackStack(null); } ft.commit(); } /** * Create a new MainFragment for the arguments provided and show it in the layout * @param fraMan the fragmentManager * @param arguments args for the fragment */ private static void createShowMainFragment(FragmentManager fraMan,@Nullable Bundle arguments, boolean addToBackStack){ FragmentTransaction ft = fraMan.beginTransaction() .replace(R.id.mainActContentFrame, MainScreenFragment.class, arguments, MainScreenFragment.FRAGMENT_TAG) .setReorderingAllowed(false) /*.setCustomAnimations( R.anim.slide_in, // enter R.anim.fade_out, // exit R.anim.fade_in, // popEnter R.anim.slide_out // popExit )*/ .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); if (addToBackStack) ft.addToBackStack(null); ft.commit(); } private void showHomeMainFragmentFromClick(){ FragmentManager fraMan = getSupportFragmentManager(); var fragment = fraMan.findFragmentByTag(MainScreenFragment.FRAGMENT_TAG); if(fragment instanceof MainScreenFragment mainFrag){ var visible = mainFrag.isVisible(); if(!mainFrag.isVisible()){ showMainFragment(fraMan, mainFrag, true); } mainFrag.showButtonsFragmentIfNotNearby(true); } else{ createShowMainFragment(fraMan, MainScreenFragment.makeArgsButtonsScreen(), true); } } private void requestMapFragment(final boolean allowReturn){ // starting from Android 11, we don't need to have the STORAGE permission anymore for the map cache FragmentManager fm = getSupportFragmentManager(); Fragment fragment = fm.findFragmentById(R.id.mainActContentFrame); if(fragment instanceof MapLibreFragment){ Log.d(DEBUG_TAG, "Requested map fragment, but it is already open"); } else { fragment = fm.findFragmentByTag(MapLibreFragment.FRAGMENT_TAG); if(fragment != null){ Log.d(DEBUG_TAG, "Found map fragment, reopening it"); var ft = fm.beginTransaction(); ft.replace(R.id.mainActContentFrame,fragment, MapLibreFragment.FRAGMENT_TAG); if(allowReturn) ft.addToBackStack(null); ft.commit(); } else { //create from scratch //The permissions are handled in the MapLibreFragment instead createAndShowMapFragment(null, allowReturn); } } } private void checkAndShowFavoritesFragment(FragmentManager fragmentManager, boolean addToBackStack){ if(getSupportFragmentManager().findFragmentById(R.id.mainActContentFrame) instanceof FavoritesFragment){ Log.d(DEBUG_TAG, "Requested favorites fragment, but it is already open"); return; } FragmentTransaction ft = fragmentManager.beginTransaction(); Fragment fragment = fragmentManager.findFragmentByTag(TAG_FAVORITES); if(fragment!=null){ ft.replace(R.id.mainActContentFrame, fragment, TAG_FAVORITES); }else{ //use new method ft.replace(R.id.mainActContentFrame,FavoritesFragment.class,null,TAG_FAVORITES); } if (addToBackStack) ft.addToBackStack("favorites_main"); ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) .setReorderingAllowed(false); ft.commit(); } private void showLinesFragment(@NonNull FragmentManager fragmentManager, boolean addToBackStack, @Nullable Bundle fragArgs){ if(getSupportFragmentManager().findFragmentById(R.id.mainActContentFrame) instanceof LinesGridShowingFragment){ Log.d(DEBUG_TAG, "Requested lines grid fragment, but it is already open"); return; } FragmentTransaction ft = fragmentManager.beginTransaction(); Fragment f = fragmentManager.findFragmentByTag(LinesGridShowingFragment.FRAGMENT_TAG); if(f!=null){ ft.replace(R.id.mainActContentFrame, f, LinesGridShowingFragment.FRAGMENT_TAG); }else{ //use new method ft.replace(R.id.mainActContentFrame,LinesGridShowingFragment.class,fragArgs, LinesGridShowingFragment.FRAGMENT_TAG); } if (addToBackStack) ft.addToBackStack("linesGrid"); ft.setReorderingAllowed(true) .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) .commit(); } @Nullable private MainScreenFragment getMainFragmentIfVisible(){ FragmentManager fraMan = getSupportFragmentManager(); Fragment fragment = fraMan.findFragmentByTag(MainScreenFragment.FRAGMENT_TAG); if (fragment!= null && fragment.isVisible()) return (MainScreenFragment) fragment; else return null; } @Override public void showFloatingActionButton(boolean yes) { var frag = getMainFragmentIfVisible(); if(frag!=null){ frag.showFloatingActionButton(yes); } } /* public void setDrawerSelectedItem(String fragmentTag){ switch (fragmentTag){ case MainScreenFragment.FRAGMENT_TAG: mNavView.setCheckedItem(R.id.nav_arrivals); break; case MapFragment.FRAGMENT_TAG: break; case FavoritesFragment.FRAGMENT_TAG: mNavView.setCheckedItem(R.id.nav_favorites_item); break; } }*/ @Override public void readyGUIfor(FragmentKind fragmentType) { //updateShowingFragmentKindInternal(fragmentType); if(BuildConfig.DEBUG) Log.d(DEBUG_TAG, "readyGUIfor fragment kind: " + fragmentType); Integer titleResId = null; switch (fragmentType){ case MAP: mNavView.setCheckedItem(R.id.nav_map_item); titleResId = R.string.map; break; case FAVORITES: mNavView.setCheckedItem(R.id.nav_favorites_item); titleResId = R.string.nav_favorites_text; break; case ARRIVALS: titleResId = R.string.nav_arrivals_text; mNavView.setCheckedItem(R.id.nav_home); //TODO: Figure out way to change title //mNavView.getCheckedItem().setTitle(R.string.nav_arrivals_text); break; case STOPS: titleResId = R.string.stop_search_view_title; mNavView.setCheckedItem(R.id.nav_home); break; case MAIN_SCREEN_FRAGMENT: case HOME_BUTTONS: titleResId=R.string.app_name_full; mNavView.setCheckedItem(R.id.nav_home); //mNavView.getCheckedItem().setTitle(R.string.nav_home_text); break; case NEARBY_STOPS: case NEARBY_ARRIVALS: titleResId=R.string.app_name_full; mNavView.setCheckedItem(R.id.nav_nearby); break; case LINES: titleResId=R.string.lines; mNavView.setCheckedItem(R.id.nav_lines_item); break; } if(getSupportActionBar()!=null && titleResId!=null) getSupportActionBar().setTitle(titleResId); MainScreenFragment mainFragmentIfVisible = getMainFragmentIfVisible(); if (mainFragmentIfVisible!=null){ mainFragmentIfVisible.readyGUIfor(fragmentType); } } @Override public void requestArrivalsForStopID(String ID) { Consumer consumer = fragment -> { fragment.setSuppressArrivalsReload(true); fragment.requestArrivalsForStopID(ID); }; boolean done = getMainFragmentAndDoStuff(consumer); if(!done){ //create the fragment final Bundle args = MainScreenFragment.makeArgsArrivals(ID); createShowMainFragment(getSupportFragmentManager(), args ,true); } } @Override public void openLineFromStop(String routeGtfsId, @Nullable String stopIDFrom){ FragmentTransaction tr = getSupportFragmentManager().beginTransaction(); tr.replace(R.id.mainActContentFrame, LinesDetailFragment.class, LinesDetailFragment.Companion.makeArgs(routeGtfsId, stopIDFrom)); tr.addToBackStack("LineFromStop-"+routeGtfsId); tr.commit(); } private boolean getMainFragmentAndDoStuff(Consumer consumer){ FragmentManager fraMan = getSupportFragmentManager(); var frag = fraMan.findFragmentByTag(MainScreenFragment.FRAGMENT_TAG); if( frag instanceof MainScreenFragment mainFrag){ if(!mainFrag.isVisible()) { showMainFragment(fraMan, mainFrag, true); mainFrag.setMainFragmentManagerTransition(true); } else{ mainFrag.setMainFragmentManagerTransition(false); } consumer.accept(mainFrag); return true; } else{ return false; } } @Override public void openLineFromVehicle(String routeGtfsId, @Nullable String optionalPatternId, @Nullable Bundle args) { FragmentTransaction tr = getSupportFragmentManager().beginTransaction(); tr.replace(R.id.mainActContentFrame, LinesDetailFragment.class, LinesDetailFragment.Companion.makeArgsPattern(routeGtfsId, optionalPatternId, args)); tr.addToBackStack("LineFromOther-"+routeGtfsId); tr.commit(); } @Override public void openNearbyStopsFragment() { boolean done = getMainFragmentAndDoStuff(MainScreenFragment::openNearbyStopsFragment); if(!done){ createShowMainFragment(getSupportFragmentManager(), MainScreenFragment.makeArgsNearby(), true); } } @Override public void openLinesFragment() { showLinesFragment(getSupportFragmentManager(), true, null); } @Override public void openFavoritesFragment() { checkAndShowFavoritesFragment(getSupportFragmentManager(), true); } @Override public void toggleSpinner(boolean state) { MainScreenFragment probableFragment = getMainFragmentIfVisible(); if (probableFragment!=null){ probableFragment.toggleSpinner(state); } } @Override public void enableRefreshLayout(boolean yes) { MainScreenFragment probableFragment = getMainFragmentIfVisible(); if (probableFragment!=null){ probableFragment.enableRefreshLayout(yes); } } @Override public void showMapCenteredOnStop(@Nullable Stop stop) { createAndShowMapFragment(stop, true); } //Map Fragment stuff void createAndShowMapFragment(@Nullable Stop stop, boolean addToBackStack){ final FragmentManager fm = getSupportFragmentManager(); final FragmentTransaction ft = fm.beginTransaction(); final MapLibreFragment fragment = MapLibreFragment.newInstance(stop); ft.replace(R.id.mainActContentFrame, fragment, MapLibreFragment.FRAGMENT_TAG); if (addToBackStack) ft.addToBackStack(null); ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); ft.commit(); } void startIntroductionActivity(){ Intent intent = new Intent(ActivityPrincipal.this, ActivityIntro.class); intent.putExtra(ActivityIntro.RESTART_MAIN, false); startActivity(intent); } class ToolbarItemClickListener implements Toolbar.OnMenuItemClickListener{ private final Context activityContext; public ToolbarItemClickListener(Context activityContext) { this.activityContext = activityContext; } @Override public boolean onMenuItemClick(MenuItem item) { final int id = item.getItemId(); if(id == R.id.action_about){ startActivity(new Intent(ActivityPrincipal.this, ActivityAbout.class)); return true; } else if (id == R.id.action_hack) { openIceweasel(getString(R.string.hack_url), activityContext); return true; } else if (id == R.id.action_source){ openIceweasel("https://gitpull.it/source/libre-busto/", activityContext); return true; } else if (id == R.id.action_licence){ openIceweasel("https://www.gnu.org/licenses/gpl-3.0.html", activityContext); return true; } else if (id == R.id.action_experiments) { startActivity(new Intent(ActivityPrincipal.this, ActivityExperiments.class)); return true; } else if (id == R.id.action_tutorial) { startIntroductionActivity(); return true; } return false; } } @Override protected void onPause() { super.onPause(); // stop updating the alerts serviceAlertsViewModel.setRunningDownloadRequests(false); } @Override protected void onResume() { super.onResume(); serviceAlertsViewModel.launchAlertsPeriodCheck(); } } diff --git a/app/src/main/java/it/reyboz/bustorino/BustoApp.java b/app/src/main/java/it/reyboz/bustorino/BustoApp.java index d9e2916..ac9bb97 100644 --- a/app/src/main/java/it/reyboz/bustorino/BustoApp.java +++ b/app/src/main/java/it/reyboz/bustorino/BustoApp.java @@ -1,75 +1,139 @@ /* BusTO - Arrival times for Turin public transport. Copyright (C) 2021 Fabio Mazza This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package it.reyboz.bustorino; import android.content.Context; +import android.content.SharedPreferences; +import androidx.appcompat.app.AppCompatDelegate; import androidx.multidex.MultiDexApplication; +import androidx.preference.PreferenceManager; +import it.reyboz.bustorino.backend.utils; +import it.reyboz.bustorino.data.PreferencesHolder; +import it.reyboz.bustorino.fragments.SettingsFragment; import org.acra.ACRA; import org.acra.BuildConfig; import org.acra.ReportField; import org.acra.config.CoreConfigurationBuilder; import org.acra.config.DialogConfigurationBuilder; import org.acra.config.MailSenderConfigurationBuilder; import org.acra.data.StringFormat; import java.lang.reflect.Array; -import java.util.ArrayList; -import java.util.List; +import java.util.*; import static org.acra.ReportField.*; public class BustoApp extends MultiDexApplication { private static final List REPORT_FIELDS = List.of(REPORT_ID, APP_VERSION_CODE, APP_VERSION_NAME, PACKAGE_NAME, PHONE_MODEL, BRAND, PRODUCT, ANDROID_VERSION, BUILD_CONFIG, CUSTOM_DATA, IS_SILENT, STACK_TRACE, INITIAL_CONFIGURATION, CRASH_CONFIGURATION, DISPLAY, USER_COMMENT, USER_APP_START_DATE, USER_CRASH_DATE, LOGCAT, SHARED_PREFERENCES); + @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); CoreConfigurationBuilder builder = new CoreConfigurationBuilder(); // mail stuff MailSenderConfigurationBuilder mailConfig = new MailSenderConfigurationBuilder(); mailConfig.withMailTo("gtt@succhia.cz") .withReportFileName(it.reyboz.bustorino.BuildConfig.VERSION_NAME +"_report.json") .withBody(getString(R.string.acra_email_message)) .setEnabled(true); //dialog stuff DialogConfigurationBuilder dialogBuild = new DialogConfigurationBuilder(); dialogBuild.withText(getString(R.string.message_crash)) .withResTheme(R.style.AppTheme).setEnabled(true); //Set options builder.withBuildConfigClass(BuildConfig.class) .withReportFormat(StringFormat.JSON) .withDeleteUnapprovedReportsOnApplicationStart(true); //Add plugins builder.withPluginConfigurations( mailConfig.build(), dialogBuild.build() ); builder.setReportContent(REPORT_FIELDS); if (!it.reyboz.bustorino.BuildConfig.DEBUG) ACRA.init(this, builder); } + @Override + public void onCreate() { + super.onCreate(); + checkApplyDefaultSettingsValues(); + + var dayorNight = PreferencesHolder.getAppThemeDayNight(this); + AppCompatDelegate.setDefaultNightMode(dayorNight); + + } + + /** + * Adjust setting to match the default ones + */ + protected void checkApplyDefaultSettingsValues(){ + SharedPreferences mainSharedPref = PreferenceManager.getDefaultSharedPreferences(this); + SharedPreferences.Editor editor = mainSharedPref.edit(); + boolean edit = false; + + //Day or night theme + var str = mainSharedPref.getString(PreferencesHolder.PREF_THEME_DAY_NIGHT, ""); + if(str.isEmpty()){ + editor.putString(PreferencesHolder.PREF_THEME_DAY_NIGHT, "system"); + edit = true; + } + + //Main fragment to show + String screen = mainSharedPref.getString(SettingsFragment.PREF_KEY_STARTUP_SCREEN, ""); + if (screen.isEmpty()){ + editor.putString(SettingsFragment.PREF_KEY_STARTUP_SCREEN, "arrivals"); + edit=true; + } + //Fetchers + final Set setSelected = mainSharedPref.getStringSet(SettingsFragment.KEY_ARRIVALS_FETCHERS_USE, new HashSet<>()); + if (setSelected.isEmpty()){ + String[] defaultVals = getResources().getStringArray(R.array.arrivals_sources_values_default); + editor.putStringSet(SettingsFragment.KEY_ARRIVALS_FETCHERS_USE, utils.convertArrayToSet(defaultVals)); + edit=true; + } + //Live bus positions + final String keySourcePositions=getString(R.string.pref_positions_source); + final String positionsSource = mainSharedPref.getString(keySourcePositions, ""); + if(positionsSource.isEmpty()){ + String[] defaultVals = getResources().getStringArray(R.array.positions_source_values); + editor.putString(keySourcePositions, defaultVals[0]); + edit=true; + } + //Map style + final String mapStylePref = mainSharedPref.getString(SettingsFragment.LIBREMAP_STYLE_PREF_KEY, ""); + if(mapStylePref.isEmpty()){ + final String[] defaultVals = getResources().getStringArray(R.array.map_style_pref_values); + editor.putString(SettingsFragment.LIBREMAP_STYLE_PREF_KEY, defaultVals[0]); + edit=true; + } + if (edit){ + editor.apply(); + } + + } } diff --git a/app/src/main/java/it/reyboz/bustorino/adapters/ArrivalsStopAdapter.java b/app/src/main/java/it/reyboz/bustorino/adapters/ArrivalsStopAdapter.java index 35ee280..d51329a 100644 --- a/app/src/main/java/it/reyboz/bustorino/adapters/ArrivalsStopAdapter.java +++ b/app/src/main/java/it/reyboz/bustorino/adapters/ArrivalsStopAdapter.java @@ -1,305 +1,304 @@ /* BusTO - UI components Copyright (C) 2017 Fabio Mazza This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package it.reyboz.bustorino.adapters; import android.content.Context; import android.content.SharedPreferences; -import android.location.Location; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.util.Pair; import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.RecyclerView; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import it.reyboz.bustorino.R; import it.reyboz.bustorino.backend.*; import it.reyboz.bustorino.fragments.FragmentListenerMain; import java.util.*; public class ArrivalsStopAdapter extends RecyclerView.Adapter implements SharedPreferences.OnSharedPreferenceChangeListener { - private final static int layoutRes = R.layout.arrivals_nearby_card; + private final static int layoutRes = R.layout.item_arrivals_nearby_card; //private List stops; private @NonNull GPSPoint userPosition; private FragmentListenerMain listener; private List< RouteWithStop > routesPairList; private final Context context; //Maximum number of stops to keep private final int MAX_STOPS = 20; //TODO: make it programmable private final String KEY_CAPITALIZE; private NameCapitalize capit; - public ArrivalsStopAdapter(@Nullable List< RouteWithStop > routesPairList, FragmentListenerMain fragmentListener, Context con, @NonNull GPSPoint pos) { + public ArrivalsStopAdapter(@Nullable List< RouteWithStop > routesPairList,@NonNull FragmentListenerMain fragmentListener, @NonNull Context con, @NonNull GPSPoint pos) { listener = fragmentListener; userPosition = pos; this.routesPairList = routesPairList; context = con.getApplicationContext(); resetListAndPosition(); // if(paline!=null) //resetRoutesPairList(paline); KEY_CAPITALIZE = context.getString(R.string.pref_arrival_times_capit); SharedPreferences defSharPref = PreferenceManager.getDefaultSharedPreferences(context); defSharPref.registerOnSharedPreferenceChangeListener(this); String capitalizeKey = defSharPref.getString(KEY_CAPITALIZE, ""); this.capit = NameCapitalize.getCapitalize(capitalizeKey); } @NonNull @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { final View view = LayoutInflater.from(parent.getContext()).inflate(layoutRes, parent, false); return new ViewHolder(view); } @Override public void onBindViewHolder(@NonNull ViewHolder holder, int position) { //DO THE ACTUAL WORK TO PUT THE DATA if(routesPairList==null || routesPairList.size() == 0) return; //NO STOPS final var stopRoutePair = routesPairList.get(position); if(stopRoutePair != null){ final Stop stop = stopRoutePair.getStop(); final Route r = stopRoutePair.getRoute(); final Double distance = stop.getDistanceFromLocation(userPosition.getLatitude(), userPosition.longitude); if(distance!=Double.POSITIVE_INFINITY){ holder.distancetextView.setText(distance.intValue()+" m"); } else { holder.distancetextView.setVisibility(View.GONE); } final String stopText = String.format(context.getResources().getString(R.string.two_strings_format),stop.getStopDisplayName(),stop.ID); holder.stopNameView.setText(stopText); //final String routeName = String.format(context.getResources().getString(R.string.two_strings_format),r.getNameForDisplay(),r.destinazione); if (r!=null) { holder.lineNameTextView.setText(r.getDisplayCode()); holder.lineDirectionTextView.setText(NameCapitalize.capitalizePass(r.destinazione, capit)); holder.arrivalsTextView.setText(r.getPassaggiToString(0,2,true)); } else { holder.lineNameTextView.setVisibility(View.INVISIBLE); holder.lineDirectionTextView.setVisibility(View.INVISIBLE); //holder.arrivalsTextView.setVisibility(View.INVISIBLE); } /* EXPERIMENTS if(r.destinazione==null || r.destinazione.trim().isEmpty()){ holder.lineDirectionTextView.setVisibility(View.GONE); RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) holder.arrivalsDescriptionTextView.getLayoutParams(); params.addRule(RelativeLayout.RIGHT_OF,holder.lineNameTextView.getId()); holder.arrivalsDescriptionTextView.setLayoutParams(params); } else { RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) holder.arrivalsDescriptionTextView.getLayoutParams(); params.removeRule(RelativeLayout.RIGHT_OF); holder.arrivalsDescriptionTextView.setLayoutParams(params); holder.lineDirectionTextView.setVisibility(View.VISIBLE); } */ holder.stopID =stop.ID; } else { Log.w("SquareStopAdapter","!! The selected stop is null !!"); } } @Override public int getItemCount() { return routesPairList.size(); } @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { if(key.equals(KEY_CAPITALIZE)){ String k = sharedPreferences.getString(KEY_CAPITALIZE, ""); capit = NameCapitalize.getCapitalize(k); notifyDataSetChanged(); } } class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { TextView lineNameTextView; TextView lineDirectionTextView; TextView stopNameView; TextView arrivalsDescriptionTextView; TextView arrivalsTextView; TextView distancetextView; String stopID; ViewHolder(View holdView){ super(holdView); holdView.setOnClickListener(this); lineNameTextView = (TextView) holdView.findViewById(R.id.lineNameTextView); lineDirectionTextView = (TextView) holdView.findViewById(R.id.lineDirectionTextView); stopNameView = (TextView) holdView.findViewById(R.id.arrivalStopName); arrivalsTextView = (TextView) holdView.findViewById(R.id.arrivalsTimeTextView); arrivalsDescriptionTextView = (TextView) holdView.findViewById(R.id.arrivalsDescriptionTextView); distancetextView = (TextView) holdView.findViewById(R.id.arrivalsDistanceTextView); } @Override public void onClick(View v) { listener.requestArrivalsForStopID(stopID); } } /* public void resetRoutesPairList(List stopList){ Collections.sort(stopList,new StopSorterByDistance(userPosition)); this.routesPairList = new ArrayList<>(stopList.size()); int maxNum = Math.min(MAX_STOPS, stopList.size()); for(Palina p: stopList.subList(0,maxNum)){ //if there are no routes available, skip stop if(p.queryAllRoutes().size() == 0) continue; for(Route r: p.queryAllRoutes()){ //if there are no routes, should not do anything routesPairList.add(new Pair<>(p,r)); } } } */ public void updatePosition(@NonNull GPSPoint pos){ userPosition = pos; } public void setRoutesPairListAndPosition(@NonNull List newList, @Nullable GPSPoint pos) { if(pos!=null) updatePosition(pos); if (routesPairList == null) { routesPairList = new ArrayList<>(newList); notifyItemRangeInserted(0, newList.size()); return; } DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() { @Override public int getOldListSize() { return routesPairList.size(); } @Override public int getNewListSize() { return newList.size(); } @Override public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { RouteWithStop oldItem = routesPairList.get(oldItemPosition); RouteWithStop newItem = newList.get(newItemPosition); // usa un ID univoco return oldItem.getId().equals(newItem.getId()); } @Override public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { RouteWithStop oldItem = routesPairList.get(oldItemPosition); RouteWithStop newItem = newList.get(newItemPosition); // confronta il contenuto return oldItem.equals(newItem); } } ); routesPairList.clear(); routesPairList.addAll(newList); diffResult.dispatchUpdatesTo(this); /*if (this.routesPairList == null || routesPairList.size() == 0){ routesPairList = mRoutesPairList; notifyDataSetChanged(); } else{ final var indexMapIn = getRouteIndexMap(mRoutesPairList); final var indexMapExisting = getRouteIndexMap(routesPairList); //List> oldList = routesPairList; routesPairList = mRoutesPairList; /* for (Pair pair: indexMapIn.keySet()){ final Integer posIn = indexMapIn.get(pair); if (posIn == null) continue; if (indexMapExisting.containsKey(pair)){ final Integer posExisting = indexMapExisting.get(pair); //THERE IS ALREADY //routesPairList.remove(posExisting.intValue()); //routesPairList.add(posIn,mRoutesPairList.get(posIn)); notifyItemMoved(posExisting, posIn); indexMapExisting.remove(pair); } else{ //INSERT IT //routesPairList.add(posIn,mRoutesPairList.get(posIn)); notifyItemInserted(posIn); } }// //REMOVE OLD STOPS for (Pair pair: indexMapExisting.keySet()) { final Integer posExisting = indexMapExisting.get(pair); if (posExisting == null) continue; //routesPairList.remove(posExisting.intValue()); notifyItemRemoved(posExisting); } */ } /** * Sort and remove the repetitions for the routesPairList */ private void resetListAndPosition(){ //Collections.sort(this.routesPairList,new RoutePositionSorter(userPosition)); //All of this to get only the first occurrences of a line (name & direction) var iterator = routesPairList.listIterator(); Set> allRoutesDirections = new HashSet<>(); while(iterator.hasNext()){ final var stopRoutePair = iterator.next(); stopRoutePair.getRoute(); final Pair routeNameDirection = new Pair<>(stopRoutePair.getRoute().getName(), stopRoutePair.getRoute().destinazione); if (allRoutesDirections.contains(routeNameDirection)) { iterator.remove(); } else { allRoutesDirections.add(routeNameDirection); } } } private static HashMap getRouteIndexMap(List routesPairList){ final HashMap myMap = new HashMap<>(); for (int i=0; i. */ package it.reyboz.bustorino.adapters; import android.content.Context; import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.widget.ImageView; import androidx.annotation.NonNull; import androidx.cardview.widget.CardView; import androidx.core.content.res.ResourcesCompat; import androidx.preference.PreferenceManager; import android.content.SharedPreferences; import android.view.Gravity; import android.os.Build; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.PopupMenu; import android.widget.TextView; import java.util.*; import androidx.recyclerview.widget.RecyclerView; import it.reyboz.bustorino.R; import it.reyboz.bustorino.backend.Palina; import it.reyboz.bustorino.backend.Passaggio; import it.reyboz.bustorino.backend.Route; import it.reyboz.bustorino.backend.utils; import it.reyboz.bustorino.util.PassaggiSorter; import it.reyboz.bustorino.util.RouteSorterByArrivalTime; import org.jetbrains.annotations.NotNull; /** * This once was a ListView Adapter for BusLine[]. * * Thanks to Framentos developers for the guide: * http://www.framentos.com/en/android-tutorial/2012/07/16/listview-in-android-using-custom-listadapter-and-viewcache/# * * @author Valerio Bozzolan * @author Ludovico Pavesi * @author Fabio Mazza */ public class PalinaAdapter extends RecyclerView.Adapter implements SharedPreferences.OnSharedPreferenceChangeListener { private static final int ROW_LAYOUT = R.layout.entry_bus_line_passage; private static final int metroBg = R.drawable.route_background_metro; private static final int busBg = R.drawable.route_background_bus; private static final int extraurbanoBg = R.drawable.route_background_bus_long_distance; - private static final int busIcon = R.drawable.bus; - private static final int trainIcon = R.drawable.subway; - private static final int tramIcon = R.drawable.tram; + private static final int busIcon = R.drawable.ic_bus; + private static final int trainIcon = R.drawable.ic_subway_filled; + private static final int tramIcon = R.drawable.ic_tram_filled_24; private final String KEY_CAPITALIZE; private Capitalize capit; private final List mRoutes; private final PalinaClickListener mRouteListener; @NonNull @NotNull @Override public PalinaViewHolder onCreateViewHolder(@NonNull @NotNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()) .inflate(ROW_LAYOUT, parent, false); return new PalinaViewHolder(view); } @Override public void onBindViewHolder(@NonNull @NotNull PalinaViewHolder vh, int position) { final Route route = mRoutes.get(position); final Context con = vh.itemView.getContext(); final Resources res = con.getResources(); vh.routeIDTextView.setText(route.getDisplayCode()); vh.routeCard.setOnClickListener(view -> mRouteListener.requestShowingRoute(route)); // Clicking anywhere on the row shows a popup menu vh.itemView.setOnClickListener(view -> openPopupMenuDetails(con,view, route) ); //vh.rowRouteDestination.getVisibility() == View.VISIBLE ? vh.rowRouteDestination : vh.itemView if(route.destinazione==null || route.destinazione.length() == 0) { vh.rowRouteDestination.setVisibility(View.GONE); // move around the route timetable final ViewGroup.MarginLayoutParams pars = (ViewGroup.MarginLayoutParams) vh.rowRouteTimetable.getLayoutParams(); if (pars!=null){ pars.topMargin = 16; if(Build.VERSION.SDK_INT >= 17) pars.setMarginStart(20); pars.leftMargin = 20; } } else { // View Holder Pattern(R) renders each element from a previous one: if the other one had an invisible rowRouteDestination, we need to make it visible. vh.rowRouteDestination.setVisibility(View.VISIBLE); String dest = route.destinazione; switch (capit){ case ALL: dest = route.destinazione.toUpperCase(Locale.ROOT); break; case FIRST: dest = utils.toTitleCase(route.destinazione, true); break; case DO_NOTHING: default: } vh.rowRouteDestination.setText(dest); } - + Drawable drawable = null; + final var resources = con.getResources(); switch (route.type) { //UNKNOWN = BUS for the moment case UNKNOWN: case BUS: default: // convertView could contain another background, reset it //vh.rowStopIcon.setBackgroundResource(busBg); - - vh.rowRouteDestination.setCompoundDrawablesWithIntrinsicBounds(busIcon, 0, 0, 0); + //drawable = ResourcesCompat.getDrawable(con.getResources(), busIcon, con.getTheme()); + //vh.rowRouteDestination.setCompoundDrawablesWithIntrinsicBounds(busIcon, 0, 0, 0); + vh.busIcon.setImageDrawable(ResourcesCompat.getDrawable(resources, busIcon, con.getTheme())); break; case LONG_DISTANCE_BUS: //vh.rowStopIcon.setBackgroundResource(extraurbanoBg); vh.routeCard.setCardBackgroundColor(ResourcesCompat.getColor(res, R.color.extraurban_bus_bg, null)); - vh.rowRouteDestination.setCompoundDrawablesWithIntrinsicBounds(busIcon, 0, 0, 0); + vh.busIcon.setImageDrawable(ResourcesCompat.getDrawable(resources, extraurbanoBg, con.getTheme())); break; case METRO: //vh.rowStopIcon.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14); //vh.rowStopIcon.setBackgroundResource(metroBg); vh.routeIDTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14); vh.routeCard.setCardBackgroundColor(ResourcesCompat.getColor(res, R.color.metro_bg, null)); - vh.rowRouteDestination.setCompoundDrawablesWithIntrinsicBounds(trainIcon, 0, 0, 0); + // vh.rowRouteDestination.setCompoundDrawablesWithIntrinsicBounds(trainIcon, 0, 0, 0); + vh.busIcon.setImageDrawable(ResourcesCompat.getDrawable(resources, trainIcon, con.getTheme())); break; case RAILWAY: //vh.rowStopIcon.setBackgroundResource(busBg); - vh.rowRouteDestination.setCompoundDrawablesWithIntrinsicBounds(trainIcon, 0, 0, 0); + //vh.rowRouteDestination.setCompoundDrawablesWithIntrinsicBounds(trainIcon, 0, 0, 0); + vh.busIcon.setImageDrawable(ResourcesCompat.getDrawable(resources, trainIcon, con.getTheme())); break; case TRAM: // never used but whatever. //vh.rowStopIcon.setBackgroundResource(busBg); - vh.rowRouteDestination.setCompoundDrawablesWithIntrinsicBounds(tramIcon, 0, 0, 0); + drawable = ResourcesCompat.getDrawable(resources, trainIcon, con.getTheme()); + assert drawable != null; + drawable.setTint(resources.getColor(R.color.black_icon_text, con.getTheme()) ); + //vh.rowRouteDestination.setCompoundDrawablesWithIntrinsicBounds(tramIcon, 0, 0, 0); + vh.busIcon.setImageDrawable(ResourcesCompat.getDrawable(resources, tramIcon, con.getTheme())); + break; } List passaggi = route.passaggi; //TODO: Sort the passaggi with realtime first if source is GTTJSONFetcher if(passaggi.size() == 0) { vh.rowRouteTimetable.setText(R.string.no_passages); } else { vh.rowRouteTimetable.setText(route.getPassaggiToString()); } } @Override public int getItemCount() { return mRoutes.size(); } //private static final int cityIcon = R.drawable.city; // hey look, a pattern! public static class PalinaViewHolder extends RecyclerView.ViewHolder { //final TextView rowStopIcon; final TextView routeIDTextView; final CardView routeCard; final TextView rowRouteDestination; final TextView rowRouteTimetable; + final ImageView busIcon; public PalinaViewHolder(@NonNull @NotNull View view) { super(view); - /* - convertView.findViewById(R.id.routeID); - vh.rowRouteDestination = (TextView) convertView.findViewById(R.id.routeDestination); - vh.rowRouteTimetable = (TextView) convertView.findViewById(R.id.routesThatStopHere); - */ - //rowStopIcon = view.findViewById(R.id.routeID); routeIDTextView = view.findViewById(R.id.routeNameTextView); routeCard = view.findViewById(R.id.routeCard); rowRouteDestination = view.findViewById(R.id.routeDestination); rowRouteTimetable = view.findViewById(R.id.routesThatStopHere); + busIcon = view.findViewById(R.id.arrivalsBusIcon); } } private static Capitalize getCapitalize(SharedPreferences shPr, String key){ String capitalize = shPr.getString(key, ""); switch (capitalize.trim()){ case "KEEP": return Capitalize.DO_NOTHING; case "CAPITALIZE_ALL": return Capitalize.ALL; case "CAPITALIZE_FIRST": return Capitalize.FIRST; } return Capitalize.DO_NOTHING; } private void openPopupMenuDetails(Context con, View view, Route route){ PopupMenu popup = new PopupMenu(con, view, Gravity.END); popup.inflate(R.menu.menu_arrivals_line_item); if (route.destinazione == null || route.destinazione.isEmpty()) { popup.getMenu().findItem(R.id.action_show_direction).setVisible(false); } popup.setOnMenuItemClickListener(item -> { int id = item.getItemId(); if (id == R.id.action_open_line) { mRouteListener.requestShowingRoute(route); return true; } else if (id == R.id.action_show_direction) { mRouteListener.showRouteFullDirection(route); return true; } return false; }); popup.show(); } public PalinaAdapter(Context context, Palina p, PalinaClickListener listener, boolean hideEmptyRoutes) { Comparator sorter = null; if (p.getPassaggiSourceIfAny()== Passaggio.Source.GTTJSON){ sorter = new PassaggiSorter(); } final List routes; if (hideEmptyRoutes){ // build the routes by filtering them routes = new ArrayList<>(); for(Route r: p.queryAllRoutes()){ //add only if there is at least one passage if (r.numPassaggi()>0){ routes.add(r); } } } else routes = p.queryAllRoutes(); for(Route r: routes){ if (sorter==null) Collections.sort(r.passaggi); else Collections.sort(r.passaggi, sorter); } Collections.sort(routes,new RouteSorterByArrivalTime()); mRoutes = routes; KEY_CAPITALIZE = context.getString(R.string.pref_arrival_times_capit); SharedPreferences defSharPref = PreferenceManager.getDefaultSharedPreferences(context); defSharPref.registerOnSharedPreferenceChangeListener(this); this.capit = getCapitalize(defSharPref, KEY_CAPITALIZE); this.mRouteListener = listener; } @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { if(key.equals(KEY_CAPITALIZE)){ capit = getCapitalize(sharedPreferences, KEY_CAPITALIZE); notifyDataSetChanged(); } } enum Capitalize{ DO_NOTHING, ALL, FIRST } public interface PalinaClickListener{ /** * Simple click listener for the whole line (show info) * @param route for toast */ void showRouteFullDirection(Route route); /** * Show the line with all the stops in the line screen * @param route partial line info */ void requestShowingRoute(Route route); } } diff --git a/app/src/main/java/it/reyboz/bustorino/adapters/SquareStopAdapter.java b/app/src/main/java/it/reyboz/bustorino/adapters/SquareStopAdapter.java index 4ff5e5b..9505a23 100644 --- a/app/src/main/java/it/reyboz/bustorino/adapters/SquareStopAdapter.java +++ b/app/src/main/java/it/reyboz/bustorino/adapters/SquareStopAdapter.java @@ -1,159 +1,157 @@ /* BusTO - UI components Copyright (C) 2017 Fabio Mazza This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package it.reyboz.bustorino.adapters; -import android.location.Location; import androidx.annotation.Nullable; import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.RecyclerView; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import it.reyboz.bustorino.R; import it.reyboz.bustorino.backend.GPSPoint; -import it.reyboz.bustorino.backend.RouteWithStop; import it.reyboz.bustorino.backend.Stop; import it.reyboz.bustorino.util.StopSorterByDistance; import it.reyboz.bustorino.fragments.FragmentListenerMain; import java.util.Collections; import java.util.List; public class SquareStopAdapter extends RecyclerView.Adapter { - private final static int layoutRes = R.layout.stop_card; + private final static int layoutRes = R.layout.item_stop_nearby_card; //private List stops; private @Nullable GPSPoint userPosition; private FragmentListenerMain listener; private List stops; public SquareStopAdapter(@Nullable List stopList, FragmentListenerMain fragmentListener, @Nullable GPSPoint pos) { listener = fragmentListener; userPosition = pos; stops = stopList; } @Override public SquareViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { final View view = LayoutInflater.from(parent.getContext()).inflate(layoutRes, parent, false); //sort the stops by distance if(stops != null && stops.size() > 0) Collections.sort(stops,new StopSorterByDistance(userPosition)); return new SquareViewHolder(view); } @Override public void onBindViewHolder(SquareViewHolder holder, int position) { //DO THE ACTUAL WORK TO PUT THE DATA if(stops==null || stops.size() == 0) return; //NO STOPS final Stop stop = stops.get(position); if(stop!=null){ if(stop.getDistanceFromLocation(userPosition)!=Double.POSITIVE_INFINITY){ Double distance = stop.getDistanceFromLocation(userPosition); holder.distancetextView.setText(distance.intValue()+" m"); } else { holder.distancetextView.setVisibility(View.GONE); } holder.stopNameView.setText(stop.getStopDisplayName()); holder.stopIDView.setText(stop.ID); String whatStopsHere = stop.routesThatStopHereToString(); if(whatStopsHere == null) { holder.routesView.setVisibility(View.GONE); } else { holder.routesView.setText(whatStopsHere); holder.routesView.setVisibility(View.VISIBLE); // might be GONE due to View Holder Pattern } holder.stopID =stop.ID; } else { Log.w("SquareStopAdapter","!! The selected stop is null !!"); } } @Override public int getItemCount() { return stops.size(); } class SquareViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { TextView stopIDView; TextView stopNameView; TextView routesView; TextView distancetextView; String stopID; SquareViewHolder(View holdView){ super(holdView); holdView.setOnClickListener(this); stopIDView = (TextView) holdView.findViewById(R.id.stop_numberText); stopNameView = (TextView) holdView.findViewById(R.id.stop_nameText); routesView = (TextView) holdView.findViewById(R.id.stop_linesText); distancetextView = (TextView) holdView.findViewById(R.id.stop_distanceTextView); } @Override public void onClick(View v) { listener.requestArrivalsForStopID(stopID); } } public void setStops(List newStops) { // this.stops = stops; DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() { @Override public int getOldListSize() { return stops.size(); } @Override public int getNewListSize() { return newStops.size(); } @Override public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { var oldItem = stops.get(oldItemPosition); var newItem = newStops.get(newItemPosition); // usa un ID univoco return oldItem.ID.equals(newItem.ID); } @Override public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { var oldItem = stops.get(oldItemPosition); var newItem = newStops.get(newItemPosition); return oldItem.equals(newItem); } }); this.stops.clear(); this.stops.addAll(newStops); diffResult.dispatchUpdatesTo(this); } public void setUserPosition(@Nullable GPSPoint userPosition) { this.userPosition = userPosition; } /* @Override public Stop getItem(int position) { return stops.get(position); } */ } diff --git a/app/src/main/java/it/reyboz/bustorino/adapters/StopAdapter.java b/app/src/main/java/it/reyboz/bustorino/adapters/StopAdapter.java index 05daef0..ca7d7c9 100644 --- a/app/src/main/java/it/reyboz/bustorino/adapters/StopAdapter.java +++ b/app/src/main/java/it/reyboz/bustorino/adapters/StopAdapter.java @@ -1,118 +1,126 @@ /* BusTO (backend components) Copyright (C) 2016 Ludovico Pavesi This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package it.reyboz.bustorino.adapters; import android.content.Context; +import android.widget.ImageView; import androidx.annotation.NonNull; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.TextView; import java.util.List; import it.reyboz.bustorino.R; import it.reyboz.bustorino.backend.Stop; /** * @see PalinaAdapter */ public class StopAdapter extends ArrayAdapter { private LayoutInflater li; private static final int row_layout = R.layout.entry_bus_stop; - private static final int busIcon = R.drawable.bus; - private static final int trainIcon = R.drawable.subway; - private static final int tramIcon = R.drawable.tram; + private static final int busIcon = R.drawable.ic_bus; + private static final int trainIcon = R.drawable.ic_subway_filled; + private static final int tramIcon = R.drawable.ic_tram_filled_24; private static final int cityIcon = R.drawable.city; private static class ViewHolder { - TextView busStopIDTextView; - TextView busStopNameTextView; + final TextView busStopIDTextView; + final TextView busStopNameTextView; //TextView busLineVehicleIcon; - TextView busStopLinesTextView; - TextView busStopLocaLityTextView; + final TextView busStopLinesTextView; + final TextView busStopLocaLityTextView; + + ViewHolder(View view) { + busStopIDTextView = view.findViewById(R.id.busStopID); + busStopNameTextView = view.findViewById(R.id.busStopName); + busStopLinesTextView = view.findViewById(R.id.routesThatStopHere); + busStopLocaLityTextView = view.findViewById(R.id.busStopLocality); + } } public StopAdapter(Context context, List stops) { super(context, row_layout, stops); li = LayoutInflater.from(context); } @NonNull @Override public View getView(int position, View convertView, @NonNull ViewGroup parent) { ViewHolder vh; if(convertView == null) { convertView = li.inflate(row_layout, null); - vh = new ViewHolder(); - vh.busStopIDTextView = (TextView) convertView.findViewById(R.id.busStopID); - vh.busStopNameTextView = (TextView) convertView.findViewById(R.id.busStopName); - vh.busStopLinesTextView = (TextView) convertView.findViewById(R.id.routesThatStopHere); - vh.busStopLocaLityTextView = (TextView) convertView.findViewById(R.id.busStopLocality); + vh = new ViewHolder(convertView); convertView.setTag(vh); } else { vh = (ViewHolder) convertView.getTag(); } Stop stop = getItem(position); vh.busStopIDTextView.setText(stop.ID); // NOTE: intentionally ignoring stop username in search results: if it's in the favorites, why are you searching for it? vh.busStopNameTextView.setText(stop.getStopDisplayName()); String whatStopsHere = stop.routesThatStopHereToString(); if(whatStopsHere == null) { vh.busStopLinesTextView.setVisibility(View.GONE); } else { vh.busStopLinesTextView.setText(whatStopsHere); vh.busStopLinesTextView.setVisibility(View.VISIBLE); // might be GONE due to View Holder Pattern } + /* + // DEPRECATED CODE: ALWAYS USE BUS STOP ICON if(stop.type == null) { - vh.busStopLinesTextView.setCompoundDrawablesWithIntrinsicBounds(busIcon, 0, 0, 0); + //vh.busStopLinesTextView.setCompoundDrawablesWithIntrinsicBounds(busIcon, 0, 0, 0); } else { switch(stop.type) { case BUS: default: vh.busStopLinesTextView.setCompoundDrawablesWithIntrinsicBounds(busIcon, 0, 0, 0); break; case METRO: case RAILWAY: vh.busStopLinesTextView.setCompoundDrawablesWithIntrinsicBounds(trainIcon, 0, 0, 0); break; case TRAM: vh.busStopLinesTextView.setCompoundDrawablesWithIntrinsicBounds(tramIcon, 0, 0, 0); break; case LONG_DISTANCE_BUS: // è l'opposto della città ma va beh, dettagli. vh.busStopLinesTextView.setCompoundDrawablesWithIntrinsicBounds(cityIcon, 0, 0, 0); } } + */ + if (stop.location == null) { vh.busStopLocaLityTextView.setVisibility(View.GONE); } else { vh.busStopLocaLityTextView.setText(stop.location); vh.busStopLocaLityTextView.setVisibility(View.VISIBLE); // might be GONE due to View Holder Pattern } return convertView; } } diff --git a/app/src/main/java/it/reyboz/bustorino/adapters/StopRecyclerAdapter.java b/app/src/main/java/it/reyboz/bustorino/adapters/StopRecyclerAdapter.java index 3a16073..f507bed 100644 --- a/app/src/main/java/it/reyboz/bustorino/adapters/StopRecyclerAdapter.java +++ b/app/src/main/java/it/reyboz/bustorino/adapters/StopRecyclerAdapter.java @@ -1,232 +1,236 @@ /* BusTO - Adapter components Copyright (C) 2021 Fabio Mazza This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package it.reyboz.bustorino.adapters; import android.view.ContextMenu; import android.view.LayoutInflater; import android.view.MenuInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import java.util.List; import java.util.Locale; import it.reyboz.bustorino.R; import it.reyboz.bustorino.backend.Stop; public class StopRecyclerAdapter extends RecyclerView.Adapter { private List stops; private static final int ITEM_LAYOUT_FAVORITES = R.layout.entry_bus_stop; private static final int ITEM_LAYOUT_LINES = R.layout.stop_line_element; - private static final int busIcon = R.drawable.bus; - private static final int trainIcon = R.drawable.subway; - private static final int tramIcon = R.drawable.tram; + private static final int busIcon = R.drawable.ic_bus; + private static final int trainIcon = R.drawable.ic_subway_filled; + private static final int tramIcon = R.drawable.ic_tram_filled_24; private static final int cityIcon = R.drawable.city; private NameCapitalize capitalizeLocation = NameCapitalize.DO_NOTHING; private final Use usedFor; private final StopAdapterListener listener; private int position; protected static class ViewHolder extends RecyclerView.ViewHolder implements View.OnCreateContextMenuListener{ TextView busStopIDTextView; TextView busStopNameTextView; //TextView busLineVehicleIcon; TextView busStopLinesTextView; TextView busStopLocaLityTextView; View topStub, bottomStub; Stop mStop; int menuResID=R.menu.menu_favourites_entry; public ViewHolder(@NonNull View itemView, StopAdapterListener listener, Use usedFor) { super(itemView); busStopIDTextView = itemView.findViewById(R.id.busStopID); busStopNameTextView = itemView.findViewById(R.id.busStopName); busStopLinesTextView = itemView.findViewById(R.id.routesThatStopHere); busStopLocaLityTextView = itemView.findViewById(R.id.busStopLocality); switch (usedFor){ case LINES: topStub = itemView.findViewById(R.id.topStub); bottomStub = itemView.findViewById(R.id.bottomStub); menuResID = R.menu.menu_line_item; break; case FAVORITES: default: topStub = null; bottomStub = null; } mStop = new Stop(""); itemView.setOnClickListener(view -> { listener.onTappedStop(mStop); }); } //many thanks to https://stackoverflow.com/questions/26466877/how-to-create-context-menu-for-recyclerview @Override public void onCreateContextMenu(ContextMenu contextMenu, View view, ContextMenu.ContextMenuInfo contextMenuInfo) { MenuInflater inflater = new MenuInflater(view.getContext()); inflater.inflate(menuResID, contextMenu); } } public StopRecyclerAdapter(List stops, StopAdapterListener listener, Use usedFor) { this.stops = stops; this.listener = listener; this.usedFor = usedFor; } public StopRecyclerAdapter(List stops, StopAdapterListener listener, Use usedFor, NameCapitalize locationCapit) { this.stops = stops; this.listener = listener; this.usedFor = usedFor; this.capitalizeLocation = locationCapit; } public NameCapitalize getCapitalizeLocation() { return capitalizeLocation; } public void setCapitalizeLocation(NameCapitalize capitalizeLocation) { this.capitalizeLocation = capitalizeLocation; notifyDataSetChanged(); } public void setStops(List stops){ this.stops = stops; notifyDataSetChanged(); } public List getStops() { return stops; } public int getPosition() { return position; } public void setPosition(int position) { this.position = position; } @NonNull @Override public StopRecyclerAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { final int layoutID; switch (usedFor){ case LINES: layoutID = ITEM_LAYOUT_LINES; break; case FAVORITES: default: layoutID = ITEM_LAYOUT_FAVORITES; } View view = LayoutInflater.from(parent.getContext()) .inflate(layoutID, parent, false); return new StopRecyclerAdapter.ViewHolder(view, listener, this.usedFor); } @Override public void onViewRecycled(@NonNull StopRecyclerAdapter.ViewHolder holder) { holder.itemView.setOnLongClickListener(null); super.onViewRecycled(holder); } @Override public void onBindViewHolder(@NonNull StopRecyclerAdapter.ViewHolder vh, int position) { //Log.d("StopRecyclerAdapter", "Called for position "+position); Stop stop = stops.get(position); vh.busStopIDTextView.setText(stop.ID); vh.mStop = stop; //Log.d("StopRecyclerAdapter", "Stop: "+stop.ID); // NOTE: intentionally ignoring stop username in search results: if it's in the favorites, why are you searching for it? vh.busStopNameTextView.setText(stop.getStopDisplayName()); String whatStopsHere = stop.routesThatStopHereToString(); if(whatStopsHere == null) { vh.busStopLinesTextView.setVisibility(View.GONE); } else { vh.busStopLinesTextView.setText(whatStopsHere); vh.busStopLinesTextView.setVisibility(View.VISIBLE); // might be GONE due to View Holder Pattern } + /* + //This is deprecated, always showing a bus if(stop.type == null) { vh.busStopLinesTextView.setCompoundDrawablesWithIntrinsicBounds(busIcon, 0, 0, 0); } else { switch(stop.type) { case BUS: default: vh.busStopLinesTextView.setCompoundDrawablesWithIntrinsicBounds(busIcon, 0, 0, 0); break; case METRO: case RAILWAY: vh.busStopLinesTextView.setCompoundDrawablesWithIntrinsicBounds(trainIcon, 0, 0, 0); break; case TRAM: vh.busStopLinesTextView.setCompoundDrawablesWithIntrinsicBounds(tramIcon, 0, 0, 0); break; case LONG_DISTANCE_BUS: // è l'opposto della città ma va beh, dettagli. vh.busStopLinesTextView.setCompoundDrawablesWithIntrinsicBounds(cityIcon, 0, 0, 0); } } + */ + if (stop.location == null || stop.location.toLowerCase(Locale.ROOT).equals("null")) { vh.busStopLocaLityTextView.setVisibility(View.GONE); } else { vh.busStopLocaLityTextView.setText(NameCapitalize.capitalizePass(stop.location, capitalizeLocation)); vh.busStopLocaLityTextView.setVisibility(View.VISIBLE); // might be GONE due to View Holder Pattern } //trick to set the position vh.itemView.setOnLongClickListener(view -> { setPosition(vh.getAdapterPosition()); return false; }); if(this.usedFor == Use.LINES){ //vh.menuResID; vh.bottomStub.setVisibility(View.VISIBLE); vh.topStub.setVisibility(View.VISIBLE); if(position == 0) { vh.topStub.setVisibility(View.INVISIBLE); } else if (position == stops.size()-1) { vh.bottomStub.setVisibility(View.GONE); } } } @Override public int getItemCount() { return stops.size(); } public enum Use{ FAVORITES, LINES } } diff --git a/app/src/main/java/it/reyboz/bustorino/backend/KotlinUtils.kt b/app/src/main/java/it/reyboz/bustorino/backend/KotlinUtils.kt new file mode 100644 index 0000000..ab5e7c5 --- /dev/null +++ b/app/src/main/java/it/reyboz/bustorino/backend/KotlinUtils.kt @@ -0,0 +1,13 @@ +package it.reyboz.bustorino.backend + +import android.content.Context +import android.content.res.Configuration + +object KotlinUtils { + + @JvmStatic + fun isDarkTheme(context: Context): Boolean { + val uiMode = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK + return uiMode == Configuration.UI_MODE_NIGHT_YES + } +} \ No newline at end of file diff --git a/app/src/main/java/it/reyboz/bustorino/backend/VehicleUtils.kt b/app/src/main/java/it/reyboz/bustorino/backend/VehicleUtils.kt index 4c95abb..4861ad2 100644 --- a/app/src/main/java/it/reyboz/bustorino/backend/VehicleUtils.kt +++ b/app/src/main/java/it/reyboz/bustorino/backend/VehicleUtils.kt @@ -1,92 +1,94 @@ package it.reyboz.bustorino.backend import it.reyboz.bustorino.backend.VehicleUtils.VehicleType import java.util.Locale.getDefault data class VehicleClassInfo( val vehClass: Int, val name: String, val kindString: String, val type: VehicleType, val matricolaStart: Int, val matricolaEnd: Int, ) { constructor(vehicleClass: Int, name: String, type: String, matricolaStart: Int, matricolaEnd: Int) : this( vehicleClass, name, type,VehicleType.fromString(type), matricolaStart, matricolaEnd ) } object VehicleUtils { - private val items = listOf( - VehicleClassInfo(2800, "Arancio", "Tram", 2801, 2857), - VehicleClassInfo(2800, "Arancio", "Tram", 2858, 2902), - VehicleClassInfo(5000, "5000", "Tram", 5000, 5053), - VehicleClassInfo(6000, "6000", "Tram", 6000, 6005), - VehicleClassInfo(6000, "6000", "Tram", 6006, 6054), - VehicleClassInfo(8000, "Hitachi", "Tram", 8000, 8099), - VehicleClassInfo(800, "Citelis 18m", "Bus 18m", 790, 797), - VehicleClassInfo(800, "Citelis 18m", "Bus 18m", 800, 869), - VehicleClassInfo(800, "Citelis 18m", "Bus 18m", 870, 874), - VehicleClassInfo(800, "Citelis 18m", "Bus 18m", 1310, 1313), - VehicleClassInfo(1350, "Conecto 18m", "Bus 18m", 1350, 1396), - VehicleClassInfo(9300, "UrbanWay 18m", "Bus 18m", 9300, 9318), - VehicleClassInfo(9300, "UrbanWay 18m", "Bus 18m", 9320, 9356), - VehicleClassInfo(30, "BYD K9", "E-Bus", 30, 49), + val items = listOf( + VehicleClassInfo(30, "BYD K9UB", "E-Bus", 30, 49), VehicleClassInfo(50, "BYD K7", "E-Bus", 50, 57), - VehicleClassInfo(60, "MiniBusE", "E-Bus", 60, 81), - VehicleClassInfo(110, "Neocity", "Bus", 110, 115), - VehicleClassInfo(2300, "Cityclass", "Bus", 2300, 2349), - VehicleClassInfo(3400, "Conecto", "Bus", 2400, 2447), - VehicleClassInfo(2300, "Cityclass", "Bus", 2700, 2787), - VehicleClassInfo(3000, "Citelis", "Bus", 3000, 3099), - VehicleClassInfo(3000, "Citelis", "Bus", 3300, 3380), - VehicleClassInfo(3400, "Conecto", "Bus", 3400, 3440), - VehicleClassInfo(9000, "BYD K9", "E-Bus", 9000, 9059), - VehicleClassInfo(9000, "BYD K9", "E-Bus", 9060, 9119), - VehicleClassInfo(9000, "BYD K9", "E-Bus", 9120, 9121), - VehicleClassInfo(9200, "Citymood", "Bus", 9200, 9251), - VehicleClassInfo(9200, "Citymood", "Bus", 9252, 9261), + VehicleClassInfo(60, "Indcar BlueBus", "E-Bus", 60, 88), + VehicleClassInfo(110, "BMC Neocity", "Bus", 110, 115), + VehicleClassInfo(800, "Irisbus Citelis 18m", "Bus 18m", 790, 797), + VehicleClassInfo(800, "Irisbus Citelis 18m", "Bus 18m", 800, 869), + VehicleClassInfo(800, "Irisbus Citelis 18m ", "Bus 18m", 870, 874), + VehicleClassInfo(800, "Irisbus Citelis CNG 18m", "Bus 18m", 1310, 1313), + VehicleClassInfo(1350, "Mercedes Conecto 18m", "Bus 18m", 1350, 1396), + VehicleClassInfo(2300, "Irisbus CityClass", "Bus", 2300, 2349), + VehicleClassInfo(3400, "Mercedes Conecto", "Bus", 2400, 2447), + VehicleClassInfo(2300, "Irisbus CityClass", "Bus", 2700, 2787), + VehicleClassInfo(2800, "2800 (prima serie)", "Tram", 2801, 2857), + VehicleClassInfo(2800, "2800 (seconda serie)", "Tram", 2858, 2902), + VehicleClassInfo(3000, "Irisbus Citelis", "Bus", 3000, 3099), + VehicleClassInfo(3000, "Irisbus Citelis", "Bus", 3300, 3380), + VehicleClassInfo(3400, "Mercedes Conecto", "Bus", 3400, 3440), + VehicleClassInfo(5000, "P.R. (5000)", "Tram", 5000, 5053), + VehicleClassInfo(6000, "CityWay (6000) monodir.", "Tram", 6000, 6005), + VehicleClassInfo(6000, "CityWay (6000) bidir.", "Tram", 6006, 6054), + VehicleClassInfo(8000, "Hitachi (8000)", "Tram", 8000, 8099), + VehicleClassInfo(9000, "BYD K9UB", "E-Bus", 9000, 9059), + VehicleClassInfo(9000, "BYD K9UB", "E-Bus", 9060, 9119), + VehicleClassInfo(9000, "BYD K9UB", "E-Bus", 9120, 9121), + VehicleClassInfo(9200, "Menarini Citymood", "Bus", 9200, 9251), + VehicleClassInfo(9200, "Menarini Citymood", "Bus", 9252, 9261), + VehicleClassInfo(9300, "Iveco Urbanway 18m", "Bus 18m", 9300, 9318), + VehicleClassInfo(9300, "Iveco Urbanway 18m", "Bus 18m", 9320, 9356), VehicleClassInfo(9400, "Iveco E-Way", "E-Bus", 9400, 9599), - VehicleClassInfo(9600, "Iveco E-Way 18m", "E-Bus", 9600, 9699) + VehicleClassInfo(9600, "Iveco E-Way 18m", "E-Bus", 9600, 9699), + VehicleClassInfo(9700, "Iveco E-Way 18m BRT", "E-Bus", 9700, 9799) ) + fun getTypeForLabel(label: String): VehicleClassInfo? { try { val matricola = Integer.parseInt(label) for (el in items) { if(matricola >= el.matricolaStart && matricola<= el.matricolaEnd) { return el } } return null } catch (e: Exception) { return null } } enum class VehicleType { BUS, TRAM, ELECTRIC_BUS; fun getName(): String { return when (this) { BUS -> "Bus" TRAM -> "Tram" ELECTRIC_BUS -> "E-Bus" } } companion object { @JvmStatic fun fromString(string: String): VehicleType { return when (string.lowercase(getDefault())) { "bus" -> BUS "bus 18m" -> BUS "tram" -> TRAM "e-bus" -> ELECTRIC_BUS "e-bus 18m" -> ELECTRIC_BUS else -> throw IllegalArgumentException("Unknown vehicle type: $string") } } } } } \ No newline at end of file diff --git a/app/src/main/java/it/reyboz/bustorino/data/GtfsMaintenanceWorker.kt b/app/src/main/java/it/reyboz/bustorino/data/GtfsMaintenanceWorker.kt index ea4489e..39a07d3 100644 --- a/app/src/main/java/it/reyboz/bustorino/data/GtfsMaintenanceWorker.kt +++ b/app/src/main/java/it/reyboz/bustorino/data/GtfsMaintenanceWorker.kt @@ -1,58 +1,58 @@ package it.reyboz.bustorino.data import android.content.Context import androidx.core.app.NotificationCompat import androidx.work.* import it.reyboz.bustorino.R import it.reyboz.bustorino.backend.Notifications class GtfsMaintenanceWorker(appContext: Context, workerParams: WorkerParameters) : CoroutineWorker(appContext, workerParams) { override suspend fun doWork(): Result { val data = inputData.getString(OPERATION_TYPE) if(data ==null){ return Result.failure() } val result = when (data){ CLEAR_GTFS_TRIPS ->clearGtfsTrips() else -> {Result.failure()} } return result } private fun clearGtfsTrips(): Result{ val gtfsRepository = GtfsRepository(applicationContext) gtfsRepository.gtfsDao.deleteAllTrips() return Result.success() } override suspend fun getForegroundInfo(): ForegroundInfo { val context = applicationContext Notifications.createDBNotificationChannelIfNeeded(context) val notification = NotificationCompat.Builder(context, Notifications.DB_UPDATE_CHANNELS_ID) //.setContentIntent(PendingIntent.getActivity(context, 0, Intent(context, MainActivity::class.java), Constants.PENDING_INTENT_FLAG_IMMUTABLE)) - .setSmallIcon(R.drawable.bus) + .setSmallIcon(R.drawable.ic_bus) .setOngoing(true) .setAutoCancel(true) .setOnlyAlertOnce(true) .setPriority(NotificationCompat.PRIORITY_MIN) .setContentTitle(context.getString(R.string.app_name)) .setLocalOnly(true) .setVisibility(NotificationCompat.VISIBILITY_SECRET) .setContentText("Database maintenance") .build() return ForegroundInfo(3671672811121.toInt(), notification) } companion object{ const val CLEAR_GTFS_TRIPS="trips_clear" const val OPERATION_TYPE="oper_type" fun makeOneTimeRequest(type: String): OneTimeWorkRequest { val data = Data.Builder().putString(OPERATION_TYPE, type).build() return OneTimeWorkRequest.Builder(GtfsMaintenanceWorker::class.java) .setInputData(data).setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) .addTag(type) .build() } } } \ No newline at end of file diff --git a/app/src/main/java/it/reyboz/bustorino/data/PreferencesHolder.java b/app/src/main/java/it/reyboz/bustorino/data/PreferencesHolder.java index c08a194..07f802a 100644 --- a/app/src/main/java/it/reyboz/bustorino/data/PreferencesHolder.java +++ b/app/src/main/java/it/reyboz/bustorino/data/PreferencesHolder.java @@ -1,108 +1,133 @@ /* BusTO - Data components Copyright (C) 2021 Fabio Mazza This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package it.reyboz.bustorino.data; import android.content.Context; import android.content.SharedPreferences; import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatDelegate; import it.reyboz.bustorino.R; import static android.content.Context.MODE_PRIVATE; import androidx.preference.PreferenceManager; +import it.reyboz.bustorino.backend.KotlinUtils; import it.reyboz.bustorino.fragments.SettingsFragment; import it.reyboz.bustorino.map.MapLibreUtils; import java.util.HashSet; import java.util.Set; /** * Static class for commonly used SharedPreference operations */ public abstract class PreferencesHolder { public static final String PREF_GTFS_DB_VERSION = "gtfs_db_version"; public static final String PREF_INTRO_ACTIVITY_RUN ="pref_intro_activity_run"; public static final String DB_GTT_VERSION_KEY = "NextGenDB.GTTVersion"; public static final String DB_LAST_UPDATE_KEY = "NextGenDB.LastDBUpdate"; public static final String PREF_FAVORITE_LINES = "pref_favorite_lines"; + // match the one in preferences.xml + public static final String PREF_THEME_DAY_NIGHT = "app_theme_day_night"; + public static final Set KEYS_MERGE_SET = Set.of(PREF_FAVORITE_LINES); public static final Set IGNORE_KEYS_LOAD_MAIN = Set.of(PREF_GTFS_DB_VERSION, PREF_INTRO_ACTIVITY_RUN, DB_GTT_VERSION_KEY, DB_LAST_UPDATE_KEY); public static SharedPreferences getMainSharedPreferences(Context context){ return context.getSharedPreferences(context.getString(R.string.mainSharedPreferences), MODE_PRIVATE); } public static SharedPreferences getAppPreferences(Context con){ return PreferenceManager.getDefaultSharedPreferences(con); } public static int getGtfsDBVersion(SharedPreferences pref){ return pref.getInt(PREF_GTFS_DB_VERSION,-1); } public static void setGtfsDBVersion(SharedPreferences pref,int version){ SharedPreferences.Editor ed = pref.edit(); ed.putInt(PREF_GTFS_DB_VERSION,version); ed.apply(); } /** * Check if the introduction activity has been run at least one * @param con the context needed * @return true if it has been run */ public static boolean hasIntroFinishedOneShot(Context con){ final SharedPreferences pref = getMainSharedPreferences(con); return pref.getBoolean(PREF_INTRO_ACTIVITY_RUN, false); } public static boolean addOrRemoveLineToFavorites(Context con, String gtfsLineId, boolean addToFavorites){ final SharedPreferences pref = getMainSharedPreferences(con); final HashSet favorites = new HashSet<>(pref.getStringSet(PREF_FAVORITE_LINES, new HashSet<>())); boolean modified = true; if(addToFavorites) favorites.add(gtfsLineId); else if(favorites.contains(gtfsLineId)) favorites.remove(gtfsLineId); else modified = false; // we are not changing anything if(modified) { final SharedPreferences.Editor editor = pref.edit(); editor.putStringSet(PREF_FAVORITE_LINES, favorites); editor.apply(); } return modified; } public static HashSet getFavoritesLinesGtfsIDs(@NonNull Context con){ final SharedPreferences pref = getMainSharedPreferences(con); return new HashSet<>(pref.getStringSet(PREF_FAVORITE_LINES, new HashSet<>())); } public static String getMapLibreStyleFile(Context con){ final SharedPreferences pref = getAppPreferences(con); final String mapStyle_val = pref.getString(SettingsFragment.LIBREMAP_STYLE_PREF_KEY, ""); - return switch (mapStyle_val) { + final boolean isNightMode = KotlinUtils.isDarkTheme(con); + String styleFile; + switch (mapStyle_val) { //MUST MATCH IN keys.xml -> map_style_pref_values - case "versatiles_c" -> MapLibreUtils.STYLE_VERSATILES_COLORFUL_JSON; - case "osm_legacy" -> MapLibreUtils.STYLE_OSM_RASTER; - default -> MapLibreUtils.getDefaultStyleJson(); + case "osm_legacy": + styleFile = MapLibreUtils.STYLE_OSM_RASTER; + break; + case "versatiles_c": + default: + styleFile = (isNightMode) ? MapLibreUtils.STYLE_VERSATILES_ECLIPSE_JSON : MapLibreUtils.STYLE_VERSATILES_COLORFUL_JSON; + } + return styleFile; + } + + public static Integer getAppThemeDayNight(Context context){ + final SharedPreferences pref = getAppPreferences(context); + final var value = pref.getString(PREF_THEME_DAY_NIGHT, ""); + return getAppThemeDayNight(value); + } + public static Integer getAppThemeDayNight(String prefValue){ + return switch (prefValue){ + case "day" -> AppCompatDelegate.MODE_NIGHT_NO; + case "night" -> AppCompatDelegate.MODE_NIGHT_YES; + case "system" -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM; + default -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM; }; } } diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/GeneralMapLibreFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/GeneralMapLibreFragment.kt index e487c29..203372d 100644 --- a/app/src/main/java/it/reyboz/bustorino/fragments/GeneralMapLibreFragment.kt +++ b/app/src/main/java/it/reyboz/bustorino/fragments/GeneralMapLibreFragment.kt @@ -1,1210 +1,1231 @@ /* BusTO - Fragments components Copyright (C) 2025 Fabio Mazza This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package it.reyboz.bustorino.fragments import android.Manifest import android.animation.ValueAnimator import android.annotation.SuppressLint import android.content.Context import android.content.Context.LOCATION_SERVICE import android.content.SharedPreferences import android.content.res.ColorStateList import android.graphics.Color import android.location.Location import android.location.LocationManager import android.os.Bundle import android.util.Log +import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.animation.LinearInterpolator import android.widget.ImageButton import android.widget.ImageView import android.widget.TextView import android.widget.Toast import androidx.activity.result.ActivityResultCallback import androidx.activity.result.contract.ActivityResultContracts import androidx.cardview.widget.CardView import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.ContextCompat import androidx.core.content.res.ResourcesCompat import androidx.core.view.ViewCompat import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.gson.JsonObject import it.reyboz.bustorino.BuildConfig import it.reyboz.bustorino.R import it.reyboz.bustorino.backend.FiveTNormalizer import it.reyboz.bustorino.backend.LivePositionTripPattern import it.reyboz.bustorino.backend.LivePositionsServiceStatus import it.reyboz.bustorino.backend.Stop import it.reyboz.bustorino.backend.VehicleUtils import it.reyboz.bustorino.backend.gtfs.GtfsUtils import it.reyboz.bustorino.backend.gtfs.LivePositionUpdate import it.reyboz.bustorino.backend.utils import it.reyboz.bustorino.data.PreferencesHolder import it.reyboz.bustorino.data.gtfs.TripAndPatternWithStops import it.reyboz.bustorino.map.MapLibreLocationEngine import it.reyboz.bustorino.map.MapLibreUtils import it.reyboz.bustorino.middleware.FusedNativeLocationProvider import it.reyboz.bustorino.util.Permissions import it.reyboz.bustorino.util.ViewUtils import it.reyboz.bustorino.viewmodels.LivePositionsViewModel import it.reyboz.bustorino.viewmodels.MapStateViewModel import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.maplibre.android.MapLibre import org.maplibre.android.camera.CameraPosition import org.maplibre.android.geometry.LatLng import org.maplibre.android.location.LocationComponent import org.maplibre.android.location.LocationComponentActivationOptions import org.maplibre.android.location.engine.LocationEngineCallback import org.maplibre.android.location.engine.LocationEngineRequest import org.maplibre.android.location.engine.LocationEngineResult import org.maplibre.android.maps.MapLibreMap import org.maplibre.android.maps.MapView import org.maplibre.android.maps.OnMapReadyCallback import org.maplibre.android.maps.Style import org.maplibre.android.plugins.annotation.Symbol import org.maplibre.android.plugins.annotation.SymbolManager import org.maplibre.android.plugins.annotation.SymbolOptions import org.maplibre.android.style.expressions.Expression import org.maplibre.android.style.layers.Property.ICON_ANCHOR_CENTER import org.maplibre.android.style.layers.Property.ICON_ROTATION_ALIGNMENT_MAP import org.maplibre.android.style.layers.Property.TEXT_ANCHOR_CENTER import org.maplibre.android.style.layers.Property.TEXT_ROTATION_ALIGNMENT_VIEWPORT import org.maplibre.android.style.layers.PropertyFactory import org.maplibre.android.style.layers.SymbolLayer import org.maplibre.android.style.sources.GeoJsonSource import org.maplibre.geojson.Feature import org.maplibre.geojson.FeatureCollection import org.maplibre.geojson.Point import kotlin.time.Duration.Companion.milliseconds abstract class GeneralMapLibreFragment: ScreenBaseFragment(), OnMapReadyCallback { protected var map: MapLibreMap? = null protected var shownStopInBottomSheet : Stop? = null //protected var savedMapStateOnPause : Bundle? = null protected var fragmentListener: CommonFragmentListener? = null // Declare a variable for MapView protected var mapView: MapView? = null protected lateinit var mapStyle: Style protected lateinit var stopsSource: GeoJsonSource protected lateinit var busesSource: GeoJsonSource protected lateinit var selectedStopSource: GeoJsonSource protected lateinit var selectedBusSource: GeoJsonSource //= GeoJsonSource(SEL_BUS_SOURCE) protected lateinit var sharedPreferences: SharedPreferences protected lateinit var bottomSheetBehavior: BottomSheetBehavior protected var locationEngine: MapLibreLocationEngine? = null protected var locationProvider: FusedNativeLocationProvider? = null protected var shownToastNoPosition = false protected var locationEnabledOnDevice = true protected var busLayerStarted = false //TODO ACTIVATE THIS private val preferenceChangeListener = SharedPreferences.OnSharedPreferenceChangeListener(){ pref, key -> /*when(key){ SettingsFragment.LIBREMAP_STYLE_PREF_KEY -> reloadMap() } */ if(key == SettingsFragment.LIBREMAP_STYLE_PREF_KEY){ Log.d(DEBUG_TAG,"ASKING RELOAD OF MAP") //reloadMap() } } /** * What to do when requesting the permission, when it's ok, initialize the map location component */ protected val positionRequestResponder = registerForActivityResult( ActivityResultContracts.RequestMultiplePermissions(), ActivityResultCallback{ res -> if(!(res.containsKey(PERM_LOC_COARSE)&&res.containsKey(PERM_LOC_FINE))){ Log.e(DEBUG_TAG, "Location request does not have the correct keys") } else if(res[PERM_LOC_COARSE]!! && res[PERM_LOC_FINE]!!){ //permission OK, init map location val mMap = map if(mMap == null){ Log.w(DEBUG_TAG, "Location request completed, but map is null!") }else{ initializeMapLocationComponent(mMap,requireContext(), null) } } else{ // PERMISSION DENIED // TODO find better way to show the necessity of the permission if(shouldShowRequestPermissionRationale(PERM_LOC_FINE)) Toast.makeText(requireContext(), R.string.enable_position_message_map, Toast.LENGTH_SHORT).show() } } ) //Bottom sheet behavior in GeneralMapLibreFragment protected var bottomLayout: ConstraintLayout? = null protected lateinit var stopTitleTextView: TextView protected lateinit var stopNumberTextView: TextView protected lateinit var linesPassingTextView: TextView protected lateinit var extraBottomTextView: TextView + protected lateinit var linesBottomTextView: TextView protected lateinit var arrivalsCard: CardView protected lateinit var directionsCard: CardView protected lateinit var bottomrightImage: ImageView protected lateinit var locationComponent: LocationComponent protected lateinit var busPositionsIconButton: ImageButton protected lateinit var vehicleIcon: ImageView protected var lastLocation : Location? = null private var lastMapStyle ="" //BUS POSITIONS protected val updatesByVehDict = HashMap(5) protected val animatorsByVeh = HashMap() protected var vehShowing: String? = null protected var lastUpdateTime:Long = -2 protected var jobUpdate: Job? = null //private val lifecycleOwnerLiveData = viewLifecycleOwnerLiveData //extra items to use the LibreMap protected var symbolManager : SymbolManager? = null protected var stopActiveSymbol: Symbol? = null protected var stopsLayerStarted = false protected val livePositionsViewModel : LivePositionsViewModel by activityViewModels() //private lateinit var symbolManager: SymbolManager protected val mapStateViewModel: MapStateViewModel by viewModels() protected var locationInitialized = false protected var mapInitialized = false protected var receivedFirstLocation = false //location callback to decide if to zoom to the user position @SuppressLint("MissingPermission") protected val mapLibreLocationCallback = object : LocationEngineCallback { override fun onSuccess(result: LocationEngineResult) { val location: Location? = result.lastLocation Log.d(DEBUG_TAG, "Received location $location") location?.let { //check timing of the location val currentTime = System.currentTimeMillis() val discard = (currentTime - it.time) > 90 * 1000.0 // discard if it is Older than 60 seconds if(!discard) { if (!receivedFirstLocation) { onFirstReceivedLocation(it) } receivedFirstLocation = true } } if(receivedFirstLocation){ //remove this listener once we have received the location locationEngine?.removeLocationUpdates(this) } } override fun onFailure(exception: Exception) { Log.e(DEBUG_TAG, "Error in getting position: ${exception.message}") } } protected val deviceLocationStatusListener = FusedNativeLocationProvider.LocationStatusListener { isEnabled -> mapStateViewModel.locationDeviceEnabled.value = isEnabled if(locationEnabledOnDevice && !isEnabled && locationInitialized) { warnLocationNotEnabledOnDevice() //setMapLocationEnabled(false) } locationEnabledOnDevice = isEnabled } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) //sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) lastMapStyle = PreferencesHolder.getMapLibreStyleFile(requireContext()) //init map MapLibre.getInstance(requireContext()) } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { //TODO: Re-create map when this preference changes lastMapStyle = PreferencesHolder.getMapLibreStyleFile(requireContext()) Log.d(DEBUG_TAG, "onCreateView lastMapStyle: $lastMapStyle") return super.onCreateView(inflater, container, savedInstanceState) } protected fun initBottomSheet(view: View){ val bottomSheet = view.findViewById(R.id.bottom_sheet) bottomLayout = bottomSheet stopTitleTextView = view.findViewById(R.id.stopTitleTextView) stopNumberTextView = view.findViewById(R.id.stopNumberTextView) linesPassingTextView = view.findViewById(R.id.descriptionTextView) arrivalsCard = view.findViewById(R.id.arrivalsCardButton) directionsCard = view.findViewById(R.id.directionsCardButton) vehicleIcon = view.findViewById(R.id.vehicleIcon) + linesBottomTextView = view.findViewById(R.id.linesBottomTextView) + linesBottomTextView.text = getString(R.string.lines_fill, "") bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet) bottomSheetBehavior.state = BottomSheetBehavior.STATE_HIDDEN } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) //init bottom sheet initBottomSheet(view) bottomrightImage = view.findViewById(R.id.rightmostImageView) extraBottomTextView = view.findViewById(R.id.extraBottomTextView) } override fun onResume() { super.onResume() mapView?.onResume() val newMapStyle = PreferencesHolder.getMapLibreStyleFile(requireContext()) Log.d(DEBUG_TAG, "onResume newMapStyle: $newMapStyle, lastMapStyle: $lastMapStyle") // TODO: reload style if user changed preferences //if(newMapStyle!=lastMapStyle){ // reloadMap() //} if(busLayerStarted) updatePositionsIcons(false) } override fun onLowMemory() { mapView?.onLowMemory() super.onLowMemory() } override fun onStart() { super.onStart() mapView?.onStart() } override fun onDestroy() { mapView?.onDestroy() Log.d(DEBUG_TAG, "Destroyed mapView Fragment!!") busLayerStarted = false super.onDestroy() } override fun onStop() { locationProvider?.removeListener(deviceLocationStatusListener) mapView?.onStop() super.onStop() } override fun onPause() { jobUpdate?.cancel() mapView?.onPause() super.onPause() } override fun onDestroyView() { bottomLayout = null mapInitialized = false locationInitialized = false super.onDestroyView() } protected fun warnLocationNotEnabledOnDevice(){ context?.let{ Toast.makeText(it,R.string.enable_location_message,Toast.LENGTH_SHORT).show() } } protected fun reloadMap(){ /*map?.let { Log.d("GeneralMapFragment", "RELOADING MAP") //save map state savedMapStateOnPause = saveMapStateInBundle() onMapDestroy() //Destroy and recreate MAP mapView.onDestroy() mapView.onCreate(null) mapView.getMapAsync(this) } */ } //For extra stuff to do when the map is destroyed abstract fun onMapDestroy() override fun onAttach(context: Context) { super.onAttach(context) if(context is CommonFragmentListener){ fragmentListener = context } else throw RuntimeException("$context must implement CommonFragmentListener") } protected fun stopToGeoJsonFeature(s: Stop): Feature{ return Feature.fromGeometry( Point.fromLngLat(s.longitude!!, s.latitude!!), JsonObject().apply { addProperty("id", s.ID) addProperty("name", s.stopDefaultName) //addProperty("routes", s.routesThatStopHereToString()) // Add routes array to JSON object } ) } protected fun isPointInsideVisibleRegion(p: LatLng, other: Boolean): Boolean{ val bounds = map?.projection?.visibleRegion?.latLngBounds var inside = other bounds?.let { inside = it.contains(p) } return inside } protected fun isPointInsideVisibleRegion(lat: Double, lon: Double, other: Boolean): Boolean{ val p = LatLng(lat, lon) return isPointInsideVisibleRegion(p, other) } protected fun removeVehiclesData(vehs: List){ for(v in vehs){ if (updatesByVehDict.contains(v)) { updatesByVehDict.remove(v) if (animatorsByVeh.contains(v)){ animatorsByVeh[v]?.cancel() animatorsByVeh.remove(v) } } if (vehShowing==v){ hideStopOrBusBottomSheet() } } } // Hide the bottom sheet and remove extra symbol protected open fun hideStopOrBusBottomSheet(){ if (stopActiveSymbol!=null){ symbolManager?.delete(stopActiveSymbol) stopActiveSymbol = null } if(!showOpenStopWithSymbolLayer()){ selectedStopSource.setGeoJson(FeatureCollection.fromFeatures(ArrayList())) } bottomSheetBehavior.state = BottomSheetBehavior.STATE_HIDDEN //isBottomSheetShowing = false //reset states shownStopInBottomSheet = null if (vehShowing!=null){ //we are hiding a vehicle vehShowing = null updatePositionsIcons(true) } extraBottomTextView.visibility = View.GONE } protected fun initSymbolManager(mapReady: MapLibreMap , style: Style){ val sm = SymbolManager(mapView!!, mapReady, style) sm.iconAllowOverlap = true sm.textAllowOverlap = false sm.addClickListener { _ -> if (stopActiveSymbol != null) { hideStopOrBusBottomSheet() return@addClickListener true } else return@addClickListener false } symbolManager = sm } /** * Change the icon indicating the status of the live Positions */ protected fun setBusPositionsIcon(enabled: Boolean, error: Boolean){ val ctx = requireContext() if(!enabled) busPositionsIconButton.setImageDrawable(ContextCompat.getDrawable(ctx, R.drawable.bus_pos_circle_inactive)) else if(error) busPositionsIconButton.setImageDrawable(ContextCompat.getDrawable(ctx, R.drawable.bus_pos_circle_notworking)) else busPositionsIconButton.setImageDrawable(ContextCompat.getDrawable(ctx, R.drawable.bus_pos_circle_active)) } abstract fun onMapLocationComponentInitialized() @SuppressLint("MissingPermission") protected fun setLocationComponentEnabled(enabled: Boolean): Boolean{ var changed = false map?.apply { if(locationComponent.isLocationComponentEnabled !=enabled) locationComponent.isLocationComponentEnabled= enabled changed = true} Log.d(DEBUG_TAG, "Asked to set location component enabled: $enabled, changed: $changed") mapStateViewModel.locationUserActive.value = enabled return changed } @SuppressLint("MissingPermission") protected fun initializeMapLocationComponent(map: MapLibreMap, context: Context, style: Style?){ val mStyle = style ?: map.style if(locationInitialized){ Log.w(DEBUG_TAG, "trying to initialize Location Component, but it is already done") return } mStyle?.let{ style -> locationComponent = map.locationComponent val locProvider = FusedNativeLocationProvider(context) locProvider.addListener(deviceLocationStatusListener) locationEngine = MapLibreLocationEngine(locProvider) locationProvider = locProvider val options = LocationComponentActivationOptions.builder(context, style) .useDefaultLocationEngine(false) .locationEngine(locationEngine) .build() locationComponent.activateLocationComponent(options) if(BuildConfig.DEBUG) Log.d(DEBUG_TAG, "Initializing location, request initial position") startInitialPositionRequest() if(!locationEnabledOnDevice){ warnLocationNotEnabledOnDevice() }else { setLocationComponentEnabled(true) } locationInitialized = true onMapLocationComponentInitialized() } } @SuppressLint("MissingPermission") protected fun startInitialPositionRequest(){ locationEngine?.requestLocationUpdates(LocationEngineRequest.Builder(500).setDisplacement(20.0f).build(), mapLibreLocationCallback, null) } protected fun stopInitialPositionRequest(){ locationEngine?.removeLocationUpdates(mapLibreLocationCallback) } /** * Update function for the bus positions * Takes the processed updates and saves them accordingly * Unified version that works with both fragments * * @param incomingData Map of updates with optional trip and pattern information * @param hasVehicleTracking If true, checks if vehShowing is updated and calls callback (default: true) * @param trackVehicleCallback Optional callback to show vehicle details when vehShowing is updated */ protected fun updateBusPositionsInMap( incomingData: HashMap>, hasVehicleTracking: Boolean = true, trackVehicleCallback: ((String) -> Unit)? = null ) { val vehsNew = HashSet(incomingData.values.map { up -> up.first.vehicle }) val vehsOld = HashSet(updatesByVehDict.keys) Log.d(DEBUG_TAG, "In fragment, have ${incomingData.size} updates to show") var countUpds = 0 var createdVehs = 0 for (upsWithTrp in incomingData.values) { val newPos = upsWithTrp.first val patternStops = upsWithTrp.second val vehID = newPos.vehicle // Validate coordinates if (!vehsOld.contains(vehID)) { if (newPos.latitude <= 0 || newPos.longitude <= 0) { Log.w(DEBUG_TAG, "Update ignored for veh $vehID on line ${newPos.routeID}, lat: ${newPos.latitude}, lon ${newPos.longitude}") continue } } if (vehsOld.contains(vehID)) { // Changing the location of an existing bus val oldPosData = updatesByVehDict[vehID]!! val oldPos = oldPosData.posUpdate val oldPattern = oldPosData.pattern var avoidShowingUpdateBecauseIsImpossible = false // Check for impossible route changes if (oldPos.routeID != newPos.routeID) { val dist = LatLng(oldPos.latitude, oldPos.longitude).distanceTo( LatLng(newPos.latitude, newPos.longitude) ) val speed = dist * 3.6 / (newPos.timestamp - oldPos.timestamp) // km/h Log.w(DEBUG_TAG, "Vehicle $vehID changed route from ${oldPos.routeID} to ${newPos.routeID}, distance: $dist, speed: $speed") if (speed > 120 || speed < 0) { avoidShowingUpdateBecauseIsImpossible = true } } if (avoidShowingUpdateBecauseIsImpossible) { Log.w(DEBUG_TAG, "Update for vehicle $vehID skipped") continue } // Check if position actually changed val samePosition = (oldPos.latitude == newPos.latitude) && (oldPos.longitude == newPos.longitude) val setPattern = (oldPattern == null) && (patternStops != null) // Copy old bearing if new one is missing if (newPos.bearing == null && oldPos.bearing != null) { newPos.bearing = oldPos.bearing } if (!samePosition || setPattern) { val newOrOldPosInBounds = isPointInsideVisibleRegion( newPos.latitude, newPos.longitude, true ) || isPointInsideVisibleRegion(oldPos.latitude, oldPos.longitude, true) if (newOrOldPosInBounds) { // Update pattern data if available patternStops?.let { updatesByVehDict[vehID]!!.pattern = it.pattern } // Animate the position change animateNewPositionMove(newPos) } else { // Update position without animation updatesByVehDict[vehID] = LivePositionTripPattern( newPos, patternStops?.pattern ) } } countUpds++ } else { // New vehicle - create entry updatesByVehDict[vehID] = LivePositionTripPattern( newPos, patternStops?.pattern ) createdVehs++ } // Update vehicle details if this is the shown/tracked vehicle if (hasVehicleTracking && vehShowing?.isNotEmpty() == true && vehID == vehShowing) { trackVehicleCallback?.invoke(vehID) } } // Remove old positions Log.d(DEBUG_TAG, "Updated $countUpds vehicles, created $createdVehs vehicles") vehsOld.removeAll(vehsNew) // Clean up stale vehicles (not updated for 2 minutes) val currentTimeStamp = System.currentTimeMillis() / 1000 for (vehID in vehsOld) { val posData = updatesByVehDict[vehID]!! if (currentTimeStamp - posData.posUpdate.timestamp > 2 * 60) { // Remove the bus updatesByVehDict.remove(vehID) // Cancel and remove animator if exists animatorsByVeh[vehID]?.cancel() animatorsByVeh.remove(vehID) } } // Update UI updatePositionsIcons(false) } /** * Shared bottom sheet setup. The [onDirectionsClick] lambda is called when * directionsCard is tapped; it receives the pattern code (empty string when * no pattern is available) so each subclass can navigate as it sees fit. */ protected fun showVehicleTripInBottomSheet( veh: String, onDirectionsClick: (patternCode: String, veh: String) -> Unit ) { val data = updatesByVehDict[veh] ?: run { Log.w(DEBUG_TAG, "Asked to show vehicle $veh, but it's not present in the updates") return } bottomLayout?.let { val lineName = FiveTNormalizer.fixShortNameForDisplay( GtfsUtils.getLineNameFromGtfsID(data.posUpdate.routeID), false ) val pat = data.pattern if (pat != null) { stopTitleTextView.text = pat.headsign stopTitleTextView.visibility = View.VISIBLE stopNumberTextView.text = getString(R.string.line_fill_towards, lineName) } else { stopTitleTextView.visibility = View.GONE stopNumberTextView.text = getString(R.string.line_fill, lineName) } directionsCard.setOnClickListener { onDirectionsClick(pat?.code ?: "", veh) } directionsCard.visibility = View.VISIBLE bottomrightImage.setImageDrawable( ResourcesCompat.getDrawable(resources, R.drawable.ic_magnifying_glass, activity?.theme) ) // if you change this, remember to change the color of the vehicleIcon - val colorBlue = ResourcesCompat.getColor(resources, R.color.blue_500, activity?.theme) + val colorBlue = ResourcesCompat.getColor(resources, R.color.bus_marker_color_selected, activity?.theme) ViewCompat.setBackgroundTintList(directionsCard, ColorStateList.valueOf(colorBlue)) linesPassingTextView.text = getString(R.string.vehicle_fill, data.posUpdate.vehicle) + linesPassingTextView.gravity = Gravity.CENTER_VERTICAL + linesBottomTextView.visibility = View.GONE arrivalsCard.visibility = View.GONE extraBottomTextView.text = getString(R.string.updated_fill, utils.unixTimestampToLocalTime(data.posUpdate.timestamp)) extraBottomTextView.visibility = View.VISIBLE val update = data.posUpdate val vehInfo = VehicleUtils.getTypeForLabel(update.vehicle) if(vehInfo == null){ vehicleIcon.visibility = View.GONE } else{ val ico = when(vehInfo.type){ - VehicleUtils.VehicleType.BUS -> R.drawable.ic_bus_small - VehicleUtils.VehicleType.ELECTRIC_BUS -> R.drawable.ic_bus_electric_small - VehicleUtils.VehicleType.TRAM -> R.drawable.ic_tram_24 + VehicleUtils.VehicleType.BUS -> R.drawable.ic_bus + VehicleUtils.VehicleType.ELECTRIC_BUS -> R.drawable.ic_bus_electric_filled + VehicleUtils.VehicleType.TRAM -> R.drawable.ic_tram_material } vehicleIcon.setImageDrawable(ResourcesCompat.getDrawable(resources, ico, activity?.theme)) vehicleIcon.visibility = View.VISIBLE vehicleIcon.setOnClickListener { - val print = "${vehInfo.type.getName()} ${vehInfo.name}" + val print = "${vehInfo.type.getName()}: ${vehInfo.name}" makeToast(print) } } } - vehShowing = null + vehShowing = veh bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED updatePositionsIcons(true) Log.d(DEBUG_TAG, "Shown vehicle $veh in bottom sheet") } /** * Update the bus positions displayed on the map, from the existing data * * @param forced If true, forces immediate update ignoring the 100ms throttle */ protected fun updatePositionsIcons(forced: Boolean) { // Avoid frequent updates - throttle to max once per 60ms val currentTime = System.currentTimeMillis() val isStarted = (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) if(forced){ // if we're running a forced update, cancel the pending one - jobUpdate?.cancel() + jobUpdate?.apply{ + cancel() + //Log.d(DEBUG_TAG, "Cancelled update") + } + } else if (currentTime - lastUpdateTime < 100) { // Schedule delayed update if(viewLifecycleOwnerLiveData.value != null) { jobUpdate?.cancel() jobUpdate = viewLifecycleOwner.lifecycleScope.launch { delay(100.milliseconds) + //Log.d(DEBUG_TAG, "Running update from delayed") updatePositionsIcons(false) } + //Log.d(DEBUG_TAG, "Cancelled previous job, delaying update") } return } if(!isStarted){ Log.w(DEBUG_TAG, "fragment is not started, ") } val busFeatures = ArrayList() val selectedBusFeatures = ArrayList() for (dat in updatesByVehDict.values) { val pos = dat.posUpdate val point = Point.fromLngLat(pos.longitude, pos.latitude) val newFeature = Feature.fromGeometry( point, JsonObject().apply { addProperty("veh", pos.vehicle) addProperty("trip", pos.tripID) addProperty("bearing", pos.bearing ?: 0.0f) addProperty("line", pos.routeID.substringBeforeLast('U')) } ) // Separate selected vehicle from others if (vehShowing?.isNotEmpty() == true && vehShowing == dat.posUpdate.vehicle) { selectedBusFeatures.add(newFeature) + //Log.d(DEBUG_TAG, "Update position for bus $vehShowing") + //TODO: Recenter the map on the vehicle } else { busFeatures.add(newFeature) } } busesSource.setGeoJson(FeatureCollection.fromFeatures(busFeatures)) selectedBusSource.setGeoJson(FeatureCollection.fromFeatures(selectedBusFeatures)) lastUpdateTime = System.currentTimeMillis() } /** * Animates the transition of a vehicle from its current position to a new position * This is the tricky part - we need to set the new positions with the data and redraw them all * * @param positionUpdate The new position update to animate to */ protected fun animateNewPositionMove(positionUpdate: LivePositionUpdate) { val vehID = positionUpdate.vehicle // Check if vehicle exists in our tracking dictionary if (vehID !in updatesByVehDict.keys) { return } val currentUpdate = updatesByVehDict[vehID] ?: run { Log.e(DEBUG_TAG, "Have to run animation for veh $vehID but not in the dict") return } // Cancel any current animation for this vehicle animatorsByVeh[vehID]?.cancel() val posUp = currentUpdate.posUpdate val currentPos = LatLng(posUp.latitude, posUp.longitude) val newPos = LatLng(positionUpdate.latitude, positionUpdate.longitude) // Create animator for smooth transition val valueAnimator = ValueAnimator.ofObject( MapLibreUtils.LatLngEvaluator(), currentPos, newPos ) valueAnimator.addUpdateListener { animation -> val latLng = animation.animatedValue as LatLng // Update position during animation updatesByVehDict[vehID]?.let { update -> update.posUpdate.latitude = latLng.latitude update.posUpdate.longitude = latLng.longitude updatePositionsIcons(false) } ?: run { Log.w(DEBUG_TAG, "The bus position to animate has been removed, but the animator is still running!") } } // Set the new position as current but keep old coordinates for animation start positionUpdate.latitude = posUp.latitude positionUpdate.longitude = posUp.longitude updatesByVehDict[vehID]!!.posUpdate = positionUpdate // Configure and start animation valueAnimator.duration = 300 valueAnimator.interpolator = LinearInterpolator() valueAnimator.start() // Store animator for potential cancellation animatorsByVeh[vehID] = valueAnimator } /// STOP OPENING abstract fun showOpenStopWithSymbolLayer(): Boolean /** * Update the bottom sheet with the stop information */ protected fun openStopInBottomSheet(stop: Stop){ bottomLayout?.let { //lay.findViewById(R.id.stopTitleTextView).text ="${stop.ID} - ${stop.stopDefaultName}" val stopName = stop.stopUserName ?: stop.stopDefaultName stopTitleTextView.text = stopName//stop.stopDefaultName stopNumberTextView.text = getString(R.string.stop_fill,stop.ID) stopTitleTextView.visibility = View.VISIBLE val string_show = if (stop.numRoutesStopping==0) "" - else requireContext().getString(R.string.lines_fill, stop.routesThatStopHereToString()) + else stop.routesThatStopHereToString() //requireContext().getString(R.string.lines_fill, stop.routesThatStopHereToString()) linesPassingTextView.text = string_show linesPassingTextView.visibility = View.VISIBLE + linesPassingTextView.gravity = Gravity.TOP + linesBottomTextView.visibility =View.VISIBLE //SET ON CLICK LISTENER arrivalsCard.setOnClickListener{ fragmentListener?.requestArrivalsForStopID(stop.ID) } arrivalsCard.visibility = View.VISIBLE directionsCard.visibility = View.VISIBLE directionsCard.setOnClickListener { ViewUtils.openStopInOutsideApp(stop, context) } context?.let { - val colorIcon = ViewUtils.getColorFromTheme(it, android.R.attr.colorAccent)//ResourcesCompat.getColor(resources,R.attr.colorAccent,activity?.theme) + val colorIcon = ViewUtils.getColorFromTheme(it, R.attr.colorAccent)//ResourcesCompat.getColor(resources,R.attr.colorAccent,activity?.theme) ViewCompat.setBackgroundTintList(directionsCard, ColorStateList.valueOf(colorIcon)) } bottomrightImage.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.navigation_right, activity?.theme)) vehicleIcon.visibility = View.GONE } //add stop marker if (stop.latitude!=null && stop.longitude!=null) { Log.d(DEBUG_TAG, "Showing stop: ${stop.ID}") if (showOpenStopWithSymbolLayer()) { stopActiveSymbol = symbolManager?.create( SymbolOptions() .withLatLng(LatLng(stop.latitude!!, stop.longitude!!)) .withIconImage(STOP_ACTIVE_IMG) .withIconAnchor(ICON_ANCHOR_CENTER) ) } else { val list = ArrayList() list.add(stopToGeoJsonFeature(stop)) selectedStopSource.setGeoJson( FeatureCollection.fromFeatures(list) ) } } Log.d(DEBUG_TAG, "Shown stop $stop in bottom sheet") shownStopInBottomSheet = stop bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED } protected fun stopAnimations(){ for(anim in animatorsByVeh.values){ anim.cancel() } } protected fun addImagesStyle(style: Style){ style.addImage( STOP_IMAGE_ID, ResourcesCompat.getDrawable(resources,R.drawable.bus_stop_new, activity?.theme)!!) style.addImage(STOP_ACTIVE_IMG, ResourcesCompat.getDrawable(resources, R.drawable.bus_stop_new_highlight, activity?.theme)!!) style.addImage("ball",ResourcesCompat.getDrawable(resources, R.drawable.ball, activity?.theme)!!) style.addImage(BUS_IMAGE_ID,ResourcesCompat.getDrawable(resources, R.drawable.map_bus_position_icon, activity?.theme)!!) style.addImage(BUS_SEL_IMAGE_ID, ResourcesCompat.getDrawable(resources, R.drawable.map_bus_position_icon_sel, activity?.theme)!!) val polyIconArrow = ResourcesCompat.getDrawable(resources, R.drawable.arrow_up_box_fill, activity?.theme)!! style.addImage(POLY_ARROW, polyIconArrow) } protected fun initStopsLayer(style: Style, stopsFeatures: FeatureCollection?){ //determine default layer - var layerAbove = "" - if (lastMapStyle == MapLibreUtils.STYLE_OSM_RASTER){ - layerAbove = "osm-raster" - } else if (lastMapStyle == MapLibreUtils.STYLE_VECTOR){ - layerAbove = "symbol-transit-airfield" + + val layerAbove = if (lastMapStyle == MapLibreUtils.STYLE_OSM_RASTER){ + "osm-raster" + } else// if (lastMapStyle == MapLibreUtils.STYLE_VERSATILES_ECLIPSE_JSON){ + "symbol-transit-airfield" + /*} else { + // + "poi_park" } + + */ initStopsLayer(style, stopsFeatures, layerAbove) } protected fun initStopsLayer(style: Style, stopsFeatures: FeatureCollection?, stopsLayerAbove: String){ stopsSource = GeoJsonSource(STOPS_SOURCE_ID,stopsFeatures ?: FeatureCollection.fromFeatures(ArrayList())) style.addSource(stopsSource) // Stops layer val stopsLayer = SymbolLayer(STOPS_LAYER_ID, STOPS_SOURCE_ID) stopsLayer.withProperties( PropertyFactory.iconImage(STOP_IMAGE_ID), PropertyFactory.iconAnchor(ICON_ANCHOR_CENTER), PropertyFactory.iconAllowOverlap(true), PropertyFactory.iconIgnorePlacement(true) ) style.addLayerAbove(stopsLayer, stopsLayerAbove ) //"label_country_1") this with OSM Bright selectedStopSource = GeoJsonSource(SEL_STOP_SOURCE, FeatureCollection.fromFeatures(ArrayList())) style.addSource(selectedStopSource) val selStopLayer = SymbolLayer(SEL_STOP_LAYER, SEL_STOP_SOURCE) selStopLayer.withProperties( PropertyFactory.iconImage(STOP_ACTIVE_IMG), PropertyFactory.iconAllowOverlap(true), PropertyFactory.iconIgnorePlacement(true), PropertyFactory.iconAnchor(ICON_ANCHOR_CENTER), ) style.addLayerAbove(selStopLayer, STOPS_LAYER_ID) stopsLayerStarted = true } /** * Setup the Map Layers */ protected fun setupBusLayer(style: Style, withLabels: Boolean =false, busIconsScale: Float = 1.0f) { // Buses source busesSource = GeoJsonSource(BUSES_SOURCE_ID) style.addSource(busesSource) //style.addImage("bus_symbol",ResourcesCompat.getDrawable(resources, R.drawable.map_bus_position_icon, activity?.theme)!!) selectedBusSource = GeoJsonSource(SEL_BUS_SOURCE) style.addSource(selectedBusSource) // Buses layer val busesLayer = SymbolLayer(BUSES_LAYER_ID, BUSES_SOURCE_ID).apply { withProperties( PropertyFactory.iconImage(BUS_IMAGE_ID), PropertyFactory.iconSize(busIconsScale), PropertyFactory.iconAllowOverlap(true), PropertyFactory.iconIgnorePlacement(true), PropertyFactory.iconRotate(Expression.get("bearing")), PropertyFactory.iconRotationAlignment(ICON_ROTATION_ALIGNMENT_MAP) ) if (withLabels){ withProperties(PropertyFactory.textAnchor(TEXT_ANCHOR_CENTER), PropertyFactory.textAllowOverlap(true), PropertyFactory.textField(Expression.get("line")), PropertyFactory.textColor(Color.WHITE), PropertyFactory.textRotationAlignment(TEXT_ROTATION_ALIGNMENT_VIEWPORT), PropertyFactory.textSize(12f), PropertyFactory.textFont(arrayOf("noto_sans_regular"))) } } style.addLayerAbove(busesLayer, STOPS_LAYER_ID) val selectedBusLayer = SymbolLayer(SEL_BUS_LAYER, SEL_BUS_SOURCE).apply { withProperties( PropertyFactory.iconImage(BUS_SEL_IMAGE_ID), PropertyFactory.iconSize(busIconsScale), PropertyFactory.iconAllowOverlap(true), PropertyFactory.iconIgnorePlacement(true), PropertyFactory.iconRotate(Expression.get("bearing")), PropertyFactory.iconRotationAlignment(ICON_ROTATION_ALIGNMENT_MAP) ) if (withLabels){ withProperties(PropertyFactory.textAnchor(TEXT_ANCHOR_CENTER), PropertyFactory.textAllowOverlap(true), PropertyFactory.textField(Expression.get("line")), PropertyFactory.textColor(Color.WHITE), PropertyFactory.textRotationAlignment(TEXT_ROTATION_ALIGNMENT_VIEWPORT), PropertyFactory.textSize(12f), PropertyFactory.textFont(arrayOf("noto_sans_regular"))) } } style.addLayerAbove(selectedBusLayer, BUSES_LAYER_ID) busLayerStarted = true } /** * Method used for enabling / disabling the location from the buttons */ protected fun switchUserLocationStatus(view: View?){ val enabled = if(locationInitialized) locationComponent.isLocationComponentEnabled else false val context = context ?: return if(enabled) { if(!receivedFirstLocation){ //use case: the user has decided to disable the location before the first position arrived stopInitialPositionRequest() } // we have to disable it setMapLocationEnabled(false) } else if(deviceHasLocationProvider()) { if(Permissions.bothLocationPermissionsGranted(context)){ if(!locationEnabledOnDevice){ warnLocationNotEnabledOnDevice() } else{ setMapLocationEnabled(true) } } else{ Log.d(DEBUG_TAG, "Requesting permissions to show location") Permissions.getInstance(context).checkRequestLocationPermissions(requireActivity(), positionRequestResponder) } } else{ context.let { Toast.makeText(it, R.string.no_gps_on_device, Toast.LENGTH_SHORT).show() } //adjust ui setLocationIconEnabled(false) } } /** * Set the map location component enabled */ @SuppressLint("MissingPermission") protected fun setMapLocationEnabled(enabled: Boolean){ Log.d(DEBUG_TAG, "Setting map location enabled: $enabled") map?.locationComponent?.isLocationComponentEnabled = enabled //map?.cameraPosition = mapStateViewModel.locationUserActive.value = enabled onMapLocationEnabled(enabled) } /** * Function to run at the first time the fragment is opened * Check if we have the permissions, and then initialize the map location component * If we don't have it, request the permission */ protected fun checkInitMapLocation(mapReady: MapLibreMap,style: Style, context: Context) { //enable location val hasGps = deviceHasLocationProvider() val permissions = Permissions.getInstance(context) if(hasGps) { if (Permissions.bothLocationPermissionsGranted(context)) { Log.d(DEBUG_TAG, "Have got the location permission, init location component") initializeMapLocationComponent(mapReady, context, style) }else { var req = false activity?.let{ req = permissions.checkRequestLocationPermissions(it, positionRequestResponder) } if(!req) { setMapLocationEnabled(false) } } } } /** * Set the UI elements showing that the user location is disabled */ abstract fun onMapLocationEnabled(active: Boolean) /** * Helper function to actually set the icon */ abstract fun setLocationIconEnabled(enabled: Boolean) /** * Called when we receive the first fix on the user location */ abstract fun onFirstReceivedLocation(location: Location) protected fun isBottomSheetShowing(): Boolean { return bottomSheetBehavior.state == BottomSheetBehavior.STATE_EXPANDED } protected fun deviceHasLocationProvider(): Boolean{ val locManager = requireContext().getSystemService(LOCATION_SERVICE) as LocationManager return locManager.allProviders.isNotEmpty() } /** * Update automatically the icon when the live position service changes status */ protected fun observeStatusLivePositions(){ livePositionsViewModel.serviceStatus.observe(viewLifecycleOwner){ status -> //if service is active, update the bus positions icon when(status) { LivePositionsServiceStatus.OK -> setBusPositionsIcon(true, error = false) LivePositionsServiceStatus.NO_POSITIONS -> setBusPositionsIcon(true, error = true) else -> setBusPositionsIcon( true, error = true) } } } /** * Clear all buses from the map */ protected fun clearAllBusPositionsInMap(){ for ((k, anim) in animatorsByVeh){ anim.cancel() } animatorsByVeh.clear() updatesByVehDict.clear() updatePositionsIcons(forced = false) } protected fun setCameraPosition(latitude: Double, longitude: Double, zoom: Double) { map?.cameraPosition = CameraPosition.Builder() .target(LatLng(latitude, longitude)) .zoom(zoom) .build() } protected fun showToastLocation(enabled: Boolean){ val textid = if (enabled) R.string.location_enabled else R.string.location_disabled context?.let{ Toast.makeText(it,textid,Toast.LENGTH_SHORT).show() } } companion object{ private const val DEBUG_TAG="GeneralMapLibreFragment" const val BUSES_SOURCE_ID = "buses-source" const val BUSES_LAYER_ID = "buses-layer" const val SEL_STOP_SOURCE="selected-stop-source" const val SEL_STOP_LAYER = "selected-stop-layer" const val SEL_BUS_SOURCE = "sel_bus_source" const val SEL_BUS_LAYER = "sel_bus_layer" const val KEY_LOCATION_ENABLED="location_enabled" protected const val STOPS_SOURCE_ID = "stops-source" protected const val STOPS_LAYER_ID = "stops-layer" protected const val STOP_IMAGE_ID = "stop-img" protected const val STOP_ACTIVE_IMG = "stop_active_img" protected const val BUS_IMAGE_ID = "bus_symbol" protected const val BUS_SEL_IMAGE_ID = "sel_bus_symbol" protected const val POLYLINE_LAYER = "polyline-layer" protected const val POLYLINE_SOURCE = "polyline-source" protected const val POLY_ARROWS_LAYER = "arrows-layer" protected const val POLY_ARROWS_SOURCE = "arrows-source" protected const val POLY_ARROW ="poly-arrow-img" private const val PERM_LOC_COARSE = Manifest.permission.ACCESS_COARSE_LOCATION private const val PERM_LOC_FINE = Manifest.permission.ACCESS_FINE_LOCATION //TODO: this is hardcoded, make it modifiable by the user protected const val MAX_DIST_KM = 90.0 } } \ No newline at end of file diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/LivePositionsDialogFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/LivePositionsDialogFragment.kt index db43348..978749a 100644 --- a/app/src/main/java/it/reyboz/bustorino/fragments/LivePositionsDialogFragment.kt +++ b/app/src/main/java/it/reyboz/bustorino/fragments/LivePositionsDialogFragment.kt @@ -1,104 +1,104 @@ /* BusTO - Fragments components Copyright (C) 2025 Fabio Mazza This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package it.reyboz.bustorino.fragments import it.reyboz.bustorino.R import android.app.Dialog import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Button import android.widget.ImageButton import android.widget.TextView import androidx.appcompat.app.AlertDialog import androidx.fragment.app.DialogFragment import androidx.fragment.app.activityViewModels import it.reyboz.bustorino.backend.LivePositionsServiceStatus import it.reyboz.bustorino.viewmodels.LivePositionsViewModel class LivePositionsDialogFragment : DialogFragment() { private val viewModel: LivePositionsViewModel by activityViewModels() private lateinit var providerNameTextView: TextView private lateinit var statusMessageTextView: TextView override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { //return super.onCreateDialog(savedInstanceState) val builder = AlertDialog.Builder(requireContext()) val view = layoutInflater.inflate(R.layout.fragment_dialog_buspositions, null) providerNameTextView = view.findViewById(R.id.providerNameTextView) statusMessageTextView = view.findViewById(R.id.statusMessageTextView) val btnSwitch = view.findViewById