Page MenuHomeGitPull.it

D184.1777330736.diff
No OneTemporary

Size
705 KB
Referenced Files
None
Subscribers
None

D184.1777330736.diff

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/app/build.gradle b/app/build.gradle
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -98,7 +98,13 @@
implementation 'com.readystatesoftware.sqliteasset:sqliteassethelper:2.0.1'
implementation 'com.android.volley:volley:1.2.1'
- implementation 'org.osmdroid:osmdroid-android:6.1.10'
+ implementation 'org.osmdroid:osmdroid-android:6.1.18'
+ //maplibre
+ implementation 'org.maplibre.gl:android-sdk:11.8.6'
+ implementation 'org.maplibre.gl:android-sdk-turf:6.0.1'
+
+ implementation 'org.maplibre.gl:android-plugin-annotation-v9:3.0.2'
+
// remember to enable maven repo jitpack.io when wanting to use osmbonuspack
//implementation 'com.github.MKergall:osmbonuspack:6.9.0'
// ACRA
diff --git a/app/src/main/assets/map_style_good.json b/app/src/main/assets/map_style_good.json
new file mode 100644
--- /dev/null
+++ b/app/src/main/assets/map_style_good.json
@@ -0,0 +1,3147 @@
+{
+ "version": 8,
+ "metadata": {"maputnik:renderer": "mlgljs"},
+ "sources": {
+ "ne2_shaded": {
+ "maxzoom": 6,
+ "tileSize": 256,
+ "tiles": [
+ "https://tiles.openfreemap.org/natural_earth/ne2sr/{z}/{x}/{y}.png"
+ ],
+ "type": "raster"
+ },
+ "openmaptiles": {
+ "type": "vector",
+ "url": "https://tiles.openfreemap.org/planet"
+ }
+ },
+ "sprite": "https://tiles.openfreemap.org/sprites/ofm_f384/ofm",
+ "glyphs": "https://tiles.openfreemap.org/fonts/{fontstack}/{range}.pbf",
+ "layers": [
+ {
+ "id": "background",
+ "type": "background",
+ "paint": {"background-color": "#f8f4f0"}
+ },
+ {
+ "id": "landcover-glacier",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landcover",
+ "filter": ["==", ["get", "subclass"], "glacier"],
+ "paint": {
+ "fill-color": "#fff",
+ "fill-opacity": ["interpolate", ["linear"], ["zoom"], 0, 0.9, 10, 0.3]
+ }
+ },
+ {
+ "id": "landuse-residential",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landuse",
+ "filter": [
+ "match",
+ ["get", "class"],
+ ["neighbourhood", "residential"],
+ true,
+ false
+ ],
+ "paint": {
+ "fill-color": [
+ "interpolate",
+ ["linear"],
+ ["zoom"],
+ 12,
+ "hsla(30,19%,90%,0.4)",
+ 16,
+ "hsla(30,19%,90%,0.2)"
+ ]
+ }
+ },
+ {
+ "id": "landuse-suburb",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landuse",
+ "maxzoom": 10,
+ "filter": ["==", ["get", "class"], "suburb"],
+ "paint": {
+ "fill-color": [
+ "interpolate",
+ ["linear"],
+ ["zoom"],
+ 8,
+ "hsla(30,19%,90%,0.4)",
+ 10,
+ "hsla(30,19%,90%,0.0)"
+ ]
+ }
+ },
+ {
+ "id": "landuse-commercial",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landuse",
+ "filter": [
+ "all",
+ ["match", ["geometry-type"], ["MultiPolygon", "Polygon"], true, false],
+ ["==", ["get", "class"], "commercial"]
+ ],
+ "paint": {"fill-color": "hsla(0,60%,87%,0.23)"}
+ },
+ {
+ "id": "landuse-industrial",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landuse",
+ "filter": [
+ "all",
+ ["match", ["geometry-type"], ["MultiPolygon", "Polygon"], true, false],
+ [
+ "match",
+ ["get", "class"],
+ ["dam", "garages", "industrial"],
+ true,
+ false
+ ]
+ ],
+ "paint": {"fill-color": "hsla(49,100%,88%,0.34)"}
+ },
+ {
+ "id": "landuse-cemetery",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landuse",
+ "filter": ["==", ["get", "class"], "cemetery"],
+ "paint": {"fill-color": "#e0e4dd"}
+ },
+ {
+ "id": "landuse-hospital",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landuse",
+ "filter": ["==", ["get", "class"], "hospital"],
+ "paint": {"fill-color": "#fde"}
+ },
+ {
+ "id": "landuse-school",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landuse",
+ "filter": ["==", ["get", "class"], "school"],
+ "paint": {"fill-color": "#f0e8f8"}
+ },
+ {
+ "id": "landuse-railway",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landuse",
+ "filter": ["==", ["get", "class"], "railway"],
+ "paint": {"fill-color": "hsla(30,19%,90%,0.4)"}
+ },
+ {
+ "id": "park",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "park",
+ "filter": [
+ "match",
+ ["geometry-type"],
+ ["MultiPolygon", "Polygon"],
+ true,
+ false
+ ],
+ "paint": {
+ "fill-color": "#d8e8c8",
+ "fill-opacity": [
+ "interpolate",
+ ["exponential", 1.8],
+ ["zoom"],
+ 9,
+ 0.5,
+ 12,
+ 0.2
+ ]
+ }
+ },
+ {
+ "id": "landcover-wood",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landcover",
+ "filter": ["==", ["get", "class"], "wood"],
+ "paint": {
+ "fill-antialias": ["step", ["zoom"], false, 9, true],
+ "fill-color": "#6a4",
+ "fill-opacity": 0.1,
+ "fill-outline-color": "hsla(0,0%,0%,0.03)"
+ }
+ },
+ {
+ "id": "landcover-grass",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landcover",
+ "filter": ["==", ["get", "class"], "grass"],
+ "paint": {"fill-color": "#d8e8c8", "fill-opacity": 1}
+ },
+ {
+ "id": "landcover-grass-park",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "park",
+ "filter": ["==", ["get", "class"], "public_park"],
+ "paint": {"fill-color": "#d8e8c8", "fill-opacity": 0.8}
+ },
+ {
+ "id": "waterway_tunnel",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "waterway",
+ "minzoom": 14,
+ "filter": [
+ "all",
+ ["match", ["get", "class"], ["canal", "river", "stream"], true, false],
+ ["==", ["get", "brunnel"], "tunnel"]
+ ],
+ "layout": {"line-cap": "round"},
+ "paint": {
+ "line-color": "#a0c8f0",
+ "line-dasharray": [2, 4],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.3],
+ ["zoom"],
+ 13,
+ 0.5,
+ 20,
+ 6
+ ]
+ }
+ },
+ {
+ "id": "waterway-other",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "waterway",
+ "filter": [
+ "all",
+ ["match", ["get", "class"], ["canal", "river", "stream"], false, true],
+ ["==", ["get", "intermittent"], 0]
+ ],
+ "layout": {"line-cap": "round"},
+ "paint": {
+ "line-color": "#a0c8f0",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.3],
+ ["zoom"],
+ 13,
+ 0.5,
+ 20,
+ 2
+ ]
+ }
+ },
+ {
+ "id": "waterway-other-intermittent",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "waterway",
+ "filter": [
+ "all",
+ ["match", ["get", "class"], ["canal", "river", "stream"], false, true],
+ ["==", ["get", "intermittent"], 1]
+ ],
+ "layout": {"line-cap": "round"},
+ "paint": {
+ "line-color": "#a0c8f0",
+ "line-dasharray": [4, 3],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.3],
+ ["zoom"],
+ 13,
+ 0.5,
+ 20,
+ 2
+ ]
+ }
+ },
+ {
+ "id": "waterway-stream-canal",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "waterway",
+ "filter": [
+ "all",
+ ["match", ["get", "class"], ["canal", "stream"], true, false],
+ ["!=", ["get", "brunnel"], "tunnel"],
+ ["==", ["get", "intermittent"], 0]
+ ],
+ "layout": {"line-cap": "round"},
+ "paint": {
+ "line-color": "#a0c8f0",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.3],
+ ["zoom"],
+ 13,
+ 0.5,
+ 20,
+ 6
+ ]
+ }
+ },
+ {
+ "id": "waterway-stream-canal-intermittent",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "waterway",
+ "filter": [
+ "all",
+ ["match", ["get", "class"], ["canal", "stream"], true, false],
+ ["!=", ["get", "brunnel"], "tunnel"],
+ ["==", ["get", "intermittent"], 1]
+ ],
+ "layout": {"line-cap": "round"},
+ "paint": {
+ "line-color": "#a0c8f0",
+ "line-dasharray": [4, 3],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.3],
+ ["zoom"],
+ 13,
+ 0.5,
+ 20,
+ 6
+ ]
+ }
+ },
+ {
+ "id": "waterway-river",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "waterway",
+ "filter": [
+ "all",
+ ["==", ["get", "class"], "river"],
+ ["!=", ["get", "brunnel"], "tunnel"],
+ ["!=", ["get", "intermittent"], 1]
+ ],
+ "layout": {"line-cap": "round"},
+ "paint": {
+ "line-color": "#a0c8f0",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 10,
+ 0.8,
+ 20,
+ 6
+ ]
+ }
+ },
+ {
+ "id": "waterway-river-intermittent",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "waterway",
+ "filter": [
+ "all",
+ ["==", ["get", "class"], "river"],
+ ["!=", ["get", "brunnel"], "tunnel"],
+ ["==", ["get", "intermittent"], 1]
+ ],
+ "layout": {"line-cap": "round"},
+ "paint": {
+ "line-color": "#a0c8f0",
+ "line-dasharray": [3, 2.5],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 10,
+ 0.8,
+ 20,
+ 6
+ ]
+ }
+ },
+ {
+ "id": "water",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "water",
+ "filter": [
+ "all",
+ ["!=", ["get", "intermittent"], 1],
+ ["!=", ["get", "brunnel"], "tunnel"]
+ ],
+ "paint": {"fill-color": "#AECFE2"}
+ },
+ {
+ "id": "water-intermittent",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "water",
+ "filter": ["==", ["get", "intermittent"], 1],
+ "paint": {"fill-color": "hsl(210,67%,85%)", "fill-opacity": 0.7}
+ },
+ {
+ "id": "landcover-ice-shelf",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landcover",
+ "filter": ["==", ["get", "subclass"], "ice_shelf"],
+ "paint": {
+ "fill-color": "#fff",
+ "fill-opacity": ["interpolate", ["linear"], ["zoom"], 0, 0.9, 10, 0.3]
+ }
+ },
+ {
+ "id": "landcover-sand",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landcover",
+ "filter": ["==", ["get", "class"], "sand"],
+ "paint": {"fill-color": "rgba(245, 238, 188, 1)", "fill-opacity": 1}
+ },
+ {
+ "id": "building",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "building",
+ "paint": {
+ "fill-antialias": true,
+ "fill-color": [
+ "interpolate",
+ ["linear"],
+ ["zoom"],
+ 15.5,
+ "#f2eae2",
+ 16,
+ "#dfdbd7"
+ ]
+ }
+ },
+ {
+ "id": "building-top",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "building",
+ "paint": {
+ "fill-color": "#f2eae2",
+ "fill-opacity": ["interpolate", ["linear"], ["zoom"], 13, 0, 16, 1],
+ "fill-outline-color": "#dfdbd7",
+ "fill-translate": [
+ "interpolate",
+ ["linear"],
+ ["zoom"],
+ 14,
+ ["literal", [0, 0]],
+ 16,
+ ["literal", [-2, -2]]
+ ]
+ }
+ },
+ {
+ "id": "tunnel-service-track-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "tunnel"],
+ ["match", ["get", "class"], ["service", "track"], true, false]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#cfcdca",
+ "line-dasharray": [0.5, 0.25],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 15,
+ 1,
+ 16,
+ 4,
+ 20,
+ 11
+ ]
+ }
+ },
+ {
+ "id": "tunnel-motorway-link-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "tunnel"],
+ ["==", ["get", "class"], "motorway"],
+ ["==", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "rgba(200, 147, 102, 1)",
+ "line-dasharray": [0.5, 0.25],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 12,
+ 1,
+ 13,
+ 3,
+ 14,
+ 4,
+ 20,
+ 15
+ ]
+ }
+ },
+ {
+ "id": "tunnel-minor-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "tunnel"],
+ ["==", ["get", "class"], "minor"]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#cfcdca",
+ "line-dasharray": [0.5, 0.25],
+ "line-opacity": ["interpolate", ["linear"], ["zoom"], 12, 0, 12.5, 1],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 12,
+ 0.5,
+ 13,
+ 1,
+ 14,
+ 4,
+ 20,
+ 15
+ ]
+ }
+ },
+ {
+ "id": "tunnel-link-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "tunnel"],
+ [
+ "match",
+ ["get", "class"],
+ ["primary", "secondary", "tertiary", "trunk"],
+ true,
+ false
+ ],
+ ["==", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-dasharray": [0.5, 0.25],
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 12,
+ 1,
+ 13,
+ 3,
+ 14,
+ 4,
+ 20,
+ 15
+ ]
+ }
+ },
+ {
+ "id": "tunnel-secondary-tertiary-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "tunnel"],
+ ["match", ["get", "class"], ["secondary", "tertiary"], true, false],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-dasharray": [0.5, 0.25],
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 8,
+ 1.5,
+ 20,
+ 17
+ ]
+ }
+ },
+ {
+ "id": "tunnel-trunk-primary-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "tunnel"],
+ ["match", ["get", "class"], ["primary", "trunk"], true, false],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 5,
+ 0.4,
+ 6,
+ 0.6,
+ 7,
+ 1.5,
+ 20,
+ 22
+ ]
+ }
+ },
+ {
+ "id": "tunnel-motorway-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "tunnel"],
+ ["==", ["get", "class"], "motorway"],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-dasharray": [0.5, 0.25],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 5,
+ 0.4,
+ 6,
+ 0.6,
+ 7,
+ 1.5,
+ 20,
+ 22
+ ]
+ }
+ },
+ {
+ "id": "tunnel-path",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["==", ["get", "brunnel"], "tunnel"],
+ ["==", ["get", "class"], "path"]
+ ],
+ "paint": {
+ "line-color": "#cba",
+ "line-dasharray": [1.5, 0.75],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 15,
+ 1.2,
+ 20,
+ 4
+ ]
+ }
+ },
+ {
+ "id": "tunnel-motorway-link",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "tunnel"],
+ ["==", ["get", "class"], "motorway"],
+ ["==", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "rgba(244, 209, 158, 1)",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 12.5,
+ 0,
+ 13,
+ 1.5,
+ 14,
+ 2.5,
+ 20,
+ 11.5
+ ]
+ }
+ },
+ {
+ "id": "tunnel-service-track",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "tunnel"],
+ ["match", ["get", "class"], ["service", "track"], true, false]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#fff",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 15.5,
+ 0,
+ 16,
+ 2,
+ 20,
+ 7.5
+ ]
+ }
+ },
+ {
+ "id": "tunnel-link",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "tunnel"],
+ [
+ "match",
+ ["get", "class"],
+ ["primary", "secondary", "tertiary", "trunk"],
+ true,
+ false
+ ],
+ ["==", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#fff4c6",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 12.5,
+ 0,
+ 13,
+ 1.5,
+ 14,
+ 2.5,
+ 20,
+ 11.5
+ ]
+ }
+ },
+ {
+ "id": "tunnel-minor",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "tunnel"],
+ ["==", ["get", "class"], "minor"]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#fff",
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 13.5,
+ 0,
+ 14,
+ 2.5,
+ 20,
+ 11.5
+ ]
+ }
+ },
+ {
+ "id": "tunnel-secondary-tertiary",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "tunnel"],
+ ["match", ["get", "class"], ["secondary", "tertiary"], true, false],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#fff4c6",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 6.5,
+ 0,
+ 7,
+ 0.5,
+ 20,
+ 10
+ ]
+ }
+ },
+ {
+ "id": "tunnel-trunk-primary",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "tunnel"],
+ ["match", ["get", "class"], ["primary", "trunk"], true, false],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#fff4c6",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 6.5,
+ 0,
+ 7,
+ 0.5,
+ 20,
+ 18
+ ]
+ }
+ },
+ {
+ "id": "tunnel-motorway",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "tunnel"],
+ ["==", ["get", "class"], "motorway"],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#ffdaa6",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 6.5,
+ 0,
+ 7,
+ 0.5,
+ 20,
+ 18
+ ]
+ }
+ },
+ {
+ "id": "tunnel-railway",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "tunnel"],
+ ["==", ["get", "class"], "rail"]
+ ],
+ "paint": {
+ "line-color": "#bbb",
+ "line-dasharray": [2, 2],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.4],
+ ["zoom"],
+ 14,
+ 0.4,
+ 15,
+ 0.75,
+ 20,
+ 2
+ ]
+ }
+ },
+ {
+ "id": "ferry",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": ["match", ["get", "class"], ["ferry"], true, false],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "rgba(108, 159, 182, 1)",
+ "line-dasharray": [2, 2],
+ "line-width": 1.1
+ }
+ },
+ {
+ "id": "aeroway-taxiway-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "aeroway",
+ "minzoom": 12,
+ "filter": ["match", ["get", "class"], ["taxiway"], true, false],
+ "layout": {"line-cap": "round", "line-join": "round"},
+ "paint": {
+ "line-color": "rgba(153, 153, 153, 1)",
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.5],
+ ["zoom"],
+ 11,
+ 2,
+ 17,
+ 12
+ ]
+ }
+ },
+ {
+ "id": "aeroway-runway-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "aeroway",
+ "minzoom": 12,
+ "filter": ["match", ["get", "class"], ["runway"], true, false],
+ "layout": {"line-cap": "round", "line-join": "round"},
+ "paint": {
+ "line-color": "rgba(153, 153, 153, 1)",
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.5],
+ ["zoom"],
+ 11,
+ 5,
+ 17,
+ 55
+ ]
+ }
+ },
+ {
+ "id": "aeroway-area",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "aeroway",
+ "minzoom": 4,
+ "filter": [
+ "all",
+ ["match", ["geometry-type"], ["MultiPolygon", "Polygon"], true, false],
+ ["match", ["get", "class"], ["runway", "taxiway"], true, false]
+ ],
+ "paint": {
+ "fill-color": "rgba(255, 255, 255, 1)",
+ "fill-opacity": ["interpolate", ["linear"], ["zoom"], 13, 0, 14, 1]
+ }
+ },
+ {
+ "id": "aeroway-taxiway",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "aeroway",
+ "minzoom": 4,
+ "filter": [
+ "all",
+ ["match", ["get", "class"], ["taxiway"], true, false],
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ]
+ ],
+ "layout": {"line-cap": "round", "line-join": "round"},
+ "paint": {
+ "line-color": "rgba(255, 255, 255, 1)",
+ "line-opacity": ["interpolate", ["linear"], ["zoom"], 11, 0, 12, 1],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.5],
+ ["zoom"],
+ 11,
+ 1,
+ 17,
+ 10
+ ]
+ }
+ },
+ {
+ "id": "aeroway-runway",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "aeroway",
+ "minzoom": 4,
+ "filter": [
+ "all",
+ ["match", ["get", "class"], ["runway"], true, false],
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ]
+ ],
+ "layout": {"line-cap": "round", "line-join": "round"},
+ "paint": {
+ "line-color": "rgba(255, 255, 255, 1)",
+ "line-opacity": ["interpolate", ["linear"], ["zoom"], 11, 0, 12, 1],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.5],
+ ["zoom"],
+ 11,
+ 4,
+ 17,
+ 50
+ ]
+ }
+ },
+ {
+ "id": "road_area_pier",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["match", ["geometry-type"], ["MultiPolygon", "Polygon"], true, false],
+ ["==", ["get", "class"], "pier"]
+ ],
+ "paint": {"fill-antialias": true, "fill-color": "#f8f4f0"}
+ },
+ {
+ "id": "road_pier",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["match", ["get", "class"], ["pier"], true, false]
+ ],
+ "layout": {"line-cap": "round", "line-join": "round"},
+ "paint": {
+ "line-color": "#f8f4f0",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 15,
+ 1,
+ 17,
+ 4
+ ]
+ }
+ },
+ {
+ "id": "highway-area",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["match", ["geometry-type"], ["MultiPolygon", "Polygon"], true, false],
+ ["match", ["get", "class"], ["pier"], false, true]
+ ],
+ "paint": {
+ "fill-antialias": false,
+ "fill-color": "hsla(0,0%,89%,0.56)",
+ "fill-opacity": 0.9,
+ "fill-outline-color": "#cfcdca"
+ }
+ },
+ {
+ "id": "highway-motorway-link-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true],
+ ["==", ["get", "class"], "motorway"],
+ ["==", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-cap": "round", "line-join": "round"},
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 12,
+ 1,
+ 13,
+ 3,
+ 14,
+ 4,
+ 20,
+ 15
+ ]
+ }
+ },
+ {
+ "id": "highway-link-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 13,
+ "filter": [
+ "all",
+ ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true],
+ [
+ "match",
+ ["get", "class"],
+ ["primary", "secondary", "tertiary", "trunk"],
+ true,
+ false
+ ],
+ ["==", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-cap": "round", "line-join": "round"},
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 12,
+ 1,
+ 13,
+ 3,
+ 14,
+ 4,
+ 20,
+ 15
+ ]
+ }
+ },
+ {
+ "id": "highway-minor-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["!=", ["get", "brunnel"], "tunnel"],
+ ["match", ["get", "class"], ["minor", "service", "track"], true, false]
+ ],
+ "layout": {"line-cap": "round", "line-join": "round"},
+ "paint": {
+ "line-color": "#cfcdca",
+ "line-opacity": ["interpolate", ["linear"], ["zoom"], 12, 0, 12.5, 1],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 12,
+ 0.5,
+ 13,
+ 1,
+ 14,
+ 4,
+ 20,
+ 15
+ ]
+ }
+ },
+ {
+ "id": "highway-secondary-tertiary-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true],
+ ["match", ["get", "class"], ["secondary", "tertiary"], true, false],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-cap": "butt", "line-join": "round"},
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 8,
+ 1.5,
+ 20,
+ 17
+ ]
+ }
+ },
+ {
+ "id": "highway-primary-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 5,
+ "filter": [
+ "all",
+ ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true],
+ ["match", ["get", "class"], ["primary"], true, false],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-cap": "butt", "line-join": "round"},
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-opacity": ["interpolate", ["linear"], ["zoom"], 7, 0, 8, 1],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 7,
+ 0,
+ 8,
+ 0.6,
+ 9,
+ 1.5,
+ 20,
+ 22
+ ]
+ }
+ },
+ {
+ "id": "highway-trunk-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 5,
+ "filter": [
+ "all",
+ ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true],
+ ["match", ["get", "class"], ["trunk"], true, false],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-cap": "butt", "line-join": "round"},
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-opacity": ["interpolate", ["linear"], ["zoom"], 5, 0, 6, 1],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 5,
+ 0,
+ 6,
+ 0.6,
+ 7,
+ 1.5,
+ 20,
+ 22
+ ]
+ }
+ },
+ {
+ "id": "highway-motorway-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 4,
+ "filter": [
+ "all",
+ ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true],
+ ["==", ["get", "class"], "motorway"],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-cap": "butt", "line-join": "round"},
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-opacity": ["interpolate", ["linear"], ["zoom"], 4, 0, 5, 1],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 4,
+ 0,
+ 5,
+ 0.4,
+ 6,
+ 0.6,
+ 7,
+ 1.5,
+ 20,
+ 22
+ ]
+ }
+ },
+ {
+ "id": "highway-path",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true],
+ ["==", ["get", "class"], "path"]
+ ],
+ "paint": {
+ "line-color": "#cba",
+ "line-dasharray": [1.5, 0.75],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 15,
+ 1.2,
+ 20,
+ 4
+ ]
+ }
+ },
+ {
+ "id": "highway-motorway-link",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 12,
+ "filter": [
+ "all",
+ ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true],
+ ["==", ["get", "class"], "motorway"],
+ ["==", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-cap": "round", "line-join": "round"},
+ "paint": {
+ "line-color": "#fc8",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 12.5,
+ 0,
+ 13,
+ 1.5,
+ 14,
+ 2.5,
+ 20,
+ 11.5
+ ]
+ }
+ },
+ {
+ "id": "highway-link",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 13,
+ "filter": [
+ "all",
+ ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true],
+ [
+ "match",
+ ["get", "class"],
+ ["primary", "secondary", "tertiary", "trunk"],
+ true,
+ false
+ ],
+ ["==", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-cap": "round", "line-join": "round"},
+ "paint": {
+ "line-color": "#fea",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 12.5,
+ 0,
+ 13,
+ 1.5,
+ 14,
+ 2.5,
+ 20,
+ 11.5
+ ]
+ }
+ },
+ {
+ "id": "highway-minor",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["!=", ["get", "brunnel"], "tunnel"],
+ ["match", ["get", "class"], ["minor", "service", "track"], true, false]
+ ],
+ "layout": {"line-cap": "round", "line-join": "round"},
+ "paint": {
+ "line-color": "#fff",
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 13.5,
+ 0,
+ 14,
+ 2.5,
+ 20,
+ 11.5
+ ]
+ }
+ },
+ {
+ "id": "highway-secondary-tertiary",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true],
+ ["match", ["get", "class"], ["secondary", "tertiary"], true, false],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-cap": "round", "line-join": "round"},
+ "paint": {
+ "line-color": "#fea",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 6.5,
+ 0,
+ 8,
+ 0.5,
+ 20,
+ 13
+ ]
+ }
+ },
+ {
+ "id": "highway-primary",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true],
+ ["match", ["get", "class"], ["primary"], true, false],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-cap": "round", "line-join": "round"},
+ "paint": {
+ "line-color": "#fea",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 8.5,
+ 0,
+ 9,
+ 0.5,
+ 20,
+ 18
+ ]
+ }
+ },
+ {
+ "id": "highway-trunk",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true],
+ ["match", ["get", "class"], ["trunk"], true, false],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-cap": "round", "line-join": "round"},
+ "paint": {
+ "line-color": "#fea",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 6.5,
+ 0,
+ 7,
+ 0.5,
+ 20,
+ 18
+ ]
+ }
+ },
+ {
+ "id": "highway-motorway",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 5,
+ "filter": [
+ "all",
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true],
+ ["==", ["get", "class"], "motorway"],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-cap": "round", "line-join": "round"},
+ "paint": {
+ "line-color": "#fc8",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 6.5,
+ 0,
+ 7,
+ 0.5,
+ 20,
+ 18
+ ]
+ }
+ },
+ {
+ "id": "railway-transit",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["==", ["get", "class"], "transit"],
+ ["match", ["get", "brunnel"], ["tunnel"], false, true]
+ ],
+ "paint": {
+ "line-color": "hsla(0,0%,73%,0.77)",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.4],
+ ["zoom"],
+ 14,
+ 0.4,
+ 20,
+ 1
+ ]
+ }
+ },
+ {
+ "id": "railway-transit-hatching",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["==", ["get", "class"], "transit"],
+ ["match", ["get", "brunnel"], ["tunnel"], false, true]
+ ],
+ "paint": {
+ "line-color": "hsla(0,0%,73%,0.68)",
+ "line-dasharray": [0.2, 8],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.4],
+ ["zoom"],
+ 14.5,
+ 0,
+ 15,
+ 2,
+ 20,
+ 6
+ ]
+ }
+ },
+ {
+ "id": "railway-service",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["==", ["get", "class"], "rail"],
+ ["has", "service"]
+ ],
+ "paint": {
+ "line-color": "hsla(0,0%,73%,0.77)",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.4],
+ ["zoom"],
+ 14,
+ 0.4,
+ 20,
+ 1
+ ]
+ }
+ },
+ {
+ "id": "railway-service-hatching",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["==", ["get", "class"], "rail"],
+ ["has", "service"]
+ ],
+ "paint": {
+ "line-color": "hsla(0,0%,73%,0.68)",
+ "line-dasharray": [0.2, 8],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.4],
+ ["zoom"],
+ 14.5,
+ 0,
+ 15,
+ 2,
+ 20,
+ 6
+ ]
+ }
+ },
+ {
+ "id": "railway",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["!", ["has", "service"]],
+ ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true],
+ ["==", ["get", "class"], "rail"]
+ ],
+ "paint": {
+ "line-color": "#bbb",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.4],
+ ["zoom"],
+ 14,
+ 0.4,
+ 15,
+ 0.75,
+ 20,
+ 2
+ ]
+ }
+ },
+ {
+ "id": "railway-hatching",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["!", ["has", "service"]],
+ ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true],
+ ["==", ["get", "class"], "rail"]
+ ],
+ "paint": {
+ "line-color": "#bbb",
+ "line-dasharray": [0.2, 8],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.4],
+ ["zoom"],
+ 14.5,
+ 0,
+ 15,
+ 3,
+ 20,
+ 8
+ ]
+ }
+ },
+ {
+ "id": "bridge-motorway-link-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "bridge"],
+ ["==", ["get", "class"], "motorway"],
+ ["==", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 12,
+ 1,
+ 13,
+ 3,
+ 14,
+ 4,
+ 20,
+ 19
+ ]
+ }
+ },
+ {
+ "id": "bridge-link-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "bridge"],
+ [
+ "match",
+ ["get", "class"],
+ ["primary", "secondary", "tertiary", "trunk"],
+ true,
+ false
+ ],
+ ["==", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 12,
+ 1,
+ 13,
+ 3,
+ 14,
+ 4,
+ 20,
+ 19
+ ]
+ }
+ },
+ {
+ "id": "bridge-secondary-tertiary-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "bridge"],
+ ["match", ["get", "class"], ["secondary", "tertiary"], true, false],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 5,
+ 0.4,
+ 7,
+ 0.6,
+ 8,
+ 1.5,
+ 20,
+ 21
+ ]
+ }
+ },
+ {
+ "id": "bridge-trunk-primary-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "bridge"],
+ ["match", ["get", "class"], ["primary", "trunk"], true, false],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "hsl(28,76%,67%)",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 5,
+ 0.4,
+ 6,
+ 0.6,
+ 7,
+ 1.5,
+ 20,
+ 26
+ ]
+ }
+ },
+ {
+ "id": "bridge-motorway-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "bridge"],
+ ["==", ["get", "class"], "motorway"],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 5,
+ 0.4,
+ 6,
+ 0.6,
+ 7,
+ 1.5,
+ 20,
+ 26
+ ]
+ }
+ },
+ {
+ "id": "bridge-minor-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["==", ["get", "brunnel"], "bridge"],
+ ["match", ["get", "class"], ["minor", "service", "track"], true, false]
+ ],
+ "layout": {"line-cap": "butt", "line-join": "round"},
+ "paint": {
+ "line-color": "#cfcdca",
+ "line-opacity": ["interpolate", ["linear"], ["zoom"], 12, 0, 12.5, 1],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 12,
+ 0.5,
+ 13,
+ 1,
+ 14,
+ 6,
+ 20,
+ 24
+ ]
+ }
+ },
+ {
+ "id": "bridge-path-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["==", ["get", "brunnel"], "bridge"],
+ ["==", ["get", "class"], "path"]
+ ],
+ "paint": {
+ "line-color": "#f8f4f0",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 15,
+ 1.2,
+ 20,
+ 18
+ ]
+ }
+ },
+ {
+ "id": "bridge-path",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["==", ["get", "brunnel"], "bridge"],
+ ["==", ["get", "class"], "path"]
+ ],
+ "paint": {
+ "line-color": "#cba",
+ "line-dasharray": [1.5, 0.75],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 15,
+ 1.2,
+ 20,
+ 4
+ ]
+ }
+ },
+ {
+ "id": "bridge-motorway-link",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "bridge"],
+ ["==", ["get", "class"], "motorway"],
+ ["==", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#fc8",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 12.5,
+ 0,
+ 13,
+ 1.5,
+ 14,
+ 2.5,
+ 20,
+ 11.5
+ ]
+ }
+ },
+ {
+ "id": "bridge-link",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "bridge"],
+ [
+ "match",
+ ["get", "class"],
+ ["primary", "secondary", "tertiary", "trunk"],
+ true,
+ false
+ ],
+ ["==", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#fea",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 12.5,
+ 0,
+ 13,
+ 1.5,
+ 14,
+ 2.5,
+ 20,
+ 11.5
+ ]
+ }
+ },
+ {
+ "id": "bridge-minor",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["==", ["get", "brunnel"], "bridge"],
+ ["match", ["get", "class"], ["minor", "service", "track"], true, false]
+ ],
+ "layout": {"line-cap": "round", "line-join": "round"},
+ "paint": {
+ "line-color": "#fff",
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 13.5,
+ 0,
+ 14,
+ 2.5,
+ 20,
+ 11.5
+ ]
+ }
+ },
+ {
+ "id": "bridge-secondary-tertiary",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "bridge"],
+ ["match", ["get", "class"], ["secondary", "tertiary"], true, false],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#fea",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 6.5,
+ 0,
+ 8,
+ 0.5,
+ 20,
+ 13
+ ]
+ }
+ },
+ {
+ "id": "bridge-trunk-primary",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "bridge"],
+ ["match", ["get", "class"], ["primary", "trunk"], true, false],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#fea",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 6.5,
+ 0,
+ 7,
+ 0.5,
+ 20,
+ 18
+ ]
+ }
+ },
+ {
+ "id": "bridge-motorway",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "bridge"],
+ ["==", ["get", "class"], "motorway"],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#fc8",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 6.5,
+ 0,
+ 7,
+ 0.5,
+ 20,
+ 18
+ ]
+ }
+ },
+ {
+ "id": "bridge-railway",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "bridge"],
+ ["==", ["get", "class"], "rail"]
+ ],
+ "paint": {
+ "line-color": "#bbb",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.4],
+ ["zoom"],
+ 14,
+ 0.4,
+ 15,
+ 0.75,
+ 20,
+ 2
+ ]
+ }
+ },
+ {
+ "id": "bridge-railway-hatching",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "bridge"],
+ ["==", ["get", "class"], "rail"]
+ ],
+ "paint": {
+ "line-color": "#bbb",
+ "line-dasharray": [0.2, 8],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.4],
+ ["zoom"],
+ 14.5,
+ 0,
+ 15,
+ 3,
+ 20,
+ 8
+ ]
+ }
+ },
+ {
+ "id": "cablecar",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 13,
+ "filter": ["==", ["get", "subclass"], "cable_car"],
+ "layout": {"line-cap": "round"},
+ "paint": {
+ "line-color": "hsl(0,0%,70%)",
+ "line-width": ["interpolate", ["linear"], ["zoom"], 11, 1, 19, 2.5]
+ }
+ },
+ {
+ "id": "cablecar-dash",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 13,
+ "filter": ["==", ["get", "subclass"], "cable_car"],
+ "layout": {"line-cap": "round"},
+ "paint": {
+ "line-color": "hsl(0,0%,70%)",
+ "line-dasharray": [2, 3],
+ "line-width": ["interpolate", ["linear"], ["zoom"], 11, 3, 19, 5.5]
+ }
+ },
+ {
+ "id": "boundary_3",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "boundary",
+ "minzoom": 5,
+ "filter": [
+ "all",
+ [">=", ["get", "admin_level"], 3],
+ ["<=", ["get", "admin_level"], 6],
+ ["!=", ["get", "maritime"], 1],
+ ["!=", ["get", "disputed"], 1],
+ ["!", ["has", "claimed_by"]]
+ ],
+ "paint": {
+ "line-color": "hsl(0,0%,70%)",
+ "line-dasharray": [1, 1],
+ "line-width": ["interpolate", ["linear", 1], ["zoom"], 7, 1, 11, 2]
+ }
+ },
+ {
+ "id": "boundary_2",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "boundary",
+ "filter": [
+ "all",
+ ["==", ["get", "admin_level"], 2],
+ ["!=", ["get", "maritime"], 1],
+ ["!=", ["get", "disputed"], 1],
+ ["!", ["has", "claimed_by"]]
+ ],
+ "layout": {"line-cap": "round", "line-join": "round"},
+ "paint": {
+ "line-color": "hsl(248,7%,66%)",
+ "line-opacity": ["interpolate", ["linear"], ["zoom"], 0, 0.4, 4, 1],
+ "line-width": ["interpolate", ["linear"], ["zoom"], 3, 1, 5, 1.2, 12, 3]
+ }
+ },
+ {
+ "id": "boundary_disputed",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "boundary",
+ "filter": [
+ "all",
+ ["!=", ["get", "maritime"], 1],
+ ["==", ["get", "disputed"], 1]
+ ],
+ "paint": {
+ "line-color": "hsl(248,7%,66%)",
+ "line-dasharray": [1, 2],
+ "line-width": ["interpolate", ["linear"], ["zoom"], 3, 1, 5, 1.2, 12, 3]
+ }
+ },
+ {
+ "id": "road_oneway",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 15,
+ "filter": [
+ "all",
+ ["==", ["get", "oneway"], 1],
+ [
+ "match",
+ ["get", "class"],
+ [
+ "minor",
+ "motorway",
+ "primary",
+ "secondary",
+ "service",
+ "tertiary",
+ "trunk"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "icon-image": "oneway",
+ "icon-padding": 2,
+ "icon-rotate": 90,
+ "icon-rotation-alignment": "map",
+ "icon-size": ["interpolate", ["linear"], ["zoom"], 15, 0.5, 19, 1],
+ "symbol-placement": "line",
+ "symbol-spacing": 75
+ },
+ "paint": {"icon-opacity": 0.5}
+ },
+ {
+ "id": "road_oneway_opposite",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 15,
+ "filter": [
+ "all",
+ ["==", ["get", "oneway"], -1],
+ [
+ "match",
+ ["get", "class"],
+ [
+ "minor",
+ "motorway",
+ "primary",
+ "secondary",
+ "service",
+ "tertiary",
+ "trunk"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "icon-image": "oneway",
+ "icon-padding": 2,
+ "icon-rotate": -90,
+ "icon-rotation-alignment": "map",
+ "icon-size": ["interpolate", ["linear"], ["zoom"], 15, 0.5, 19, 1],
+ "symbol-placement": "line",
+ "symbol-spacing": 75
+ },
+ "paint": {"icon-opacity": 0.5}
+ },
+ {
+ "id": "waterway_line_label",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "waterway",
+ "minzoom": 10,
+ "filter": [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ "layout": {
+ "symbol-placement": "line",
+ "symbol-spacing": 350,
+ "text-field": [
+ "case",
+ ["has", "name:nonlatin"],
+ ["concat", ["get", "name:latin"], " ", ["get", "name:nonlatin"]],
+ ["coalesce", ["get", "name_en"], ["get", "name"]]
+ ],
+ "text-font": ["Noto Sans Italic"],
+ "text-letter-spacing": 0.2,
+ "text-max-width": 5,
+ "text-size": 14
+ },
+ "paint": {
+ "text-color": "#74aee9",
+ "text-halo-color": "rgba(255,255,255,0.7)",
+ "text-halo-width": 1.5
+ }
+ },
+ {
+ "id": "water_name_point_label",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "water_name",
+ "filter": [
+ "match",
+ ["geometry-type"],
+ ["MultiPoint", "Point"],
+ true,
+ false
+ ],
+ "layout": {
+ "text-field": [
+ "case",
+ ["has", "name:nonlatin"],
+ ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]],
+ ["coalesce", ["get", "name_en"], ["get", "name"]]
+ ],
+ "text-font": ["Noto Sans Italic"],
+ "text-letter-spacing": 0.2,
+ "text-max-width": 5,
+ "text-size": ["interpolate", ["linear"], ["zoom"], 0, 10, 8, 14]
+ },
+ "paint": {
+ "text-color": "#495e91",
+ "text-halo-color": "rgba(255,255,255,0.7)",
+ "text-halo-width": 1.5
+ }
+ },
+ {
+ "id": "water_name_line_label",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "water_name",
+ "filter": [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ "layout": {
+ "symbol-placement": "line",
+ "symbol-spacing": 350,
+ "text-field": [
+ "case",
+ ["has", "name:nonlatin"],
+ ["concat", ["get", "name:latin"], " ", ["get", "name:nonlatin"]],
+ ["coalesce", ["get", "name_en"], ["get", "name"]]
+ ],
+ "text-font": ["Noto Sans Italic"],
+ "text-letter-spacing": 0.2,
+ "text-max-width": 5,
+ "text-size": 14
+ },
+ "paint": {
+ "text-color": "#495e91",
+ "text-halo-color": "rgba(255,255,255,0.7)",
+ "text-halo-width": 1.5
+ }
+ },
+ {
+ "id": "poi_hos",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "poi",
+ "minzoom": 15,
+ "filter": [
+ "all",
+ ["==", "$type", "Point"],
+ ["has", "name"],
+ ["==", "class", "railway"],
+ ["==", "subclass", "station"]
+ ],
+ "layout": {
+ "icon-image": [
+ "match",
+ ["get", "subclass"],
+ ["florist", "furniture"],
+ ["get", "subclass"],
+ ["get", "class"]
+ ],
+ "text-anchor": "top",
+ "text-field": [
+ "case",
+ ["has", "name:nonlatin"],
+ ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]],
+ ["coalesce", ["get", "name_en"], ["get", "name"]]
+ ],
+ "text-font": ["Noto Sans Italic"],
+ "text-max-width": 9,
+ "text-offset": [0, 0.6],
+ "text-size": 12,
+ "visibility": "none"
+ },
+ "paint": {
+ "text-color": "#666",
+ "text-halo-blur": 0.5,
+ "text-halo-color": "#ffffff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "poi_r1",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "poi",
+ "minzoom": 15,
+ "filter": [
+ "all",
+ ["match", ["geometry-type"], ["MultiPoint", "Point"], true, false],
+ [">=", ["get", "rank"], 1],
+ ["<=", ["get", "rank"], 6],
+ ["!=", ["get", "subclass"], "bus_stop"],
+ ["!=", ["get", "subclass"], "tram_stop"]
+ ],
+ "layout": {
+ "icon-image": [
+ "match",
+ ["get", "subclass"],
+ ["florist", "furniture"],
+ ["get", "subclass"],
+ ["get", "class"]
+ ],
+ "text-anchor": "top",
+ "text-field": [
+ "case",
+ ["has", "name:nonlatin"],
+ ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]],
+ ["coalesce", ["get", "name_en"], ["get", "name"]]
+ ],
+ "text-font": ["Noto Sans Italic"],
+ "text-max-width": 9,
+ "text-offset": [0, 0.6],
+ "text-size": 12,
+ "visibility": "visible"
+ },
+ "paint": {
+ "text-color": "#666",
+ "text-halo-blur": 0.5,
+ "text-halo-color": "#ffffff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "highway-name-path",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "transportation_name",
+ "minzoom": 15.5,
+ "filter": ["==", ["get", "class"], "path"],
+ "layout": {
+ "symbol-placement": "line",
+ "text-field": [
+ "case",
+ ["has", "name:nonlatin"],
+ ["concat", ["get", "name:latin"], " ", ["get", "name:nonlatin"]],
+ ["coalesce", ["get", "name_en"], ["get", "name"]]
+ ],
+ "text-font": ["Noto Sans Regular"],
+ "text-rotation-alignment": "map",
+ "text-size": ["interpolate", ["linear"], ["zoom"], 13, 12, 14, 13]
+ },
+ "paint": {
+ "text-color": "hsl(30,23%,62%)",
+ "text-halo-color": "#f8f4f0",
+ "text-halo-width": 0.5
+ }
+ },
+ {
+ "id": "highway-name-minor",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "transportation_name",
+ "minzoom": 15,
+ "filter": [
+ "all",
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["match", ["get", "class"], ["minor", "service", "track"], true, false]
+ ],
+ "layout": {
+ "symbol-placement": "line",
+ "text-field": [
+ "case",
+ ["has", "name:nonlatin"],
+ ["concat", ["get", "name:latin"], " ", ["get", "name:nonlatin"]],
+ ["coalesce", ["get", "name_en"], ["get", "name"]]
+ ],
+ "text-font": ["Noto Sans Regular"],
+ "text-rotation-alignment": "map",
+ "text-size": ["interpolate", ["linear"], ["zoom"], 13, 12, 14, 13]
+ },
+ "paint": {
+ "text-color": "#666",
+ "text-halo-blur": 0.5,
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "highway-name-major",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "transportation_name",
+ "minzoom": 12.2,
+ "filter": [
+ "match",
+ ["get", "class"],
+ ["primary", "secondary", "tertiary", "trunk"],
+ true,
+ false
+ ],
+ "layout": {
+ "symbol-placement": "line",
+ "text-field": [
+ "case",
+ ["has", "name:nonlatin"],
+ ["concat", ["get", "name:latin"], " ", ["get", "name:nonlatin"]],
+ ["coalesce", ["get", "name_en"], ["get", "name"]]
+ ],
+ "text-font": ["Noto Sans Regular"],
+ "text-rotation-alignment": "map",
+ "text-size": ["interpolate", ["linear"], ["zoom"], 13, 12, 14, 13]
+ },
+ "paint": {
+ "text-color": "#666",
+ "text-halo-blur": 0.5,
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "highway-shield-non-us",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "transportation_name",
+ "minzoom": 8,
+ "filter": [
+ "all",
+ ["<=", ["get", "ref_length"], 6],
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ [
+ "match",
+ ["get", "network"],
+ ["us-highway", "us-interstate", "us-state"],
+ false,
+ true
+ ]
+ ],
+ "layout": {
+ "icon-image": ["concat", "road_", ["get", "ref_length"]],
+ "icon-rotation-alignment": "viewport",
+ "icon-size": 1,
+ "symbol-placement": ["step", ["zoom"], "point", 11, "line"],
+ "symbol-spacing": 200,
+ "text-field": ["to-string", ["get", "ref"]],
+ "text-font": ["Noto Sans Regular"],
+ "text-rotation-alignment": "viewport",
+ "text-size": 10
+ }
+ },
+ {
+ "id": "highway-shield-us-interstate",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "transportation_name",
+ "minzoom": 7,
+ "filter": [
+ "all",
+ ["<=", ["get", "ref_length"], 6],
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["match", ["get", "network"], ["us-interstate"], true, false]
+ ],
+ "layout": {
+ "icon-image": [
+ "concat",
+ ["get", "network"],
+ "_",
+ ["get", "ref_length"]
+ ],
+ "icon-rotation-alignment": "viewport",
+ "icon-size": 1,
+ "symbol-placement": ["step", ["zoom"], "point", 7, "line", 8, "line"],
+ "symbol-spacing": 200,
+ "text-field": ["to-string", ["get", "ref"]],
+ "text-font": ["Noto Sans Regular"],
+ "text-rotation-alignment": "viewport",
+ "text-size": 10
+ }
+ },
+ {
+ "id": "road_shield_us",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "transportation_name",
+ "minzoom": 9,
+ "filter": [
+ "all",
+ ["<=", ["get", "ref_length"], 6],
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["match", ["get", "network"], ["us-highway", "us-state"], true, false]
+ ],
+ "layout": {
+ "icon-image": [
+ "concat",
+ ["get", "network"],
+ "_",
+ ["get", "ref_length"]
+ ],
+ "icon-rotation-alignment": "viewport",
+ "icon-size": 1,
+ "symbol-placement": ["step", ["zoom"], "point", 11, "line"],
+ "symbol-spacing": 200,
+ "text-field": ["to-string", ["get", "ref"]],
+ "text-font": ["Noto Sans Regular"],
+ "text-rotation-alignment": "viewport",
+ "text-size": 10
+ }
+ },
+ {
+ "id": "airport",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "aerodrome_label",
+ "minzoom": 10,
+ "filter": ["all", ["has", "iata"]],
+ "layout": {
+ "icon-image": "airport_11",
+ "icon-size": 1,
+ "text-anchor": "top",
+ "text-field": [
+ "case",
+ ["has", "name:nonlatin"],
+ ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]],
+ ["coalesce", ["get", "name_en"], ["get", "name"]]
+ ],
+ "text-font": ["Noto Sans Regular"],
+ "text-max-width": 9,
+ "text-offset": [0, 0.6],
+ "text-optional": true,
+ "text-padding": 2,
+ "text-size": 12
+ },
+ "paint": {
+ "text-color": "#666",
+ "text-halo-blur": 0.5,
+ "text-halo-color": "#ffffff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "label_other",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "minzoom": 8,
+ "filter": [
+ "match",
+ ["get", "class"],
+ ["city", "continent", "country", "state", "town", "village"],
+ false,
+ true
+ ],
+ "layout": {
+ "text-field": [
+ "case",
+ ["has", "name:nonlatin"],
+ ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]],
+ ["coalesce", ["get", "name_en"], ["get", "name"]]
+ ],
+ "text-font": ["Noto Sans Italic"],
+ "text-letter-spacing": 0.1,
+ "text-max-width": 9,
+ "text-size": ["interpolate", ["linear"], ["zoom"], 8, 9, 12, 10],
+ "text-transform": "uppercase"
+ },
+ "paint": {
+ "text-color": "#333",
+ "text-halo-blur": 1,
+ "text-halo-color": "#fff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "label_village",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "minzoom": 9,
+ "filter": ["==", ["get", "class"], "village"],
+ "layout": {
+ "icon-allow-overlap": true,
+ "icon-image": ["step", ["zoom"], "circle_11_black", 10, ""],
+ "icon-optional": false,
+ "icon-size": 0.2,
+ "text-anchor": "bottom",
+ "text-field": [
+ "case",
+ ["has", "name:nonlatin"],
+ ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]],
+ ["coalesce", ["get", "name_en"], ["get", "name"]]
+ ],
+ "text-font": ["Noto Sans Regular"],
+ "text-max-width": 8,
+ "text-size": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 7,
+ 10,
+ 11,
+ 12
+ ]
+ },
+ "paint": {
+ "text-color": "#000",
+ "text-halo-blur": 1,
+ "text-halo-color": "#fff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "label_town",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "minzoom": 6,
+ "filter": ["==", ["get", "class"], "town"],
+ "layout": {
+ "icon-allow-overlap": true,
+ "icon-image": ["step", ["zoom"], "circle_11_black", 10, ""],
+ "icon-optional": false,
+ "icon-size": 0.2,
+ "text-anchor": "bottom",
+ "text-field": [
+ "case",
+ ["has", "name:nonlatin"],
+ ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]],
+ ["coalesce", ["get", "name_en"], ["get", "name"]]
+ ],
+ "text-font": ["Noto Sans Regular"],
+ "text-max-width": 8,
+ "text-size": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 7,
+ 12,
+ 11,
+ 14
+ ]
+ },
+ "paint": {
+ "text-color": "#000",
+ "text-halo-blur": 1,
+ "text-halo-color": "#fff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "label_state",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "minzoom": 5,
+ "maxzoom": 8,
+ "filter": ["==", ["get", "class"], "state"],
+ "layout": {
+ "text-field": [
+ "case",
+ ["has", "name:nonlatin"],
+ ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]],
+ ["coalesce", ["get", "name_en"], ["get", "name"]]
+ ],
+ "text-font": ["Noto Sans Italic"],
+ "text-letter-spacing": 0.2,
+ "text-max-width": 9,
+ "text-size": ["interpolate", ["linear"], ["zoom"], 5, 10, 8, 14],
+ "text-transform": "uppercase"
+ },
+ "paint": {
+ "text-color": "#333",
+ "text-halo-blur": 1,
+ "text-halo-color": "#fff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "label_city",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "minzoom": 3,
+ "filter": [
+ "all",
+ ["==", ["get", "class"], "city"],
+ ["!=", ["get", "capital"], 2]
+ ],
+ "layout": {
+ "icon-allow-overlap": true,
+ "icon-image": ["step", ["zoom"], "circle_11_black", 9, ""],
+ "icon-optional": false,
+ "icon-size": 0.4,
+ "text-anchor": "bottom",
+ "text-field": [
+ "case",
+ ["has", "name:nonlatin"],
+ ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]],
+ ["coalesce", ["get", "name_en"], ["get", "name"]]
+ ],
+ "text-font": ["Noto Sans Regular"],
+ "text-max-width": 8,
+ "text-offset": [0, -0.1],
+ "text-size": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 4,
+ 11,
+ 7,
+ 13,
+ 11,
+ 18
+ ]
+ },
+ "paint": {
+ "text-color": "#000",
+ "text-halo-blur": 1,
+ "text-halo-color": "#fff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "label_city_capital",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "minzoom": 3,
+ "filter": [
+ "all",
+ ["==", ["get", "class"], "city"],
+ ["==", ["get", "capital"], 2]
+ ],
+ "layout": {
+ "icon-allow-overlap": true,
+ "icon-image": ["step", ["zoom"], "circle_11_black", 9, ""],
+ "icon-optional": false,
+ "icon-size": 0.5,
+ "text-anchor": "bottom",
+ "text-field": [
+ "case",
+ ["has", "name:nonlatin"],
+ ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]],
+ ["coalesce", ["get", "name_en"], ["get", "name"]]
+ ],
+ "text-font": ["Noto Sans Bold"],
+ "text-max-width": 8,
+ "text-offset": [0, -0.2],
+ "text-size": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 4,
+ 12,
+ 7,
+ 14,
+ 11,
+ 20
+ ]
+ },
+ "paint": {
+ "text-color": "#000",
+ "text-halo-blur": 1,
+ "text-halo-color": "#fff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "label_country_3",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "minzoom": 2,
+ "maxzoom": 9,
+ "filter": [
+ "all",
+ ["==", ["get", "class"], "country"],
+ [">=", ["get", "rank"], 3]
+ ],
+ "layout": {
+ "text-field": [
+ "case",
+ ["has", "name:nonlatin"],
+ ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]],
+ ["coalesce", ["get", "name_en"], ["get", "name"]]
+ ],
+ "text-font": ["Noto Sans Bold"],
+ "text-max-width": 6.25,
+ "text-size": ["interpolate", ["linear"], ["zoom"], 3, 9, 7, 17]
+ },
+ "paint": {
+ "text-color": "#000",
+ "text-halo-blur": 1,
+ "text-halo-color": "#fff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "label_country_2",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "maxzoom": 9,
+ "filter": [
+ "all",
+ ["==", ["get", "class"], "country"],
+ ["==", ["get", "rank"], 2]
+ ],
+ "layout": {
+ "text-field": [
+ "case",
+ ["has", "name:nonlatin"],
+ ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]],
+ ["coalesce", ["get", "name_en"], ["get", "name"]]
+ ],
+ "text-font": ["Noto Sans Bold"],
+ "text-max-width": 6.25,
+ "text-size": ["interpolate", ["linear"], ["zoom"], 2, 9, 5, 17]
+ },
+ "paint": {
+ "text-color": "#000",
+ "text-halo-blur": 1,
+ "text-halo-color": "#fff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "label_country_1",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "maxzoom": 9,
+ "filter": [
+ "all",
+ ["==", ["get", "class"], "country"],
+ ["==", ["get", "rank"], 1]
+ ],
+ "layout": {
+ "text-field": [
+ "case",
+ ["has", "name:nonlatin"],
+ ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]],
+ ["coalesce", ["get", "name_en"], ["get", "name"]]
+ ],
+ "text-font": ["Noto Sans Bold"],
+ "text-max-width": 6.25,
+ "text-size": ["interpolate", ["linear"], ["zoom"], 1, 9, 4, 17]
+ },
+ "paint": {
+ "text-color": "#000",
+ "text-halo-blur": 1,
+ "text-halo-color": "#fff",
+ "text-halo-width": 1
+ }
+ }
+ ],
+ "id": "94huyrm"
+}
diff --git a/app/src/main/assets/map_style_good_noshops.json b/app/src/main/assets/map_style_good_noshops.json
new file mode 100644
--- /dev/null
+++ b/app/src/main/assets/map_style_good_noshops.json
@@ -0,0 +1,3107 @@
+{
+ "version": 8,
+ "metadata": {"maputnik:renderer": "mlgljs"},
+ "sources": {
+ "ne2_shaded": {
+ "maxzoom": 6,
+ "tileSize": 256,
+ "tiles": [
+ "https://tiles.openfreemap.org/natural_earth/ne2sr/{z}/{x}/{y}.png"
+ ],
+ "type": "raster"
+ },
+ "openmaptiles": {
+ "type": "vector",
+ "url": "https://tiles.openfreemap.org/planet"
+ }
+ },
+ "sprite": "https://tiles.openfreemap.org/sprites/ofm_f384/ofm",
+ "glyphs": "https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf",
+ "layers": [
+ {
+ "id": "background",
+ "type": "background",
+ "paint": {"background-color": "#f8f4f0"}
+ },
+ {
+ "id": "landcover-glacier",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landcover",
+ "filter": ["==", ["get", "subclass"], "glacier"],
+ "paint": {
+ "fill-color": "#fff",
+ "fill-opacity": ["interpolate", ["linear"], ["zoom"], 0, 0.9, 10, 0.3]
+ }
+ },
+ {
+ "id": "landuse-residential",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landuse",
+ "filter": [
+ "match",
+ ["get", "class"],
+ ["neighbourhood", "residential"],
+ true,
+ false
+ ],
+ "paint": {
+ "fill-color": [
+ "interpolate",
+ ["linear"],
+ ["zoom"],
+ 12,
+ "hsla(30,19%,90%,0.4)",
+ 16,
+ "hsla(30,19%,90%,0.2)"
+ ]
+ }
+ },
+ {
+ "id": "landuse-suburb",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landuse",
+ "maxzoom": 10,
+ "filter": ["==", ["get", "class"], "suburb"],
+ "paint": {
+ "fill-color": [
+ "interpolate",
+ ["linear"],
+ ["zoom"],
+ 8,
+ "hsla(30,19%,90%,0.4)",
+ 10,
+ "hsla(30,19%,90%,0.0)"
+ ]
+ }
+ },
+ {
+ "id": "landuse-commercial",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landuse",
+ "filter": [
+ "all",
+ ["match", ["geometry-type"], ["MultiPolygon", "Polygon"], true, false],
+ ["==", ["get", "class"], "commercial"]
+ ],
+ "paint": {"fill-color": "hsla(0,60%,87%,0.23)"}
+ },
+ {
+ "id": "landuse-industrial",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landuse",
+ "filter": [
+ "all",
+ ["match", ["geometry-type"], ["MultiPolygon", "Polygon"], true, false],
+ [
+ "match",
+ ["get", "class"],
+ ["dam", "garages", "industrial"],
+ true,
+ false
+ ]
+ ],
+ "paint": {"fill-color": "hsla(49,100%,88%,0.34)"}
+ },
+ {
+ "id": "landuse-cemetery",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landuse",
+ "filter": ["==", ["get", "class"], "cemetery"],
+ "paint": {"fill-color": "#e0e4dd"}
+ },
+ {
+ "id": "landuse-hospital",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landuse",
+ "filter": ["==", ["get", "class"], "hospital"],
+ "paint": {"fill-color": "#fde"}
+ },
+ {
+ "id": "landuse-school",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landuse",
+ "filter": ["==", ["get", "class"], "school"],
+ "paint": {"fill-color": "#f0e8f8"}
+ },
+ {
+ "id": "landuse-railway",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landuse",
+ "filter": ["==", ["get", "class"], "railway"],
+ "paint": {"fill-color": "hsla(30,19%,90%,0.4)"}
+ },
+ {
+ "id": "park",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "park",
+ "filter": [
+ "match",
+ ["geometry-type"],
+ ["MultiPolygon", "Polygon"],
+ true,
+ false
+ ],
+ "paint": {
+ "fill-color": "#d8e8c8",
+ "fill-opacity": [
+ "interpolate",
+ ["exponential", 1.8],
+ ["zoom"],
+ 9,
+ 0.5,
+ 12,
+ 0.2
+ ]
+ }
+ },
+ {
+ "id": "landcover-wood",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landcover",
+ "filter": ["==", ["get", "class"], "wood"],
+ "paint": {
+ "fill-antialias": ["step", ["zoom"], false, 9, true],
+ "fill-color": "#6a4",
+ "fill-opacity": 0.1,
+ "fill-outline-color": "hsla(0,0%,0%,0.03)"
+ }
+ },
+ {
+ "id": "landcover-grass",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landcover",
+ "filter": ["==", ["get", "class"], "grass"],
+ "paint": {"fill-color": "#d8e8c8", "fill-opacity": 1}
+ },
+ {
+ "id": "landcover-grass-park",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "park",
+ "filter": ["==", ["get", "class"], "public_park"],
+ "paint": {"fill-color": "#d8e8c8", "fill-opacity": 0.8}
+ },
+ {
+ "id": "waterway_tunnel",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "waterway",
+ "minzoom": 14,
+ "filter": [
+ "all",
+ ["match", ["get", "class"], ["canal", "river", "stream"], true, false],
+ ["==", ["get", "brunnel"], "tunnel"]
+ ],
+ "layout": {"line-cap": "round"},
+ "paint": {
+ "line-color": "#a0c8f0",
+ "line-dasharray": [2, 4],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.3],
+ ["zoom"],
+ 13,
+ 0.5,
+ 20,
+ 6
+ ]
+ }
+ },
+ {
+ "id": "waterway-other",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "waterway",
+ "filter": [
+ "all",
+ ["match", ["get", "class"], ["canal", "river", "stream"], false, true],
+ ["==", ["get", "intermittent"], 0]
+ ],
+ "layout": {"line-cap": "round"},
+ "paint": {
+ "line-color": "#a0c8f0",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.3],
+ ["zoom"],
+ 13,
+ 0.5,
+ 20,
+ 2
+ ]
+ }
+ },
+ {
+ "id": "waterway-other-intermittent",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "waterway",
+ "filter": [
+ "all",
+ ["match", ["get", "class"], ["canal", "river", "stream"], false, true],
+ ["==", ["get", "intermittent"], 1]
+ ],
+ "layout": {"line-cap": "round"},
+ "paint": {
+ "line-color": "#a0c8f0",
+ "line-dasharray": [4, 3],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.3],
+ ["zoom"],
+ 13,
+ 0.5,
+ 20,
+ 2
+ ]
+ }
+ },
+ {
+ "id": "waterway-stream-canal",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "waterway",
+ "filter": [
+ "all",
+ ["match", ["get", "class"], ["canal", "stream"], true, false],
+ ["!=", ["get", "brunnel"], "tunnel"],
+ ["==", ["get", "intermittent"], 0]
+ ],
+ "layout": {"line-cap": "round"},
+ "paint": {
+ "line-color": "#a0c8f0",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.3],
+ ["zoom"],
+ 13,
+ 0.5,
+ 20,
+ 6
+ ]
+ }
+ },
+ {
+ "id": "waterway-stream-canal-intermittent",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "waterway",
+ "filter": [
+ "all",
+ ["match", ["get", "class"], ["canal", "stream"], true, false],
+ ["!=", ["get", "brunnel"], "tunnel"],
+ ["==", ["get", "intermittent"], 1]
+ ],
+ "layout": {"line-cap": "round"},
+ "paint": {
+ "line-color": "#a0c8f0",
+ "line-dasharray": [4, 3],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.3],
+ ["zoom"],
+ 13,
+ 0.5,
+ 20,
+ 6
+ ]
+ }
+ },
+ {
+ "id": "waterway-river",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "waterway",
+ "filter": [
+ "all",
+ ["==", ["get", "class"], "river"],
+ ["!=", ["get", "brunnel"], "tunnel"],
+ ["!=", ["get", "intermittent"], 1]
+ ],
+ "layout": {"line-cap": "round"},
+ "paint": {
+ "line-color": "#a0c8f0",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 10,
+ 0.8,
+ 20,
+ 6
+ ]
+ }
+ },
+ {
+ "id": "waterway-river-intermittent",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "waterway",
+ "filter": [
+ "all",
+ ["==", ["get", "class"], "river"],
+ ["!=", ["get", "brunnel"], "tunnel"],
+ ["==", ["get", "intermittent"], 1]
+ ],
+ "layout": {"line-cap": "round"},
+ "paint": {
+ "line-color": "#a0c8f0",
+ "line-dasharray": [3, 2.5],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 10,
+ 0.8,
+ 20,
+ 6
+ ]
+ }
+ },
+ {
+ "id": "water",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "water",
+ "filter": [
+ "all",
+ ["!=", ["get", "intermittent"], 1],
+ ["!=", ["get", "brunnel"], "tunnel"]
+ ],
+ "paint": {"fill-color": "#AECFE2"}
+ },
+ {
+ "id": "water-intermittent",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "water",
+ "filter": ["==", ["get", "intermittent"], 1],
+ "paint": {"fill-color": "hsl(210,67%,85%)", "fill-opacity": 0.7}
+ },
+ {
+ "id": "landcover-ice-shelf",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landcover",
+ "filter": ["==", ["get", "subclass"], "ice_shelf"],
+ "paint": {
+ "fill-color": "#fff",
+ "fill-opacity": ["interpolate", ["linear"], ["zoom"], 0, 0.9, 10, 0.3]
+ }
+ },
+ {
+ "id": "landcover-sand",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landcover",
+ "filter": ["==", ["get", "class"], "sand"],
+ "paint": {"fill-color": "rgba(245, 238, 188, 1)", "fill-opacity": 1}
+ },
+ {
+ "id": "building",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "building",
+ "paint": {
+ "fill-antialias": true,
+ "fill-color": [
+ "interpolate",
+ ["linear"],
+ ["zoom"],
+ 15.5,
+ "#f2eae2",
+ 16,
+ "#dfdbd7"
+ ]
+ }
+ },
+ {
+ "id": "building-top",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "building",
+ "paint": {
+ "fill-color": "#f2eae2",
+ "fill-opacity": ["interpolate", ["linear"], ["zoom"], 13, 0, 16, 1],
+ "fill-outline-color": "#dfdbd7",
+ "fill-translate": [
+ "interpolate",
+ ["linear"],
+ ["zoom"],
+ 14,
+ ["literal", [0, 0]],
+ 16,
+ ["literal", [-2, -2]]
+ ]
+ }
+ },
+ {
+ "id": "tunnel-service-track-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "tunnel"],
+ ["match", ["get", "class"], ["service", "track"], true, false]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#cfcdca",
+ "line-dasharray": [0.5, 0.25],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 15,
+ 1,
+ 16,
+ 4,
+ 20,
+ 11
+ ]
+ }
+ },
+ {
+ "id": "tunnel-motorway-link-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "tunnel"],
+ ["==", ["get", "class"], "motorway"],
+ ["==", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "rgba(200, 147, 102, 1)",
+ "line-dasharray": [0.5, 0.25],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 12,
+ 1,
+ 13,
+ 3,
+ 14,
+ 4,
+ 20,
+ 15
+ ]
+ }
+ },
+ {
+ "id": "tunnel-minor-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "tunnel"],
+ ["==", ["get", "class"], "minor"]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#cfcdca",
+ "line-dasharray": [0.5, 0.25],
+ "line-opacity": ["interpolate", ["linear"], ["zoom"], 12, 0, 12.5, 1],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 12,
+ 0.5,
+ 13,
+ 1,
+ 14,
+ 4,
+ 20,
+ 15
+ ]
+ }
+ },
+ {
+ "id": "tunnel-link-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "tunnel"],
+ [
+ "match",
+ ["get", "class"],
+ ["primary", "secondary", "tertiary", "trunk"],
+ true,
+ false
+ ],
+ ["==", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-dasharray": [0.5, 0.25],
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 12,
+ 1,
+ 13,
+ 3,
+ 14,
+ 4,
+ 20,
+ 15
+ ]
+ }
+ },
+ {
+ "id": "tunnel-secondary-tertiary-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "tunnel"],
+ ["match", ["get", "class"], ["secondary", "tertiary"], true, false],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-dasharray": [0.5, 0.25],
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 8,
+ 1.5,
+ 20,
+ 17
+ ]
+ }
+ },
+ {
+ "id": "tunnel-trunk-primary-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "tunnel"],
+ ["match", ["get", "class"], ["primary", "trunk"], true, false],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 5,
+ 0.4,
+ 6,
+ 0.6,
+ 7,
+ 1.5,
+ 20,
+ 22
+ ]
+ }
+ },
+ {
+ "id": "tunnel-motorway-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "tunnel"],
+ ["==", ["get", "class"], "motorway"],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-dasharray": [0.5, 0.25],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 5,
+ 0.4,
+ 6,
+ 0.6,
+ 7,
+ 1.5,
+ 20,
+ 22
+ ]
+ }
+ },
+ {
+ "id": "tunnel-path",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["==", ["get", "brunnel"], "tunnel"],
+ ["==", ["get", "class"], "path"]
+ ],
+ "paint": {
+ "line-color": "#cba",
+ "line-dasharray": [1.5, 0.75],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 15,
+ 1.2,
+ 20,
+ 4
+ ]
+ }
+ },
+ {
+ "id": "tunnel-motorway-link",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "tunnel"],
+ ["==", ["get", "class"], "motorway"],
+ ["==", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "rgba(244, 209, 158, 1)",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 12.5,
+ 0,
+ 13,
+ 1.5,
+ 14,
+ 2.5,
+ 20,
+ 11.5
+ ]
+ }
+ },
+ {
+ "id": "tunnel-service-track",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "tunnel"],
+ ["match", ["get", "class"], ["service", "track"], true, false]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#fff",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 15.5,
+ 0,
+ 16,
+ 2,
+ 20,
+ 7.5
+ ]
+ }
+ },
+ {
+ "id": "tunnel-link",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "tunnel"],
+ [
+ "match",
+ ["get", "class"],
+ ["primary", "secondary", "tertiary", "trunk"],
+ true,
+ false
+ ],
+ ["==", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#fff4c6",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 12.5,
+ 0,
+ 13,
+ 1.5,
+ 14,
+ 2.5,
+ 20,
+ 11.5
+ ]
+ }
+ },
+ {
+ "id": "tunnel-minor",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "tunnel"],
+ ["==", ["get", "class"], "minor"]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#fff",
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 13.5,
+ 0,
+ 14,
+ 2.5,
+ 20,
+ 11.5
+ ]
+ }
+ },
+ {
+ "id": "tunnel-secondary-tertiary",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "tunnel"],
+ ["match", ["get", "class"], ["secondary", "tertiary"], true, false],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#fff4c6",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 6.5,
+ 0,
+ 7,
+ 0.5,
+ 20,
+ 10
+ ]
+ }
+ },
+ {
+ "id": "tunnel-trunk-primary",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "tunnel"],
+ ["match", ["get", "class"], ["primary", "trunk"], true, false],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#fff4c6",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 6.5,
+ 0,
+ 7,
+ 0.5,
+ 20,
+ 18
+ ]
+ }
+ },
+ {
+ "id": "tunnel-motorway",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "tunnel"],
+ ["==", ["get", "class"], "motorway"],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#ffdaa6",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 6.5,
+ 0,
+ 7,
+ 0.5,
+ 20,
+ 18
+ ]
+ }
+ },
+ {
+ "id": "tunnel-railway",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "tunnel"],
+ ["==", ["get", "class"], "rail"]
+ ],
+ "paint": {
+ "line-color": "#bbb",
+ "line-dasharray": [2, 2],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.4],
+ ["zoom"],
+ 14,
+ 0.4,
+ 15,
+ 0.75,
+ 20,
+ 2
+ ]
+ }
+ },
+ {
+ "id": "ferry",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": ["match", ["get", "class"], ["ferry"], true, false],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "rgba(108, 159, 182, 1)",
+ "line-dasharray": [2, 2],
+ "line-width": 1.1
+ }
+ },
+ {
+ "id": "aeroway-taxiway-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "aeroway",
+ "minzoom": 12,
+ "filter": ["match", ["get", "class"], ["taxiway"], true, false],
+ "layout": {"line-cap": "round", "line-join": "round"},
+ "paint": {
+ "line-color": "rgba(153, 153, 153, 1)",
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.5],
+ ["zoom"],
+ 11,
+ 2,
+ 17,
+ 12
+ ]
+ }
+ },
+ {
+ "id": "aeroway-runway-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "aeroway",
+ "minzoom": 12,
+ "filter": ["match", ["get", "class"], ["runway"], true, false],
+ "layout": {"line-cap": "round", "line-join": "round"},
+ "paint": {
+ "line-color": "rgba(153, 153, 153, 1)",
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.5],
+ ["zoom"],
+ 11,
+ 5,
+ 17,
+ 55
+ ]
+ }
+ },
+ {
+ "id": "aeroway-area",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "aeroway",
+ "minzoom": 4,
+ "filter": [
+ "all",
+ ["match", ["geometry-type"], ["MultiPolygon", "Polygon"], true, false],
+ ["match", ["get", "class"], ["runway", "taxiway"], true, false]
+ ],
+ "paint": {
+ "fill-color": "rgba(255, 255, 255, 1)",
+ "fill-opacity": ["interpolate", ["linear"], ["zoom"], 13, 0, 14, 1]
+ }
+ },
+ {
+ "id": "aeroway-taxiway",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "aeroway",
+ "minzoom": 4,
+ "filter": [
+ "all",
+ ["match", ["get", "class"], ["taxiway"], true, false],
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ]
+ ],
+ "layout": {"line-cap": "round", "line-join": "round"},
+ "paint": {
+ "line-color": "rgba(255, 255, 255, 1)",
+ "line-opacity": ["interpolate", ["linear"], ["zoom"], 11, 0, 12, 1],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.5],
+ ["zoom"],
+ 11,
+ 1,
+ 17,
+ 10
+ ]
+ }
+ },
+ {
+ "id": "aeroway-runway",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "aeroway",
+ "minzoom": 4,
+ "filter": [
+ "all",
+ ["match", ["get", "class"], ["runway"], true, false],
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ]
+ ],
+ "layout": {"line-cap": "round", "line-join": "round"},
+ "paint": {
+ "line-color": "rgba(255, 255, 255, 1)",
+ "line-opacity": ["interpolate", ["linear"], ["zoom"], 11, 0, 12, 1],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.5],
+ ["zoom"],
+ 11,
+ 4,
+ 17,
+ 50
+ ]
+ }
+ },
+ {
+ "id": "road_area_pier",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["match", ["geometry-type"], ["MultiPolygon", "Polygon"], true, false],
+ ["==", ["get", "class"], "pier"]
+ ],
+ "paint": {"fill-antialias": true, "fill-color": "#f8f4f0"}
+ },
+ {
+ "id": "road_pier",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["match", ["get", "class"], ["pier"], true, false]
+ ],
+ "layout": {"line-cap": "round", "line-join": "round"},
+ "paint": {
+ "line-color": "#f8f4f0",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 15,
+ 1,
+ 17,
+ 4
+ ]
+ }
+ },
+ {
+ "id": "highway-area",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["match", ["geometry-type"], ["MultiPolygon", "Polygon"], true, false],
+ ["match", ["get", "class"], ["pier"], false, true]
+ ],
+ "paint": {
+ "fill-antialias": false,
+ "fill-color": "hsla(0,0%,89%,0.56)",
+ "fill-opacity": 0.9,
+ "fill-outline-color": "#cfcdca"
+ }
+ },
+ {
+ "id": "highway-motorway-link-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true],
+ ["==", ["get", "class"], "motorway"],
+ ["==", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-cap": "round", "line-join": "round"},
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 12,
+ 1,
+ 13,
+ 3,
+ 14,
+ 4,
+ 20,
+ 15
+ ]
+ }
+ },
+ {
+ "id": "highway-link-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 13,
+ "filter": [
+ "all",
+ ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true],
+ [
+ "match",
+ ["get", "class"],
+ ["primary", "secondary", "tertiary", "trunk"],
+ true,
+ false
+ ],
+ ["==", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-cap": "round", "line-join": "round"},
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 12,
+ 1,
+ 13,
+ 3,
+ 14,
+ 4,
+ 20,
+ 15
+ ]
+ }
+ },
+ {
+ "id": "highway-minor-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["!=", ["get", "brunnel"], "tunnel"],
+ ["match", ["get", "class"], ["minor", "service", "track"], true, false]
+ ],
+ "layout": {"line-cap": "round", "line-join": "round"},
+ "paint": {
+ "line-color": "#cfcdca",
+ "line-opacity": ["interpolate", ["linear"], ["zoom"], 12, 0, 12.5, 1],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 12,
+ 0.5,
+ 13,
+ 1,
+ 14,
+ 4,
+ 20,
+ 15
+ ]
+ }
+ },
+ {
+ "id": "highway-secondary-tertiary-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true],
+ ["match", ["get", "class"], ["secondary", "tertiary"], true, false],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-cap": "butt", "line-join": "round"},
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 8,
+ 1.5,
+ 20,
+ 17
+ ]
+ }
+ },
+ {
+ "id": "highway-primary-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 5,
+ "filter": [
+ "all",
+ ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true],
+ ["match", ["get", "class"], ["primary"], true, false],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-cap": "butt", "line-join": "round"},
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-opacity": ["interpolate", ["linear"], ["zoom"], 7, 0, 8, 1],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 7,
+ 0,
+ 8,
+ 0.6,
+ 9,
+ 1.5,
+ 20,
+ 22
+ ]
+ }
+ },
+ {
+ "id": "highway-trunk-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 5,
+ "filter": [
+ "all",
+ ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true],
+ ["match", ["get", "class"], ["trunk"], true, false],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-cap": "butt", "line-join": "round"},
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-opacity": ["interpolate", ["linear"], ["zoom"], 5, 0, 6, 1],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 5,
+ 0,
+ 6,
+ 0.6,
+ 7,
+ 1.5,
+ 20,
+ 22
+ ]
+ }
+ },
+ {
+ "id": "highway-motorway-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 4,
+ "filter": [
+ "all",
+ ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true],
+ ["==", ["get", "class"], "motorway"],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-cap": "butt", "line-join": "round"},
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-opacity": ["interpolate", ["linear"], ["zoom"], 4, 0, 5, 1],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 4,
+ 0,
+ 5,
+ 0.4,
+ 6,
+ 0.6,
+ 7,
+ 1.5,
+ 20,
+ 22
+ ]
+ }
+ },
+ {
+ "id": "highway-path",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true],
+ ["==", ["get", "class"], "path"]
+ ],
+ "paint": {
+ "line-color": "#cba",
+ "line-dasharray": [1.5, 0.75],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 15,
+ 1.2,
+ 20,
+ 4
+ ]
+ }
+ },
+ {
+ "id": "highway-motorway-link",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 12,
+ "filter": [
+ "all",
+ ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true],
+ ["==", ["get", "class"], "motorway"],
+ ["==", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-cap": "round", "line-join": "round"},
+ "paint": {
+ "line-color": "#fc8",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 12.5,
+ 0,
+ 13,
+ 1.5,
+ 14,
+ 2.5,
+ 20,
+ 11.5
+ ]
+ }
+ },
+ {
+ "id": "highway-link",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 13,
+ "filter": [
+ "all",
+ ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true],
+ [
+ "match",
+ ["get", "class"],
+ ["primary", "secondary", "tertiary", "trunk"],
+ true,
+ false
+ ],
+ ["==", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-cap": "round", "line-join": "round"},
+ "paint": {
+ "line-color": "#fea",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 12.5,
+ 0,
+ 13,
+ 1.5,
+ 14,
+ 2.5,
+ 20,
+ 11.5
+ ]
+ }
+ },
+ {
+ "id": "highway-minor",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["!=", ["get", "brunnel"], "tunnel"],
+ ["match", ["get", "class"], ["minor", "service", "track"], true, false]
+ ],
+ "layout": {"line-cap": "round", "line-join": "round"},
+ "paint": {
+ "line-color": "#fff",
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 13.5,
+ 0,
+ 14,
+ 2.5,
+ 20,
+ 11.5
+ ]
+ }
+ },
+ {
+ "id": "highway-secondary-tertiary",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true],
+ ["match", ["get", "class"], ["secondary", "tertiary"], true, false],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-cap": "round", "line-join": "round"},
+ "paint": {
+ "line-color": "#fea",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 6.5,
+ 0,
+ 8,
+ 0.5,
+ 20,
+ 13
+ ]
+ }
+ },
+ {
+ "id": "highway-primary",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true],
+ ["match", ["get", "class"], ["primary"], true, false],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-cap": "round", "line-join": "round"},
+ "paint": {
+ "line-color": "#fea",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 8.5,
+ 0,
+ 9,
+ 0.5,
+ 20,
+ 18
+ ]
+ }
+ },
+ {
+ "id": "highway-trunk",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true],
+ ["match", ["get", "class"], ["trunk"], true, false],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-cap": "round", "line-join": "round"},
+ "paint": {
+ "line-color": "#fea",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 6.5,
+ 0,
+ 7,
+ 0.5,
+ 20,
+ 18
+ ]
+ }
+ },
+ {
+ "id": "highway-motorway",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 5,
+ "filter": [
+ "all",
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true],
+ ["==", ["get", "class"], "motorway"],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-cap": "round", "line-join": "round"},
+ "paint": {
+ "line-color": "#fc8",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 6.5,
+ 0,
+ 7,
+ 0.5,
+ 20,
+ 18
+ ]
+ }
+ },
+ {
+ "id": "railway-transit",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["==", ["get", "class"], "transit"],
+ ["match", ["get", "brunnel"], ["tunnel"], false, true]
+ ],
+ "paint": {
+ "line-color": "hsla(0,0%,73%,0.77)",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.4],
+ ["zoom"],
+ 14,
+ 0.4,
+ 20,
+ 1
+ ]
+ }
+ },
+ {
+ "id": "railway-transit-hatching",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["==", ["get", "class"], "transit"],
+ ["match", ["get", "brunnel"], ["tunnel"], false, true]
+ ],
+ "paint": {
+ "line-color": "hsla(0,0%,73%,0.68)",
+ "line-dasharray": [0.2, 8],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.4],
+ ["zoom"],
+ 14.5,
+ 0,
+ 15,
+ 2,
+ 20,
+ 6
+ ]
+ }
+ },
+ {
+ "id": "railway-service",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["==", ["get", "class"], "rail"],
+ ["has", "service"]
+ ],
+ "paint": {
+ "line-color": "hsla(0,0%,73%,0.77)",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.4],
+ ["zoom"],
+ 14,
+ 0.4,
+ 20,
+ 1
+ ]
+ }
+ },
+ {
+ "id": "railway-service-hatching",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["==", ["get", "class"], "rail"],
+ ["has", "service"]
+ ],
+ "paint": {
+ "line-color": "hsla(0,0%,73%,0.68)",
+ "line-dasharray": [0.2, 8],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.4],
+ ["zoom"],
+ 14.5,
+ 0,
+ 15,
+ 2,
+ 20,
+ 6
+ ]
+ }
+ },
+ {
+ "id": "railway",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["!", ["has", "service"]],
+ ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true],
+ ["==", ["get", "class"], "rail"]
+ ],
+ "paint": {
+ "line-color": "#bbb",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.4],
+ ["zoom"],
+ 14,
+ 0.4,
+ 15,
+ 0.75,
+ 20,
+ 2
+ ]
+ }
+ },
+ {
+ "id": "railway-hatching",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["!", ["has", "service"]],
+ ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true],
+ ["==", ["get", "class"], "rail"]
+ ],
+ "paint": {
+ "line-color": "#bbb",
+ "line-dasharray": [0.2, 8],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.4],
+ ["zoom"],
+ 14.5,
+ 0,
+ 15,
+ 3,
+ 20,
+ 8
+ ]
+ }
+ },
+ {
+ "id": "bridge-motorway-link-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "bridge"],
+ ["==", ["get", "class"], "motorway"],
+ ["==", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 12,
+ 1,
+ 13,
+ 3,
+ 14,
+ 4,
+ 20,
+ 19
+ ]
+ }
+ },
+ {
+ "id": "bridge-link-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "bridge"],
+ [
+ "match",
+ ["get", "class"],
+ ["primary", "secondary", "tertiary", "trunk"],
+ true,
+ false
+ ],
+ ["==", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 12,
+ 1,
+ 13,
+ 3,
+ 14,
+ 4,
+ 20,
+ 19
+ ]
+ }
+ },
+ {
+ "id": "bridge-secondary-tertiary-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "bridge"],
+ ["match", ["get", "class"], ["secondary", "tertiary"], true, false],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 5,
+ 0.4,
+ 7,
+ 0.6,
+ 8,
+ 1.5,
+ 20,
+ 21
+ ]
+ }
+ },
+ {
+ "id": "bridge-trunk-primary-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "bridge"],
+ ["match", ["get", "class"], ["primary", "trunk"], true, false],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "hsl(28,76%,67%)",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 5,
+ 0.4,
+ 6,
+ 0.6,
+ 7,
+ 1.5,
+ 20,
+ 26
+ ]
+ }
+ },
+ {
+ "id": "bridge-motorway-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "bridge"],
+ ["==", ["get", "class"], "motorway"],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 5,
+ 0.4,
+ 6,
+ 0.6,
+ 7,
+ 1.5,
+ 20,
+ 26
+ ]
+ }
+ },
+ {
+ "id": "bridge-minor-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["==", ["get", "brunnel"], "bridge"],
+ ["match", ["get", "class"], ["minor", "service", "track"], true, false]
+ ],
+ "layout": {"line-cap": "butt", "line-join": "round"},
+ "paint": {
+ "line-color": "#cfcdca",
+ "line-opacity": ["interpolate", ["linear"], ["zoom"], 12, 0, 12.5, 1],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 12,
+ 0.5,
+ 13,
+ 1,
+ 14,
+ 6,
+ 20,
+ 24
+ ]
+ }
+ },
+ {
+ "id": "bridge-path-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["==", ["get", "brunnel"], "bridge"],
+ ["==", ["get", "class"], "path"]
+ ],
+ "paint": {
+ "line-color": "#f8f4f0",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 15,
+ 1.2,
+ 20,
+ 18
+ ]
+ }
+ },
+ {
+ "id": "bridge-path",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["==", ["get", "brunnel"], "bridge"],
+ ["==", ["get", "class"], "path"]
+ ],
+ "paint": {
+ "line-color": "#cba",
+ "line-dasharray": [1.5, 0.75],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 15,
+ 1.2,
+ 20,
+ 4
+ ]
+ }
+ },
+ {
+ "id": "bridge-motorway-link",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "bridge"],
+ ["==", ["get", "class"], "motorway"],
+ ["==", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#fc8",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 12.5,
+ 0,
+ 13,
+ 1.5,
+ 14,
+ 2.5,
+ 20,
+ 11.5
+ ]
+ }
+ },
+ {
+ "id": "bridge-link",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "bridge"],
+ [
+ "match",
+ ["get", "class"],
+ ["primary", "secondary", "tertiary", "trunk"],
+ true,
+ false
+ ],
+ ["==", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#fea",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 12.5,
+ 0,
+ 13,
+ 1.5,
+ 14,
+ 2.5,
+ 20,
+ 11.5
+ ]
+ }
+ },
+ {
+ "id": "bridge-minor",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["==", ["get", "brunnel"], "bridge"],
+ ["match", ["get", "class"], ["minor", "service", "track"], true, false]
+ ],
+ "layout": {"line-cap": "round", "line-join": "round"},
+ "paint": {
+ "line-color": "#fff",
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 13.5,
+ 0,
+ 14,
+ 2.5,
+ 20,
+ 11.5
+ ]
+ }
+ },
+ {
+ "id": "bridge-secondary-tertiary",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "bridge"],
+ ["match", ["get", "class"], ["secondary", "tertiary"], true, false],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#fea",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 6.5,
+ 0,
+ 8,
+ 0.5,
+ 20,
+ 13
+ ]
+ }
+ },
+ {
+ "id": "bridge-trunk-primary",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "bridge"],
+ ["match", ["get", "class"], ["primary", "trunk"], true, false],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#fea",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 6.5,
+ 0,
+ 7,
+ 0.5,
+ 20,
+ 18
+ ]
+ }
+ },
+ {
+ "id": "bridge-motorway",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "bridge"],
+ ["==", ["get", "class"], "motorway"],
+ ["!=", ["get", "ramp"], 1]
+ ],
+ "layout": {"line-join": "round"},
+ "paint": {
+ "line-color": "#fc8",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 6.5,
+ 0,
+ 7,
+ 0.5,
+ 20,
+ 18
+ ]
+ }
+ },
+ {
+ "id": "bridge-railway",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "bridge"],
+ ["==", ["get", "class"], "rail"]
+ ],
+ "paint": {
+ "line-color": "#bbb",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.4],
+ ["zoom"],
+ 14,
+ 0.4,
+ 15,
+ 0.75,
+ 20,
+ 2
+ ]
+ }
+ },
+ {
+ "id": "bridge-railway-hatching",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ ["==", ["get", "brunnel"], "bridge"],
+ ["==", ["get", "class"], "rail"]
+ ],
+ "paint": {
+ "line-color": "#bbb",
+ "line-dasharray": [0.2, 8],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.4],
+ ["zoom"],
+ 14.5,
+ 0,
+ 15,
+ 3,
+ 20,
+ 8
+ ]
+ }
+ },
+ {
+ "id": "cablecar",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 13,
+ "filter": ["==", ["get", "subclass"], "cable_car"],
+ "layout": {"line-cap": "round"},
+ "paint": {
+ "line-color": "hsl(0,0%,70%)",
+ "line-width": ["interpolate", ["linear"], ["zoom"], 11, 1, 19, 2.5]
+ }
+ },
+ {
+ "id": "cablecar-dash",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 13,
+ "filter": ["==", ["get", "subclass"], "cable_car"],
+ "layout": {"line-cap": "round"},
+ "paint": {
+ "line-color": "hsl(0,0%,70%)",
+ "line-dasharray": [2, 3],
+ "line-width": ["interpolate", ["linear"], ["zoom"], 11, 3, 19, 5.5]
+ }
+ },
+ {
+ "id": "boundary_3",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "boundary",
+ "minzoom": 5,
+ "filter": [
+ "all",
+ [">=", ["get", "admin_level"], 3],
+ ["<=", ["get", "admin_level"], 6],
+ ["!=", ["get", "maritime"], 1],
+ ["!=", ["get", "disputed"], 1],
+ ["!", ["has", "claimed_by"]]
+ ],
+ "paint": {
+ "line-color": "hsl(0,0%,70%)",
+ "line-dasharray": [1, 1],
+ "line-width": ["interpolate", ["linear", 1], ["zoom"], 7, 1, 11, 2]
+ }
+ },
+ {
+ "id": "boundary_2",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "boundary",
+ "filter": [
+ "all",
+ ["==", ["get", "admin_level"], 2],
+ ["!=", ["get", "maritime"], 1],
+ ["!=", ["get", "disputed"], 1],
+ ["!", ["has", "claimed_by"]]
+ ],
+ "layout": {"line-cap": "round", "line-join": "round"},
+ "paint": {
+ "line-color": "hsl(248,7%,66%)",
+ "line-opacity": ["interpolate", ["linear"], ["zoom"], 0, 0.4, 4, 1],
+ "line-width": ["interpolate", ["linear"], ["zoom"], 3, 1, 5, 1.2, 12, 3]
+ }
+ },
+ {
+ "id": "boundary_disputed",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "boundary",
+ "filter": [
+ "all",
+ ["!=", ["get", "maritime"], 1],
+ ["==", ["get", "disputed"], 1]
+ ],
+ "paint": {
+ "line-color": "hsl(248,7%,66%)",
+ "line-dasharray": [1, 2],
+ "line-width": ["interpolate", ["linear"], ["zoom"], 3, 1, 5, 1.2, 12, 3]
+ }
+ },
+ {
+ "id": "road_oneway",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 15,
+ "filter": [
+ "all",
+ ["==", ["get", "oneway"], 1],
+ [
+ "match",
+ ["get", "class"],
+ [
+ "minor",
+ "motorway",
+ "primary",
+ "secondary",
+ "service",
+ "tertiary",
+ "trunk"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "icon-image": "oneway",
+ "icon-padding": 2,
+ "icon-rotate": 90,
+ "icon-rotation-alignment": "map",
+ "icon-size": ["interpolate", ["linear"], ["zoom"], 15, 0.5, 19, 1],
+ "symbol-placement": "line",
+ "symbol-spacing": 75
+ },
+ "paint": {"icon-opacity": 0.5}
+ },
+ {
+ "id": "road_oneway_opposite",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 15,
+ "filter": [
+ "all",
+ ["==", ["get", "oneway"], -1],
+ [
+ "match",
+ ["get", "class"],
+ [
+ "minor",
+ "motorway",
+ "primary",
+ "secondary",
+ "service",
+ "tertiary",
+ "trunk"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "icon-image": "oneway",
+ "icon-padding": 2,
+ "icon-rotate": -90,
+ "icon-rotation-alignment": "map",
+ "icon-size": ["interpolate", ["linear"], ["zoom"], 15, 0.5, 19, 1],
+ "symbol-placement": "line",
+ "symbol-spacing": 75
+ },
+ "paint": {"icon-opacity": 0.5}
+ },
+ {
+ "id": "waterway_line_label",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "waterway",
+ "minzoom": 10,
+ "filter": [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ "layout": {
+ "symbol-placement": "line",
+ "symbol-spacing": 350,
+ "text-field": [
+ "case",
+ ["has", "name:nonlatin"],
+ ["concat", ["get", "name:latin"], " ", ["get", "name:nonlatin"]],
+ ["coalesce", ["get", "name_en"], ["get", "name"]]
+ ],
+ "text-font": ["Noto Sans Italic"],
+ "text-letter-spacing": 0.2,
+ "text-max-width": 5,
+ "text-size": 14
+ },
+ "paint": {
+ "text-color": "#74aee9",
+ "text-halo-color": "rgba(255,255,255,0.7)",
+ "text-halo-width": 1.5
+ }
+ },
+ {
+ "id": "water_name_point_label",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "water_name",
+ "filter": [
+ "match",
+ ["geometry-type"],
+ ["MultiPoint", "Point"],
+ true,
+ false
+ ],
+ "layout": {
+ "text-field": [
+ "case",
+ ["has", "name:nonlatin"],
+ ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]],
+ ["coalesce", ["get", "name_en"], ["get", "name"]]
+ ],
+ "text-font": ["Noto Sans Italic"],
+ "text-letter-spacing": 0.2,
+ "text-max-width": 5,
+ "text-size": ["interpolate", ["linear"], ["zoom"], 0, 10, 8, 14]
+ },
+ "paint": {
+ "text-color": "#495e91",
+ "text-halo-color": "rgba(255,255,255,0.7)",
+ "text-halo-width": 1.5
+ }
+ },
+ {
+ "id": "water_name_line_label",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "water_name",
+ "filter": [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ "layout": {
+ "symbol-placement": "line",
+ "symbol-spacing": 350,
+ "text-field": [
+ "case",
+ ["has", "name:nonlatin"],
+ ["concat", ["get", "name:latin"], " ", ["get", "name:nonlatin"]],
+ ["coalesce", ["get", "name_en"], ["get", "name"]]
+ ],
+ "text-font": ["Noto Sans Italic"],
+ "text-letter-spacing": 0.2,
+ "text-max-width": 5,
+ "text-size": 14
+ },
+ "paint": {
+ "text-color": "#495e91",
+ "text-halo-color": "rgba(255,255,255,0.7)",
+ "text-halo-width": 1.5
+ }
+ },
+ {
+ "id": "poi_r1",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "poi",
+ "minzoom": 15,
+ "filter": [
+ "all",
+ ["match", ["geometry-type"], ["MultiPoint", "Point"], true, false],
+ [">=", ["get", "rank"], 1],
+ ["<=", ["get", "rank"], 6],
+ ["!=", ["get", "subclass"], "bus_stop"],
+ ["!=", ["get", "subclass"], "tram_stop"],
+ ["!=", ["get", "class"], "shop"]
+ ],
+ "layout": {
+ "icon-image": [
+ "match",
+ ["get", "subclass"],
+ ["florist", "furniture"],
+ ["get", "subclass"],
+ ["get", "class"]
+ ],
+ "text-anchor": "top",
+ "text-field": [
+ "case",
+ ["has", "name:nonlatin"],
+ ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]],
+ ["coalesce", ["get", "name_en"], ["get", "name"]]
+ ],
+ "text-font": ["Noto Sans Italic"],
+ "text-max-width": 9,
+ "text-offset": [0, 0.6],
+ "text-size": 12,
+ "visibility": "visible"
+ },
+ "paint": {
+ "text-color": "#666",
+ "text-halo-blur": 0.5,
+ "text-halo-color": "#ffffff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "highway-name-path",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "transportation_name",
+ "minzoom": 15.5,
+ "filter": ["==", ["get", "class"], "path"],
+ "layout": {
+ "symbol-placement": "line",
+ "text-field": [
+ "case",
+ ["has", "name:nonlatin"],
+ ["concat", ["get", "name:latin"], " ", ["get", "name:nonlatin"]],
+ ["coalesce", ["get", "name_en"], ["get", "name"]]
+ ],
+ "text-font": ["Noto Sans Regular"],
+ "text-rotation-alignment": "map",
+ "text-size": ["interpolate", ["linear"], ["zoom"], 13, 12, 14, 13]
+ },
+ "paint": {
+ "text-color": "hsl(30,23%,62%)",
+ "text-halo-color": "#f8f4f0",
+ "text-halo-width": 0.5
+ }
+ },
+ {
+ "id": "highway-name-minor",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "transportation_name",
+ "minzoom": 15,
+ "filter": [
+ "all",
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["match", ["get", "class"], ["minor", "service", "track"], true, false]
+ ],
+ "layout": {
+ "symbol-placement": "line",
+ "text-field": [
+ "case",
+ ["has", "name:nonlatin"],
+ ["concat", ["get", "name:latin"], " ", ["get", "name:nonlatin"]],
+ ["coalesce", ["get", "name_en"], ["get", "name"]]
+ ],
+ "text-font": ["Noto Sans Regular"],
+ "text-rotation-alignment": "map",
+ "text-size": ["interpolate", ["linear"], ["zoom"], 13, 12, 14, 13]
+ },
+ "paint": {
+ "text-color": "#666",
+ "text-halo-blur": 0.5,
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "highway-name-major",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "transportation_name",
+ "minzoom": 12.2,
+ "filter": [
+ "match",
+ ["get", "class"],
+ ["primary", "secondary", "tertiary", "trunk"],
+ true,
+ false
+ ],
+ "layout": {
+ "symbol-placement": "line",
+ "text-field": [
+ "case",
+ ["has", "name:nonlatin"],
+ ["concat", ["get", "name:latin"], " ", ["get", "name:nonlatin"]],
+ ["coalesce", ["get", "name_en"], ["get", "name"]]
+ ],
+ "text-font": ["Noto Sans Regular"],
+ "text-rotation-alignment": "map",
+ "text-size": ["interpolate", ["linear"], ["zoom"], 13, 12, 14, 13]
+ },
+ "paint": {
+ "text-color": "#666",
+ "text-halo-blur": 0.5,
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "highway-shield-non-us",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "transportation_name",
+ "minzoom": 8,
+ "filter": [
+ "all",
+ ["<=", ["get", "ref_length"], 6],
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ [
+ "match",
+ ["get", "network"],
+ ["us-highway", "us-interstate", "us-state"],
+ false,
+ true
+ ]
+ ],
+ "layout": {
+ "icon-image": ["concat", "road_", ["get", "ref_length"]],
+ "icon-rotation-alignment": "viewport",
+ "icon-size": 1,
+ "symbol-placement": ["step", ["zoom"], "point", 11, "line"],
+ "symbol-spacing": 200,
+ "text-field": ["to-string", ["get", "ref"]],
+ "text-font": ["Noto Sans Regular"],
+ "text-rotation-alignment": "viewport",
+ "text-size": 10
+ }
+ },
+ {
+ "id": "highway-shield-us-interstate",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "transportation_name",
+ "minzoom": 7,
+ "filter": [
+ "all",
+ ["<=", ["get", "ref_length"], 6],
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["match", ["get", "network"], ["us-interstate"], true, false]
+ ],
+ "layout": {
+ "icon-image": [
+ "concat",
+ ["get", "network"],
+ "_",
+ ["get", "ref_length"]
+ ],
+ "icon-rotation-alignment": "viewport",
+ "icon-size": 1,
+ "symbol-placement": ["step", ["zoom"], "point", 7, "line", 8, "line"],
+ "symbol-spacing": 200,
+ "text-field": ["to-string", ["get", "ref"]],
+ "text-font": ["Noto Sans Regular"],
+ "text-rotation-alignment": "viewport",
+ "text-size": 10
+ }
+ },
+ {
+ "id": "road_shield_us",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "transportation_name",
+ "minzoom": 9,
+ "filter": [
+ "all",
+ ["<=", ["get", "ref_length"], 6],
+ [
+ "match",
+ ["geometry-type"],
+ ["LineString", "MultiLineString"],
+ true,
+ false
+ ],
+ ["match", ["get", "network"], ["us-highway", "us-state"], true, false]
+ ],
+ "layout": {
+ "icon-image": [
+ "concat",
+ ["get", "network"],
+ "_",
+ ["get", "ref_length"]
+ ],
+ "icon-rotation-alignment": "viewport",
+ "icon-size": 1,
+ "symbol-placement": ["step", ["zoom"], "point", 11, "line"],
+ "symbol-spacing": 200,
+ "text-field": ["to-string", ["get", "ref"]],
+ "text-font": ["Noto Sans Regular"],
+ "text-rotation-alignment": "viewport",
+ "text-size": 10
+ }
+ },
+ {
+ "id": "airport",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "aerodrome_label",
+ "minzoom": 10,
+ "filter": ["all", ["has", "iata"]],
+ "layout": {
+ "icon-image": "airport_11",
+ "icon-size": 1,
+ "text-anchor": "top",
+ "text-field": [
+ "case",
+ ["has", "name:nonlatin"],
+ ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]],
+ ["coalesce", ["get", "name_en"], ["get", "name"]]
+ ],
+ "text-font": ["Noto Sans Regular"],
+ "text-max-width": 9,
+ "text-offset": [0, 0.6],
+ "text-optional": true,
+ "text-padding": 2,
+ "text-size": 12
+ },
+ "paint": {
+ "text-color": "#666",
+ "text-halo-blur": 0.5,
+ "text-halo-color": "#ffffff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "label_other",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "minzoom": 8,
+ "filter": [
+ "match",
+ ["get", "class"],
+ ["city", "continent", "country", "state", "town", "village"],
+ false,
+ true
+ ],
+ "layout": {
+ "text-field": [
+ "case",
+ ["has", "name:nonlatin"],
+ ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]],
+ ["coalesce", ["get", "name_en"], ["get", "name"]]
+ ],
+ "text-font": ["Noto Sans Italic"],
+ "text-letter-spacing": 0.1,
+ "text-max-width": 9,
+ "text-size": ["interpolate", ["linear"], ["zoom"], 8, 9, 12, 10],
+ "text-transform": "uppercase"
+ },
+ "paint": {
+ "text-color": "#333",
+ "text-halo-blur": 1,
+ "text-halo-color": "#fff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "label_village",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "minzoom": 9,
+ "filter": ["==", ["get", "class"], "village"],
+ "layout": {
+ "icon-allow-overlap": true,
+ "icon-image": ["step", ["zoom"], "circle_11_black", 10, ""],
+ "icon-optional": false,
+ "icon-size": 0.2,
+ "text-anchor": "bottom",
+ "text-field": [
+ "case",
+ ["has", "name:nonlatin"],
+ ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]],
+ ["coalesce", ["get", "name_en"], ["get", "name"]]
+ ],
+ "text-font": ["Noto Sans Regular"],
+ "text-max-width": 8,
+ "text-size": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 7,
+ 10,
+ 11,
+ 12
+ ]
+ },
+ "paint": {
+ "text-color": "#000",
+ "text-halo-blur": 1,
+ "text-halo-color": "#fff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "label_town",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "minzoom": 6,
+ "filter": ["==", ["get", "class"], "town"],
+ "layout": {
+ "icon-allow-overlap": true,
+ "icon-image": ["step", ["zoom"], "circle_11_black", 10, ""],
+ "icon-optional": false,
+ "icon-size": 0.2,
+ "text-anchor": "bottom",
+ "text-field": [
+ "case",
+ ["has", "name:nonlatin"],
+ ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]],
+ ["coalesce", ["get", "name_en"], ["get", "name"]]
+ ],
+ "text-font": ["Noto Sans Regular"],
+ "text-max-width": 8,
+ "text-size": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 7,
+ 12,
+ 11,
+ 14
+ ]
+ },
+ "paint": {
+ "text-color": "#000",
+ "text-halo-blur": 1,
+ "text-halo-color": "#fff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "label_state",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "minzoom": 5,
+ "maxzoom": 8,
+ "filter": ["==", ["get", "class"], "state"],
+ "layout": {
+ "text-field": [
+ "case",
+ ["has", "name:nonlatin"],
+ ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]],
+ ["coalesce", ["get", "name_en"], ["get", "name"]]
+ ],
+ "text-font": ["Noto Sans Italic"],
+ "text-letter-spacing": 0.2,
+ "text-max-width": 9,
+ "text-size": ["interpolate", ["linear"], ["zoom"], 5, 10, 8, 14],
+ "text-transform": "uppercase"
+ },
+ "paint": {
+ "text-color": "#333",
+ "text-halo-blur": 1,
+ "text-halo-color": "#fff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "label_city",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "minzoom": 3,
+ "filter": [
+ "all",
+ ["==", ["get", "class"], "city"],
+ ["!=", ["get", "capital"], 2]
+ ],
+ "layout": {
+ "icon-allow-overlap": true,
+ "icon-image": ["step", ["zoom"], "circle_11_black", 9, ""],
+ "icon-optional": false,
+ "icon-size": 0.4,
+ "text-anchor": "bottom",
+ "text-field": [
+ "case",
+ ["has", "name:nonlatin"],
+ ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]],
+ ["coalesce", ["get", "name_en"], ["get", "name"]]
+ ],
+ "text-font": ["Noto Sans Regular"],
+ "text-max-width": 8,
+ "text-offset": [0, -0.1],
+ "text-size": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 4,
+ 11,
+ 7,
+ 13,
+ 11,
+ 18
+ ]
+ },
+ "paint": {
+ "text-color": "#000",
+ "text-halo-blur": 1,
+ "text-halo-color": "#fff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "label_city_capital",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "minzoom": 3,
+ "filter": [
+ "all",
+ ["==", ["get", "class"], "city"],
+ ["==", ["get", "capital"], 2]
+ ],
+ "layout": {
+ "icon-allow-overlap": true,
+ "icon-image": ["step", ["zoom"], "circle_11_black", 9, ""],
+ "icon-optional": false,
+ "icon-size": 0.5,
+ "text-anchor": "bottom",
+ "text-field": [
+ "case",
+ ["has", "name:nonlatin"],
+ ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]],
+ ["coalesce", ["get", "name_en"], ["get", "name"]]
+ ],
+ "text-font": ["Noto Sans Bold"],
+ "text-max-width": 8,
+ "text-offset": [0, -0.2],
+ "text-size": [
+ "interpolate",
+ ["exponential", 1.2],
+ ["zoom"],
+ 4,
+ 12,
+ 7,
+ 14,
+ 11,
+ 20
+ ]
+ },
+ "paint": {
+ "text-color": "#000",
+ "text-halo-blur": 1,
+ "text-halo-color": "#fff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "label_country_3",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "minzoom": 2,
+ "maxzoom": 9,
+ "filter": [
+ "all",
+ ["==", ["get", "class"], "country"],
+ [">=", ["get", "rank"], 3]
+ ],
+ "layout": {
+ "text-field": [
+ "case",
+ ["has", "name:nonlatin"],
+ ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]],
+ ["coalesce", ["get", "name_en"], ["get", "name"]]
+ ],
+ "text-font": ["Noto Sans Bold"],
+ "text-max-width": 6.25,
+ "text-size": ["interpolate", ["linear"], ["zoom"], 3, 9, 7, 17]
+ },
+ "paint": {
+ "text-color": "#000",
+ "text-halo-blur": 1,
+ "text-halo-color": "#fff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "label_country_2",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "maxzoom": 9,
+ "filter": [
+ "all",
+ ["==", ["get", "class"], "country"],
+ ["==", ["get", "rank"], 2]
+ ],
+ "layout": {
+ "text-field": [
+ "case",
+ ["has", "name:nonlatin"],
+ ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]],
+ ["coalesce", ["get", "name_en"], ["get", "name"]]
+ ],
+ "text-font": ["Noto Sans Bold"],
+ "text-max-width": 6.25,
+ "text-size": ["interpolate", ["linear"], ["zoom"], 2, 9, 5, 17]
+ },
+ "paint": {
+ "text-color": "#000",
+ "text-halo-blur": 1,
+ "text-halo-color": "#fff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "label_country_1",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "maxzoom": 9,
+ "filter": [
+ "all",
+ ["==", ["get", "class"], "country"],
+ ["==", ["get", "rank"], 1]
+ ],
+ "layout": {
+ "text-field": [
+ "case",
+ ["has", "name:nonlatin"],
+ ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]],
+ ["coalesce", ["get", "name_en"], ["get", "name"]]
+ ],
+ "text-font": ["Noto Sans Bold"],
+ "text-max-width": 6.25,
+ "text-size": ["interpolate", ["linear"], ["zoom"], 1, 9, 4, 17]
+ },
+ "paint": {
+ "text-color": "#000",
+ "text-halo-blur": 1,
+ "text-halo-color": "#fff",
+ "text-halo-width": 1
+ }
+ }
+ ],
+ "id": "94huyrm"
+}
\ No newline at end of file
diff --git a/app/src/main/assets/openstreetmap_raster.json b/app/src/main/assets/openstreetmap_raster.json
new file mode 100644
--- /dev/null
+++ b/app/src/main/assets/openstreetmap_raster.json
@@ -0,0 +1,21 @@
+{
+ "version": 8,
+ "sources": {
+ "osm": {
+ "type": "raster",
+ "tiles": ["https://a.tile.openstreetmap.org/{z}/{x}/{y}.png","https://b.tile.openstreetmap.org/{z}/{x}/{y}.png"],
+ "tileSize": 256,
+ "attribution": "&copy; OpenStreetMap Contributors",
+ "maxzoom": 19
+ }
+ },
+ "glyphs": "https://tiles.versatiles.org/assets/glyphs/{fontstack}/{range}.pbf",
+
+ "layers": [
+ {
+ "id": "osm-raster",
+ "type": "raster",
+ "source": "osm"
+ }
+ ]
+}
diff --git a/app/src/main/assets/versatiles_colorful_light.json b/app/src/main/assets/versatiles_colorful_light.json
new file mode 100644
--- /dev/null
+++ b/app/src/main/assets/versatiles_colorful_light.json
@@ -0,0 +1,5956 @@
+{
+ "version": 8,
+ "name": "versatiles-colorful",
+ "metadata": {
+ "license": "https://creativecommons.org/publicdomain/zero/1.0/",
+ "maputnik:renderer": "mlgljs"
+ },
+ "sources": {
+ "versatiles-shortbread": {
+ "attribution": "© <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> 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": "rgb(249,244,238)"}
+ },
+ {
+ "id": "water-ocean",
+ "type": "fill",
+ "source": "versatiles-shortbread",
+ "source-layer": "ocean",
+ "paint": {"fill-color": "rgb(190,221,243)"}
+ },
+ {
+ "id": "land-glacier",
+ "type": "fill",
+ "source": "versatiles-shortbread",
+ "source-layer": "water_polygons",
+ "filter": ["all", ["==", "kind", "glacier"]],
+ "paint": {"fill-color": "rgb(255,255,255)"}
+ },
+ {
+ "id": "land-commercial",
+ "type": "fill",
+ "source": "versatiles-shortbread",
+ "source-layer": "land",
+ "filter": ["all", ["in", "kind", "commercial", "retail"]],
+ "paint": {
+ "fill-color": "rgba(247,222,237,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": "rgba(255,244,194,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": "rgba(234,230,225,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": "rgb(240,231,209)",
+ "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": "rgb(219,214,189)",
+ "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": "rgb(217,217,165)",
+ "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": "rgb(217,217,165)",
+ "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": "rgb(221,219,202)",
+ "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": "rgb(231,237,222)"}
+ },
+ {
+ "id": "land-rock",
+ "type": "fill",
+ "source": "versatiles-shortbread",
+ "source-layer": "land",
+ "filter": ["all", ["in", "kind", "bare_rock", "scree", "shingle"]],
+ "paint": {"fill-color": "rgb(224,228,229)"}
+ },
+ {
+ "id": "land-forest",
+ "type": "fill",
+ "source": "versatiles-shortbread",
+ "source-layer": "land",
+ "filter": ["all", ["in", "kind", "forest"]],
+ "paint": {
+ "fill-color": "rgb(102,170,68)",
+ "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": "rgb(216,232,200)",
+ "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": "rgb(217,217,165)",
+ "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": "rgb(250,250,237)"}
+ },
+ {
+ "id": "land-wetland",
+ "type": "fill",
+ "source": "versatiles-shortbread",
+ "source-layer": "land",
+ "filter": ["all", ["in", "kind", "bog", "marsh", "string_bog", "swamp"]],
+ "paint": {"fill-color": "rgb(211,230,219)"}
+ },
+ {
+ "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": "rgb(190,221,243)",
+ "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": "rgb(190,221,243)",
+ "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": "rgb(190,221,243)",
+ "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": "rgb(190,221,243)",
+ "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": "rgb(190,221,243)",
+ "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": "rgb(190,221,243)",
+ "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": "rgb(190,221,243)",
+ "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": "rgb(249,244,238)",
+ "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": "rgb(190,221,243)"}
+ },
+ {
+ "id": "water-pier-area",
+ "type": "fill",
+ "source": "versatiles-shortbread",
+ "source-layer": "pier_polygons",
+ "filter": ["in", "kind", "pier", "breakwater", "groyne"],
+ "paint": {
+ "fill-color": "rgb(249,244,238)",
+ "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": "rgb(249,244,238)"}
+ },
+ {
+ "id": "site-dangerarea",
+ "type": "fill",
+ "source": "versatiles-shortbread",
+ "source-layer": "sites",
+ "filter": ["in", "kind", "danger_area"],
+ "paint": {
+ "fill-color": "rgb(255,0,0)",
+ "fill-outline-color": "rgb(255,0,0)",
+ "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": "rgb(255,255,128)", "fill-opacity": 0.1}
+ },
+ {
+ "id": "site-college",
+ "type": "fill",
+ "source": "versatiles-shortbread",
+ "source-layer": "sites",
+ "filter": ["in", "kind", "college"],
+ "paint": {"fill-color": "rgb(255,255,128)", "fill-opacity": 0.1}
+ },
+ {
+ "id": "site-school",
+ "type": "fill",
+ "source": "versatiles-shortbread",
+ "source-layer": "sites",
+ "filter": ["in", "kind", "school"],
+ "paint": {"fill-color": "rgb(255,255,128)", "fill-opacity": 0.1}
+ },
+ {
+ "id": "site-hospital",
+ "type": "fill",
+ "source": "versatiles-shortbread",
+ "source-layer": "sites",
+ "filter": ["in", "kind", "hospital"],
+ "paint": {"fill-color": "rgb(255,102,102)", "fill-opacity": 0.1}
+ },
+ {
+ "id": "site-prison",
+ "type": "fill",
+ "source": "versatiles-shortbread",
+ "source-layer": "sites",
+ "filter": ["in", "kind", "prison"],
+ "paint": {
+ "fill-color": "rgb(253,242,252)",
+ "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": "rgb(235,232,230)"}
+ },
+ {
+ "id": "site-bicycleparking",
+ "type": "fill",
+ "source": "versatiles-shortbread",
+ "source-layer": "sites",
+ "filter": ["in", "kind", "bicycle_parking"],
+ "paint": {"fill-color": "rgb(235,232,230)"}
+ },
+ {
+ "id": "site-construction",
+ "type": "fill",
+ "source": "versatiles-shortbread",
+ "source-layer": "sites",
+ "filter": ["in", "kind", "construction"],
+ "paint": {
+ "fill-color": "rgb(169,169,169)",
+ "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": "rgb(255,255,255)", "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": "rgb(207,205,202)",
+ "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": "rgb(207,205,202)",
+ "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": "rgb(255,255,255)",
+ "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": "rgb(255,255,255)",
+ "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": "rgb(223,219,215)",
+ "fill-opacity": {"stops": [[14, 0], [15, 1]]}
+ }
+ },
+ {
+ "id": "building",
+ "type": "fill",
+ "source": "versatiles-shortbread",
+ "source-layer": "buildings",
+ "paint": {
+ "fill-color": "rgb(242,234,226)",
+ "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(247,247,247)",
+ "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%,86%)"
+ }
+ },
+ {
+ "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%,86%)"
+ }
+ },
+ {
+ "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%,86%)"
+ }
+ },
+ {
+ "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%,87%)"
+ }
+ },
+ {
+ "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(222,222,222)",
+ "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(222,222,222)",
+ "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(221,220,218)",
+ "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(222,222,222)",
+ "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(222,222,222)",
+ "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(222,222,222)",
+ "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-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(222,222,222)",
+ "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(234,176,126)",
+ "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(234,176,126)",
+ "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(234,176,126)",
+ "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(234,176,126)",
+ "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(222,222,222)",
+ "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(234,176,126)",
+ "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(234,176,126)",
+ "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(234,176,126)",
+ "line-dasharray": [1, 0.3],
+ "line-width": {
+ "stops": [
+ [7, 0],
+ [8, 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(234,176,126)",
+ "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%,94%)",
+ "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%,94%)",
+ "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%,94%)",
+ "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%,95%)",
+ "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(247,247,247)",
+ "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(247,247,247)",
+ "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(247,247,247)",
+ "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(247,247,247)",
+ "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(247,247,247)",
+ "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(247,247,247)",
+ "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-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(247,247,247)"}
+ },
+ {
+ "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": "rgb(239,249,255)",
+ "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(247,247,247)"}
+ },
+ {
+ "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": "rgb(239,249,255)",
+ "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": "rgb(239,249,255)",
+ "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": "rgb(239,249,255)",
+ "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(247,247,247)",
+ "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(255,240,179)",
+ "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(255,240,179)",
+ "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(255,240,179)",
+ "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(255,209,148)",
+ "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(247,247,247)",
+ "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(255,240,179)",
+ "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(255,240,179)",
+ "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(255,240,179)",
+ "line-width": {
+ "stops": [
+ [7, 0],
+ [8, 1],
+ [10, 3],
+ [14, 5],
+ [16, 10],
+ [18, 34],
+ [19, 70],
+ [20, 140]
+ ]
+ },
+ "line-opacity": {"stops": [[7, 0], [8, 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(255,209,148)",
+ "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": "rgb(177,187,196)",
+ "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": "rgb(177,187,196)",
+ "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": "rgb(166,184,199)",
+ "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": "rgb(177,187,196)",
+ "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": "rgb(177,187,196)",
+ "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": "rgb(177,187,196)",
+ "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": "rgb(177,187,196)",
+ "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": "rgb(177,187,196)",
+ "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": "rgb(177,187,196)",
+ "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": "rgb(177,187,196)"
+ }
+ },
+ {
+ "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": "rgb(177,187,196)"
+ }
+ },
+ {
+ "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(188,202,213)",
+ "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(197,204,211)",
+ "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(197,204,211)",
+ "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(197,204,211)",
+ "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(197,204,211)",
+ "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": "rgb(177,187,196)"
+ }
+ },
+ {
+ "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": "rgb(177,187,196)"
+ }
+ },
+ {
+ "id": "bridge",
+ "type": "fill",
+ "source": "versatiles-shortbread",
+ "source-layer": "bridges",
+ "paint": {
+ "fill-color": "rgb(244,239,233)",
+ "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(251,235,255,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(226,212,230)"
+ }
+ },
+ {
+ "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(226,212,230)"
+ }
+ },
+ {
+ "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(226,212,230)"
+ }
+ },
+ {
+ "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(215,224,230)"
+ }
+ },
+ {
+ "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": "rgb(207,205,202)",
+ "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": "rgb(207,205,202)",
+ "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(221,220,218)",
+ "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": "rgb(207,205,202)",
+ "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": "rgb(207,205,202)",
+ "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": "rgb(207,205,202)",
+ "line-width": {
+ "stops": [[12, 2], [14, 3], [16, 6], [18, 26], [19, 64], [20, 128]]
+ },
+ "line-opacity": {"stops": [[12, 0], [13, 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": "rgb(207,205,202)",
+ "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": "rgb(233,172,119)",
+ "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": "rgb(233,172,119)",
+ "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": "rgb(233,172,119)",
+ "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": "rgb(233,172,119)",
+ "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": "rgb(207,205,202)",
+ "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": "rgb(233,172,119)",
+ "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": "rgb(233,172,119)",
+ "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": "rgb(233,172,119)",
+ "line-width": {
+ "stops": [
+ [7, 0],
+ [8, 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": "rgb(233,172,119)",
+ "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(251,235,255)"
+ }
+ },
+ {
+ "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(251,235,255)"
+ }
+ },
+ {
+ "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(251,235,255)"
+ }
+ },
+ {
+ "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": "rgb(239,249,255)"
+ }
+ },
+ {
+ "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": "rgb(255,255,255)",
+ "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": "rgb(251,235,255)",
+ "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(247,247,247)",
+ "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": "rgb(255,255,255)",
+ "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": "rgb(255,255,255)",
+ "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": "rgb(255,255,255)",
+ "line-width": {
+ "stops": [[12, 1], [14, 2], [16, 5], [18, 24], [19, 60], [20, 120]]
+ },
+ "line-opacity": {"stops": [[12, 0], [13, 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": "rgb(255,255,255)"}
+ },
+ {
+ "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": "rgb(239,249,255)",
+ "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": "rgb(255,255,255)"}
+ },
+ {
+ "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": "rgb(239,249,255)",
+ "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": "rgb(239,249,255)",
+ "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": "rgb(239,249,255)",
+ "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": "rgb(255,255,255)",
+ "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": "rgb(255,238,170)",
+ "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": "rgb(255,238,170)",
+ "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": "rgb(255,238,170)",
+ "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": "rgb(255,204,136)",
+ "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": "rgb(255,255,255)",
+ "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": "rgb(255,238,170)",
+ "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": "rgb(255,238,170)",
+ "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": "rgb(255,238,170)",
+ "line-width": {
+ "stops": [
+ [7, 0],
+ [8, 1],
+ [10, 3],
+ [14, 5],
+ [16, 10],
+ [18, 34],
+ [19, 70],
+ [20, 140]
+ ]
+ },
+ "line-opacity": {"stops": [[7, 0], [8, 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": "rgb(255,204,136)",
+ "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": "rgb(177,187,196)",
+ "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": "rgb(177,187,196)",
+ "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": "rgb(166,184,199)",
+ "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": "rgb(177,187,196)",
+ "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": "rgb(177,187,196)",
+ "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": "rgb(177,187,196)",
+ "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": "rgb(177,187,196)",
+ "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": "rgb(177,187,196)",
+ "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": "rgb(177,187,196)",
+ "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": "rgb(177,187,196)"
+ }
+ },
+ {
+ "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": "rgb(177,187,196)"
+ }
+ },
+ {
+ "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(188,202,213)",
+ "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(197,204,211)",
+ "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(197,204,211)",
+ "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(197,204,211)",
+ "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(197,204,211)",
+ "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": "rgb(177,187,196)"
+ }
+ },
+ {
+ "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": "rgb(177,187,196)"
+ }
+ },
+ {
+ "id": "transport-ferry",
+ "type": "line",
+ "source": "versatiles-shortbread",
+ "source-layer": "ferries",
+ "minzoom": 10,
+ "paint": {
+ "line-color": "rgb(171,199,219)",
+ "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(244,239,233)",
+ "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(244,239,233)",
+ "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(244,239,233)",
+ "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(244,239,233)",
+ "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(244,239,233)",
+ "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(244,239,233)",
+ "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(244,239,233)",
+ "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(244,239,233)",
+ "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(244,239,233)",
+ "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(244,239,233)",
+ "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-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(244,239,233)",
+ "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(244,239,233)",
+ "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(244,239,233)",
+ "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(244,239,233)",
+ "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(244,239,233)",
+ "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(244,239,233)",
+ "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(244,239,233)",
+ "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(244,239,233)",
+ "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(244,239,233)",
+ "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(244,239,233)",
+ "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": "rgb(255,255,255)",
+ "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(226,212,230)"
+ }
+ },
+ {
+ "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(226,212,230)"
+ }
+ },
+ {
+ "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(226,212,230)"
+ }
+ },
+ {
+ "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(215,224,230)"
+ }
+ },
+ {
+ "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(217,217,217)",
+ "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(217,217,217)",
+ "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(221,220,218)",
+ "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(217,217,217)",
+ "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(217,217,217)",
+ "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(217,217,217)",
+ "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-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(217,217,217)",
+ "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": "rgb(233,172,119)",
+ "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": "rgb(233,172,119)",
+ "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": "rgb(233,172,119)",
+ "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": "rgb(233,172,119)",
+ "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(217,217,217)",
+ "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": "rgb(233,172,119)",
+ "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": "rgb(233,172,119)",
+ "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": "rgb(233,172,119)",
+ "line-width": {
+ "stops": [
+ [7, 0],
+ [8, 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": "rgb(233,172,119)",
+ "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(251,235,255)"
+ }
+ },
+ {
+ "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(251,235,255)"
+ }
+ },
+ {
+ "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(251,235,255)"
+ }
+ },
+ {
+ "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": "rgb(239,249,255)"
+ }
+ },
+ {
+ "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": "rgb(255,255,255)",
+ "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": "rgb(255,255,255)",
+ "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(247,247,247)",
+ "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": "rgb(255,255,255)",
+ "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": "rgb(255,255,255)",
+ "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": "rgb(255,255,255)",
+ "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-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": "rgb(255,255,255)"}
+ },
+ {
+ "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": "rgb(239,249,255)",
+ "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": "rgb(255,255,255)"}
+ },
+ {
+ "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": "rgb(239,249,255)",
+ "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": "rgb(239,249,255)",
+ "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": "rgb(239,249,255)",
+ "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": "rgb(255,255,255)",
+ "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": "rgb(255,238,170)",
+ "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": "rgb(255,238,170)",
+ "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": "rgb(255,238,170)",
+ "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": "rgb(255,204,136)",
+ "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": "rgb(255,255,255)",
+ "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": "rgb(255,238,170)",
+ "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": "rgb(255,238,170)",
+ "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": "rgb(255,238,170)",
+ "line-width": {
+ "stops": [
+ [7, 0],
+ [8, 1],
+ [10, 3],
+ [14, 5],
+ [16, 10],
+ [18, 34],
+ [19, 70],
+ [20, 140]
+ ]
+ },
+ "line-opacity": {"stops": [[7, 0], [8, 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": "rgb(255,204,136)",
+ "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": "rgb(177,187,196)",
+ "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": "rgb(177,187,196)",
+ "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": "rgb(166,184,199)",
+ "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": "rgb(177,187,196)",
+ "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": "rgb(177,187,196)",
+ "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": "rgb(177,187,196)",
+ "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": "rgb(177,187,196)",
+ "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": "rgb(177,187,196)",
+ "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": "rgb(177,187,196)",
+ "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": "rgb(177,187,196)"
+ }
+ },
+ {
+ "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": "rgb(177,187,196)"
+ }
+ },
+ {
+ "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(188,202,213)",
+ "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(197,204,211)",
+ "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(197,204,211)",
+ "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(197,204,211)",
+ "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(197,204,211)",
+ "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": "rgb(177,187,196)"
+ }
+ },
+ {
+ "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": "rgb(177,187,196)"
+ }
+ },
+ {
+ "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": "rgb(85,85,85)",
+ "text-color": "rgb(85,85,85)"
+ }
+ },
+ {
+ "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": "rgb(85,85,85)",
+ "text-color": "rgb(85,85,85)"
+ }
+ },
+ {
+ "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": "rgb(85,85,85)",
+ "text-color": "rgb(85,85,85)"
+ }
+ },
+ {
+ "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": "rgb(85,85,85)",
+ "text-color": "rgb(85,85,85)"
+ }
+ },
+ {
+ "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": "rgb(85,85,85)",
+ "text-color": "rgb(85,85,85)"
+ }
+ },
+ {
+ "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": "rgb(85,85,85)",
+ "text-color": "rgb(85,85,85)"
+ }
+ },
+ {
+ "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": "rgb(85,85,85)",
+ "text-color": "rgb(85,85,85)"
+ }
+ },
+ {
+ "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": "rgb(85,85,85)",
+ "text-color": "rgb(85,85,85)"
+ }
+ },
+ {
+ "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": "rgb(85,85,85)",
+ "text-color": "rgb(85,85,85)"
+ }
+ },
+ {
+ "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(249,245,239)",
+ "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(249,245,239)"
+ }
+ },
+ {
+ "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(250,245,240)",
+ "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": "rgb(166,166,200)",
+ "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": "rgb(190,188,207)",
+ "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": "rgb(166,166,200)",
+ "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": {
+ "text-halo-color": "rgb(243,235,227)",
+ "text-halo-width": 2,
+ "text-halo-blur": 1,
+ "icon-color": "rgb(169,164,158)",
+ "text-color": "rgb(169,164,158)"
+ }
+ },
+ {
+ "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": "rgb(255,255,255)",
+ "text-color": "rgb(255,255,255)",
+ "text-halo-color": "rgb(255,204,136)",
+ "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": "{name}",
+ "text-font": ["noto_sans_regular"],
+ "symbol-placement": "line",
+ "text-anchor": "center",
+ "text-size": {"stops": [[12, 10], [15, 13]]}
+ },
+ "paint": {
+ "icon-color": "rgb(51,51,68)",
+ "text-color": "rgb(51,51,68)",
+ "text-halo-color": "rgba(255,255,255,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": "{name}",
+ "text-font": ["noto_sans_regular"],
+ "symbol-placement": "line",
+ "text-anchor": "center",
+ "text-size": {"stops": [[12, 10], [15, 13]]}
+ },
+ "paint": {
+ "icon-color": "rgb(51,51,68)",
+ "text-color": "rgb(51,51,68)",
+ "text-halo-color": "rgba(255,255,255,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": "{name}",
+ "text-font": ["noto_sans_regular"],
+ "symbol-placement": "line",
+ "text-anchor": "center",
+ "text-size": {"stops": [[12, 10], [15, 13]]}
+ },
+ "paint": {
+ "icon-color": "rgb(51,51,68)",
+ "text-color": "rgb(51,51,68)",
+ "text-halo-color": "rgba(255,255,255,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": "{name}",
+ "text-font": ["noto_sans_regular"],
+ "symbol-placement": "line",
+ "text-anchor": "center",
+ "text-size": {"stops": [[12, 10], [15, 13]]}
+ },
+ "paint": {
+ "icon-color": "rgb(51,51,68)",
+ "text-color": "rgb(51,51,68)",
+ "text-halo-color": "rgba(255,255,255,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": "{name}",
+ "text-font": ["noto_sans_regular"],
+ "symbol-placement": "line",
+ "text-anchor": "center",
+ "text-size": {"stops": [[12, 10], [15, 13]]}
+ },
+ "paint": {
+ "icon-color": "rgb(51,51,68)",
+ "text-color": "rgb(51,51,68)",
+ "text-halo-color": "rgba(255,255,255,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": "{name}",
+ "text-font": ["noto_sans_regular"],
+ "symbol-placement": "line",
+ "text-anchor": "center",
+ "text-size": {"stops": [[12, 10], [15, 13]]}
+ },
+ "paint": {
+ "icon-color": "rgb(51,51,68)",
+ "text-color": "rgb(51,51,68)",
+ "text-halo-color": "rgba(255,255,255,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": "{name}",
+ "text-font": ["noto_sans_regular"],
+ "symbol-placement": "line",
+ "text-anchor": "center",
+ "text-size": {"stops": [[12, 10], [15, 13]]}
+ },
+ "paint": {
+ "icon-color": "rgb(51,51,68)",
+ "text-color": "rgb(51,51,68)",
+ "text-halo-color": "rgba(255,255,255,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": "{name}",
+ "text-font": ["noto_sans_regular"],
+ "symbol-placement": "line",
+ "text-anchor": "center",
+ "text-size": {"stops": [[12, 10], [15, 13]]}
+ },
+ "paint": {
+ "icon-color": "rgb(51,51,68)",
+ "text-color": "rgb(51,51,68)",
+ "text-halo-color": "rgba(255,255,255,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": "{name}",
+ "text-font": ["noto_sans_regular"],
+ "text-size": {"stops": [[14, 12]]},
+ "text-transform": "uppercase"
+ },
+ "paint": {
+ "icon-color": "rgb(40,67,73)",
+ "text-color": "rgb(40,67,73)",
+ "text-halo-color": "rgba(255,255,255,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": "{name}",
+ "text-font": ["noto_sans_regular"],
+ "text-size": {"stops": [[13, 13]]},
+ "text-transform": "uppercase"
+ },
+ "paint": {
+ "icon-color": "rgb(40,62,73)",
+ "text-color": "rgb(40,62,73)",
+ "text-halo-color": "rgba(255,255,255,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": "{name}",
+ "text-font": ["noto_sans_regular"],
+ "text-size": {"stops": [[11, 11], [13, 14]]},
+ "text-transform": "uppercase"
+ },
+ "paint": {
+ "icon-color": "rgb(40,57,73)",
+ "text-color": "rgb(40,57,73)",
+ "text-halo-color": "rgba(255,255,255,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": "{name}",
+ "text-font": ["noto_sans_regular"],
+ "text-size": {"stops": [[10, 11], [12, 14]]}
+ },
+ "paint": {
+ "icon-color": "rgb(40,48,73)",
+ "text-color": "rgb(40,48,73)",
+ "text-halo-color": "rgba(255,255,255,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": "{name}",
+ "text-font": ["noto_sans_regular"],
+ "text-size": {"stops": [[9, 11], [12, 14]]}
+ },
+ "paint": {
+ "icon-color": "rgb(40,48,73)",
+ "text-color": "rgb(40,48,73)",
+ "text-halo-color": "rgba(255,255,255,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": "{name}",
+ "text-font": ["noto_sans_regular"],
+ "text-size": {"stops": [[8, 11], [12, 14]]}
+ },
+ "paint": {
+ "icon-color": "rgb(40,48,73)",
+ "text-color": "rgb(40,48,73)",
+ "text-halo-color": "rgba(255,255,255,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": "{name}",
+ "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(61,61,77)",
+ "text-color": "rgb(61,61,77)",
+ "text-halo-color": "rgba(255,255,255,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": "{name}",
+ "text-font": ["noto_sans_regular"],
+ "text-size": {"stops": [[7, 11], [10, 14]]}
+ },
+ "paint": {
+ "icon-color": "rgb(40,48,73)",
+ "text-color": "rgb(40,48,73)",
+ "text-halo-color": "rgba(255,255,255,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": "{name}",
+ "text-font": ["noto_sans_regular"],
+ "text-size": {"stops": [[6, 11], [10, 15]]}
+ },
+ "paint": {
+ "icon-color": "rgb(40,48,73)",
+ "text-color": "rgb(40,48,73)",
+ "text-halo-color": "rgba(255,255,255,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": "{name}",
+ "text-font": ["noto_sans_regular"],
+ "text-size": {"stops": [[5, 12], [10, 16]]}
+ },
+ "paint": {
+ "icon-color": "rgb(40,48,73)",
+ "text-color": "rgb(40,48,73)",
+ "text-halo-color": "rgba(255,255,255,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": "{name}",
+ "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": "rgb(51,51,68)",
+ "text-color": "rgb(51,51,68)",
+ "text-halo-color": "rgba(255,255,255,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": "{name}",
+ "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": "rgb(51,51,68)",
+ "text-color": "rgb(51,51,68)",
+ "text-halo-color": "rgba(255,255,255,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": "{name}",
+ "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": "rgb(51,51,68)",
+ "text-color": "rgb(51,51,68)",
+ "text-halo-color": "rgba(255,255,255,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-subway",
+ "type": "symbol",
+ "source": "versatiles-shortbread",
+ "source-layer": "public_transport",
+ "minzoom": 14,
+ "filter": [
+ "all",
+ ["in", "kind", "station", "halt"],
+ ["==", "station", "subway"]
+ ],
+ "layout": {
+ "text-field": "{name}",
+ "icon-size": {"stops": [[14, 0.5], [16, 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_metro",
+ "visibility": "visible"
+ },
+ "paint": {
+ "icon-opacity": 0.7,
+ "icon-color": "rgb(102,98,106)",
+ "text-color": "rgb(102,98,106)",
+ "text-halo-color": "rgba(255,255,255,0.8)",
+ "text-halo-width": 2,
+ "text-halo-blur": 1
+ }
+ },
+ {
+ "id": "symbol-transit-lightrail",
+ "type": "symbol",
+ "source": "versatiles-shortbread",
+ "source-layer": "public_transport",
+ "minzoom": 14,
+ "filter": [
+ "all",
+ ["in", "kind", "station", "halt"],
+ ["==", "station", "light_rail"]
+ ],
+ "layout": {
+ "text-field": "{name}",
+ "icon-size": {"stops": [[14, 0.5], [16, 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_light",
+ "visibility": "visible"
+ },
+ "paint": {
+ "icon-opacity": 0.7,
+ "icon-color": "rgb(102,98,106)",
+ "text-color": "rgb(102,98,106)",
+ "text-halo-color": "rgba(255,255,255,0.8)",
+ "text-halo-width": 2,
+ "text-halo-blur": 1
+ }
+ },
+ {
+ "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": "{name}",
+ "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": "rgb(102,98,106)",
+ "text-color": "rgb(102,98,106)",
+ "text-halo-color": "rgba(255,255,255,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": "{name}",
+ "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": "rgb(102,98,106)",
+ "text-color": "rgb(102,98,106)",
+ "text-halo-color": "rgba(255,255,255,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": "{name}",
+ "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": "rgb(102,98,106)",
+ "text-color": "rgb(102,98,106)",
+ "text-halo-color": "rgba(255,255,255,0.8)",
+ "text-halo-width": 2,
+ "text-halo-blur": 1
+ }
+ }
+ ],
+ "id": "vootuu7"
+}
diff --git a/app/src/main/java/it/reyboz/bustorino/ActivityExperiments.java b/app/src/main/java/it/reyboz/bustorino/ActivityExperiments.java
--- a/app/src/main/java/it/reyboz/bustorino/ActivityExperiments.java
+++ b/app/src/main/java/it/reyboz/bustorino/ActivityExperiments.java
@@ -19,6 +19,7 @@
import android.os.Bundle;
import android.util.Log;
+import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.fragment.app.FragmentTransaction;
import it.reyboz.bustorino.backend.Stop;
@@ -54,7 +55,7 @@
//.add(R.id.fragment_container_view, LinesDetailFragment.class,
// LinesDetailFragment.Companion.makeArgs("gtt:4U"))
- .add(R.id.fragment_container_view, BackupImportFragment.class, null)
+ .add(R.id.fragment_container_view, MapLibreFragment.class, null)
.commit();
}
}
@@ -79,15 +80,15 @@
}
@Override
- public void showLineOnMap(String routeGtfsId){
+ public void showLineOnMap(String routeGtfsId, @Nullable String stopIDFrom){
readyGUIfor(FragmentKind.LINES);
FragmentTransaction tr = getSupportFragmentManager().beginTransaction();
tr.replace(R.id.fragment_container_view, LinesDetailFragment.class,
- LinesDetailFragment.Companion.makeArgs(routeGtfsId));
+ LinesDetailFragment.Companion.makeArgs(routeGtfsId, stopIDFrom));
tr.addToBackStack("LineonMap-"+routeGtfsId);
tr.commit();
}
}
\ 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
--- a/app/src/main/java/it/reyboz/bustorino/ActivityPrincipal.java
+++ b/app/src/main/java/it/reyboz/bustorino/ActivityPrincipal.java
@@ -24,7 +24,6 @@
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.net.Uri;
-import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.Gravity;
@@ -260,7 +259,7 @@
onCreateComplete = true;
//last but not least, set the good default values
- manageDefaultValuesForSettings();
+ setDefaultSettingsValuesWhenMissing();
//check if first run activity (IntroActivity) has been started once or not
boolean hasIntroRun = theShPr.getBoolean(PreferencesHolder.PREF_INTRO_ACTIVITY_RUN,false);
@@ -498,7 +497,7 @@
private void requestMapFragment(final boolean allowReturn){
// starting from Android 11, we don't need to have the STORAGE permission anymore for the map cache
- if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.R){
+ /*if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.R){
//nothing to do
Log.d(DEBUG_TAG, "Build codes allow the showing of the map");
createAndShowMapFragment(null, allowReturn);
@@ -520,6 +519,10 @@
String text = getString(R.string.too_many_permission_asks, storage_perm);
Toast.makeText(getApplicationContext(),text, Toast.LENGTH_LONG).show();
}
+
+ */
+ //The permissions are handled in the MapLibreFragment instead
+ createAndShowMapFragment(null, allowReturn);
}
private static void checkAndShowFavoritesFragment(FragmentManager fragmentManager, boolean addToBackStack){
@@ -676,13 +679,13 @@
mNavView.setCheckedItem(R.id.nav_arrivals);
}
@Override
- public void showLineOnMap(String routeGtfsId){
+ public void showLineOnMap(String routeGtfsId, @Nullable String stopIDFrom){
readyGUIfor(FragmentKind.LINES);
FragmentTransaction tr = getSupportFragmentManager().beginTransaction();
tr.replace(R.id.mainActContentFrame, LinesDetailFragment.class,
- LinesDetailFragment.Companion.makeArgs(routeGtfsId));
+ LinesDetailFragment.Companion.makeArgs(routeGtfsId, stopIDFrom));
tr.addToBackStack("LineonMap-"+routeGtfsId);
tr.commit();
@@ -713,10 +716,10 @@
//Map Fragment stuff
void createAndShowMapFragment(@Nullable Stop stop, boolean addToBackStack){
- FragmentManager fm = getSupportFragmentManager();
- FragmentTransaction ft = fm.beginTransaction();
- MapFragment fragment = stop == null? MapFragment.getInstance(): MapFragment.getInstance(stop);
- ft.replace(R.id.mainActContentFrame, fragment, MapFragment.FRAGMENT_TAG);
+ final FragmentManager fm = getSupportFragmentManager();
+ final FragmentTransaction ft = fm.beginTransaction();
+ final MapLibreFragment fragment = MapLibreFragment.Companion.newInstance(stop);
+ ft.replace(R.id.mainActContentFrame, fragment, MapFragmentKt.FRAGMENT_TAG);
if (addToBackStack) ft.addToBackStack(null);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
@@ -765,7 +768,7 @@
/**
* Adjust setting to match the default ones
*/
- private void manageDefaultValuesForSettings(){
+ private void setDefaultSettingsValuesWhenMissing(){
SharedPreferences mainSharedPref = PreferenceManager.getDefaultSharedPreferences(this);
SharedPreferences.Editor editor = mainSharedPref.edit();
//Main fragment to show
@@ -790,6 +793,13 @@
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.commit();
}
diff --git a/app/src/main/java/it/reyboz/bustorino/backend/LivePositionTripPattern.kt b/app/src/main/java/it/reyboz/bustorino/backend/LivePositionTripPattern.kt
new file mode 100644
--- /dev/null
+++ b/app/src/main/java/it/reyboz/bustorino/backend/LivePositionTripPattern.kt
@@ -0,0 +1,9 @@
+package it.reyboz.bustorino.backend
+
+import it.reyboz.bustorino.backend.gtfs.LivePositionUpdate
+import it.reyboz.bustorino.data.gtfs.MatoPattern
+
+data class LivePositionTripPattern(
+ var posUpdate: LivePositionUpdate,
+ var pattern: MatoPattern?
+)
diff --git a/app/src/main/java/it/reyboz/bustorino/backend/Stop.java b/app/src/main/java/it/reyboz/bustorino/backend/Stop.java
--- a/app/src/main/java/it/reyboz/bustorino/backend/Stop.java
+++ b/app/src/main/java/it/reyboz/bustorino/backend/Stop.java
@@ -19,6 +19,7 @@
package it.reyboz.bustorino.backend;
import android.location.Location;
+import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -26,6 +27,7 @@
import org.osmdroid.api.IGeoPoint;
import java.net.URLEncoder;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
@@ -123,6 +125,14 @@
this.routesThatStopHereString = routesStopping;
}
+ public int getNumRoutesStopping(){
+ if(this.routesThatStopHere == null) {
+ return 0;
+ } else {
+ return this.routesThatStopHere.size();
+ }
+ }
+
@Nullable
protected List<String> getRoutesThatStopHere(){
return routesThatStopHere;
@@ -287,6 +297,11 @@
return url;
}
+ @Override
+ public String toString(){
+ return "id:"+ID+" { name: "+this.name+", lines: "+this.routesThatStopHereToString()+"}";
+ }
+
@Nullable
public Double getLatitude() {
return lat;
@@ -305,4 +320,41 @@
if(this.lat!=null && this.lon !=null)
return utils.measuredistanceBetween(this.lat,this.lon,latitude, longitude);
else return Double.POSITIVE_INFINITY;
- }}
+ }
+
+ public Bundle toBundle(Bundle bundle) {
+ //Bundle bundle = new Bundle();
+ if(bundle==null) return null;
+ bundle.putString("ID", ID);
+ bundle.putString("name", name);
+ bundle.putString("username", username);
+ bundle.putString("location", location);
+ bundle.putString("type", (type != null) ? type.name() : null);
+ bundle.putStringArrayList("routesThatStopHere", (routesThatStopHere != null) ? new ArrayList<>(routesThatStopHere) : null);
+ if (lat != null) bundle.putDouble("lat", lat);
+ if (lon != null) bundle.putDouble("lon", lon);
+ if (gtfsID !=null) bundle.putString("gtfsID", gtfsID);
+ return bundle;
+ }
+
+ public Bundle toBundle(){
+ return toBundle(new Bundle());
+ }
+
+ @Nullable
+ public static Stop fromBundle(Bundle bundle) {
+ String ID = bundle.getString("ID");
+ if (ID == null) return null; //throw new IllegalArgumentException("ID cannot be null");
+ String name = bundle.getString("name");
+ String username = bundle.getString("username");
+ String location = bundle.getString("location");
+ String typeStr = bundle.getString("type");
+ Route.Type type = (typeStr != null) ? Route.Type.valueOf(typeStr) : null;
+ List<String> routesThatStopHere = bundle.getStringArrayList("routesThatStopHere");
+ Double lat = bundle.containsKey("lat") ? bundle.getDouble("lat") : null;
+ Double lon = bundle.containsKey("lon") ? bundle.getDouble("lon") : null;
+ String gtfsId = bundle.getString("gtfsID");
+
+ return new Stop(ID, name, username, location, type, routesThatStopHere, lat, lon, gtfsId);
+ }
+}
diff --git a/app/src/main/java/it/reyboz/bustorino/backend/gtfs/LivePositionUpdate.kt b/app/src/main/java/it/reyboz/bustorino/backend/gtfs/LivePositionUpdate.kt
--- a/app/src/main/java/it/reyboz/bustorino/backend/gtfs/LivePositionUpdate.kt
+++ b/app/src/main/java/it/reyboz/bustorino/backend/gtfs/LivePositionUpdate.kt
@@ -17,9 +17,7 @@
*/
package it.reyboz.bustorino.backend.gtfs
-import com.google.transit.realtime.GtfsRealtime.TripUpdate.StopTimeUpdate.ScheduleRelationship
import com.google.transit.realtime.GtfsRealtime.VehiclePosition
-import com.google.transit.realtime.GtfsRealtime.VehiclePosition.OccupancyStatus
data class LivePositionUpdate(
val tripID: String, //tripID WITHOUT THE "gtt:" prefix
@@ -28,10 +26,10 @@
val routeID: String,
val vehicle: String,
- val latitude: Double,
- val longitude: Double,
- val bearing: Float?,
-
+ var latitude: Double,
+ var longitude: Double,
+ var bearing: Float?,
+ //the timestamp IN SECONDS
val timestamp: Long,
val nextStop: String?,
@@ -40,6 +38,7 @@
val occupancyStatus: OccupancyStatus?,
val scheduleRelationship: ScheduleRelationship?
*/
+ //var tripInfo: TripAndPatternWithStops?,
){
constructor(position: VehiclePosition) : this(
position.trip.tripId,
@@ -60,6 +59,17 @@
)
*/
+ /*fun withNewPositionAndBearing(latitude: Double, longitude: Double, bearing: Float) =
+ LivePositionUpdate(this.tripID, this.startTime, this.startTime,
+ this.routeID, this.vehicle, latitude, longitude, bearing,
+ this.timestamp,this.nextStop)
+
+ fun withNewPosition(latitude: Double, longitude: Double) =
+ LivePositionUpdate(this.tripID, this.startTime, this.startTime,
+ this.routeID, this.vehicle, latitude, longitude, this.bearing,
+ this.timestamp,this.nextStop)
+
+ */
}
diff --git a/app/src/main/java/it/reyboz/bustorino/backend/gtfs/PolylineParser.java b/app/src/main/java/it/reyboz/bustorino/backend/gtfs/PolylineParser.java
--- a/app/src/main/java/it/reyboz/bustorino/backend/gtfs/PolylineParser.java
+++ b/app/src/main/java/it/reyboz/bustorino/backend/gtfs/PolylineParser.java
@@ -1,6 +1,6 @@
package it.reyboz.bustorino.backend.gtfs;
-import org.osmdroid.util.GeoPoint;
+import org.maplibre.android.geometry.LatLng;
import java.util.ArrayList;
@@ -12,8 +12,8 @@
* @param initial_capacity for the list
* @return the list of points correspoding to the polyline
*/
- public static ArrayList<GeoPoint> decodePolyline(String encodedPolyline, int initial_capacity) {
- ArrayList<GeoPoint> points = new ArrayList<>(initial_capacity);
+ public static ArrayList<LatLng> decodePolyline(String encodedPolyline, int initial_capacity) {
+ ArrayList<LatLng> points = new ArrayList<>(initial_capacity);
int truck = 0;
int carriage_q = 0;
int longit=0, latit=0;
@@ -36,7 +36,7 @@
is_lat = false;
} else{
longit += truck;
- points.add(new GeoPoint((double)latit/1e5,(double)longit/1e5));
+ points.add(new LatLng((double)latit/1e5,(double)longit/1e5));
is_lat=true;
}
carriage_q = 0;
diff --git a/app/src/main/java/it/reyboz/bustorino/backend/mato/MQTTMatoClient.kt b/app/src/main/java/it/reyboz/bustorino/backend/mato/MQTTMatoClient.kt
--- a/app/src/main/java/it/reyboz/bustorino/backend/mato/MQTTMatoClient.kt
+++ b/app/src/main/java/it/reyboz/bustorino/backend/mato/MQTTMatoClient.kt
@@ -69,8 +69,8 @@
Log.d(DEBUG_TAG,"client name: $clientID")
//actually connect
- client!!.connect(options,null, iMqttActionListener)
isStarted = true
+ client!!.connect(options,null, iMqttActionListener)
client!!.setCallback(this)
if (this.context ==null)
@@ -159,7 +159,7 @@
//if (done) break
if (v.isEmpty()){
//actually unsubscribe
- client!!.unsubscribe( mapTopic(line))
+ client?.unsubscribe( mapTopic(line))
}
removed = done || removed
}
diff --git a/app/src/main/java/it/reyboz/bustorino/data/PreferencesHolder.java b/app/src/main/java/it/reyboz/bustorino/data/PreferencesHolder.java
--- a/app/src/main/java/it/reyboz/bustorino/data/PreferencesHolder.java
+++ b/app/src/main/java/it/reyboz/bustorino/data/PreferencesHolder.java
@@ -19,11 +19,14 @@
import android.content.Context;
import android.content.SharedPreferences;
+import androidx.annotation.NonNull;
import it.reyboz.bustorino.R;
import static android.content.Context.MODE_PRIVATE;
import androidx.preference.PreferenceManager;
+import it.reyboz.bustorino.fragments.SettingsFragment;
+import it.reyboz.bustorino.map.MapLibreUtils;
import java.util.HashSet;
import java.util.Set;
@@ -87,8 +90,19 @@
return modified;
}
- public static HashSet<String> getFavoritesLinesGtfsIDs(Context con){
+ public static HashSet<String> 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) {
+ //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();
+ };
+ }
}
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/ArrivalsFragment.java b/app/src/main/java/it/reyboz/bustorino/fragments/ArrivalsFragment.java
--- a/app/src/main/java/it/reyboz/bustorino/fragments/ArrivalsFragment.java
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/ArrivalsFragment.java
@@ -132,11 +132,11 @@
public void requestShowingRoute(Route route) {
Log.d(DEBUG_TAG, "Need to show line for route:\ngtfsID "+route.getGtfsId()+ " name "+route.getName());
if(route.getGtfsId()!=null){
- mListener.showLineOnMap(route.getGtfsId());
+ mListener.showLineOnMap(route.getGtfsId(), stopID);
} else {
String gtfsID = FiveTNormalizer.getGtfsRouteID(route);
Log.d(DEBUG_TAG, "GtfsID for route is: " + gtfsID);
- mListener.showLineOnMap(gtfsID);
+ mListener.showLineOnMap(gtfsID, stopID);
}
}
};
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/CommonFragmentListener.java b/app/src/main/java/it/reyboz/bustorino/fragments/CommonFragmentListener.java
--- a/app/src/main/java/it/reyboz/bustorino/fragments/CommonFragmentListener.java
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/CommonFragmentListener.java
@@ -4,6 +4,8 @@
import android.view.View;
import it.reyboz.bustorino.backend.Stop;
+import javax.annotation.Nullable;
+
public interface CommonFragmentListener {
@@ -41,5 +43,5 @@
* We want to show the line in detail for route
* @param routeGtfsId the route gtfsID (eg, "gtt:10U")
*/
- void showLineOnMap(String routeGtfsId);
+ void showLineOnMap(String routeGtfsId,@Nullable String fromStopID);
}
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/GeneralMapLibreFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/GeneralMapLibreFragment.kt
new file mode 100644
--- /dev/null
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/GeneralMapLibreFragment.kt
@@ -0,0 +1,171 @@
+package it.reyboz.bustorino.fragments
+
+import android.content.SharedPreferences
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.preference.PreferenceManager
+import com.google.gson.JsonObject
+import it.reyboz.bustorino.backend.Stop
+import it.reyboz.bustorino.data.PreferencesHolder
+import org.maplibre.android.MapLibre
+import org.maplibre.android.camera.CameraPosition
+import org.maplibre.android.geometry.LatLng
+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.style.sources.GeoJsonSource
+import org.maplibre.geojson.Feature
+import org.maplibre.geojson.Point
+
+abstract class GeneralMapLibreFragment: ScreenBaseFragment(), OnMapReadyCallback {
+ protected var map: MapLibreMap? = null
+ protected var shownStopInBottomSheet : Stop? = null
+ protected var savedMapStateOnPause : Bundle? = null
+
+ // Declare a variable for MapView
+ protected lateinit var mapView: MapView
+ protected lateinit var mapStyle: Style
+ protected lateinit var stopsSource: GeoJsonSource
+ protected lateinit var busesSource: GeoJsonSource
+ protected lateinit var selectedStopSource: GeoJsonSource
+
+ protected lateinit var sharedPreferences: SharedPreferences
+
+ 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()
+ }
+ }
+
+ private var lastMapStyle =""
+
+ 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? {
+ lastMapStyle = PreferencesHolder.getMapLibreStyleFile(requireContext())
+ Log.d(DEBUG_TAG, "onCreateView lastMapStyle: $lastMapStyle")
+ return super.onCreateView(inflater, container, savedInstanceState)
+ }
+
+ override fun onResume() {
+ super.onResume()
+ val newMapStyle = PreferencesHolder.getMapLibreStyleFile(requireContext())
+ Log.d(DEBUG_TAG, "onResume newMapStyle: $newMapStyle, lastMapStyle: $lastMapStyle")
+ if(newMapStyle!=lastMapStyle){
+ reloadMap()
+ }
+ }
+
+ override fun onLowMemory() {
+ super.onLowMemory()
+ mapView.onLowMemory()
+ }
+
+
+ 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)
+ }
+
+ */
+ //TODO figure out how to switch map safely
+ }
+
+ abstract fun openStopInBottomSheet(stop: Stop)
+
+ //For extra stuff to do when the map is destroyed
+ abstract fun onMapDestroy()
+
+ protected fun restoreMapStateFromBundle(bundle: Bundle){
+ val nullDouble = -10_000.0
+ val latCenter = bundle.getDouble("center_map_lat", -10.0)
+ val lonCenter = bundle.getDouble("center_map_lon",-10.0)
+ val zoom = bundle.getDouble("map_zoom", -10.0)
+ val bearing = bundle.getDouble("map_bearing", nullDouble)
+ val tilt = bundle.getDouble("map_tilt", nullDouble)
+ if(lonCenter>=0 &&latCenter>=0) map?.let {
+ val newPos = CameraPosition.Builder().target(LatLng(latCenter,lonCenter))
+ if(zoom>0) newPos.zoom(zoom)
+ if(bearing!=nullDouble) newPos.bearing(bearing)
+ if(tilt != nullDouble) newPos.tilt(tilt)
+ it.cameraPosition=newPos.build()
+
+ }
+ val mStop = bundle.getBundle("shown_stop")?.let {
+ Stop.fromBundle(it)
+ }
+ mStop?.let { openStopInBottomSheet(it) }
+ }
+
+ protected fun saveMapStateBeforePause(bundle: Bundle){
+ map?.let {
+ val newBbox = it.projection.visibleRegion.latLngBounds
+
+
+ val cp = it.cameraPosition
+ bundle.putDouble("center_map_lat", newBbox.center.latitude)
+ bundle.putDouble("center_map_lon", newBbox.center.longitude)
+ it.cameraPosition.zoom.let { z-> bundle.putDouble("map_zoom",z) }
+ bundle.putDouble("map_bearing",cp.bearing)
+ bundle.putDouble("map_tilt", cp.tilt)
+ }
+ shownStopInBottomSheet?.let {
+ bundle.putBundle("shown_stop", it.toBundle())
+ }
+ }
+
+ protected fun saveMapStateInBundle(): Bundle {
+ val b = Bundle()
+ saveMapStateBeforePause(b)
+ return b
+ }
+
+ 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
+ }
+ )
+ }
+
+ 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"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/LinesDetailFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/LinesDetailFragment.kt
--- a/app/src/main/java/it/reyboz/bustorino/fragments/LinesDetailFragment.kt
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/LinesDetailFragment.kt
@@ -17,31 +17,41 @@
*/
package it.reyboz.bustorino.fragments
+
+import android.Manifest
import android.animation.ObjectAnimator
+import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.content.Context
import android.content.SharedPreferences
-import android.graphics.Paint
+import android.content.res.ColorStateList
+import android.location.Location
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.view.animation.LinearInterpolator
import android.widget.*
+import androidx.activity.result.ActivityResultCallback
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.content.res.AppCompatResources
+import androidx.cardview.widget.CardView
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
+import androidx.core.view.ViewCompat
import androidx.fragment.app.viewModels
-import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
+import com.google.android.material.bottomsheet.BottomSheetBehavior
+import com.google.gson.JsonObject
import it.reyboz.bustorino.R
import it.reyboz.bustorino.adapters.NameCapitalize
import it.reyboz.bustorino.adapters.StopAdapterListener
import it.reyboz.bustorino.adapters.StopRecyclerAdapter
import it.reyboz.bustorino.backend.FiveTNormalizer
+import it.reyboz.bustorino.backend.LivePositionTripPattern
import it.reyboz.bustorino.backend.Stop
import it.reyboz.bustorino.backend.gtfs.GtfsUtils
import it.reyboz.bustorino.backend.gtfs.LivePositionUpdate
@@ -49,40 +59,69 @@
import it.reyboz.bustorino.backend.utils
import it.reyboz.bustorino.data.MatoTripsDownloadWorker
import it.reyboz.bustorino.data.PreferencesHolder
-import it.reyboz.bustorino.data.gtfs.MatoPattern
import it.reyboz.bustorino.data.gtfs.MatoPatternWithStops
import it.reyboz.bustorino.data.gtfs.TripAndPatternWithStops
import it.reyboz.bustorino.map.*
-import it.reyboz.bustorino.map.CustomInfoWindow.TouchResponder
import it.reyboz.bustorino.middleware.LocationUtils
import it.reyboz.bustorino.util.Permissions
+import it.reyboz.bustorino.util.ViewUtils
import it.reyboz.bustorino.viewmodels.LinesViewModel
import it.reyboz.bustorino.viewmodels.LivePositionsViewModel
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
-import org.osmdroid.config.Configuration
-import org.osmdroid.tileprovider.tilesource.TileSourceFactory
-import org.osmdroid.util.BoundingBox
-import org.osmdroid.util.GeoPoint
-import org.osmdroid.views.MapView
-import org.osmdroid.views.overlay.FolderOverlay
-import org.osmdroid.views.overlay.Marker
-import org.osmdroid.views.overlay.Polyline
-import org.osmdroid.views.overlay.advancedpolyline.MonochromaticPaintList
-
-
-class LinesDetailFragment() : ScreenBaseFragment() {
+import kotlinx.coroutines.Runnable
+import org.maplibre.android.camera.CameraPosition
+import org.maplibre.android.camera.CameraUpdateFactory
+import org.maplibre.android.geometry.LatLng
+import org.maplibre.android.geometry.LatLngBounds
+import org.maplibre.android.location.LocationComponent
+import org.maplibre.android.location.LocationComponentOptions
+import org.maplibre.android.maps.MapLibreMap
+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.LineLayer
+import org.maplibre.android.style.layers.Property
+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.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.LineString
+import org.maplibre.geojson.Point
+import java.util.concurrent.atomic.AtomicBoolean
+
+
+class LinesDetailFragment() : GeneralMapLibreFragment() {
private var lineID = ""
private lateinit var patternsSpinner: Spinner
private var patternsAdapter: ArrayAdapter<String>? = null
+ //Bottom sheet behavior
+ private lateinit var bottomSheetBehavior: BottomSheetBehavior<RelativeLayout>
+ private var bottomLayout: RelativeLayout? = null
+ private lateinit var stopTitleTextView: TextView
+ private lateinit var stopNumberTextView: TextView
+ private lateinit var linesPassingTextView: TextView
+ private lateinit var arrivalsCard: CardView
+ private lateinit var directionsCard: CardView
+ private lateinit var bottomrightImage: ImageView
+
+ //private var isBottomSheetShowing = false
+ private var shouldMapLocationBeReactivated = true
+
+ private var toRunWhenMapReady : Runnable? = null
+ private var mapInitialized = AtomicBoolean(false)
+
//private var patternsSpinnerState: Parcelable? = null
private lateinit var currentPatterns: List<MatoPatternWithStops>
- private lateinit var map: MapView
- private var viewingPattern: MatoPatternWithStops? = null
+ //private lateinit var map: MapView
+ private var patternShown: MatoPatternWithStops? = null
private val viewModel: LinesViewModel by viewModels()
private val mapViewModel: MapViewModel by viewModels()
@@ -119,6 +158,7 @@
private lateinit var stopsRecyclerView: RecyclerView
private lateinit var descripTextView: TextView
+ private var stopIDFromToShow: String? = null
//adapter for recyclerView
private val stopAdapterListener= object : StopAdapterListener {
override fun onTappedStop(stop: Stop?) {
@@ -143,57 +183,95 @@
}
}
+ private val patternsSorter = Comparator{ p1: MatoPatternWithStops, p2: MatoPatternWithStops ->
+ if(p1.pattern.directionId != p2.pattern.directionId)
+ return@Comparator p1.pattern.directionId - p2.pattern.directionId
+ else
+ return@Comparator -1*(p1.stopsIndices.size - p2.stopsIndices.size)
+ }
- private var polyline: Polyline? = null
+ //map data
+ //style and sources are in GeneralMapLibreFragment
+ private lateinit var locationComponent: LocationComponent
+ private lateinit var polylineSource: GeoJsonSource
+ private lateinit var polyArrowSource: GeoJsonSource
+
+ private var savedCameraPosition: CameraPosition? = null
+ private var vehShowing = ""
+
+ private var stopsLayerStarted = false
+ private var lastStopsSizeShown = 0
+ private var lastUpdateTime:Long = -2
+
+ //BUS POSITIONS
+ private val updatesByVehDict = HashMap<String, LivePositionTripPattern>(5)
+ private val animatorsByVeh = HashMap<String, ValueAnimator>()
+
+ private var lastLocation : Location? = null
+ private var enablingPositionFromClick = false
+
+ private var polyline: LineString? = null
+
+ private val showUserPositionRequestLauncher =
+ registerForActivityResult(
+ ActivityResultContracts.RequestMultiplePermissions(),
+ ActivityResultCallback { result ->
+ if (result == null) {
+ Log.w(DEBUG_TAG, "Got asked permission but request is null, doing nothing?")
+ } else if (java.lang.Boolean.TRUE == result[Manifest.permission.ACCESS_COARSE_LOCATION]
+ && java.lang.Boolean.TRUE == result[Manifest.permission.ACCESS_FINE_LOCATION]) {
+ // We can use the position, restart location overlay
+ if (context == null || requireContext().getSystemService(Context.LOCATION_SERVICE) == null)
+ return@ActivityResultCallback ///@registerForActivityResult
+ setMapUserLocationEnabled(true, true, enablingPositionFromClick)
+ } else Log.w(DEBUG_TAG, "No location permission")
+ })
//private var stopPosList = ArrayList<GeoPoint>()
- private lateinit var stopsOverlay: FolderOverlay
- private lateinit var locationOverlay: LocationOverlay
- private val locationOverlayResponder = object : LocationOverlay.OverlayCallbacks{
- override fun onDisableFollowMyLocation() {
- Log.d(DEBUG_TAG, "Follow location disabled")
- }
-
- override fun onEnableFollowMyLocation() {
- Log.d(DEBUG_TAG, "Follow location enabled")
- }
- }
- //location request responder
- private val locationRequestResLauncher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()){ res ->
- //onActivityResult(res: map<String,Boolean>)
- if(res[Permissions.LOCATION_PERMISSIONS[0]] ==true || res[Permissions.LOCATION_PERMISSIONS[1]] ==true)
- locationIcon?.let { onPositionIconButtonClick(it) }
- else{
- context?.let { Toast.makeText(it,R.string.location_permission_not_granted, Toast.LENGTH_SHORT).show() }
- }
- }
//fragment actions
private lateinit var fragmentListener: CommonFragmentListener
- private val stopTouchResponder = TouchResponder { stopID, stopName ->
- Log.d(DEBUG_TAG, "Asked to show arrivals for stop ID: $stopID")
- fragmentListener.requestArrivalsForStopID(stopID)
- }
- private var showOnTopOfLine = true
+ private var showOnTopOfLine = false
private var recyclerInitDone = false
private var useMQTTPositions = true
+
+
//position of live markers
- private val busPositionMarkersByTrip = HashMap<String,Marker>()
- private var busPositionsOverlay = FolderOverlay()
private val tripMarkersAnimators = HashMap<String, ObjectAnimator>()
private val liveBusViewModel: LivePositionsViewModel by viewModels()
+
+ //extra items to use the LibreMap
+ private lateinit var symbolManager : SymbolManager
+ private var stopActiveSymbol: Symbol? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val args = requireArguments()
+ lineID = args.getString(LINEID_KEY,"")
+ stopIDFromToShow = args.getString(STOPID_FROM_KEY)
+ }
+
@SuppressLint("SetTextI18n")
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
+ //reset statuses
+ //isBottomSheetShowing = false
+ //stopsLayerStarted = false
+ lastStopsSizeShown = 0
+ mapInitialized.set(false)
+
val rootView = inflater.inflate(R.layout.fragment_lines_detail, container, false)
- lineID = requireArguments().getString(LINEID_KEY, "")
+ //lineID = requireArguments().getString(LINEID_KEY, "")
+ arguments?.let {
+ lineID = it.getString(LINEID_KEY, "")
+ }
switchButton = rootView.findViewById(R.id.switchImageButton)
locationIcon = rootView.findViewById(R.id.locationEnableIcon)
favoritesButton = rootView.findViewById(R.id.favoritesButton)
@@ -201,6 +279,27 @@
descripTextView = rootView.findViewById(R.id.lineDescripTextView)
descripTextView.visibility = View.INVISIBLE
+ //map stuff
+ mapView = rootView.findViewById(R.id.lineMap)
+ mapView.getMapAsync(this)
+
+
+ //init bottom sheet
+ val bottomSheet = rootView.findViewById<RelativeLayout>(R.id.bottom_sheet)
+ bottomLayout = bottomSheet
+ stopTitleTextView = bottomSheet.findViewById(R.id.stopTitleTextView)
+ stopNumberTextView = bottomSheet.findViewById(R.id.stopNumberTextView)
+ linesPassingTextView = bottomSheet.findViewById(R.id.linesPassingTextView)
+ arrivalsCard = bottomSheet.findViewById(R.id.arrivalsCardButton)
+ directionsCard = bottomSheet.findViewById(R.id.directionsCardButton)
+ bottomrightImage = bottomSheet.findViewById(R.id.rightmostImageView)
+ bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet)
+
+ // Setup close button
+ rootView.findViewById<View>(R.id.btnClose).setOnClickListener {
+ hideStopBottomSheet()
+ }
+
val titleTextView = rootView.findViewById<TextView>(R.id.titleTextView)
titleTextView.text = getString(R.string.line)+" "+FiveTNormalizer.fixShortNameForDisplay(
GtfsUtils.getLineNameFromGtfsID(lineID), true)
@@ -223,35 +322,14 @@
patternsAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_dropdown_item, ArrayList<String>())
patternsSpinner.adapter = patternsAdapter
- initializeMap(rootView)
initializeRecyclerView()
switchButton.setOnClickListener{
- if(map.visibility == View.VISIBLE){
- map.visibility = View.GONE
- stopsRecyclerView.visibility = View.VISIBLE
- locationIcon?.visibility = View.GONE
-
- viewModel.setMapShowing(false)
- liveBusViewModel.stopMatoUpdates()
- //map.overlayManager.remove(busPositionsOverlay)
-
- switchButton.setImageDrawable(AppCompatResources.getDrawable(requireContext(), R.drawable.ic_map_white_30))
+ if(mapView.visibility == View.VISIBLE){
+ hideMapAndShowStopList()
} else{
- stopsRecyclerView.visibility = View.GONE
- map.visibility = View.VISIBLE
- locationIcon?.visibility = View.VISIBLE
- viewModel.setMapShowing(true)
-
- //map.overlayManager.add(busPositionsOverlay)
- //map.
- if(useMQTTPositions)
- liveBusViewModel.requestMatoPosUpdates(GtfsUtils.getLineNameFromGtfsID(lineID))
- else
- liveBusViewModel.requestGTFSUpdates()
-
- switchButton.setImageDrawable(AppCompatResources.getDrawable(requireContext(), R.drawable.ic_list_30))
+ hideStopListAndShowMap()
}
}
locationIcon?.let {view ->
@@ -261,9 +339,9 @@
view.setOnClickListener(this::onPositionIconButtonClick)
}
//set
-
-
+ //INITIALIZE VIEW MODELS
viewModel.setRouteIDQuery(lineID)
+ liveBusViewModel.setGtfsLineToFilterPos(lineID, null)
val keySourcePositions = getString(R.string.pref_positions_source)
useMQTTPositions = PreferenceManager.getDefaultSharedPreferences(requireContext())
@@ -273,14 +351,18 @@
patterns -> savePatternsToShow(patterns)
}
/*
- We have the pattern and the stops here, time to display them
*/
viewModel.stopsForPatternLiveData.observe(viewLifecycleOwner) { stops ->
- if(map.visibility ==View.VISIBLE)
- showPatternWithStopsOnMap(stops)
+ if(mapView.visibility ==View.VISIBLE)
+ patternShown?.let{
+ // We have the pattern and the stops here, time to display them
+ displayPatternWithStopsOnMap(it,stops, true)
+ } ?:{
+ Log.w(DEBUG_TAG, "The viewingPattern is null!")
+ }
else{
if(stopsRecyclerView.visibility==View.VISIBLE)
- showStopsAsList(stops)
+ showStopsInRecyclerView(stops)
}
}
viewModel.gtfsRoute.observe(viewLifecycleOwner){route->
@@ -292,71 +374,278 @@
descripTextView.text = route.longName
descripTextView.visibility = View.VISIBLE
}
- if(pausedFragment && viewModel.selectedPatternLiveData.value!=null){
- val patt = viewModel.selectedPatternLiveData.value!!
- Log.d(DEBUG_TAG, "Recreating views on resume, setting pattern: ${patt.pattern.code}")
- showPattern(patt)
- pausedFragment = false
- }
+ /*
+
+ */
Log.d(DEBUG_TAG,"Data ${viewModel.stopsForPatternLiveData.value}")
//listeners
patternsSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(p0: AdapterView<*>?, p1: View?, position: Int, p3: Long) {
- val patternWithStops = currentPatterns.get(position)
- //viewModel.setPatternToDisplay(patternWithStops)
- setPatternAndReqStops(patternWithStops)
+ val currentShownPattern = patternShown?.pattern
+ val patternWithStops = currentPatterns[position]
- Log.d(DEBUG_TAG, "item Selected, cleaning bus markers")
- if(map?.visibility == View.VISIBLE) {
- busPositionsOverlay.closeAllInfoWindows()
- busPositionsOverlay.items.clear()
- busPositionMarkersByTrip.clear()
+ Log.d(DEBUG_TAG, "request stops for pattern ${patternWithStops.pattern.code}")
+ setPatternAndReqStops(patternWithStops)
- stopAnimations()
- tripMarkersAnimators.clear()
- liveBusViewModel.retriggerPositionUpdate()
+ if(mapView.visibility == View.VISIBLE) {
+ //Clear buses if we are changing direction
+ currentShownPattern?.let { patt ->
+ if(patt.directionId != patternWithStops.pattern.directionId){
+ stopAnimations()
+ updatesByVehDict.clear()
+ updatePositionsIcons(true)
+ liveBusViewModel.retriggerPositionUpdate()
+ }
+ }
}
+ liveBusViewModel.setGtfsLineToFilterPos(lineID, patternWithStops.pattern)
+
}
override fun onNothingSelected(p0: AdapterView<*>?) {
}
}
+ Log.d(DEBUG_TAG, "Views created!")
+ return rootView
+ }
- //live bus positions
- liveBusViewModel.updatesWithTripAndPatterns.observe(viewLifecycleOwner){
- if(map.visibility == View.GONE || viewingPattern ==null){
- //DO NOTHING
- return@observe
+ // ------------- UI switch stuff ---------
+
+ private fun hideMapAndShowStopList(){
+ mapView.visibility = View.GONE
+ stopsRecyclerView.visibility = View.VISIBLE
+ locationIcon?.visibility = View.GONE
+
+ viewModel.setMapShowing(false)
+ if(useMQTTPositions) liveBusViewModel.stopMatoUpdates()
+ //map.overlayManager.remove(busPositionsOverlay)
+
+ switchButton.setImageDrawable(AppCompatResources.getDrawable(requireContext(), R.drawable.ic_map_white_30))
+
+ hideStopBottomSheet()
+
+ if(locationComponent.isLocationComponentEnabled){
+ locationComponent.isLocationComponentEnabled = false
+ shouldMapLocationBeReactivated = true
+ } else
+ shouldMapLocationBeReactivated = false
+ }
+
+ private fun hideStopListAndShowMap(){
+ stopsRecyclerView.visibility = View.GONE
+ mapView.visibility = View.VISIBLE
+ locationIcon?.visibility = View.VISIBLE
+ viewModel.setMapShowing(true)
+
+ //map.overlayManager.add(busPositionsOverlay)
+ //map.
+ if(useMQTTPositions)
+ liveBusViewModel.requestMatoPosUpdates(GtfsUtils.getLineNameFromGtfsID(lineID))
+ else
+ liveBusViewModel.requestGTFSUpdates()
+
+ switchButton.setImageDrawable(AppCompatResources.getDrawable(requireContext(), R.drawable.ic_list_30))
+
+ if(shouldMapLocationBeReactivated && Permissions.bothLocationPermissionsGranted(requireContext())){
+ locationComponent.isLocationComponentEnabled = true
+ }
+ }
+
+ private fun setLocationIconEnabled(setTrue: Boolean){
+ if(setTrue)
+ locationIcon?.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.location_circlew_red))
+ else
+ locationIcon?.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.location_circlew_grey))
+ }
+
+ /**
+ * Handles logic of enabling the user location on the map
+ */
+ @SuppressLint("MissingPermission")
+ private fun setMapUserLocationEnabled(enabled: Boolean, assumePermissions: Boolean, fromClick: Boolean) {
+ if (enabled) {
+ val permissionOk = assumePermissions || Permissions.bothLocationPermissionsGranted(requireContext())
+
+ if (permissionOk) {
+ Log.d(DEBUG_TAG, "Permission OK, starting location component, assumed: $assumePermissions")
+ locationComponent.isLocationComponentEnabled = true
+ //locationComponent.cameraMode = CameraMode.TRACKING //CameraMode.TRACKING
+
+ setLocationIconEnabled(true)
+ if (fromClick) Toast.makeText(context, R.string.location_enabled, Toast.LENGTH_SHORT).show()
+ } else {
+ if (shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)) {
+ //TODO: show dialog for permission rationale
+ Toast.makeText(activity, R.string.enable_position_message_map, Toast.LENGTH_SHORT).show()
+ }
+ Log.d(DEBUG_TAG, "Requesting permission to show user location")
+ enablingPositionFromClick = fromClick
+ showUserPositionRequestLauncher.launch(Permissions.LOCATION_PERMISSIONS)
}
- val filtdLineID = GtfsUtils.stripGtfsPrefix(lineID)
- //filter buses with direction, show those only with the same direction
- val outmap = HashMap<String, Pair<LivePositionUpdate, TripAndPatternWithStops?>>()
- val currentPattern = viewingPattern!!.pattern
- val numUpds = it.entries.size
- Log.d(DEBUG_TAG, "Got $numUpds updates, current pattern is: ${currentPattern.name}, directionID: ${currentPattern.directionId}")
- val patternsDirections = HashMap<String,Int>()
- for((tripId, pair) in it.entries){
- //remove trips with wrong line ideas
- if(pair.first.routeID!=filtdLineID)
- continue
+ } else{
+ locationComponent.isLocationComponentEnabled = false
+ setLocationIconEnabled(false)
+ if (fromClick) {
+ Toast.makeText(requireContext(), R.string.location_disabled, Toast.LENGTH_SHORT).show()
+ //TODO: Cancel the request for the enablement of the position if needed
+ }
+ }
+
+ }
+
+ /**
+ * Switch position icon from activ
+ */
+ private fun onPositionIconButtonClick(view: View){
+ if(locationComponent.isLocationComponentEnabled) setMapUserLocationEnabled(false, false, true)
+ else{
+ setMapUserLocationEnabled(true, false, true)
+ }
+ }
+
+ // ------------- Map Code -------------------------
+ /**
+ * This method sets up the map and the layers
+ */
+ override fun onMapReady(mapReady: MapLibreMap) {
+ this.map = mapReady
+
+ val context = requireContext()
+ val mjson = Styles.getJsonStyleFromAsset(context, PreferencesHolder.getMapLibreStyleFile(context)) //ViewUtils.loadJsonFromAsset(requireContext(),"map_style_good.json")
+
+ activity?.run {
+ val builder = Style.Builder().fromJson(mjson!!)
+
+ mapReady.setStyle(builder) { style ->
+
+ mapStyle = style
+ //setupLayers(style)
+
+ symbolManager = SymbolManager(mapView,mapReady,style)
+ symbolManager.iconAllowOverlap = true
+ symbolManager.textAllowOverlap = false
+
+ symbolManager.addClickListener{ _ ->
+ if (stopActiveSymbol!=null){
+ hideStopBottomSheet()
+
+ return@addClickListener true
+ } else
+ return@addClickListener false
+ }
+
+ // Start observing data
+ initMapUserLocation(style, mapReady, requireContext())
+
+ //if(!stopsLayerStarted)
+ initStopsPolyLineLayers(style, FeatureCollection.fromFeatures(ArrayList<Feature>()), null, null)
+ /*if(!stopsLayerStarted) {
+ Log.d(DEBUG_TAG, "Stop layer is not started yet")
+ initStopsPolyLineLayers(style, FeatureCollection.fromFeatures(ArrayList<Feature>()), null)
+ }
+ */
+ setupBusLayer(style)
+
+ mapViewModel.stopShowing?.let {
+ openStopInBottomSheet(it)
+ }
+ mapViewModel.stopShowing = null
+ toRunWhenMapReady?.run()
+ toRunWhenMapReady = null
+ mapInitialized.set(true)
+
+ if(patternShown!=null){
+ viewModel.stopsForPatternLiveData.value?.let {
+ Log.d(DEBUG_TAG, "Show stops from the cache")
+ displayPatternWithStopsOnMap(patternShown!!, it, true)
+ }
+ }
- if(pair.second!=null && pair.second?.pattern !=null){
- val dir = pair.second?.pattern?.directionId
- if(dir !=null && dir == currentPattern.directionId){
- outmap[tripId] = pair
+ }
+
+ mapReady.addOnMapClickListener { point ->
+ val screenPoint = mapReady.projection.toScreenLocation(point)
+ val features = mapReady.queryRenderedFeatures(screenPoint, STOPS_LAYER_ID)
+ val busNearby = mapReady.queryRenderedFeatures(screenPoint, BUSES_LAYER_ID)
+ if (features.isNotEmpty()) {
+ val feature = features[0]
+ val id = feature.getStringProperty("id")
+ val name = feature.getStringProperty("name")
+ //Toast.makeText(requireContext(), "Clicked on $name ($id)", Toast.LENGTH_SHORT).show()
+ val stop = viewModel.getStopByID(id)
+ stop?.let {
+ if (isBottomSheetShowing() || vehShowing.isNotEmpty()){
+ hideStopBottomSheet()
+ }
+ openStopInBottomSheet(it)
+
+ //move camera
+ if(it.latitude!=null && it.longitude!=null)
+ mapReady.animateCamera(CameraUpdateFactory.newLatLng(LatLng(it.latitude!!,it.longitude!!)),750)
+ }
+ return@addOnMapClickListener true
+ } else if (busNearby.isNotEmpty()){
+ val feature = busNearby[0]
+ val vehid = feature.getStringProperty("veh")
+ val route = feature.getStringProperty("line")
+ if(isBottomSheetShowing())
+ hideStopBottomSheet()
+ //if(context!=null){
+ // Toast.makeText(context, "Veh $vehid on route ${route.slice(0..route.length-2)}", Toast.LENGTH_SHORT).show()
+ //}
+ showVehicleTripInBottomSheet(vehid)
+ updatesByVehDict[vehid]?.let {
+ //if (it.posUpdate.latitude != null && it.longitude != null)
+ mapReady.animateCamera(
+ CameraUpdateFactory.newLatLng(LatLng(it.posUpdate.latitude, it.posUpdate.longitude)),
+ 750
+ )
}
- patternsDirections.set(tripId,if (dir!=null) dir else -10)
- } else{
- outmap[tripId] = pair
- //Log.d(DEBUG_TAG, "No pattern for tripID: $tripId")
- patternsDirections[tripId] = -10
+
+ return@addOnMapClickListener true
}
+ false
+ }
+
+ // we start requesting the bus positions now
+ observeBusPositionUpdates()
+
+ }
+ /*savedMapStateOnPause?.let{
+ restoreMapStateFromBundle(it)
+ pendingLocationActivation = false
+ Log.d(DEBUG_TAG, "Restored map state from the saved bundle")
+ }
+
+ */
+
+ val zoom = 12.0
+ val latlngTarget = LatLng(MapLibreFragment.DEFAULT_CENTER_LAT, MapLibreFragment.DEFAULT_CENTER_LON)
+
+ mapReady.cameraPosition = savedCameraPosition ?:CameraPosition.Builder().target(latlngTarget).zoom(zoom).build()
+
+ savedCameraPosition = null
+
+ if(shouldMapLocationBeReactivated) setMapUserLocationEnabled(true, false, false)
+ }
+
+ private fun observeBusPositionUpdates(){
+ //live bus positions
+ liveBusViewModel.filteredLocationUpdates.observe(viewLifecycleOwner){ pair ->
+ //Log.d(DEBUG_TAG, "Received ${updates.size} updates for the positions")
+ val updates = pair.first
+ val vehiclesNotOnCorrectDir = pair.second
+ if(mapView.visibility == View.GONE || patternShown ==null){
+ //DO NOTHING
+ Log.w(DEBUG_TAG, "not doing anything because map is not visible")
+ return@observe
}
- Log.d(DEBUG_TAG, " Filtered updates are ${outmap.keys.size}") // Original updates directs: $patternsDirections\n
- updateBusPositionsInMap(outmap)
+ //remove vehicles not on this direction
+ removeVehiclesData(vehiclesNotOnCorrectDir)
+ updateBusPositionsInMap(updates)
//if not using MQTT positions
if(!useMQTTPositions){
liveBusViewModel.requestDelayedGTFSUpdates(2000)
@@ -371,108 +660,237 @@
"BusTO-MatoTripDownload"
)
}
-
-
- return rootView
}
- private fun setLocationIconEnabled(setTrue: Boolean){
- if(setTrue)
- locationIcon?.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.location_circlew_red))
- else
- locationIcon?.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.location_circlew_grey))
+ private fun isBottomSheetShowing(): Boolean{
+ return bottomSheetBehavior.state == BottomSheetBehavior.STATE_EXPANDED
}
/**
- * Switch position icon from activ
+ * Initialize the map location, but do not enable the component
*/
- private fun onPositionIconButtonClick(view: View){
- if(locationOverlay.isMyLocationEnabled){
- //switch off
- locationOverlay.disableMyLocation()
- //set image on respective button
- setLocationIconEnabled(false)
- if(context!=null) {
- if (LocationUtils.isLocationEnabled(context)) {
- //show message
- Toast.makeText(context, R.string.location_disabled, Toast.LENGTH_SHORT).show()
- }
+ @SuppressLint("MissingPermission")
+ private fun initMapUserLocation(style: Style, map: MapLibreMap, context: Context){
+ locationComponent = map.locationComponent
+ val locationComponentOptions =
+ LocationComponentOptions.builder(context)
+ .pulseEnabled(false)
+ .build()
+ val locationComponentActivationOptions =
+ MapLibreUtils.buildLocationComponentActivationOptions(style, locationComponentOptions, context)
+ locationComponent.activateLocationComponent(locationComponentActivationOptions)
+ locationComponent.isLocationComponentEnabled = false
+
+ lastLocation?.let {
+ if (it.accuracy < 200)
+ locationComponent.forceLocationUpdate(it)
+ }
+ }
+ /**
+ * Update the bottom sheet with the stop information
+ */
+ override fun openStopInBottomSheet(stop: Stop){
+ bottomLayout?.let {
+
+ //lay.findViewById<TextView>(R.id.stopTitleTextView).text ="${stop.ID} - ${stop.stopDefaultName}"
+ val stopName = stop.stopUserName ?: stop.stopDefaultName
+ stopTitleTextView.text = stopName//stop.stopDefaultName
+ stopNumberTextView.text = stop.ID
+ stopTitleTextView.visibility = View.VISIBLE
+
+ val string_show = if (stop.numRoutesStopping==0) ""
+ else if (stop.numRoutesStopping <= 1)
+ requireContext().getString(R.string.line_fill, stop.routesThatStopHereToString())
+ else requireContext().getString(R.string.lines_fill, stop.routesThatStopHereToString())
+ linesPassingTextView.text = string_show
+
+ //SET ON CLICK LISTENER
+ arrivalsCard.setOnClickListener{
+ fragmentListener?.requestArrivalsForStopID(stop.ID)
}
- } else{
- //switch on
- locationOverlay.enableMyLocation()
- if(context!=null) {
- if(!Permissions.anyLocationPermissionsGranted(context)) {
- locationRequestResLauncher.launch(Permissions.LOCATION_PERMISSIONS)
- Toast.makeText(context, R.string.enable_position_message_map, Toast.LENGTH_SHORT).show()
- }
- else if (LocationUtils.isLocationEnabled(context)) {
- //set image on button
- setLocationIconEnabled(true)
- //show message
- Toast.makeText(context, R.string.location_enabled, Toast.LENGTH_SHORT).show()
- } else{
- Toast.makeText(context, R.string.map_location_disabled_device, Toast.LENGTH_SHORT).show()
- }
+
+ arrivalsCard.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)
+ ViewCompat.setBackgroundTintList(directionsCard, ColorStateList.valueOf(colorIcon))
+ }
+
+ bottomrightImage.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.navigation_right, activity?.theme))
+
+ }
+ //add stop marker
+ if (stop.latitude!=null && stop.longitude!=null) {
+ stopActiveSymbol = symbolManager.create(
+ SymbolOptions()
+ .withLatLng(LatLng(stop.latitude!!, stop.longitude!!))
+ .withIconImage(STOP_ACTIVE_IMG)
+ .withIconAnchor(ICON_ANCHOR_CENTER)
+
+ )
+
}
+ Log.d(DEBUG_TAG, "Shown stop $stop in bottom sheet")
+ shownStopInBottomSheet = stop
+
+ bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
+ //isBottomSheetShowing = true
}
+ // Hide the bottom sheet and remove extra symbol
+ private fun hideStopBottomSheet(){
+ if (stopActiveSymbol!=null){
+ symbolManager.delete(stopActiveSymbol)
+ stopActiveSymbol = null
+ }
+ bottomSheetBehavior.state = BottomSheetBehavior.STATE_HIDDEN
+ //isBottomSheetShowing = false
- private fun initializeMap(rootView : View){
- val ctx = requireContext().applicationContext
- Configuration.getInstance().load(ctx, PreferenceManager.getDefaultSharedPreferences(ctx))
+ //reset states
+ shownStopInBottomSheet = null
+ vehShowing = ""
- map = rootView.findViewById(R.id.lineMap)
- map.let {
- it.setTileSource(TileSourceFactory.MAPNIK)
+ }
- locationOverlay = LocationOverlay.createLocationOverlay(true, it, requireContext(), locationOverlayResponder)
- locationOverlay.disableFollowLocation()
+ private fun showVehicleTripInBottomSheet(veh: String){
+ val data = updatesByVehDict[veh]
+ if(data==null) {
+ Log.w(DEBUG_TAG,"Asked to show vehicle $veh, but it's not present in the updates")
+ return
+ }
- stopsOverlay = FolderOverlay()
- busPositionsOverlay = FolderOverlay()
+ bottomLayout?.let {
+ val lineName = FiveTNormalizer.fixShortNameForDisplay(
+ GtfsUtils.getLineNameFromGtfsID(data.posUpdate.routeID), true)
+ stopNumberTextView.text = requireContext().getString(R.string.line_fill, lineName)
+ val pat = data.pattern
+ if (pat!=null){
+ stopTitleTextView.text = pat.headsign
+ stopTitleTextView.visibility = View.VISIBLE
+ Log.d(DEBUG_TAG, "Showing headsign ${pat.headsign} for vehicle $veh")
+ } else {
+ //stopTitleTextView.text = "NN"
+ stopTitleTextView.visibility = View.GONE
+ }
+ linesPassingTextView.text = data.posUpdate.vehicle
+ }
+ arrivalsCard.visibility=View.GONE
+ bottomrightImage.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ic_magnifying_glass, activity?.theme))
+ directionsCard.setOnClickListener {
+ data.pattern?.let {
+
+ if(patternShown?.pattern?.code == it.code){
+ context?.let { c->Toast.makeText(c, R.string.showing_same_direction, Toast.LENGTH_SHORT).show() }
+ }else
+ showPatternWithCode(it.code)
+ } //TODO
+ // ?: {
+ // context?.let { ctx -> Toast.makeText(ctx,"") }
+ //}
+ }
+ //set color
+ val colorBlue = ResourcesCompat.getColor(resources,R.color.blue_620,activity?.theme)
+ ViewCompat.setBackgroundTintList(directionsCard, ColorStateList.valueOf(colorBlue))
+
+ vehShowing = veh
+ bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
+ //isBottomSheetShowing = true
+ Log.d(DEBUG_TAG, "Shown vehicle $veh in bottom layout")
+ }
- //map.setTilesScaledToDpi(true);
- //map.setTilesScaledToDpi(true);
- it.setFlingEnabled(true)
- it.setUseDataConnection(true)
+ // ------- MAP LAYERS INITIALIZE ----
+ /**
+ * Initialize the map layers for the stops
+ */
+ private fun initStopsPolyLineLayers(style: Style, stopFeatures:FeatureCollection, lineFeature: Feature?, arrowFeatures: FeatureCollection?){
+
+ Log.d(DEBUG_TAG, "INIT STOPS CALLED")
+ stopsSource = GeoJsonSource(STOPS_SOURCE_ID)
+ style.addSource(stopsSource)
+ //val context = requireContext()
+ val stopIcon = ResourcesCompat.getDrawable(resources,R.drawable.ball, activity?.theme)!!
+
+ val imgStop = ResourcesCompat.getDrawable(resources,R.drawable.bus_stop_new, activity?.theme)!!
+ val polyIconArrow = ResourcesCompat.getDrawable(resources, R.drawable.arrow_up_box_fill, activity?.theme)!!
+ //set the image tint
+ //DrawableCompat.setTint(imgBus,ContextCompat.getColor(context,R.color.line_drawn_poly))
+
+ // add icon
+ style.addImage(STOP_IMAGE_ID,stopIcon)
+ style.addImage(POLY_ARROW, polyIconArrow)
+ style.addImage(STOP_ACTIVE_IMG, ResourcesCompat.getDrawable(resources, R.drawable.bus_stop_new_highlight, activity?.theme)!!)
+ // Stops layer
+ val stopsLayer = SymbolLayer(STOPS_LAYER_ID, STOPS_SOURCE_ID)
+ stopsLayer.withProperties(
+ PropertyFactory.iconImage(STOP_IMAGE_ID),
+ PropertyFactory.iconAllowOverlap(true),
+ PropertyFactory.iconIgnorePlacement(true)
+ )
+
+ polylineSource = GeoJsonSource(POLYLINE_SOURCE) //lineFeature?.let { GeoJsonSource(POLYLINE_SOURCE, it) } ?: GeoJsonSource(POLYLINE_SOURCE)
+ style.addSource(polylineSource)
+
+ val color=ContextCompat.getColor(requireContext(),R.color.line_drawn_poly)
+ //paint.style = Paint.Style.FILL_AND_STROKE
+ //paint.strokeJoin = Paint.Join.ROUND
+ //paint.strokeCap = Paint.Cap.ROUND
+
+ val lineLayer = LineLayer(POLYLINE_LAYER, POLYLINE_SOURCE).withProperties(
+ PropertyFactory.lineColor(color),
+ PropertyFactory.lineWidth(5.0f), //originally 13f
+ PropertyFactory.lineOpacity(1.0f),
+ PropertyFactory.lineJoin(Property.LINE_JOIN_ROUND),
+ PropertyFactory.lineCap(Property.LINE_CAP_ROUND)
+
+ )
+ polyArrowSource = GeoJsonSource(POLY_ARROWS_SOURCE, arrowFeatures)
+ style.addSource(polyArrowSource)
+ val arrowsLayer = SymbolLayer(POLY_ARROWS_LAYER, POLY_ARROWS_SOURCE).withProperties(
+ PropertyFactory.iconImage(POLY_ARROW),
+ PropertyFactory.iconRotate(Expression.get("bearing")),
+ PropertyFactory.iconRotationAlignment(ICON_ROTATION_ALIGNMENT_MAP)
+ )
+
+ val layers = style.layers
+ val lastLayers = layers.filter { l-> l.id.contains("city") }
+ //Log.d(DEBUG_TAG,"Layers:\n ${style.layers.map { l -> l.id }}")
+ Log.d(DEBUG_TAG, "City layers: ${lastLayers.map { l-> l.id }}")
+ if(lastLayers.isNotEmpty())
+ style.addLayerAbove(lineLayer,lastLayers[0].id)
+ else
+ style.addLayerBelow(lineLayer,"label_country_1")
+ style.addLayerAbove(stopsLayer, POLYLINE_LAYER)
+ style.addLayerAbove(arrowsLayer, POLYLINE_LAYER)
- // add ability to zoom with 2 fingers
- it.setMultiTouchControls(true)
- it.minZoomLevel = 12.0
+ stopsLayerStarted = true
+ }
- //map controller setup
- val mapController = it.controller
- var zoom = 12.0
- var centerMap = GeoPoint(DEFAULT_CENTER_LAT, DEFAULT_CENTER_LON)
- if(mapViewModel.currentLat.value!=MapViewModel.INVALID) {
- Log.d(DEBUG_TAG, "mapViewModel posi: ${mapViewModel.currentLat.value}, ${mapViewModel.currentLong.value}"+
- " zoom ${mapViewModel.currentZoom.value}")
- zoom = mapViewModel.currentZoom.value!!
- centerMap = GeoPoint(mapViewModel.currentLat.value!!, mapViewModel.currentLong.value!!)
- /*viewLifecycleOwner.lifecycleScope.launch {
- delay(100)
- Log.d(DEBUG_TAG, "zooming back to point")
- controller.animateTo(GeoPoint(mapViewModel.currentLat.value!!, mapViewModel.currentLong.value!!),
- mapViewModel.currentZoom.value!!,null,null)
- //controller.setCenter(GeoPoint(mapViewModel.currentLat.value!!, mapViewModel.currentLong.value!!))
- //controller.setZoom(mapViewModel.currentZoom.value!!)
- */
- }
- mapController.setZoom(zoom)
- mapController.setCenter(centerMap)
- Log.d(DEBUG_TAG, "Initializing map, first init $firstInit")
- //map.invalidate()
- it.overlayManager.add(stopsOverlay)
- it.overlayManager.add(locationOverlay)
- it.overlayManager.add(busPositionsOverlay)
+ /**
+ * Setup the Map Layers
+ */
+ private fun setupBusLayer(style: Style) {
+ // 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)!!)
+
+ // Buses layer
+ val busesLayer = SymbolLayer(BUSES_LAYER_ID, BUSES_SOURCE_ID).apply {
+ withProperties(
+ PropertyFactory.iconImage("bus_symbol"),
+ //PropertyFactory.iconSize(1.2f),
+ PropertyFactory.iconAllowOverlap(true),
+ PropertyFactory.iconIgnorePlacement(true),
+ PropertyFactory.iconRotate(Expression.get("bearing")),
+ PropertyFactory.iconRotationAlignment(ICON_ROTATION_ALIGNMENT_MAP)
- zoomToCurrentPattern()
- firstInit = false
+ )
}
-
+ style.addLayerAbove(busesLayer, STOPS_LAYER_ID)
}
@@ -486,19 +904,16 @@
private fun stopAnimations(){
- for(anim in tripMarkersAnimators.values){
+ for(anim in animatorsByVeh.values){
anim.cancel()
}
}
+ /**
+ * Save the loaded pattern data, without the stops!
+ */
private fun savePatternsToShow(patterns: List<MatoPatternWithStops>){
- val patternsSorter = Comparator{ p1: MatoPatternWithStops, p2: MatoPatternWithStops ->
- if(p1.pattern.directionId != p2.pattern.directionId)
- return@Comparator p1.pattern.directionId - p2.pattern.directionId
- else
- return@Comparator -1*(p1.stopsIndices.size - p2.stopsIndices.size)
- }
currentPatterns = patterns.sortedWith(patternsSorter)
patternsAdapter?.let {
@@ -506,7 +921,40 @@
it.addAll(currentPatterns.map { p->"${p.pattern.directionId} - ${p.pattern.headsign}" })
it.notifyDataSetChanged()
}
- viewingPattern?.let {
+ // if we are loading from a stop, find it
+ val patternToShow = stopIDFromToShow?.let { sID ->
+ val stopGtfsID = "gtt:$sID"
+ var p: MatoPatternWithStops? = null
+ var pLength = 0
+ for(patt in currentPatterns){
+ for(pstop in patt.stopsIndices){
+ if(pstop.stopGtfsId == stopGtfsID){
+ //found
+ if (patt.stopsIndices.size>pLength){
+ p = patt
+ pLength = patt.stopsIndices.size
+ }
+ //break here, we have determined this pattern has the stop we're looking for
+ break
+ }
+ }
+ }
+ p
+ }
+ if(stopIDFromToShow!=null){
+ if(patternToShow==null)
+ Log.w(DEBUG_TAG, "We had to show the pattern from stop $stopIDFromToShow, but we didn't find it")
+ else
+ Log.d(DEBUG_TAG, "Requesting to show pattern from stop $stopIDFromToShow, found pattern ${patternToShow.pattern.code}")
+ }
+ //unset the stopID to show
+ if(patternToShow!=null) {
+
+ //showPattern(patternToShow)
+ patternShown = patternToShow
+ stopIDFromToShow = null
+ }
+ patternShown?.let {
showPattern(it)
}
@@ -519,124 +967,147 @@
Log.d(DEBUG_TAG, "Requesting stops for pattern ${patternWithStops.pattern.code}")
viewModel.selectedPatternLiveData.value = patternWithStops
viewModel.currentPatternStops.value = patternWithStops.stopsIndices.sortedBy { i-> i.order }
- viewingPattern = patternWithStops
+ patternShown = patternWithStops
viewModel.requestStopsForPatternWithStops(patternWithStops)
}
private fun showPattern(patternWs: MatoPatternWithStops){
- Log.d(DEBUG_TAG, "Finding pattern to show: ${patternWs.pattern.code}")
+ //Log.d(DEBUG_TAG, "Finding pattern to show: ${patternWs.pattern.code}")
var pos = -2
val code = patternWs.pattern.code.trim()
- for(k in currentPatterns.indices){
- if(currentPatterns[k].pattern.code.trim() == code){
+ for (k in currentPatterns.indices) {
+ if (currentPatterns[k].pattern.code.trim() == code) {
pos = k
break
}
}
- Log.d(DEBUG_TAG, "Found pattern $code in position: $pos")
- if(pos>=0)
+ Log.d(DEBUG_TAG, "Requesting stops fro pattern $code in position: $pos")
+ if (pos !=-2)
patternsSpinner.setSelection(pos)
- //set pattern
- setPatternAndReqStops(patternWs)
+ else
+ Log.e(DEBUG_TAG, "Pattern with code $code not found!!")
+ //request pattern stops from DB
+ //setPatternAndReqStops(patternWs)
}
private fun zoomToCurrentPattern(){
- var pointsList: List<GeoPoint>
- if(viewingPattern==null) {
- Log.e(DEBUG_TAG, "asked to zoom to pattern but current viewing pattern is null")
- if(polyline!=null)
- pointsList = polyline!!.actualPoints
- else {
- Log.d(DEBUG_TAG, "The polyline is null")
- return
- }
- }else{
- val pattern = viewingPattern!!.pattern
-
- pointsList = PolylineParser.decodePolyline(pattern.patternGeometryPoly, pattern.patternGeometryLength)
- }
-
- var maxLat = -4000.0
- var minLat = -4000.0
- var minLong = -4000.0
- var maxLong = -4000.0
- for (p in pointsList){
- // get max latitude
- if(maxLat == -4000.0)
- maxLat = p.latitude
- else if (maxLat < p.latitude) maxLat = p.latitude
- // find min latitude
- if (minLat == -4000.0)
- minLat = p.latitude
- else if (minLat > p.latitude) minLat = p.latitude
- if(maxLong == -4000.0 || maxLong < p.longitude )
- maxLong = p.longitude
- if (minLong == -4000.0 || minLong > p.longitude)
- minLong = p.longitude
- }
-
- val del = 0.008
- //map.controller.c
- Log.d(DEBUG_TAG, "Setting limits of bounding box of line: $minLat -> $maxLat, $minLong -> $maxLong")
- map.zoomToBoundingBox(BoundingBox(maxLat+del, maxLong+del, minLat-del, minLong-del), false)
- }
-
- private fun showPatternWithStopsOnMap(stops: List<Stop>){
- Log.d(DEBUG_TAG, "Got the stops: ${stops.map { s->s.gtfsID }}}")
- if(viewingPattern==null || map == null) return
-
- val pattern = viewingPattern!!.pattern
+ if(polyline==null) return
+ val NULL_VALUE = -4000.0
+ var maxLat = NULL_VALUE
+ var minLat = NULL_VALUE
+ var minLong = NULL_VALUE
+ var maxLong = NULL_VALUE
+
+ polyline?.let {
+ for(p in it.coordinates()){
+ val lat = p.latitude()
+ val lon = p.longitude()
+ // get max latitude
+ if(maxLat == NULL_VALUE)
+ maxLat =lat
+ else if (maxLat < lat) maxLat = lat
+ // find min latitude
+ if (minLat ==NULL_VALUE)
+ minLat = lat
+ else if (minLat > lat) minLat = lat
+ if(maxLong == NULL_VALUE || maxLong < lon )
+ maxLong = lon
+ if (minLong == NULL_VALUE || minLong > lon)
+ minLong = lon
+ }
+ val padding = 50 // Pixel di padding intorno ai limiti
+
+ Log.d(DEBUG_TAG, "Setting limits of bounding box of line: $minLat -> $maxLat, $minLong -> $maxLong")
+ val bbox = LatLngBounds.from(maxLat,maxLong, minLat, minLong)
+ //map.zoomToBoundingBox(BoundingBox(maxLat+del, maxLong+del, minLat-del, minLong-del), false)
+ map?.animateCamera(CameraUpdateFactory.newLatLngBounds(bbox, padding))
+ }
+
+
+ }
+ private fun displayPatternWithStopsOnMap(patternWs: MatoPatternWithStops, stopsToSort: List<Stop>, zoomToPattern: Boolean){
+ if(!mapInitialized.get()){
+ //set the runnable and do nothing else
+ Log.d(DEBUG_TAG, "Delaying pattern display to when map is Ready: ${patternWs.pattern.code}")
+ toRunWhenMapReady = Runnable {
+ displayPatternWithStopsOnMap(patternWs, stopsToSort, zoomToPattern)
+ }
+ return
+ }
+
+ Log.d(DEBUG_TAG, "Got the stops: ${stopsToSort.map { s->s.gtfsID }}}")
+ patternShown = patternWs
+ //Problem: stops are not sorted
+ val stopOrderD = patternWs.stopsIndices.withIndex().associate{it.value.stopGtfsId to it.index}
+ val stopsSorted = stopsToSort.sortedBy { s-> stopOrderD[s.gtfsID] }
+
+ val pattern = patternWs.pattern
val pointsList = PolylineParser.decodePolyline(pattern.patternGeometryPoly, pattern.patternGeometryLength)
- var maxLat = -4000.0
- var minLat = -4000.0
- var minLong = -4000.0
- var maxLong = -4000.0
- for (p in pointsList){
- // get max latitude
- if(maxLat == -4000.0)
- maxLat = p.latitude
- else if (maxLat < p.latitude) maxLat = p.latitude
- // find min latitude
- if (minLat == -4000.0)
- minLat = p.latitude
- else if (minLat > p.latitude) minLat = p.latitude
- if(maxLong == -4000.0 || maxLong < p.longitude )
- maxLong = p.longitude
- if (minLong == -4000.0 || minLong > p.longitude)
- minLong = p.longitude
- }
- //val polyLine=Polyline(map)
- //polyLine.setPoints(pointsList)
- //save points
- if(map.overlayManager.contains(polyline)){
- map.overlayManager.remove(polyline)
- }
- polyline = Polyline(map, false)
- polyline!!.setPoints(pointsList)
- //polyline.color = ContextCompat.getColor(context!!,R.color.brown_vd)
- polyline!!.infoWindow = null
- val paint = Paint()
- paint.color = ContextCompat.getColor(requireContext(),R.color.line_drawn_poly)
- paint.isAntiAlias = true
- paint.strokeWidth = 13f
-
- paint.style = Paint.Style.FILL_AND_STROKE
- paint.strokeJoin = Paint.Join.ROUND
- paint.strokeCap = Paint.Cap.ROUND
- polyline!!.outlinePaintLists.add(MonochromaticPaintList(paint))
-
- map.overlayManager.add(0,polyline!!)
-
- stopsOverlay.closeAllInfoWindows()
- stopsOverlay.items.clear()
- val stopIcon = ContextCompat.getDrawable(requireContext(), R.drawable.ball)
+ val pointsToShow = pointsList.map { Point.fromLngLat(it.longitude, it.latitude) }
+ Log.d(DEBUG_TAG, "The polyline has ${pointsToShow.size} points to display")
+ polyline = LineString.fromLngLats(pointsToShow)
+ val lineFeature = Feature.fromGeometry(polyline)
+ //Log.d(DEBUG_TAG, "Polyline in JSON is: ${lineFeature.toJson()}")
+
+ // --- STOPS---
+ val features = ArrayList<Feature>()
+ for (s in stopsSorted){
+ if (s.latitude!=null && s.longitude!=null) {
+ val loc = if (showOnTopOfLine) findOptimalPosition(s, pointsList)
+ else LatLng(s.latitude!!, s.longitude!!)
+ features.add(
+ Feature.fromGeometry(
+ Point.fromLngLat(loc.longitude, loc.latitude),
+ JsonObject().apply {
+ addProperty("id", s.ID)
+ addProperty("name", s.stopDefaultName)
+ //addProperty("routes", s.routesThatStopHereToString()) // Add routes array to JSON object
+ }
+ )
+ )
+ }
+ }
+ // -- ARROWS --
+ //val splitPolyline = MapLibreUtils.splitPolyWhenDistanceTooBig(pointsList, 200.0)
+ val arrowFeatures = ArrayList<Feature>()
+ val pointsIndexToShowIcon = MapLibreUtils.findPointsToPutDirectionMarkers(pointsList, stopsSorted, 750.0)
+
+ for (idx in pointsIndexToShowIcon){
+ val pnow = pointsList[idx]
+ val otherp = if(idx>1) pointsList[idx-1] else pointsList[idx+1]
+ val bearing = if (idx>1) MapLibreUtils.getBearing(pointsList[idx-1], pnow) else MapLibreUtils.getBearing(pnow, pointsList[idx+1])
+
+ arrowFeatures.add(Feature.fromGeometry(
+ Point.fromLngLat((pnow.longitude+otherp.longitude)/2, (pnow.latitude+otherp.latitude)/2 ), //average
+ JsonObject().apply {
+ addProperty("bearing", bearing)
+ }
+ ))
+ }
+ Log.d(DEBUG_TAG,"Have put ${features.size} stops to display")
+
+ // if the layer is already started, substitute the stops inside, otherwise start it
+ if (stopsLayerStarted) {
+ stopsSource.setGeoJson(FeatureCollection.fromFeatures(features))
+ polylineSource.setGeoJson(lineFeature)
+ polyArrowSource.setGeoJson(FeatureCollection.fromFeatures(arrowFeatures))
+ lastStopsSizeShown = features.size
+ } else
+ map?.let {
+ Log.d(DEBUG_TAG, "Map stop layer is not started yet, init layer")
+ initStopsPolyLineLayers(mapStyle, FeatureCollection.fromFeatures(features),lineFeature, FeatureCollection.fromFeatures(arrowFeatures))
+ Log.d(DEBUG_TAG,"Started stops layer on map")
+ lastStopsSizeShown = features.size
+ stopsLayerStarted = true
+ } ?:{
+ Log.e(DEBUG_TAG, "Stops layer is not started!!")
+ }
+ /* OLD CODE
for(s in stops){
- val gp = if (showOnTopOfLine)
- findOptimalPosition(s,pointsList)
- else GeoPoint(s.latitude!!,s.longitude!!)
+ val gp =
val marker = MarkerUtils.makeMarker(
gp, s.ID, s.stopDefaultName,
@@ -648,18 +1119,12 @@
marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER)
stopsOverlay.add(marker)
}
+ */
//POINTS LIST IS NOT IN ORDER ANY MORE
//if(!map.overlayManager.contains(stopsOverlay)){
// map.overlayManager.add(stopsOverlay)
//}
- polyline!!.setOnClickListener(Polyline.OnClickListener { polyline, mapView, eventPos ->
- Log.d(DEBUG_TAG, "clicked")
- true
- })
-
- //map.controller.zoomToB//#animateTo(pointsList[0])
- val del = 0.008
- map.zoomToBoundingBox(BoundingBox(maxLat+del, maxLong+del, minLat-del, minLong-del), true)
+ if(zoomToPattern) zoomToCurrentPattern()
//map.invalidate()
}
@@ -669,7 +1134,7 @@
stopsRecyclerView.layoutManager = llManager
}
- private fun showStopsAsList(stops: List<Stop>){
+ private fun showStopsInRecyclerView(stops: List<Stop>){
Log.d(DEBUG_TAG, "Setting stops from: "+viewModel.currentPatternStops.value)
val orderBy = viewModel.currentPatternStops.value!!.withIndex().associate{it.value.stopGtfsId to it.index}
@@ -686,32 +1151,12 @@
}
-
-
}
-
/**
- * Remove bus marker from overlay associated with tripID
+ * This method fixes the display of the pattern, to be used when clicking on a bus
*/
- private fun removeBusMarker(tripID: String){
- if(!busPositionMarkersByTrip.containsKey(tripID)){
- Log.e(DEBUG_TAG, "Asked to remove veh with tripID $tripID but it's supposedly not shown")
- return
- }
- val marker = busPositionMarkersByTrip[tripID]
- busPositionsOverlay.remove(marker)
- busPositionMarkersByTrip.remove(tripID)
-
- val animator = tripMarkersAnimators[tripID]
- animator?.let{
- it.cancel()
- tripMarkersAnimators.remove(tripID)
- }
-
- }
-
- private fun showPatternWithStop(patternId: String){
+ private fun showPatternWithCode(patternId: String){
//var index = 0
Log.d(DEBUG_TAG, "Showing pattern with code $patternId ")
for (i in currentPatterns.indices){
@@ -725,94 +1170,234 @@
}
}
+ private fun removeVehiclesData(vehs: List<String>){
+ for(v in vehs){
+ if (updatesByVehDict.contains(v))
+ updatesByVehDict.remove(v)
+ }
+ }
+
/**
- * draw the position of the buses in the map. Copied from MapFragment
+ * Update function for the bus positions
+ * Takes the processed updates and saves them accordingly
+ * Copied from MapLibreFragment, removing the labels
*/
- private fun updateBusPositionsInMap(tripsPatterns: java.util.HashMap<String, Pair<LivePositionUpdate, TripAndPatternWithStops?>>
- ) {
- //Log.d(MapFragment.DEBUG_TAG, "Updating positions of the buses")
- //if(busPositionsOverlay == null) busPositionsOverlay = new FolderOverlay();
- // cleanup the patterns
- // at first run, the buses which have no direction are still displayed. If those become missing in the data,
- // it becomes clear that they don't have the same direction
- val currentBusesTripsIds = HashSet(busPositionMarkersByTrip.keys)
- for (tripID in currentBusesTripsIds){
- if (!tripsPatterns.keys.contains(tripID)){
- //the tripId is not in the updates anymore, remove it
- removeBusMarker(tripID)
- }
- }
-
- val noPatternsTrips = ArrayList<String>()
- for (tripID in tripsPatterns.keys) {
- val (update, tripWithPatternStops) = tripsPatterns[tripID] ?: continue
-
- var marker: Marker? = null
- //check if Marker is already created
- if (busPositionMarkersByTrip.containsKey(tripID)) {
-
- //check if the trip direction ID is the same, if not remove
- if(tripWithPatternStops?.pattern != null &&
- tripWithPatternStops.pattern.directionId != viewingPattern?.pattern?.directionId){
- removeBusMarker(tripID)
-
- } else {
- //need to change the position of the marker
- marker = busPositionMarkersByTrip.get(tripID)!!
- BusPositionUtils.updateBusPositionMarker(map, marker, update, tripMarkersAnimators, false)
- // Set the pattern to add the info
- if (marker.infoWindow != null && marker.infoWindow is BusInfoWindow) {
- val window = marker.infoWindow as BusInfoWindow
- if (window.pattern == null && tripWithPatternStops != null) {
- //Log.d(DEBUG_TAG, "Update pattern for trip: "+tripID);
- window.setPatternAndDraw(tripWithPatternStops.pattern)
+ private fun updateBusPositionsInMap(incomingData: HashMap<String, Pair<LivePositionUpdate,TripAndPatternWithStops?>>){
+ 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
+ //val symbolsToUpdate = ArrayList<Symbol>()
+ for (upsWithTrp in incomingData.values){
+ val pos = upsWithTrp.first
+ val patternStops = upsWithTrp.second
+ val vehID = pos.vehicle
+ var animate = false
+ if (vehsOld.contains(vehID)){
+ //update position only if the starting or the stopping position of the animation are in the view
+ val oldPos = updatesByVehDict[vehID]?.posUpdate
+ val oldPattern = updatesByVehDict[vehID]?.pattern
+ var avoidShowingUpdateBecauseIsImpossible = false
+ oldPos?.let{
+
+ if(it.routeID!=pos.routeID) {
+ val dist = LatLng(it.latitude, it.longitude).distanceTo(LatLng(pos.latitude, pos.longitude))
+ val speed = dist*3.6 / (pos.timestamp - it.timestamp) //this should be in km/h
+ Log.w(DEBUG_TAG, "Vehicle $vehID changed route from ${oldPos.routeID} to ${pos.routeID}, distance: $dist, speed: $speed")
+ if (speed > 120 || speed < 0){
+ avoidShowingUpdateBecauseIsImpossible = true
}
}
}
- } else {
- //marker is not there, need to make it
- //if (mapView == null) Log.e(MapFragment.DEBUG_TAG, "Creating marker with null map, things will explode")
- marker = Marker(map)
-
- //String route = GtfsUtils.getLineNameFromGtfsID(update.getRouteID());
- val mdraw = ResourcesCompat.getDrawable(getResources(), R.drawable.map_bus_position_icon, null)!!
- //mdraw.setBounds(0,0,28,28);
-
- marker.icon = mdraw
- var markerPattern: MatoPattern? = null
- if (tripWithPatternStops != null) {
- if (tripWithPatternStops.pattern != null)
- markerPattern = tripWithPatternStops.pattern
- }
- marker.infoWindow = BusInfoWindow(map, update, markerPattern, true) {
- // set pattern to show
- if(it!=null)
- showPatternWithStop(it.code)
+ if (avoidShowingUpdateBecauseIsImpossible){
+ // DO NOT SHOW THIS SHIT
+ Log.w(DEBUG_TAG, "Update for vehicle $vehID skipped")
+ continue
}
- //marker.infoWindow as BusInfoWindow
- marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER)
- marker.setInfoWindowAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER)
- BusPositionUtils.updateBusPositionMarker(map,marker, update, tripMarkersAnimators,true)
- // the overlay is null when it's not attached yet?
- // cannot recreate it because it becomes null very soon
- // if(busPositionsOverlay == null) busPositionsOverlay = new FolderOverlay();
- //save the marker
- if (busPositionsOverlay != null) {
- busPositionsOverlay.add(marker)
- busPositionMarkersByTrip.put(tripID, marker)
+
+ val samePosition = oldPos?.let { (it.latitude==pos.latitude)&&(it.longitude == pos.longitude) }?:false
+ val setPattern = (oldPattern==null) && (patternStops!=null)
+ if((!samePosition)|| setPattern) {
+ //TODO RESTORE THIS PART
+ /*val isPositionInBounds = isInsideVisibleRegion(
+ pos.latitude, pos.longitude, true
+ ) || (oldPos?.let { isInsideVisibleRegion(it.latitude,it.longitude,true) } ?: false)
+
+ */
+ val skip = true
+ if (skip) {
+ //animate = true
+ // set the pattern data too
+ updatesByVehDict[vehID]!!.pattern = patternStops?.pattern
+ //this moves both the icon and the label
+ animateNewPositionMove(pos)
+
+ } else {
+ //update
+ updatesByVehDict[vehID] = LivePositionTripPattern(pos,patternStops?.pattern)
+ /*busLabelSymbolsByVeh[vehID]?.let {
+ it.latLng = LatLng(pos.latitude, pos.longitude)
+ symbolsToUpdate.add(it)
+ }*/
+ //if(vehShowing==vehID)
+ // map?.animateCamera(CameraUpdateFactory.newLatLng(LatLng(pos.latitude, pos.longitude)),500)
+ //TODO: Follow the vehicle
+ }
}
+ countUpds++
+ }
+ else{
+ //not inside
+ // update it simply
+ updatesByVehDict[vehID] = LivePositionTripPattern(pos, patternStops?.pattern)
+ //createLabelForVehicle(pos)
+ //if(vehShowing==vehID)
+ // map?.animateCamera(CameraUpdateFactory.newLatLng(LatLng(pos.latitude, pos.longitude)),500)
+ }
+ if (vehID == vehShowing){
+ //update the data
+ showVehicleTripInBottomSheet(vehID)
+ }
+ }
+ //symbolManager.update(symbolsToUpdate)
+ //remove old positions
+ Log.d(DEBUG_TAG, "Updated $countUpds vehicles")
+ vehsOld.removeAll(vehsNew)
+ //now vehsOld contains the vehicles id for those that have NOT been updated
+ val currentTimeStamp = System.currentTimeMillis() /1000
+ for(vehID in vehsOld){
+ //remove after 2 minutes of inactivity
+ if (updatesByVehDict[vehID]!!.posUpdate.timestamp - currentTimeStamp > 2*60){
+ updatesByVehDict.remove(vehID)
+ //removeVehicleLabel(vehID)
}
}
+ //update UI
+ updatePositionsIcons(false)
+ }
+
+ /**
+ * This is the tricky part, animating the transitions
+ * Basically, we need to set the new positions with the data and redraw them all
+ */
+ private fun animateNewPositionMove(positionUpdate: LivePositionUpdate){
+ if (positionUpdate.vehicle !in updatesByVehDict.keys)
+ return
+ val vehID = positionUpdate.vehicle
+ val currentUpdate = updatesByVehDict[positionUpdate.vehicle]
+ currentUpdate?.let { it ->
+ //cancel current animation on vehicle
+ animatorsByVeh[vehID]?.cancel()
+ val posUp = it.posUpdate
+
+ val currentPos = LatLng(posUp.latitude, posUp.longitude)
+ val newPos = LatLng(positionUpdate.latitude, positionUpdate.longitude)
+ val valueAnimator = ValueAnimator.ofObject(MapLibreUtils.LatLngEvaluator(), currentPos, newPos)
+ valueAnimator.addUpdateListener(object : ValueAnimator.AnimatorUpdateListener {
+ private var latLng: LatLng? = null
+ override fun onAnimationUpdate(animation: ValueAnimator) {
+ latLng = animation.animatedValue as LatLng
+ //update position on animation
+ val update = updatesByVehDict[positionUpdate.vehicle]!!
+ latLng?.let { ll->
+ update.posUpdate.latitude = ll.latitude
+ update.posUpdate.longitude = ll.longitude
+ updatePositionsIcons(false)
+ }
+ }
+ })
+ /*valueAnimator.addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationStart(animation: Animator) {
+ super.onAnimationStart(animation)
+ //val update = positionsByVehDict[positionUpdate.vehicle]!!
+ //remove the label at the start of the animation
+ //removeVehicleLabel(vehID)
+ val annot = busLabelSymbolsByVeh[vehID]
+ annot?.let { sym ->
+ sym.textOpacity = 0.0f
+ symbolsToUpdate.add(sym)
+ }
+ }
+
+ override fun onAnimationEnd(animation: Animator) {
+ super.onAnimationEnd(animation)
+ /*val annot = busLabelSymbolsByVeh[vehID]
+ annot?.let { sym ->
+ sym.textOpacity = 1.0f
+ sym.latLng = newPos //LatLng(newPos)
+ symbolsToUpdate.add(sym)
+ }
+
+ */
+ }
+ })
+ */
+ animatorsByVeh[vehID]?.cancel()
+ //set the new position as the current one but with the old lat and lng
+ positionUpdate.latitude = posUp.latitude
+ positionUpdate.longitude = posUp.longitude
+ updatesByVehDict[vehID]!!.posUpdate = positionUpdate
+ valueAnimator.duration = 300
+ valueAnimator.interpolator = LinearInterpolator()
+ valueAnimator.start()
+
+ animatorsByVeh[vehID] = valueAnimator
+
+ } ?: {
+ Log.e(DEBUG_TAG, "Have to run animation for veh ${positionUpdate.vehicle} but not in the dict, adding")
+ //updatesByVehDict[positionUpdate.vehicle] = positionUpdate
+ }
+ }
+
+ /**
+ * Update the bus positions displayed on the map, from the existing data
+ */
+ private fun updatePositionsIcons(forced: Boolean){
+ //avoid frequent updates
+ val currentTime = System.currentTimeMillis()
+ if(!forced && currentTime - lastUpdateTime < 60){
+ //DO NOT UPDATE THE MAP
+ return
+ }
+ val features = ArrayList<Feature>()//stops.mapNotNull { stop ->
+ //stop.latitude?.let { lat ->
+ // stop.longitude?.let { lon ->
+ for (dat in updatesByVehDict.values){
+ //if (s.latitude!=null && s.longitude!=null)
+ val pos = dat.posUpdate
+ val point = Point.fromLngLat(pos.longitude, pos.latitude)
+ features.add(
+ Feature.fromGeometry(
+ point,
+ JsonObject().apply {
+ addProperty("veh", pos.vehicle)
+ addProperty("trip", pos.tripID)
+ addProperty("bearing", pos.bearing ?:0.0f)
+ addProperty("line", pos.routeID)
+ }
+ )
+ )
+ /*busLabelSymbolsByVeh[pos.vehicle]?.let {
+ it.latLng = LatLng(pos.latitude, pos.longitude)
+ symbolsToUpdate.add(it)
+ }
- if (noPatternsTrips.size > 0) {
- Log.i(DEBUG_TAG, "These trips have no matching pattern: $noPatternsTrips")
+ */
}
+ busesSource.setGeoJson(FeatureCollection.fromFeatures(features))
+ //update labels, clear cache to be used
+ //symbolManager.update(symbolsToUpdate)
+ //symbolsToUpdate.clear()
+ lastUpdateTime = System.currentTimeMillis()
}
+
override fun onResume() {
super.onResume()
Log.d(DEBUG_TAG, "Resetting paused from onResume")
+ mapView.onResume()
pausedFragment = false
val keySourcePositions = getString(R.string.pref_positions_source)
@@ -829,7 +1414,8 @@
if(mapViewModel.currentLat.value!=MapViewModel.INVALID) {
Log.d(DEBUG_TAG, "mapViewModel posi: ${mapViewModel.currentLat.value}, ${mapViewModel.currentLong.value}"+
" zoom ${mapViewModel.currentZoom.value}")
- val controller = map.controller
+ //THIS WAS A FIX FOR THE OLD OSMDROID MAP
+ /*val controller = map.controller
viewLifecycleOwner.lifecycleScope.launch {
delay(100)
Log.d(DEBUG_TAG, "zooming back to point")
@@ -838,8 +1424,7 @@
//controller.setCenter(GeoPoint(mapViewModel.currentLat.value!!, mapViewModel.currentLong.value!!))
//controller.setZoom(mapViewModel.currentZoom.value!!)
}
-
- //controller.setZoom()
+ */
}
//initialize GUI here
fragmentListener.readyGUIfor(FragmentKind.LINES)
@@ -848,13 +1433,69 @@
override fun onPause() {
super.onPause()
- liveBusViewModel.stopMatoUpdates()
+ mapView.onPause()
+ if(useMQTTPositions) liveBusViewModel.stopMatoUpdates()
pausedFragment = true
//save map
- val center = map.mapCenter
- mapViewModel.currentLat.value = center.latitude
- mapViewModel.currentLong.value = center.longitude
- mapViewModel.currentZoom.value = map.zoomLevel.toDouble()
+ val camera = map?.cameraPosition
+ camera?.let {cam->
+ mapViewModel.currentLat.value = cam.target?.latitude ?: -400.0
+ mapViewModel.currentLong.value = cam.target?.longitude ?: -400.0
+ mapViewModel.currentZoom.value = cam.zoom
+ }
+
+ }
+
+ override fun onStart() {
+ super.onStart()
+ mapView.onStart()
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ mapView.onDestroy()
+ }
+
+ override fun onStop() {
+ super.onStop()
+ mapView.onStop()
+ shownStopInBottomSheet?.let {
+ mapViewModel.stopShowing = it
+ }
+ shouldMapLocationBeReactivated = locationComponent.isLocationComponentEnabled
+ }
+
+ override fun onDestroyView() {
+ map?.run {
+ Log.d(DEBUG_TAG, "Saving camera position")
+ savedCameraPosition = cameraPosition
+ }
+
+ super.onDestroyView()
+ Log.d(DEBUG_TAG, "Destroying the views")
+
+ /*mapStyle.removeLayer(STOPS_LAYER_ID)
+
+ mapStyle?.removeSource(STOPS_SOURCE_ID)
+
+ mapStyle.removeLayer(POLYLINE_LAYER)
+ mapStyle.removeSource(POLYLINE_SOURCE)
+ */
+ //stopsLayerStarted = false
+ }
+
+ override fun onMapDestroy() {
+ mapStyle.removeLayer(STOPS_LAYER_ID)
+
+ mapStyle.removeSource(STOPS_SOURCE_ID)
+
+ mapStyle.removeLayer(POLYLINE_LAYER)
+ mapStyle.removeSource(POLYLINE_SOURCE)
+ mapStyle.removeLayer(BUSES_LAYER_ID)
+ mapStyle.removeSource(BUSES_SOURCE_ID)
+
+
+ map?.locationComponent?.isLocationComponentEnabled = false
}
override fun getBaseViewForSnackBar(): View? {
@@ -863,16 +1504,31 @@
companion object {
private const val LINEID_KEY="lineID"
- fun newInstance() = LinesDetailFragment()
- const val DEBUG_TAG="LinesDetailFragment"
+ private const val STOPID_FROM_KEY="stopID"
+ private const val STOPS_SOURCE_ID = "stops-source"
+ private const val STOPS_LAYER_ID = "stops-layer"
+ private const val STOP_ACTIVE_IMG = "stop_active_img"
+ private const val STOP_IMAGE_ID = "stop-img"
+ private const val POLYLINE_LAYER = "polyline-layer"
+ private const val POLYLINE_SOURCE = "polyline-source"
+
+ private const val POLY_ARROWS_LAYER = "arrows-layer"
+ private const val POLY_ARROWS_SOURCE = "arrows-source"
+ private const val POLY_ARROW ="poly-arrow-img"
- fun makeArgs(lineID: String): Bundle{
+ private const val DEBUG_TAG="BusTO-LineDetalFragment"
+
+ fun makeArgs(lineID: String, stopIDFrom: String?): Bundle{
val b = Bundle()
b.putString(LINEID_KEY, lineID)
+ b.putString(STOPID_FROM_KEY, stopIDFrom)
return b
}
+ fun newInstance(lineID: String?, stopIDFrom: String?) = LinesDetailFragment().apply {
+ lineID?.let { arguments = makeArgs(it, stopIDFrom) }
+ }
@JvmStatic
- private fun findOptimalPosition(stop: Stop, pointsList: MutableList<GeoPoint>): GeoPoint{
+ private fun findOptimalPosition(stop: Stop, pointsList: MutableList<LatLng>): LatLng{
if(stop.latitude==null || stop.longitude ==null|| pointsList.isEmpty())
throw IllegalArgumentException()
val sLat = stop.latitude!!
@@ -885,10 +1541,10 @@
val p2 = pointsList[1]
if (p1.longitude == p2.longitude){
//Log.e(DEBUG_TAG, "Same longitude")
- return GeoPoint(sLat, p1.longitude)
+ return LatLng(sLat, p1.longitude)
} else if (p1.latitude == p2.latitude){
//Log.d(DEBUG_TAG, "Same latitude")
- return GeoPoint(p2.latitude,sLong)
+ return LatLng(p2.latitude,sLong)
}
val m = (p1.latitude - p2.latitude) / (p1.longitude - p2.longitude)
@@ -898,10 +1554,14 @@
val longNew = (minv * sLong + sLat -cR ) / (m+minv)
val latNew = (m*longNew + cR)
//Log.d(DEBUG_TAG,"Stop ${stop.ID} old pos: ($sLat, $sLong), new pos ($latNew,$longNew)")
- return GeoPoint(latNew,longNew)
+ return LatLng(latNew,longNew)
}
private const val DEFAULT_CENTER_LAT = 45.12
private const val DEFAULT_CENTER_LON = 7.6858
}
+
+ enum class BottomShowing{
+ STOP, VEHICLE
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/LinesGridShowingFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/LinesGridShowingFragment.kt
--- a/app/src/main/java/it/reyboz/bustorino/fragments/LinesGridShowingFragment.kt
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/LinesGridShowingFragment.kt
@@ -65,7 +65,7 @@
}
private val routeClickListener = RouteAdapter.ItemClicker {
- fragmentListener.showLineOnMap(it.gtfsId)
+ fragmentListener.showLineOnMap(it.gtfsId, null)
}
private val arrows = HashMap<String, ImageView>()
private val durations = HashMap<String, Long>()
@@ -158,7 +158,7 @@
//create new item click listener every time
val adapter = RouteOnlyLineAdapter(routesNames){ pos, _ ->
val r = routes[pos]
- fragmentListener.showLineOnMap(r.gtfsId)
+ fragmentListener.showLineOnMap(r.gtfsId, null)
}
favoritesRecyclerView.adapter = adapter
}
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/MainScreenFragment.java b/app/src/main/java/it/reyboz/bustorino/fragments/MainScreenFragment.java
--- a/app/src/main/java/it/reyboz/bustorino/fragments/MainScreenFragment.java
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/MainScreenFragment.java
@@ -725,9 +725,9 @@
}
@Override
- public void showLineOnMap(String routeGtfsId) {
+ public void showLineOnMap(String routeGtfsId, @Nullable String stopIDFrom) {
//pass to activity
- mListener.showLineOnMap(routeGtfsId);
+ mListener.showLineOnMap(routeGtfsId, stopIDFrom);
}
@Override
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/MapFragment.java b/app/src/main/java/it/reyboz/bustorino/fragments/MapFragment.java
--- a/app/src/main/java/it/reyboz/bustorino/fragments/MapFragment.java
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/MapFragment.java
@@ -218,8 +218,8 @@
// add ability to zoom with 2 fingers
map.setMultiTouchControls(true);
- btCenterMap = root.findViewById(R.id.icon_center_map);
- btFollowMe = root.findViewById(R.id.icon_follow);
+ btCenterMap = root.findViewById(R.id.centerMapImageButton);
+ btFollowMe = root.findViewById(R.id.followUserImageButton);
coordLayout = root.findViewById(R.id.coord_layout);
//setup FolderOverlay
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/MapFragmentKt.kt b/app/src/main/java/it/reyboz/bustorino/fragments/MapFragmentKt.kt
new file mode 100644
--- /dev/null
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/MapFragmentKt.kt
@@ -0,0 +1,750 @@
+/*
+ BusTO - Fragments components
+ Copyright (C) 2020 Andrea Ugo
+ 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 <http://www.gnu.org/licenses/>.
+ */
+package it.reyboz.bustorino.fragments
+
+import android.Manifest
+import android.animation.ObjectAnimator
+import android.annotation.SuppressLint
+import android.content.Context
+import android.location.LocationManager
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageButton
+import android.widget.Toast
+import androidx.activity.result.ActivityResultCallback
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.coordinatorlayout.widget.CoordinatorLayout
+import androidx.core.content.res.ResourcesCompat
+import androidx.fragment.app.viewModels
+import androidx.preference.PreferenceManager
+import it.reyboz.bustorino.R
+import it.reyboz.bustorino.backend.Stop
+import it.reyboz.bustorino.backend.gtfs.LivePositionUpdate
+import it.reyboz.bustorino.backend.mato.MQTTMatoClient
+import it.reyboz.bustorino.backend.utils
+import it.reyboz.bustorino.data.gtfs.MatoPattern
+import it.reyboz.bustorino.data.gtfs.TripAndPatternWithStops
+import it.reyboz.bustorino.map.BusInfoWindow
+import it.reyboz.bustorino.map.CustomInfoWindow
+import it.reyboz.bustorino.map.CustomInfoWindow.TouchResponder
+import it.reyboz.bustorino.map.LocationOverlay
+import it.reyboz.bustorino.map.LocationOverlay.OverlayCallbacks
+import it.reyboz.bustorino.map.MarkerUtils
+import it.reyboz.bustorino.middleware.GeneralActivity
+import it.reyboz.bustorino.util.Permissions
+import it.reyboz.bustorino.viewmodels.LivePositionsViewModel
+import it.reyboz.bustorino.viewmodels.StopsMapViewModel
+import org.osmdroid.config.Configuration
+import org.osmdroid.events.DelayedMapListener
+import org.osmdroid.events.MapListener
+import org.osmdroid.events.ScrollEvent
+import org.osmdroid.events.ZoomEvent
+import org.osmdroid.tileprovider.tilesource.TileSourceFactory
+import org.osmdroid.util.GeoPoint
+import org.osmdroid.views.MapView
+import org.osmdroid.views.overlay.FolderOverlay
+import org.osmdroid.views.overlay.Marker
+import org.osmdroid.views.overlay.infowindow.InfoWindow
+import org.osmdroid.views.overlay.mylocation.GpsMyLocationProvider
+
+open class MapFragmentKt : ScreenBaseFragment() {
+ protected var listenerMain: FragmentListenerMain? = null
+ private var shownStops: HashSet<String>? = null
+ private lateinit var map: MapView
+ var ctx: Context? = null
+ private lateinit var mLocationOverlay: LocationOverlay
+ private lateinit var stopsFolderOverlay: FolderOverlay
+ private var savedMapState: Bundle? = null
+ protected lateinit var btCenterMap: ImageButton
+ protected lateinit var btFollowMe: ImageButton
+ protected var coordLayout: CoordinatorLayout? = null
+ private var hasMapStartFinished = false
+ private var followingLocation = false
+
+ //the ViewModel from which we get the stop to display in the map
+ private val stopsViewModel: StopsMapViewModel by viewModels()
+
+ //private GtfsPositionsViewModel gtfsPosViewModel; //= new ViewModelProvider(this).get(MapViewModel.class);
+ private val livePositionsViewModel: LivePositionsViewModel by viewModels()
+ private var useMQTTViewModel = true
+ private val busPositionMarkersByTrip = HashMap<String, Marker>()
+ private var busPositionsOverlay: FolderOverlay? = null
+ private val tripMarkersAnimators = HashMap<String, ObjectAnimator>()
+ protected val responder = TouchResponder { stopID, stopName ->
+ if (listenerMain != null) {
+ Log.d(DEBUG_TAG, "Asked to show arrivals for stop ID: $stopID")
+ listenerMain!!.requestArrivalsForStopID(stopID)
+ }
+ }
+ protected val locationCallbacks: OverlayCallbacks = object : OverlayCallbacks {
+ override fun onDisableFollowMyLocation() {
+ updateGUIForLocationFollowing(false)
+ followingLocation = false
+ }
+
+ override fun onEnableFollowMyLocation() {
+ updateGUIForLocationFollowing(true)
+ followingLocation = true
+ }
+ }
+ private val positionRequestLauncher =
+ registerForActivityResult<Array<String>, Map<String, Boolean>>(
+ ActivityResultContracts.RequestMultiplePermissions(),
+ ActivityResultCallback { result ->
+ if (result == null) {
+ Log.w(DEBUG_TAG, "Got asked permission but request is null, doing nothing?")
+ } else if (java.lang.Boolean.TRUE == result[Manifest.permission.ACCESS_COARSE_LOCATION]
+ && java.lang.Boolean.TRUE == result[Manifest.permission.ACCESS_FINE_LOCATION]) {
+ // We can use the position, restart location overlay
+ map.overlays.remove(mLocationOverlay)
+ startLocationOverlay(true, map)
+ if (context == null || requireContext().getSystemService(Context.LOCATION_SERVICE) == null)
+ return@ActivityResultCallback ///@registerForActivityResult
+ val locationManager =
+ requireContext().getSystemService(Context.LOCATION_SERVICE) as LocationManager
+ @SuppressLint("MissingPermission") val userLocation =
+ locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
+ if (userLocation != null) {
+ map!!.controller.setZoom(POSITION_FOUND_ZOOM)
+ val startPoint = GeoPoint(userLocation)
+ setLocationFollowing(true)
+ map!!.controller.setCenter(startPoint)
+ }
+ } else Log.w(DEBUG_TAG, "No location permission")
+ })
+
+ //public static MapFragment getInstance(@NonNull Stop stop){
+ // return getInstance(stop.getLatitude(), stop.getLongitude(), stop.getStopDisplayName(), stop.ID);
+ //}
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ //use the same layout as the activity
+ val root = inflater.inflate(R.layout.fragment_map, container, false)
+ val context = requireContext()
+ ctx = context.applicationContext
+ Configuration.getInstance().load(ctx, PreferenceManager.getDefaultSharedPreferences(context))
+ map = root.findViewById(R.id.map)
+ map.setTileSource(TileSourceFactory.MAPNIK)
+ //map.setTilesScaledToDpi(true);
+ map.setFlingEnabled(true)
+
+ // add ability to zoom with 2 fingers
+ map.setMultiTouchControls(true)
+ btCenterMap = root.findViewById(R.id.centerMapImageButton)
+ btFollowMe = root.findViewById(R.id.followUserImageButton)
+ coordLayout = root.findViewById(R.id.coord_layout)
+
+ //setup FolderOverlay
+ stopsFolderOverlay = FolderOverlay()
+ //setup Bus Markers Overlay
+ busPositionsOverlay = FolderOverlay()
+ //reset shown bus updates
+ busPositionMarkersByTrip.clear()
+ tripMarkersAnimators.clear()
+ //set map not done
+ hasMapStartFinished = false
+ val keySourcePositions = getString(R.string.pref_positions_source)
+ useMQTTViewModel = PreferenceManager.getDefaultSharedPreferences(requireContext())
+ .getString(keySourcePositions, SettingsFragment.LIVE_POSITIONS_PREF_MQTT_VALUE)
+ .contentEquals(SettingsFragment.LIVE_POSITIONS_PREF_MQTT_VALUE)
+
+
+ //Start map from bundle
+ if (savedInstanceState != null) startMap(arguments, savedInstanceState) else startMap(
+ arguments, savedMapState
+ )
+ //set listeners
+ map.addMapListener(DelayedMapListener(object : MapListener {
+ override fun onScroll(paramScrollEvent: ScrollEvent): Boolean {
+ requestStopsToShow()
+ //Log.d(DEBUG_TAG, "Scrolling");
+ //if (moveTriggeredByCode) moveTriggeredByCode =false;
+ //else setLocationFollowing(false);
+ return true
+ }
+
+ override fun onZoom(event: ZoomEvent): Boolean {
+ requestStopsToShow()
+ return true
+ }
+ }))
+ btCenterMap.setOnClickListener(View.OnClickListener { v: View? ->
+ //Log.i(TAG, "centerMap clicked ");
+ if (Permissions.bothLocationPermissionsGranted(context)) {
+ val myPosition = mLocationOverlay!!.myLocation
+ map.getController().animateTo(myPosition)
+ } else Toast.makeText(context, R.string.enable_position_message_map, Toast.LENGTH_SHORT)
+ .show()
+ })
+ btFollowMe.setOnClickListener(View.OnClickListener { v: View? ->
+ //Log.i(TAG, "btFollowMe clicked ");
+ if (Permissions.bothLocationPermissionsGranted(context)) setLocationFollowing(!followingLocation) else Toast.makeText(
+ context, R.string.enable_position_message_map, Toast.LENGTH_SHORT
+ )
+ .show()
+ })
+ return root
+ }
+
+ override fun onAttach(context: Context) {
+ super.onAttach(context)
+ listenerMain = if (context is FragmentListenerMain) {
+ context
+ } else {
+ throw RuntimeException(
+ context.toString()
+ + " must implement FragmentListenerMain"
+ )
+ }
+ }
+
+ override fun onDetach() {
+ super.onDetach()
+ listenerMain = null
+
+ Log.w(DEBUG_TAG, "Fragment detached")
+ }
+
+ override fun onPause() {
+ super.onPause()
+ Log.w(DEBUG_TAG, "On pause called mapfrag")
+ saveMapState()
+ for (animator in tripMarkersAnimators.values) {
+ if (animator != null && animator.isRunning) {
+ animator.cancel()
+ }
+ }
+ tripMarkersAnimators.clear()
+ if (useMQTTViewModel) livePositionsViewModel.stopMatoUpdates()
+ }
+
+ /**
+ * Save the map state inside the fragment
+ * (calls saveMapState(bundle))
+ */
+ private fun saveMapState() {
+ savedMapState = Bundle()
+ saveMapState(savedMapState!!)
+ }
+
+ /**
+ * Save the state of the map to restore it to a later time
+ * @param bundle the bundle in which to save the data
+ */
+ private fun saveMapState(bundle: Bundle) {
+ Log.d(DEBUG_TAG, "Saving state, location following: $followingLocation")
+ bundle.putBoolean(FOLLOWING_LOCAT_KEY, followingLocation)
+ if (map == null) {
+ //The map is null, it can happen?
+ Log.e(DEBUG_TAG, "Cannot save map center, map is null")
+ return
+ }
+ val loc = map!!.mapCenter
+ bundle.putDouble(MAP_CENTER_LAT_KEY, loc.latitude)
+ bundle.putDouble(MAP_CENTER_LON_KEY, loc.longitude)
+ bundle.putDouble(MAP_CURRENT_ZOOM_KEY, map!!.zoomLevelDouble)
+ }
+
+ override fun onResume() {
+ super.onResume()
+ //TODO: cleanup duplicate code (maybe merging the positions classes?)
+ if (listenerMain != null) listenerMain!!.readyGUIfor(FragmentKind.MAP)
+ /// choose which to use
+ val keySourcePositions = getString(R.string.pref_positions_source)
+ useMQTTViewModel = PreferenceManager.getDefaultSharedPreferences(requireContext())
+ .getString(keySourcePositions, SettingsFragment.LIVE_POSITIONS_PREF_MQTT_VALUE)
+ .contentEquals(
+ SettingsFragment.LIVE_POSITIONS_PREF_MQTT_VALUE
+ )
+ //gtfsPosViewModel.requestUpdates();
+ if (useMQTTViewModel) livePositionsViewModel.requestMatoPosUpdates(MQTTMatoClient.LINES_ALL)
+ else livePositionsViewModel.requestGTFSUpdates()
+ //mapViewModel.testCascade();
+ livePositionsViewModel.isLastWorkResultGood.observe(this) { d: Boolean ->
+ Log.d(
+ DEBUG_TAG, "Last trip download result is $d"
+ )
+ }
+ livePositionsViewModel.tripsGtfsIDsToQuery.observe(this) { dat: List<String> ->
+ Log.i(DEBUG_TAG, "Have these trips IDs missing from the DB, to be queried: $dat")
+ livePositionsViewModel.downloadTripsFromMato(dat)
+ }
+
+ //rerequest stop
+ stopsViewModel!!.requestStopsInBoundingBox(map!!.boundingBox)
+ }
+
+ private fun startRequestsPositions() {
+ if (livePositionsViewModel != null) {
+ //should always be the case
+ livePositionsViewModel!!.updatesWithTripAndPatterns.observe(viewLifecycleOwner) { data: HashMap<String, Pair<LivePositionUpdate, TripAndPatternWithStops?>> ->
+ Log.d(
+ DEBUG_TAG,
+ "Have " + data.size + " trip updates, has Map start finished: " + hasMapStartFinished
+ )
+ if (hasMapStartFinished) updateBusPositionsInMap(data)
+ if (!isDetached && !useMQTTViewModel) livePositionsViewModel!!.requestDelayedGTFSUpdates(
+ 3000
+ )
+ }
+ } else {
+ Log.e(DEBUG_TAG, "PositionsViewModel is null")
+ }
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ saveMapState(outState)
+ super.onSaveInstanceState(outState)
+ }
+ //own methods
+ /**
+ * Switch following the location on and off
+ * @param value true if we want to follow location
+ */
+ fun setLocationFollowing(value: Boolean) {
+ followingLocation = value
+ if (mLocationOverlay == null || context == null || map == null) //nothing else to do
+ return
+ if (value) {
+ mLocationOverlay!!.enableFollowLocation()
+ } else {
+ mLocationOverlay!!.disableFollowLocation()
+ }
+ }
+
+ /**
+ * Do all the stuff you need to do on the gui, when parameter is changed to value
+ * @param following value
+ */
+ protected fun updateGUIForLocationFollowing(following: Boolean) {
+ if (following) btFollowMe!!.setImageResource(R.drawable.ic_follow_me_on) else btFollowMe!!.setImageResource(
+ R.drawable.ic_follow_me
+ )
+ }
+
+ /**
+ * Build the location overlay. Enable only when
+ * a) we know we have the permission
+ * b) the location map is set
+ */
+ private fun startLocationOverlay(enableLocation: Boolean, map: MapView?) {
+ checkNotNull(activity) { "Cannot enable LocationOverlay now" }
+ // Location Overlay
+ // from OpenBikeSharing (THANK GOD)
+ Log.d(DEBUG_TAG, "Starting position overlay")
+ val imlp = GpsMyLocationProvider(requireActivity().baseContext)
+ imlp.locationUpdateMinDistance = 5f
+ imlp.locationUpdateMinTime = 2000
+ val overlay = LocationOverlay(imlp, map, locationCallbacks)
+ if (enableLocation) overlay.enableMyLocation()
+ overlay.isOptionsMenuEnabled = true
+
+ //map.getOverlays().add(this.mLocationOverlay);
+ mLocationOverlay = overlay
+ map!!.overlays.add(mLocationOverlay)
+ }
+
+ fun startMap(incoming: Bundle?, savedInstanceState: Bundle?) {
+ //Check that we're attached
+ val activity = if (activity is GeneralActivity) activity as GeneralActivity? else null
+ if (context == null || activity == null) {
+ //we are not attached
+ Log.e(DEBUG_TAG, "Calling startMap when not attached")
+ return
+ } else {
+ Log.d(DEBUG_TAG, "Starting map from scratch")
+ }
+ //clear previous overlays
+ map!!.overlays.clear()
+
+
+ //parse incoming bundle
+ var marker: GeoPoint? = null
+ var name: String? = null
+ var ID: String? = null
+ var routesStopping: String? = ""
+ if (incoming != null) {
+ val lat = incoming.getDouble(BUNDLE_LATIT)
+ val lon = incoming.getDouble(BUNDLE_LONGIT)
+ marker = GeoPoint(lat, lon)
+ name = incoming.getString(BUNDLE_NAME)
+ ID = incoming.getString(BUNDLE_ID)
+ routesStopping = incoming.getString(BUNDLE_ROUTES_STOPPING, "")
+ }
+
+
+ //ask for location permission
+ if (!Permissions.bothLocationPermissionsGranted(activity)) {
+ if (shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)) {
+ //TODO: show dialog for permission rationale
+ Toast.makeText(activity, R.string.enable_position_message_map, Toast.LENGTH_SHORT)
+ .show()
+ }
+ positionRequestLauncher.launch(Permissions.LOCATION_PERMISSIONS)
+ }
+ shownStops = HashSet()
+ // move the map on the marker position or on a default view point: Turin, Piazza Castello
+ // and set the start zoom
+ val mapController = map!!.controller
+ var startPoint: GeoPoint? = null
+ startLocationOverlay(
+ Permissions.bothLocationPermissionsGranted(activity),
+ map
+ )
+ // set the center point
+ if (marker != null) {
+ //startPoint = marker;
+ mapController.setZoom(POSITION_FOUND_ZOOM)
+ setLocationFollowing(false)
+ // put the center a little bit off (animate later)
+ startPoint = GeoPoint(marker)
+ startPoint.latitude = marker.latitude + utils.angleRawDifferenceFromMeters(20.0)
+ startPoint.longitude = marker.longitude - utils.angleRawDifferenceFromMeters(20.0)
+ //don't need to do all the rest since we want to show a point
+ } else if (savedInstanceState != null && savedInstanceState.containsKey(MAP_CURRENT_ZOOM_KEY)) {
+ mapController.setZoom(savedInstanceState.getDouble(MAP_CURRENT_ZOOM_KEY))
+ mapController.setCenter(
+ GeoPoint(
+ savedInstanceState.getDouble(MAP_CENTER_LAT_KEY),
+ savedInstanceState.getDouble(MAP_CENTER_LON_KEY)
+ )
+ )
+ Log.d(
+ DEBUG_TAG,
+ "Location following from savedInstanceState: " + savedInstanceState.getBoolean(
+ FOLLOWING_LOCAT_KEY
+ )
+ )
+ setLocationFollowing(savedInstanceState.getBoolean(FOLLOWING_LOCAT_KEY))
+ } else {
+ Log.d(DEBUG_TAG, "No position found from intent or saved state")
+ var found = false
+ val locationManager =
+ requireContext().getSystemService(Context.LOCATION_SERVICE) as LocationManager
+ //check for permission
+ if (Permissions.bothLocationPermissionsGranted(activity)) {
+ @SuppressLint("MissingPermission") val userLocation =
+ locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
+ if (userLocation != null) {
+ val distan = utils.measuredistanceBetween(
+ userLocation.latitude, userLocation.longitude,
+ DEFAULT_CENTER_LAT, DEFAULT_CENTER_LON
+ )
+ if (distan < 100000.0) {
+ mapController.setZoom(POSITION_FOUND_ZOOM)
+ startPoint = GeoPoint(userLocation)
+ found = true
+ setLocationFollowing(true)
+ }
+ }
+ }
+ if (!found) {
+ startPoint = GeoPoint(DEFAULT_CENTER_LAT, DEFAULT_CENTER_LON)
+ mapController.setZoom(NO_POSITION_ZOOM)
+ setLocationFollowing(false)
+ }
+ }
+
+ // set the minimum zoom level
+ map!!.minZoomLevel = 15.0
+ //add contingency check (shouldn't happen..., but)
+ if (startPoint != null) {
+ mapController.setCenter(startPoint)
+ }
+
+
+ //add stops overlay
+ //map.getOverlays().add(mLocationOverlay);
+ map!!.overlays.add(stopsFolderOverlay)
+ Log.d(DEBUG_TAG, "Requesting stops load")
+ // This is not necessary, by setting the center we already move
+ // the map and we trigger a stop request
+ //requestStopsToShow();
+ if (marker != null) {
+ // make a marker with the info window open for the searched marker
+ //TODO: make Stop Bundle-able
+ val stopMarker = makeMarker(marker, ID, name, routesStopping, true)
+ map!!.controller.animateTo(marker)
+ }
+ //add the overlays with the bus stops
+ if (busPositionsOverlay == null) {
+ //Log.i(DEBUG_TAG, "Null bus positions overlay,redo");
+ busPositionsOverlay = FolderOverlay()
+ }
+ startRequestsPositions()
+ if (stopsViewModel != null) {
+ stopsViewModel!!.stopsInBoundingBox.observe(viewLifecycleOwner) { stops: List<Stop>? ->
+ showStopsMarkers(
+ stops
+ )
+ }
+ } else Log.d(DEBUG_TAG, "Cannot observe new stops in map, stopsViewModel is null")
+ map!!.overlays.add(busPositionsOverlay)
+ //set map as started
+ hasMapStartFinished = true
+ }
+
+ /**
+ * Start a request to load the stops that are in the current view
+ * from the database
+ */
+ private fun requestStopsToShow() {
+ // get the top, bottom, left and right screen's coordinate
+ val bb = map!!.boundingBox
+ Log.d(
+ DEBUG_TAG,
+ "Requesting stops in bounding box, stopViewModel is null " + (false)
+ )
+ stopsViewModel.requestStopsInBoundingBox(bb)
+
+ }
+
+ private fun updateBusMarker(
+ marker: Marker?,
+ posUpdate: LivePositionUpdate,
+ justCreated: Boolean
+ ) {
+ val position: GeoPoint
+ val updateID = posUpdate.tripID
+ if (!justCreated) {
+ position = marker!!.position
+ if (posUpdate.latitude != position.latitude || posUpdate.longitude != position.longitude) {
+ val newpos = GeoPoint(posUpdate.latitude, posUpdate.longitude)
+ val valueAnimator = MarkerUtils.makeMarkerAnimator(
+ map, marker, newpos, MarkerUtils.LINEAR_ANIMATION, 1200
+ )
+ valueAnimator.setAutoCancel(true)
+ tripMarkersAnimators[updateID] = valueAnimator
+ valueAnimator.start()
+ }
+ //marker.setPosition(new GeoPoint(posUpdate.getLatitude(), posUpdate.getLongitude()));
+ } else {
+ position = GeoPoint(posUpdate.latitude, posUpdate.longitude)
+ marker!!.position = position
+ }
+ marker.rotation = posUpdate.bearing?.let { it*-1f } ?: 0.0f
+ }
+
+ private fun updateBusPositionsInMap(tripsPatterns: HashMap<String, Pair<LivePositionUpdate, TripAndPatternWithStops?>>) {
+ Log.d(DEBUG_TAG, "Updating positions of the buses")
+ //if(busPositionsOverlay == null) busPositionsOverlay = new FolderOverlay();
+ val noPatternsTrips = ArrayList<String>()
+ for (tripID in tripsPatterns.keys) {
+ val (update, tripWithPatternStops) = tripsPatterns[tripID] ?: continue
+
+
+ //check if Marker is already created
+ if (busPositionMarkersByTrip.containsKey(tripID)) {
+ //need to change the position of the marker
+ val marker = busPositionMarkersByTrip[tripID]!!
+ updateBusMarker(marker, update, false)
+ if (marker.infoWindow != null && marker.infoWindow is BusInfoWindow) {
+ val window = marker.infoWindow as BusInfoWindow
+ if (tripWithPatternStops != null) {
+ //Log.d(DEBUG_TAG, "Update pattern for trip: "+tripID);
+ window.setPatternAndDraw(tripWithPatternStops.pattern)
+ }
+ }
+ } else {
+ //marker is not there, need to make it
+ val marker = Marker(map)
+
+ /*final Drawable mDrawable = DrawableUtils.Companion.getScaledDrawableResources(
+ getResources(),
+ R.drawable.point_heading_icon,
+ R.dimen.map_icons_size, R.dimen.map_icons_size);
+
+ */
+ //String route = GtfsUtils.getLineNameFromGtfsID(update.getRouteID());
+ val mdraw =
+ ResourcesCompat.getDrawable(resources, R.drawable.map_bus_position_icon, null)!!
+ //mdraw.setBounds(0,0,28,28);
+ marker.icon = mdraw
+ if (tripWithPatternStops == null) {
+ noPatternsTrips.add(tripID)
+ }
+ var markerPattern: MatoPattern? = null
+ if (tripWithPatternStops != null && tripWithPatternStops.pattern != null) markerPattern =
+ tripWithPatternStops.pattern
+ marker.infoWindow =
+ BusInfoWindow(map!!, update, markerPattern, false) { pattern: MatoPattern? -> }
+ marker.setInfoWindowAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER)
+ marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER)
+ updateBusMarker(marker, update, true)
+ // the overlay is null when it's not attached yet?5
+ // cannot recreate it because it becomes null very soon
+ // if(busPositionsOverlay == null) busPositionsOverlay = new FolderOverlay();
+ //save the marker
+ if (busPositionsOverlay != null) {
+ busPositionsOverlay!!.add(marker)
+ busPositionMarkersByTrip[tripID] = marker
+ }
+ }
+ }
+ if (noPatternsTrips.size > 0) {
+ Log.i(DEBUG_TAG, "These trips have no matching pattern: $noPatternsTrips")
+ }
+ }
+
+ /**
+ * Add stops as Markers on the map
+ * @param stops the list of stops that must be included
+ */
+ protected fun showStopsMarkers(stops: List<Stop>?) {
+ if (context == null || stops == null) {
+ //we are not attached
+ return
+ }
+ var good = true
+ for (stop in stops) {
+ if (shownStops!!.contains(stop.ID)) {
+ continue
+ }
+ if (stop.longitude == null || stop.latitude == null) continue
+ shownStops!!.add(stop.ID)
+ if (!map!!.isShown) {
+ if (good) Log.d(
+ DEBUG_TAG,
+ "Need to show stop but map is not shown, probably detached already"
+ )
+ good = false
+ continue
+ } else if (map!!.repository == null) {
+ Log.e(DEBUG_TAG, "Map view repository is null")
+ }
+ val marker = GeoPoint(stop.latitude!!, stop.longitude!!)
+ val stopMarker = makeMarker(marker, stop, false)
+ stopsFolderOverlay!!.add(stopMarker)
+ if (!map!!.overlays.contains(stopsFolderOverlay)) {
+ Log.w(DEBUG_TAG, "Map doesn't have folder overlay")
+ }
+ good = true
+ }
+ //Log.d(DEBUG_TAG,"We have " +stopsFolderOverlay.getItems().size()+" stops in the folderOverlay");
+ //force redraw of markers
+ map!!.invalidate()
+ }
+
+ fun makeMarker(geoPoint: GeoPoint?, stop: Stop, isStartMarker: Boolean): Marker {
+ return makeMarker(
+ geoPoint, stop.ID,
+ stop.stopDefaultName,
+ stop.routesThatStopHereToString(), isStartMarker
+ )
+ }
+
+ fun makeMarker(
+ geoPoint: GeoPoint?, stopID: String?, stopName: String?,
+ routesStopping: String?, isStartMarker: Boolean
+ ): Marker {
+
+ // add a marker
+ val marker = Marker(map)
+
+ // set custom info window as info window
+ val popup = CustomInfoWindow(
+ map, stopID, stopName, routesStopping,
+ responder, R.layout.linedetail_stop_infowindow, R.color.red_darker
+ )
+ marker.infoWindow = popup
+
+ // make the marker clickable
+ marker.setOnMarkerClickListener { thisMarker: Marker, mapView: MapView? ->
+ if (thisMarker.isInfoWindowOpen) {
+ // on second click
+ Log.w(DEBUG_TAG, "Pressed on the click marker")
+ } else {
+ // on first click
+
+ // hide all opened info window
+ InfoWindow.closeAllInfoWindowsOn(map)
+ // show this particular info window
+ thisMarker.showInfoWindow()
+ // move the map to its position
+ map!!.controller.animateTo(thisMarker.position)
+ }
+ true
+ }
+
+ // set its position
+ marker.position = geoPoint
+ marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER)
+ // add to it an icon
+ //marker.setIcon(getResources().getDrawable(R.drawable.bus_marker));
+ marker.icon = ResourcesCompat.getDrawable(resources, R.drawable.bus_stop, ctx!!.theme)
+ // add to it a title
+ marker.title = stopName
+ // set the description as the ID
+ marker.snippet = stopID
+
+ // show popup info window of the searched marker
+ if (isStartMarker) {
+ marker.showInfoWindow()
+ //map.getController().animateTo(marker.getPosition());
+ }
+ return marker
+ }
+
+ override fun getBaseViewForSnackBar(): View? {
+ return coordLayout
+ }
+
+ companion object {
+ //private static final String TAG = "Busto-MapActivity";
+ private const val MAP_CURRENT_ZOOM_KEY = "map-current-zoom"
+ private const val MAP_CENTER_LAT_KEY = "map-center-lat"
+ private const val MAP_CENTER_LON_KEY = "map-center-lon"
+ private const val FOLLOWING_LOCAT_KEY = "following"
+ const val BUNDLE_LATIT = "lat"
+ const val BUNDLE_LONGIT = "lon"
+ const val BUNDLE_NAME = "name"
+ const val BUNDLE_ID = "ID"
+ const val BUNDLE_ROUTES_STOPPING = "routesStopping"
+ const val FRAGMENT_TAG = "BusTOMapFragment"
+ private const val DEFAULT_CENTER_LAT = 45.0708
+ private const val DEFAULT_CENTER_LON = 7.6858
+ private const val POSITION_FOUND_ZOOM = 18.3
+ const val NO_POSITION_ZOOM = 17.1
+ private const val DEBUG_TAG = FRAGMENT_TAG
+
+ @JvmStatic
+ fun getInstance(): MapFragmentKt {
+ return MapFragmentKt()
+ }
+ @JvmStatic
+ fun getInstance(stop: Stop): MapFragmentKt {
+ val fragment = MapFragmentKt()
+ val args = Bundle()
+ args.putDouble(MapFragment.BUNDLE_LATIT, stop.latitude!!)
+ args.putDouble(MapFragment.BUNDLE_LONGIT, stop.longitude!!)
+ args.putString(MapFragment.BUNDLE_NAME, stop.stopDisplayName)
+ args.putString(MapFragment.BUNDLE_ID, stop.ID)
+ args.putString(MapFragment.BUNDLE_ROUTES_STOPPING, stop.routesThatStopHereToString())
+ fragment.arguments = args
+
+ return fragment
+ }
+ }
+}
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/MapLibreFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/MapLibreFragment.kt
new file mode 100644
--- /dev/null
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/MapLibreFragment.kt
@@ -0,0 +1,1148 @@
+package it.reyboz.bustorino.fragments
+
+
+import android.Manifest
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.Intent
+import android.graphics.Color
+import android.location.Location
+import android.location.LocationListener
+import android.location.LocationManager
+import android.net.Uri
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.animation.LinearInterpolator
+import android.widget.ImageButton
+import android.widget.RelativeLayout
+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.core.content.ContextCompat
+import androidx.core.content.res.ResourcesCompat
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import androidx.preference.PreferenceManager
+import com.google.android.material.bottomsheet.BottomSheetBehavior
+import com.google.gson.Gson
+import com.google.gson.JsonObject
+import it.reyboz.bustorino.R
+import it.reyboz.bustorino.backend.Stop
+import it.reyboz.bustorino.backend.gtfs.LivePositionUpdate
+import it.reyboz.bustorino.backend.mato.MQTTMatoClient
+import it.reyboz.bustorino.data.PreferencesHolder
+import it.reyboz.bustorino.data.gtfs.TripAndPatternWithStops
+import it.reyboz.bustorino.fragments.SettingsFragment.LIVE_POSITIONS_PREF_MQTT_VALUE
+import it.reyboz.bustorino.map.MapLibreUtils
+import it.reyboz.bustorino.map.Styles
+import it.reyboz.bustorino.util.Permissions
+import it.reyboz.bustorino.util.ViewUtils
+import it.reyboz.bustorino.viewmodels.LivePositionsViewModel
+import it.reyboz.bustorino.viewmodels.StopsMapViewModel
+import org.maplibre.android.MapLibre
+import org.maplibre.android.camera.CameraPosition
+import org.maplibre.android.camera.CameraUpdateFactory
+import org.maplibre.android.geometry.LatLng
+import org.maplibre.android.geometry.LatLngBounds
+import org.maplibre.android.location.LocationComponent
+import org.maplibre.android.location.LocationComponentOptions
+import org.maplibre.android.location.modes.CameraMode
+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
+import org.maplibre.android.style.layers.Property.*
+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
+
+
+// TODO: Rename parameter arguments, choose names that match
+// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
+private const val STOP_TO_SHOW = "stoptoshow"
+
+/**
+ * A simple [Fragment] subclass.
+ * Use the [MapLibreFragment.newInstance] factory method to
+ * create an instance of this fragment.
+ */
+class MapLibreFragment : GeneralMapLibreFragment() {
+
+
+ protected var fragmentListener: CommonFragmentListener? = null
+ private lateinit var locationComponent: LocationComponent
+ private var lastLocation: Location? = null
+ private val stopsViewModel: StopsMapViewModel by viewModels()
+ private var stopsShowing = ArrayList<Stop>(0)
+ private var isBottomSheetShowing = false
+ //private lateinit var symbolManager: SymbolManager
+
+
+ // Sources for stops and buses are in GeneralMapLibreFragment
+
+ private var stopsLayerStarted = false
+ private var lastStopsSizeShown = 0
+ private var lastBBox = LatLngBounds.from(2.0, 2.0, 1.0,1.0)
+ private var mapInitCompleted =false
+ private var stopsRedrawnTimes = 0
+
+ //bottom Sheet behavior
+ private lateinit var bottomSheetBehavior: BottomSheetBehavior<RelativeLayout>
+ private var bottomLayout: RelativeLayout? = null
+ private lateinit var stopTitleTextView: TextView
+ private lateinit var stopNumberTextView: TextView
+ private lateinit var linesPassingTextView: TextView
+ private lateinit var arrivalsCard: CardView
+ private lateinit var directionsCard: CardView
+
+ //private var stopActiveSymbol: Symbol? = null
+
+ // Location stuff
+ private lateinit var locationManager: LocationManager
+ private lateinit var showUserPositionButton: ImageButton
+ private lateinit var centerUserButton: ImageButton
+ private lateinit var followUserButton: ImageButton
+ private var followingUserLocation = false
+ private var pendingLocationActivation = false
+ private var ignoreCameraMovementForFollowing = true
+ private var enablingPositionFromClick = false
+ private val positionRequestLauncher =
+ registerForActivityResult<Array<String>, Map<String, Boolean>>(
+ ActivityResultContracts.RequestMultiplePermissions(),
+ ActivityResultCallback { result ->
+ if (result == null) {
+ Log.w(DEBUG_TAG, "Got asked permission but request is null, doing nothing?")
+ } else if (java.lang.Boolean.TRUE == result[Manifest.permission.ACCESS_COARSE_LOCATION]
+ && java.lang.Boolean.TRUE == result[Manifest.permission.ACCESS_FINE_LOCATION]) {
+ // We can use the position, restart location overlay
+ Log.d(DEBUG_TAG, "HAVE THE PERMISSIONS")
+ if (context == null || requireContext().getSystemService(Context.LOCATION_SERVICE) == null)
+ return@ActivityResultCallback ///@registerForActivityResult
+ val locationManager =
+ requireContext().getSystemService(Context.LOCATION_SERVICE) as LocationManager
+ @SuppressLint("MissingPermission") val userLocation =
+ locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
+ if (userLocation != null) {
+ if(LatLng(userLocation.latitude, userLocation.longitude).distanceTo(DEFAULT_LATLNG) >= MAX_DIST_KM*1000){
+ setMapLocationEnabled(true, true, false)
+ }
+ } else requestInitialUserLocation()
+
+ } else{
+ Toast.makeText(requireContext(),"User location disabled", Toast.LENGTH_SHORT).show()
+ Log.w(DEBUG_TAG, "No location permission")
+ }
+ })
+ private val showUserPositionRequestLauncher =
+ registerForActivityResult<Array<String>, Map<String, Boolean>>(
+ ActivityResultContracts.RequestMultiplePermissions(),
+ ActivityResultCallback { result ->
+ if (result == null) {
+ Log.w(DEBUG_TAG, "Got asked permission but request is null, doing nothing?")
+ } else if (java.lang.Boolean.TRUE == result[Manifest.permission.ACCESS_COARSE_LOCATION]
+ && java.lang.Boolean.TRUE == result[Manifest.permission.ACCESS_FINE_LOCATION]) {
+ // We can use the position, restart location overlay
+ if (context == null || requireContext().getSystemService(Context.LOCATION_SERVICE) == null)
+ return@ActivityResultCallback ///@registerForActivityResult
+ setMapLocationEnabled(true, true, enablingPositionFromClick)
+ } else Log.w(DEBUG_TAG, "No location permission")
+ })
+
+ //BUS POSITIONS
+ private var useMQTTViewModel = true
+ private val livePositionsViewModel : LivePositionsViewModel by viewModels()
+
+ private val positionsByVehDict = HashMap<String, LivePositionUpdate>(5)
+ private val animatorsByVeh = HashMap<String, ValueAnimator>()
+ private var lastUpdateTime : Long = -1
+ //private var busLabelSymbolsByVeh = HashMap<String,Symbol>()
+ private val symbolsToUpdate = ArrayList<Symbol>()
+
+ private var initialStopToShow : Stop? = null
+ private var initialStopShown = false
+
+ //shown stuff
+ //private var savedStateOnStop : Bundle? = null
+
+ private val showBusLayer = true
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ arguments?.let {
+ initialStopToShow = Stop.fromBundle(arguments)
+ }
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ // Inflate the layout for this fragment
+ val rootView = inflater.inflate(R.layout.fragment_map_libre,
+ container, false)
+ //reset the counter
+ lastStopsSizeShown = 0
+ stopsRedrawnTimes = 0
+ stopsLayerStarted = false
+ symbolsToUpdate.clear()
+
+ // Init layout view
+
+ // Init the MapView
+ mapView = rootView.findViewById(R.id.libreMapView)
+
+ val restoreBundle = stopsViewModel.savedState
+ if(restoreBundle!=null){
+ mapView.onCreate(restoreBundle)
+ } else mapView.onCreate(savedInstanceState)
+ mapView.getMapAsync(this) //{ //map ->
+ //map.setStyle("https://demotiles.maplibre.org/style.json") }
+
+
+ //init bottom sheet
+ val bottomSheet = rootView.findViewById<RelativeLayout>(R.id.bottom_sheet)
+ bottomLayout = bottomSheet
+ stopTitleTextView = bottomSheet.findViewById(R.id.stopTitleTextView)
+ stopNumberTextView = bottomSheet.findViewById(R.id.stopNumberTextView)
+ linesPassingTextView = bottomSheet.findViewById(R.id.linesPassingTextView)
+ arrivalsCard = bottomSheet.findViewById(R.id.arrivalsCardButton)
+ directionsCard = bottomSheet.findViewById(R.id.directionsCardButton)
+
+ showUserPositionButton = rootView.findViewById(R.id.locationEnableIcon)
+ showUserPositionButton.setOnClickListener(this::switchUserLocationStatus)
+ followUserButton = rootView.findViewById(R.id.followUserImageButton)
+ centerUserButton = rootView.findViewById(R.id.centerMapImageButton)
+ bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet)
+ bottomSheetBehavior.state = BottomSheetBehavior.STATE_HIDDEN
+
+ arrivalsCard.setOnClickListener {
+ if(context!=null){
+ Toast.makeText(context,"ARRIVALS", Toast.LENGTH_SHORT).show()
+ }
+ }
+ centerUserButton.setOnClickListener {
+ if(context!=null && locationComponent.isLocationComponentEnabled) {
+ val location = locationComponent.lastKnownLocation
+
+ location?.let {
+ mapView.getMapAsync { map ->
+ map.animateCamera(CameraUpdateFactory.newCameraPosition(
+ CameraPosition.Builder().target(LatLng(location.latitude, location.longitude)).build()), 500)
+ }
+ }
+ }
+ }
+ followUserButton.setOnClickListener {
+ // onClick user following button
+ if(context!=null && locationComponent.isLocationComponentEnabled){
+ if(followingUserLocation)
+ locationComponent.cameraMode = CameraMode.NONE
+ else locationComponent.cameraMode = CameraMode.TRACKING
+ // CameraMode.TRACKING makes the camera move and jump to the location
+
+ setFollowingUser(!followingUserLocation)
+ }
+ }
+ locationManager = requireActivity().getSystemService(Context.LOCATION_SERVICE) as LocationManager
+ if (Permissions.bothLocationPermissionsGranted(requireContext())) {
+ requestInitialUserLocation()
+ } else{
+ if (shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)) {
+ //TODO: show dialog for permission rationale
+ Toast.makeText(activity, R.string.enable_position_message_map, Toast.LENGTH_SHORT)
+ .show()
+ }
+ positionRequestLauncher.launch(Permissions.LOCATION_PERMISSIONS)
+ }
+
+
+ // Setup close button
+ rootView.findViewById<View>(R.id.btnClose).setOnClickListener {
+ hideStopBottomSheet()
+ }
+
+ Log.d(DEBUG_TAG, "Fragment View Created!")
+
+ //TODO: Reshow last open stop when switching back to the map fragment
+ return rootView
+ }
+
+ /**
+ * This method sets up the map and the layers
+ */
+ override fun onMapReady(mapReady: MapLibreMap) {
+ this.map = mapReady
+ val context = requireContext()
+ val mjson = Styles.getJsonStyleFromAsset(context, PreferencesHolder.getMapLibreStyleFile(context))
+ //ViewUtils.loadJsonFromAsset(requireContext(),"map_style_good.json")
+
+ activity?.run {
+ val builder = Style.Builder().fromJson(mjson!!)
+
+ mapReady.setStyle(builder) { style ->
+
+ mapStyle = style
+ //setupLayers(style)
+
+ // Start observing data
+ observeStops()
+ initMapLocation(style, mapReady, requireContext())
+ //init stop layer with this
+ val stopsInCache = stopsViewModel.getAllStopsLoaded()
+ if(stopsInCache.isEmpty())
+ initStopsLayer(style, FeatureCollection.fromFeatures(ArrayList<Feature>()))
+ else
+ displayStops(stopsInCache)
+ if(showBusLayer) setupBusLayer(style)
+
+
+ /*symbolManager = SymbolManager(mapView,mapReady,style, null, "symbol-transit-airfield")
+ symbolManager.iconAllowOverlap = true
+ symbolManager.textAllowOverlap = false
+ symbolManager.textIgnorePlacement =true
+
+
+ */
+ /*symbolManager.addClickListener{ _ ->
+ if (stopActiveSymbol!=null){
+ hideStopBottomSheet()
+
+ return@addClickListener true
+ } else
+ return@addClickListener false
+ }
+
+ */
+
+ }
+
+ mapReady.addOnCameraIdleListener {
+ map?.let {
+ val newBbox = it.projection.visibleRegion.latLngBounds
+ if ((newBbox.center==lastBBox.center) && (newBbox.latitudeSpan==lastBBox.latitudeSpan) && (newBbox.longitudeSpan==lastBBox.latitudeSpan)){
+ //do nothing
+ } else {
+ stopsViewModel.loadStopsInLatLngBounds(newBbox)
+ lastBBox = newBbox
+
+ }
+
+ }
+
+ }
+ mapReady.addOnCameraMoveStartedListener {
+
+ map?.let { setFollowingUser(it.locationComponent.cameraMode == CameraMode.TRACKING) }
+ //setFollowingUser()
+
+ }
+
+ mapReady.addOnMapClickListener { point ->
+ onMapClickReact(point)
+ }
+
+ mapInitCompleted = true
+ // we start requesting the bus positions now
+ startRequestingPositions()
+ }
+ savedMapStateOnPause?.let{
+ restoreMapStateFromBundle(it)
+ pendingLocationActivation = false
+ Log.d(DEBUG_TAG, "Restored map state from the saved bundle")
+ }
+ //reset saved State at the end
+ if( savedMapStateOnPause == null) {
+ //set initial position
+ val zoom = 15.0
+ //center position
+ val latlngTarget = initialStopToShow?.let {
+ LatLng(it.latitude!!, it.longitude!!)
+ } ?: LatLng(DEFAULT_CENTER_LAT, DEFAULT_CENTER_LON)
+ mapReady.cameraPosition = CameraPosition.Builder().target(latlngTarget).zoom(zoom).build()
+ }
+ //reset saved state
+ savedMapStateOnPause = null
+ }
+
+ private fun onMapClickReact(point: LatLng): Boolean{
+ map?.let { mapReady ->
+ val screenPoint = mapReady.projection.toScreenLocation(point)
+ val features = mapReady.queryRenderedFeatures(screenPoint, STOPS_LAYER_ID)
+ val busNearby = mapReady.queryRenderedFeatures(screenPoint, BUSES_LAYER_ID)
+ if (features.isNotEmpty()) {
+ val feature = features[0]
+ val id = feature.getStringProperty("id")
+ val name = feature.getStringProperty("name")
+ //Toast.makeText(requireContext(), "Clicked on $name ($id)", Toast.LENGTH_SHORT).show()
+ val stop = stopsViewModel.getStopByID(id)
+ stop?.let { newstop ->
+ val sameStopClicked = shownStopInBottomSheet?.let { newstop.ID==it.ID } ?: false
+ if (isBottomSheetShowing) {
+ hideStopBottomSheet()
+ }
+ if(!sameStopClicked){
+ openStopInBottomSheet(newstop)
+ //isBottomSheetShowing = true
+ //move camera
+ if (newstop.latitude != null && newstop.longitude != null)
+ //mapReady.cameraPosition = CameraPosition.Builder().target(LatLng(it.latitude!!, it.longitude!!)).build()
+ mapReady.animateCamera(
+ CameraUpdateFactory.newLatLng(LatLng(newstop.latitude!!, newstop.longitude!!)),
+ 750
+ )
+ }
+
+ }
+ return true
+ } else if (busNearby.isNotEmpty()) {
+ val feature = busNearby[0]
+ val vehid = feature.getStringProperty("veh")
+ val route = feature.getStringProperty("line")
+
+ Toast.makeText(context, "Veh $vehid on route $route", Toast.LENGTH_SHORT).show()
+ return true
+ }
+ }
+ return false
+ }
+
+
+ private fun initStopsLayer(style: Style, features:FeatureCollection){
+
+ stopsSource = GeoJsonSource(STOPS_SOURCE_ID,features)
+ style.addSource(stopsSource)
+
+ // add icon
+ 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)!!)
+ // 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.addLayerBelow(stopsLayer, "symbol-transit-airfield") //"label_country_1") this with OSM Bright
+
+
+ selectedStopSource = GeoJsonSource(SEL_STOP_SOURCE, FeatureCollection.fromFeatures(ArrayList<Feature>()))
+ 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
+ */
+ private fun setupBusLayer(style: Style) {
+ // 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)!!)
+
+ // Buses layer
+ val busesLayer = SymbolLayer(BUSES_LAYER_ID, BUSES_SOURCE_ID).apply {
+ withProperties(
+ PropertyFactory.iconImage("bus_symbol"),
+ PropertyFactory.iconSize(1.2f),
+ PropertyFactory.iconAllowOverlap(true),
+ PropertyFactory.iconIgnorePlacement(true),
+ PropertyFactory.iconRotate(Expression.get("bearing")),
+ PropertyFactory.iconRotationAlignment(ICON_ROTATION_ALIGNMENT_MAP),
+
+ 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)
+
+ //Line names layer
+ /*vehiclesLabelsSource = GeoJsonSource(LABELS_SOURCE)
+ style.addSource(vehiclesLabelsSource)
+ val textLayer = SymbolLayer(LABELS_LAYER_ID, LABELS_SOURCE).apply {
+ withProperties(
+ PropertyFactory.textField("label"),
+ PropertyFactory.textSize(30f),
+ //PropertyFactory.textHaloColor(Color.BLACK),
+ //PropertyFactory.textHaloWidth(1f),
+
+ 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)
+
+
+ )
+ }
+ style.addLayerAbove(textLayer, BUSES_LAYER_ID)
+
+ */
+
+ }
+
+ /**
+ * Update the bottom sheet with the stop information
+ */
+ override fun openStopInBottomSheet(stop: Stop){
+ bottomLayout?.let {
+
+ //lay.findViewById<TextView>(R.id.stopTitleTextView).text ="${stop.ID} - ${stop.stopDefaultName}"
+ val stopName = stop.stopUserName ?: stop.stopDefaultName
+ stopTitleTextView.text = stopName//stop.stopDefaultName
+ stopNumberTextView.text = stop.ID
+ val string_show = if (stop.numRoutesStopping==0) ""
+ else if (stop.numRoutesStopping <= 1)
+ requireContext().getString(R.string.line_fill, stop.routesThatStopHereToString())
+ else requireContext().getString(R.string.lines_fill, stop.routesThatStopHereToString())
+ linesPassingTextView.text = string_show
+
+ //SET ON CLICK LISTENER
+ arrivalsCard.setOnClickListener{
+ fragmentListener?.requestArrivalsForStopID(stop.ID)
+ }
+
+ directionsCard.setOnClickListener {
+ ViewUtils.openStopInOutsideApp(stop, context)
+ }
+
+
+ }
+ //add stop marker
+ if (stop.latitude!=null && stop.longitude!=null) {
+ /*stopActiveSymbol = symbolManager.create(
+ SymbolOptions()
+ .withLatLng(LatLng(stop.latitude!!, stop.longitude!!))
+ .withIconImage(STOP_ACTIVE_IMG)
+ .withIconAnchor(ICON_ANCHOR_CENTER)
+ //.withTextFont(arrayOf("noto_sans_regular")))
+ */
+ Log.d(DEBUG_TAG, "Showing stop: ${stop.ID}")
+ val list = ArrayList<Feature>()
+ list.add(stopToGeoJsonFeature(stop))
+ selectedStopSource.setGeoJson(
+ FeatureCollection.fromFeatures(list)
+ )
+ }
+ shownStopInBottomSheet = stop
+ bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
+ isBottomSheetShowing = true
+ }
+ override fun onAttach(context: Context) {
+ super.onAttach(context)
+ fragmentListener = if (context is CommonFragmentListener) {
+ context
+ } else {
+ throw RuntimeException(
+ context.toString()
+ + " must implement FragmentListenerMain"
+ )
+ }
+ }
+ override fun onDetach() {
+ super.onDetach()
+ fragmentListener = null
+ }
+
+ override fun onStart() {
+ super.onStart()
+ mapView.onStart()
+ }
+
+ override fun onResume() {
+ super.onResume()
+ mapView.onResume()
+
+ val keySourcePositions = getString(R.string.pref_positions_source)
+ if(showBusLayer) {
+ useMQTTViewModel = PreferenceManager.getDefaultSharedPreferences(requireContext())
+ .getString(keySourcePositions, LIVE_POSITIONS_PREF_MQTT_VALUE)
+ .contentEquals(LIVE_POSITIONS_PREF_MQTT_VALUE)
+
+ if (useMQTTViewModel) livePositionsViewModel.requestMatoPosUpdates(MQTTMatoClient.LINES_ALL)
+ else livePositionsViewModel.requestGTFSUpdates()
+ //mapViewModel.testCascade();
+ livePositionsViewModel.isLastWorkResultGood.observe(this) { d: Boolean ->
+ Log.d(
+ DEBUG_TAG, "Last trip download result is $d"
+ )
+ }
+ livePositionsViewModel.tripsGtfsIDsToQuery.observe(this) { dat: List<String> ->
+ Log.i(DEBUG_TAG, "Have these trips IDs missing from the DB, to be queried: $dat")
+ livePositionsViewModel.downloadTripsFromMato(dat)
+ }
+ }
+
+ fragmentListener?.readyGUIfor(FragmentKind.MAP)
+ }
+
+ override fun onPause() {
+ super.onPause()
+ mapView.onPause()
+ Log.d(DEBUG_TAG, "Fragment paused")
+
+ savedMapStateOnPause = saveMapStateInBundle()
+ if (useMQTTViewModel) livePositionsViewModel.stopMatoUpdates()
+
+ }
+
+ override fun onStop() {
+ super.onStop()
+ mapView.onStop()
+ Log.d(DEBUG_TAG, "Fragment stopped!")
+ stopsViewModel.savedState = Bundle().let {
+ mapView.onSaveInstanceState(it)
+ it
+ }
+
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ mapView.onDestroy()
+ Log.d(DEBUG_TAG, "Destroyed map Fragment!!")
+ }
+
+ override fun onMapDestroy() {
+ mapStyle.removeLayer(STOPS_LAYER_ID)
+ mapStyle.removeSource(STOPS_SOURCE_ID)
+
+ mapStyle.removeLayer(BUSES_LAYER_ID)
+ mapStyle.removeSource(BUSES_SOURCE_ID)
+
+
+ map?.locationComponent?.isLocationComponentEnabled = false
+ }
+ override fun getBaseViewForSnackBar(): View? {
+ return mapView
+ }
+
+ private fun observeStops() {
+ // Observe stops
+ stopsViewModel.stopsToShow.observe(viewLifecycleOwner) { stops ->
+ stopsShowing = ArrayList(stops)
+ displayStops(stopsShowing)
+ initialStopToShow?.let{ s->
+ //show the stop in the bottom sheet
+ if(!initialStopShown) {
+ openStopInBottomSheet(s)
+ initialStopShown = true
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Add the stops to the layers
+ */
+ private fun displayStops(stops: List<Stop>?) {
+ if (stops.isNullOrEmpty()) return
+
+ if (stops.size==lastStopsSizeShown){
+ Log.d(DEBUG_TAG, "Not updating, have same number of stops. After 3 times")
+ return
+ }
+ /*if(stops.size> lastStopsSizeShown){
+ stopsRedrawnTimes = 0
+ } else{
+ stopsRedrawnTimes++
+ }
+
+ */
+
+ val features = ArrayList<Feature>()//stops.mapNotNull { stop ->
+ //stop.latitude?.let { lat ->
+ // stop.longitude?.let { lon ->
+ for (s in stops){
+ if (s.latitude!=null && s.longitude!=null)
+ features.add(stopToGeoJsonFeature(s))
+
+
+ }
+ Log.d(DEBUG_TAG,"Have put ${features.size} stops to display")
+
+ // if the layer is already started, substitute the stops inside, otherwise start it
+ if (stopsLayerStarted) {
+ stopsSource.setGeoJson(FeatureCollection.fromFeatures(features))
+ lastStopsSizeShown = features.size
+ } else
+ map?.let {
+ Log.d(DEBUG_TAG, "Map stop layer is not started yet, init layer")
+ initStopsLayer(mapStyle, FeatureCollection.fromFeatures(features))
+ Log.d(DEBUG_TAG,"Started stops layer on map")
+ lastStopsSizeShown = features.size
+ stopsLayerStarted = true
+ }
+ }
+ // Hide the bottom sheet and remove extra symbol
+ private fun hideStopBottomSheet(){
+ /*if (stopActiveSymbol!=null){
+ symbolManager.delete(stopActiveSymbol)
+ stopActiveSymbol = null
+ }
+ */
+ //empty the source
+ selectedStopSource.setGeoJson(FeatureCollection.fromFeatures(ArrayList<Feature>()))
+
+ bottomSheetBehavior.state = BottomSheetBehavior.STATE_HIDDEN
+ //remove initial stop
+ if(initialStopToShow!=null){
+ initialStopToShow = null
+ }
+ //set showing
+ isBottomSheetShowing = false
+ shownStopInBottomSheet = null
+ }
+ // --------------- BUS LOCATIONS STUFF --------------------------
+ /**
+ * Start requesting position updates
+ */
+ private fun startRequestingPositions() {
+ livePositionsViewModel.updatesWithTripAndPatterns.observe(viewLifecycleOwner) { data: HashMap<String, Pair<LivePositionUpdate, TripAndPatternWithStops?>> ->
+ Log.d(
+ DEBUG_TAG,
+ "Have " + data.size + " trip updates, has Map start finished: " + mapInitCompleted
+ )
+ if (mapInitCompleted) updateBusPositionsInMap(data)
+ if (!isDetached && !useMQTTViewModel) livePositionsViewModel.requestDelayedGTFSUpdates(
+ 3000
+ )
+ }
+ }
+ private fun isInsideVisibleRegion(latitude: Double, longitude: Double, nullValue: Boolean): Boolean{
+ var isInside = nullValue
+ val visibleRegion = map?.projection?.visibleRegion
+ visibleRegion?.let {
+ val bounds = it.latLngBounds
+ isInside = bounds.contains(LatLng(latitude, longitude))
+ }
+ return isInside
+ }
+
+ /*private fun createLabelForVehicle(positionUpdate: LivePositionUpdate){
+ val symOpt = SymbolOptions()
+ .withLatLng(LatLng(positionUpdate.latitude, positionUpdate.longitude))
+ .withTextColor("#ffffff")
+ .withTextField(positionUpdate.routeID.substringBeforeLast('U'))
+ .withTextSize(13f)
+ .withTextAnchor(TEXT_ANCHOR_CENTER)
+ .withTextFont(arrayOf( "noto_sans_regular"))//"noto_sans_regular", "sans-serif")) //"noto_sans_regular"))
+
+ val newSymbol = symbolManager.create(symOpt
+ )
+ Log.d(DEBUG_TAG, "Symbol for veh ${positionUpdate.vehicle}: $newSymbol")
+ busLabelSymbolsByVeh[positionUpdate.vehicle] = newSymbol
+ }
+ private fun removeVehicleLabel(vehicle: String){
+ busLabelSymbolsByVeh[vehicle]?.let {
+ symbolManager.delete(it)
+ busLabelSymbolsByVeh.remove(vehicle)
+ }
+ }
+
+ */
+
+ /**
+ * Update function for the bus positions
+ * Takes the processed updates and saves them accordingly
+ */
+ private fun updateBusPositionsInMap(incomingData: HashMap<String, Pair<LivePositionUpdate,TripAndPatternWithStops?>>){
+ val vehsNew = HashSet(incomingData.values.map { up -> up.first.vehicle })
+ val vehsOld = HashSet(positionsByVehDict.keys)
+
+ val symbolsToUpdate = ArrayList<Symbol>()
+ for (upsWithTrp in incomingData.values){
+ val pos = upsWithTrp.first
+ val vehID = pos.vehicle
+ var animate = false
+ if (vehsOld.contains(vehID)){
+ //update position only if the starting or the stopping position of the animation are in the view
+ val oldPos = positionsByVehDict[vehID]
+ var avoidShowingUpdateBecauseIsImpossible = false
+ oldPos?.let{
+ if(oldPos.routeID!=pos.routeID) {
+ val dist = LatLng(it.latitude, it.longitude).distanceTo(LatLng(pos.latitude, pos.longitude))
+ val speed = dist*3.6 / (pos.timestamp - it.timestamp) //this should be in km/h
+ Log.w(DEBUG_TAG, "Vehicle $vehID changed route from ${oldPos.routeID} to ${pos.routeID}, distance: $dist, speed: $speed")
+ if (speed > 120 || speed < 0){
+ avoidShowingUpdateBecauseIsImpossible = true
+ }
+ }
+ }
+ if (avoidShowingUpdateBecauseIsImpossible){
+ // DO NOT SHOW THIS SHIT
+ Log.w(DEBUG_TAG, "Update for vehicle $vehID skipped")
+ continue
+ }
+
+ val samePosition = oldPos?.let { (oldPos.latitude==pos.latitude)&&(oldPos.longitude == pos.longitude) }?:false
+ if(!samePosition) {
+ val isPositionInBounds = isInsideVisibleRegion(
+ pos.latitude, pos.longitude, true
+ ) || (oldPos?.let { isInsideVisibleRegion(it.latitude,it.longitude,true) } ?: false)
+ if (isPositionInBounds) {
+ //animate = true
+ //this moves both the icon and the label
+ moveVehicleToNewPosition(pos)
+ } else {
+ positionsByVehDict[vehID] = pos
+ /*busLabelSymbolsByVeh[vehID]?.let {
+ it.latLng = LatLng(pos.latitude, pos.longitude)
+ symbolsToUpdate.add(it)
+ }
+
+ */
+ }
+ }
+ }
+ else if(pos.latitude>0 && pos.longitude>0) {
+ //we should not have to check for this
+ // update it simply
+ positionsByVehDict[vehID] = pos
+ //createLabelForVehicle(pos)
+ }else{
+ Log.w(DEBUG_TAG, "Update ignored for veh $vehID on line ${pos.routeID}, lat: ${pos.latitude}, lon ${pos.longitude}")
+ }
+
+ }
+ // symbolManager.update(symbolsToUpdate)
+ //remove old positions
+ vehsOld.removeAll(vehsNew)
+ //now vehsOld contains the vehicles id for those that have NOT been updated
+ val currentTimeStamp = System.currentTimeMillis() /1000
+ for(vehID in vehsOld){
+ //remove after 2 minutes of inactivity
+ if (positionsByVehDict[vehID]!!.timestamp - currentTimeStamp > 2*60){
+ positionsByVehDict.remove(vehID)
+ //removeVehicleLabel(vehID)
+ }
+ }
+ //update UI
+ updatePositionsIcons()
+ }
+
+ /**
+ * This is the tricky part, animating the transitions
+ * Basically, we need to set the new positions with the data and redraw them all
+ */
+ private fun moveVehicleToNewPosition(positionUpdate: LivePositionUpdate){
+ if (positionUpdate.vehicle !in positionsByVehDict.keys)
+ return
+ val vehID = positionUpdate.vehicle
+ val currentUpdate = positionsByVehDict[positionUpdate.vehicle]
+ currentUpdate?.let { it ->
+ //cancel current animation on vehicle
+ animatorsByVeh[vehID]?.cancel()
+
+ val currentPos = LatLng(it.latitude, it.longitude)
+ val newPos = LatLng(positionUpdate.latitude, positionUpdate.longitude)
+ val valueAnimator = ValueAnimator.ofObject(MapLibreUtils.LatLngEvaluator(), currentPos, newPos)
+ valueAnimator.addUpdateListener(object : ValueAnimator.AnimatorUpdateListener {
+ private var latLng: LatLng? = null
+ override fun onAnimationUpdate(animation: ValueAnimator) {
+ latLng = animation.animatedValue as LatLng
+ //update position on animation
+ val update = positionsByVehDict[positionUpdate.vehicle]!!
+ latLng?.let { ll->
+ update.latitude = ll.latitude
+ update.longitude = ll.longitude
+ updatePositionsIcons()
+ }
+ }
+ })
+ valueAnimator.addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationStart(animation: Animator) {
+ super.onAnimationStart(animation)
+ //val update = positionsByVehDict[positionUpdate.vehicle]!!
+ //remove the label at the start of the animation
+ /*val annot = busLabelSymbolsByVeh[vehID]
+ annot?.let { sym ->
+ sym.textOpacity = 0.0f
+ symbolsToUpdate.add(sym)
+ }
+
+ */
+
+ }
+
+ override fun onAnimationEnd(animation: Animator) {
+ super.onAnimationEnd(animation)
+ //recreate the label at the end of the animation
+ //createLabelForVehicle(positionUpdate)
+ /*val annot = busLabelSymbolsByVeh[vehID]
+ annot?.let { sym ->
+ sym.textOpacity = 1.0f
+ sym.latLng = newPos //LatLng(newPos)
+ symbolsToUpdate.add(sym)
+ }
+
+ */
+ }
+ })
+
+ //set the new position as the current one but with the old lat and lng
+ positionUpdate.latitude = currentUpdate.latitude
+ positionUpdate.longitude = currentUpdate.longitude
+ positionsByVehDict[vehID] = positionUpdate
+ valueAnimator.duration = 300
+ valueAnimator.interpolator = LinearInterpolator()
+ valueAnimator.start()
+
+ animatorsByVeh[vehID] = valueAnimator
+
+ } ?: {
+ Log.e(DEBUG_TAG, "Have to run animation for veh ${positionUpdate.vehicle} but not in the dict, adding")
+ positionsByVehDict[positionUpdate.vehicle] = positionUpdate
+ }
+ }
+
+ /**
+ * Update the bus positions displayed on the map, from the existing data
+ */
+ private fun updatePositionsIcons(){
+ //avoid frequent updates
+ val currentTime = System.currentTimeMillis()
+ if(currentTime - lastUpdateTime < 60){
+ //DO NOT UPDATE THE MAP
+ return
+ }
+ val features = ArrayList<Feature>()//stops.mapNotNull { stop ->
+ //stop.latitude?.let { lat ->
+ // stop.longitude?.let { lon ->
+ for (pos in positionsByVehDict.values){
+ //if (s.latitude!=null && s.longitude!=null)
+ val point = Point.fromLngLat(pos.longitude, pos.latitude)
+ features.add(
+ 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'))
+ }
+ )
+ )
+ /*busLabelSymbolsByVeh[pos.vehicle]?.let {
+ it.latLng = LatLng(pos.latitude, pos.longitude)
+ symbolsToUpdate.add(it)
+ }
+
+ */
+ }
+ //this updates the positions
+ busesSource.setGeoJson(FeatureCollection.fromFeatures(features))
+ //update labels, clear cache to be used
+ //symbolManager.update(symbolsToUpdate)
+ symbolsToUpdate.clear()
+ lastUpdateTime = System.currentTimeMillis()
+ }
+
+ // ------ LOCATION STUFF -----
+ @SuppressLint("MissingPermission")
+ private fun requestInitialUserLocation() {
+ val provider : String = LocationManager.GPS_PROVIDER//getBestLocationProvider()
+
+ //provider.let {
+ setLocationIconEnabled(true)
+ Toast.makeText(requireContext(), R.string.position_searching_message, Toast.LENGTH_SHORT).show()
+ pendingLocationActivation = true
+ locationManager.requestSingleUpdate(provider, object : LocationListener {
+ override fun onLocationChanged(location: Location) {
+ val userLatLng = LatLng(location.latitude, location.longitude)
+ val distanceToTarget = userLatLng.distanceTo(DEFAULT_LATLNG)
+
+ if (distanceToTarget <= MAX_DIST_KM*1000.0) {
+ map?.let{
+ // if we are still waiting for the position to enable
+ if(pendingLocationActivation)
+ setMapLocationEnabled(true, true, false)
+ }
+ } else {
+ Toast.makeText(context, "You are too far, not showing the position", Toast.LENGTH_SHORT).show()
+ }
+ }
+
+ override fun onProviderDisabled(provider: String) {}
+ override fun onProviderEnabled(provider: String) {}
+
+ @Deprecated("Deprecated in Java")
+ override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {}
+ }, null)
+
+ }
+
+ /**
+ * Initialize the map location, but do not enable the component
+ */
+ @SuppressLint("MissingPermission")
+ private fun initMapLocation(style: Style, map: MapLibreMap, context: Context){
+ locationComponent = map.locationComponent
+ val locationComponentOptions =
+ LocationComponentOptions.builder(context)
+ .pulseEnabled(true)
+ .build()
+ val locationComponentActivationOptions =
+ MapLibreUtils.buildLocationComponentActivationOptions(style, locationComponentOptions, context)
+ locationComponent.activateLocationComponent(locationComponentActivationOptions)
+ locationComponent.isLocationComponentEnabled = false
+
+ lastLocation?.let {
+ if (it.accuracy < 200)
+ locationComponent.forceLocationUpdate(it)
+ }
+ }
+
+
+
+ /**
+ * Handles logic of enabling the user location on the map
+ */
+ @SuppressLint("MissingPermission")
+ private fun setMapLocationEnabled(enabled: Boolean, assumePermissions: Boolean, fromClick: Boolean) {
+ if (enabled) {
+ val permissionOk = assumePermissions || Permissions.bothLocationPermissionsGranted(requireContext())
+
+ if (permissionOk) {
+ Log.d(DEBUG_TAG, "Permission OK, starting location component, assumed: $assumePermissions")
+ locationComponent.isLocationComponentEnabled = true
+ if (initialStopToShow==null) {
+ locationComponent.cameraMode = CameraMode.TRACKING //CameraMode.TRACKING
+ setFollowingUser(true)
+ }
+ setLocationIconEnabled(true)
+ if (fromClick) Toast.makeText(context, R.string.location_enabled, Toast.LENGTH_SHORT).show()
+ } else {
+ if (shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)) {
+ //TODO: show dialog for permission rationale
+ Toast.makeText(activity, R.string.enable_position_message_map, Toast.LENGTH_SHORT).show()
+ }
+ Log.d(DEBUG_TAG, "Requesting permission to show user location")
+ enablingPositionFromClick = fromClick
+ showUserPositionRequestLauncher.launch(Permissions.LOCATION_PERMISSIONS)
+ }
+ } else{
+ locationComponent.isLocationComponentEnabled = false
+ setFollowingUser(false)
+ setLocationIconEnabled(false)
+ if (fromClick) {
+ Toast.makeText(requireContext(), R.string.location_disabled, Toast.LENGTH_SHORT).show()
+ if(pendingLocationActivation) pendingLocationActivation=false //Cancel the request for the enablement of the position
+ }
+ }
+
+ }
+
+
+ private fun setLocationIconEnabled(enabled: Boolean){
+ if (enabled)
+ showUserPositionButton.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.location_circlew_red))
+ else
+ showUserPositionButton.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.location_circlew_grey))
+
+ }
+
+ /**
+ * Helper method for GUI
+ */
+ private fun updateFollowingIcon(enabled: Boolean){
+ if(enabled)
+ followUserButton.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_follow_me_on))
+ else
+ followUserButton.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_follow_me))
+
+ }
+ private fun setFollowingUser(following: Boolean){
+ updateFollowingIcon(following)
+ followingUserLocation = following
+ if(following)
+ ignoreCameraMovementForFollowing = true
+ }
+
+
+
+ private fun switchUserLocationStatus(view: View?){
+ if(pendingLocationActivation || locationComponent.isLocationComponentEnabled) setMapLocationEnabled(false, false, true)
+ else{
+ Log.d(DEBUG_TAG, "Request enable location")
+ setMapLocationEnabled(true, false, true)
+ }
+ }
+
+ companion object {
+ private const val STOPS_SOURCE_ID = "stops-source"
+ private const val STOPS_LAYER_ID = "stops-layer"
+ private const val STOPS_LAYER_SEL_ID ="stops-layer-selected"
+
+ private const val LABELS_LAYER_ID = "bus-labels-layer"
+ private const val LABELS_SOURCE = "labels-source"
+ private const val STOP_IMAGE_ID ="bus-stop-icon"
+ const val DEFAULT_CENTER_LAT = 45.0708
+ const val DEFAULT_CENTER_LON = 7.6858
+ private val DEFAULT_LATLNG = LatLng(DEFAULT_CENTER_LAT, DEFAULT_CENTER_LON)
+ private const val POSITION_FOUND_ZOOM = 16.5
+ private const val NO_POSITION_ZOOM = 17.1
+ private const val MAX_DIST_KM = 90.0
+
+ private const val DEBUG_TAG = "BusTO-MapLibreFrag"
+ private const val STOP_ACTIVE_IMG = "Stop-active"
+
+ private const val LOCATION_PERMISSION_REQUEST_CODE = 981202
+
+ /**
+ * Use this factory method to create a new instance of
+ * this fragment using the provided parameters.
+ *
+ * @param stop Eventual stop to center the map into
+ * @return A new instance of fragment MapLibreFragment.
+ */
+ @JvmStatic
+ fun newInstance(stop: Stop?) =
+ MapLibreFragment().apply {
+ arguments = Bundle().let {
+ // Cannot use Parcelable as it requires higher version of Android
+ //stop?.let{putParcelable(STOP_TO_SHOW, it)}
+ stop?.toBundle(it)
+ }
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/SettingsFragment.java b/app/src/main/java/it/reyboz/bustorino/fragments/SettingsFragment.java
--- a/app/src/main/java/it/reyboz/bustorino/fragments/SettingsFragment.java
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/SettingsFragment.java
@@ -51,9 +51,11 @@
"androidx.preference.PreferenceFragment.DIALOG";
//private static final
Handler mHandler;
+ // Matching preferences.xml
public final static String PREF_KEY_STARTUP_SCREEN="startup_screen_to_show";
public final static String KEY_ARRIVALS_FETCHERS_USE = "arrivals_fetchers_use_setting";
public final static String LIVE_POSITIONS_PREF_MQTT_VALUE="mqtt";
+ public final static String LIBREMAP_STYLE_PREF_KEY = "libremap_style_1";
private boolean setSummaryStartupPref = false;
@@ -90,7 +92,7 @@
Preference dbUpdateNow = findPreference("pref_db_update_now");
if (dbUpdateNow!=null)
- dbUpdateNow.setOnPreferenceClickListener(
+ dbUpdateNow.setOnPreferenceClickListener(
preference -> {
//trigger update
if(getContext()!=null) {
diff --git a/app/src/main/java/it/reyboz/bustorino/map/BusPositionUtils.kt b/app/src/main/java/it/reyboz/bustorino/map/BusPositionUtils.kt
--- a/app/src/main/java/it/reyboz/bustorino/map/BusPositionUtils.kt
+++ b/app/src/main/java/it/reyboz/bustorino/map/BusPositionUtils.kt
@@ -6,7 +6,6 @@
import it.reyboz.bustorino.backend.gtfs.LivePositionUpdate
import it.reyboz.bustorino.data.gtfs.MatoPattern
import it.reyboz.bustorino.data.gtfs.TripAndPatternWithStops
-import it.reyboz.bustorino.fragments.MapFragment
import org.osmdroid.util.GeoPoint
import org.osmdroid.views.MapView
import org.osmdroid.views.overlay.Marker
@@ -35,7 +34,8 @@
position = GeoPoint(posUpdate.latitude, posUpdate.longitude)
marker!!.position = position
}
- if (posUpdate.bearing != null) marker.rotation = posUpdate.bearing * -1f
+ //if (posUpdate.bearing != null) marker.rotation = posUpdate.bearing * -1f
+ marker.rotation = posUpdate.bearing?.let { it*-1f } ?: 0.0f
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/it/reyboz/bustorino/map/MapLibreUtils.kt b/app/src/main/java/it/reyboz/bustorino/map/MapLibreUtils.kt
new file mode 100644
--- /dev/null
+++ b/app/src/main/java/it/reyboz/bustorino/map/MapLibreUtils.kt
@@ -0,0 +1,229 @@
+package it.reyboz.bustorino.map
+
+import android.animation.TypeEvaluator
+import android.content.Context
+import android.util.Log
+import it.reyboz.bustorino.backend.Stop
+import org.maplibre.android.geometry.LatLng
+import org.maplibre.android.location.LocationComponentActivationOptions
+import org.maplibre.android.location.LocationComponentOptions
+import org.maplibre.android.location.engine.LocationEngineRequest
+import org.maplibre.android.maps.Style
+import org.maplibre.geojson.Point
+import org.maplibre.turf.TurfMeasurement
+
+
+class MapLibreUtils {
+ companion object{
+ const val STYLE_BRIGHT_DEFAULT_JSON = "map_style_good_noshops.json"
+ const val STYLE_VERSATILES_COLORFUL_JSON = "versatiles_colorful_light.json"
+ const val STYLE_OSM_RASTER="openstreetmap_raster.json"
+ private const val DEBUG_TAG ="BusTO-MapLibreUtils"
+
+ @JvmStatic
+ fun getDefaultStyleJson() = STYLE_VERSATILES_COLORFUL_JSON
+
+ @JvmStatic
+ fun shortestRotation(from: Float, to: Float): Float {
+ var delta = (to - from) % 360
+ if (delta > 180) delta -= 360
+ if (delta < -180) delta += 360
+ return from + delta
+ }
+ @JvmStatic
+ fun buildLocationComponentActivationOptions(
+ style: Style,
+ locationComponentOptions: LocationComponentOptions,
+ context: Context
+ ): LocationComponentActivationOptions {
+ return LocationComponentActivationOptions
+ .builder(context, style)
+ .locationComponentOptions(locationComponentOptions)
+ .useDefaultLocationEngine(true)
+ .locationEngineRequest(
+ LocationEngineRequest.Builder(750)
+ .setFastestInterval(750)
+ .setPriority(LocationEngineRequest.PRIORITY_HIGH_ACCURACY)
+ .build()
+ )
+ .build()
+ }
+ @JvmStatic
+ fun calcDistanceInSegment(points: List<LatLng>, from: Int, to:Int): Double{
+ var d=0.0
+ var prev = points[from]
+ for(i in from+1..to){
+ d += prev.distanceTo(points[i])
+ prev = points[i]
+ }
+ return d
+ }
+ @JvmStatic
+ fun findIndexMidPoint(points: List<LatLng>, from: Int, to: Int, distThresh:Double): Int{
+ var totdist=0.0
+ var idx = -2
+ var prev = points[from]
+ for(i in from+1..to){
+ totdist += prev.distanceTo(points[i])
+ prev = points[i]
+ if (totdist >= distThresh){
+ idx = i
+ break
+ }
+ }
+ if(idx==-2) throw Error("Distance out of bounds, total distance is $totdist")
+ return idx
+ }
+
+ @JvmStatic
+ fun findPointsToPutDirectionMarkers(polyPoints: List<LatLng>, stops: List<Stop>, distanceIcon: Double): List<Int>{
+ val closestIndices = findIndicesClosestPointsForStops(polyPoints, stops)
+ Log.d(DEBUG_TAG, "idcs: $closestIndices")
+
+ val distancesSec = mutableListOf<Double>()
+ var pi = closestIndices[0]
+ val cumulativeDist = mutableListOf<Double>()
+ var sum = 0.0
+
+ val pointsOutput = mutableListOf<Int>()
+ var nPoints = 0
+ var distFromLastPoint = 0.0
+ for(i in 1..<stops.size){
+ val newi = closestIndices[i]
+ val dd = calcDistanceInSegment(polyPoints, pi, newi)
+ distancesSec.add(dd)
+ sum += dd
+ cumulativeDist.add(sum)
+ distFromLastPoint += dd
+
+
+ //check if sum is above dist
+ if (distFromLastPoint >= distanceIcon){
+ Log.d(DEBUG_TAG, "Add between stop ${stops[i-1]} and stop ${stops[i]}, distance between: $dd")
+ if(dd>100) {
+ val imid = findIndexMidPoint(polyPoints, pi, newi, dd / 2)
+ pointsOutput.add(imid)
+ nPoints += 1
+ distFromLastPoint=0.0
+ }
+ } else{
+ //add the last distance
+ //distFromLastPoint+=dd/2
+ }
+ pi= newi
+ }
+ return pointsOutput
+ }
+ /*
+ VERSION WITH TOTAL DISTANCE
+ val distancesSec = mutableListOf<Double>()
+ var prevk = 0
+ val cumulativeDist = mutableListOf<Double>()
+ var sum = 0.0
+
+ val pointsOutput = mutableListOf<Int>()
+ var nPoints = 0
+ //for(i in 1..<stops.size){
+ var lastStopidx = 0
+ var distFromLast = 0.0
+ for(k in 1..<polyPoints.size){
+ //val newi = closestIndices[i]
+ val dd = calcDistanceInSegment(polyPoints, prevk, k)
+ distancesSec.add(dd)
+ sum += dd
+ cumulativeDist.add(sum)
+ distFromLast += dd
+
+
+ //check if sum is above dist
+ if (distFromLast >= distanceIcon ){
+
+ //Log.d(DEBUG_TAG, "Add between stop ${stops[lastStopidx]} and stop ${stops[lastStopidx+1]}")
+ //find closest stops
+ var stopDist =
+ Math.min(polyPoints[prevk].distanceTo(polyPoints[lastStopidx]),polyPoints[k].distanceTo(polyPoints[lastStopidx]))
+
+ if(lastStopidx+1 < stops.size){
+ stopDist = Math.min(stopDist,
+ Math.min(polyPoints[prevk].distanceTo(polyPoints[lastStopidx+1]),polyPoints[k].distanceTo(polyPoints[lastStopidx+1]))
+ )
+ }
+ if(stopDist>100) {
+ val imid = findIndexMidPoint(polyPoints, prevk, k, dd / 2)
+ pointsOutput.add(imid)
+ nPoints += 1
+ distFromLast = 0.0
+ }
+ }
+ prevk = k
+ if (k>closestIndices[lastStopidx])
+ lastStopidx +=1
+ }
+ return pointsOutput
+ */
+ @JvmStatic
+ fun splitPolyWhenDistanceTooBig(points: List<LatLng>, distMax: Double): List<LatLng>{
+ val outList = mutableListOf(points[0])
+ var oldP = points[0]
+ for(i in 1..<points.size){
+ val newP = points[i]
+ val d = oldP.distanceTo(points[i])
+ if(d > distMax){
+ val newLat = (oldP.latitude+newP.latitude)/2
+ val newLong = (oldP.longitude+newP.longitude)/2
+ val extraP = LatLng(newLat,newLong)
+ outList.add(extraP)
+ }
+ outList.add(newP)
+
+ oldP=newP
+ }
+
+ return outList
+ }
+
+ @JvmStatic
+ fun findIndicesClosestPointsForStops(points:List<LatLng>, stops:List<Stop>): List<Int> {
+
+ val closestIndices = stops.map { s->
+ val p = LatLng(s.latitude!!, s.longitude!!)
+ var dist = 10_000_000.0 // in meters
+ var id = -1
+ for (j in points.indices){
+ val newd = p.distanceTo(points[j])
+ if (newd<dist) {
+ id = j
+ dist = newd
+ }
+ }
+ if(id==-1) throw Error("The distance is bigger than 10_000_000")
+ id
+ }
+
+ return closestIndices
+ }
+
+ @JvmStatic
+ fun getBearing(from: LatLng, to: LatLng): Float {
+ return TurfMeasurement.bearing(
+ Point.fromLngLat(from.longitude, from.latitude),
+ Point.fromLngLat(to.longitude, to.latitude)
+ ).toFloat()
+ }
+
+ }
+
+
+
+ //TODO: Do the same for LatLng and bearing, if possible
+ class LatLngEvaluator : TypeEvaluator<LatLng> {
+ private val latLng = LatLng()
+ override fun evaluate(fraction: Float, startValue: LatLng, endValue: LatLng): LatLng {
+ latLng.latitude = startValue.latitude + (endValue.latitude - startValue.latitude) * fraction
+ latLng.longitude = startValue.longitude + (endValue.longitude - startValue.longitude) * fraction
+ return latLng
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/it/reyboz/bustorino/map/MapViewModel.kt b/app/src/main/java/it/reyboz/bustorino/map/MapViewModel.kt
--- a/app/src/main/java/it/reyboz/bustorino/map/MapViewModel.kt
+++ b/app/src/main/java/it/reyboz/bustorino/map/MapViewModel.kt
@@ -2,6 +2,7 @@
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
+import it.reyboz.bustorino.backend.Stop
class MapViewModel : ViewModel() {
@@ -9,7 +10,9 @@
val currentLong = MutableLiveData(INVALID)
val currentZoom = MutableLiveData(-10.0)
+ var stopShowing: Stop? = null
+
companion object{
const val INVALID = -1000.0
}
}
\ No newline at end of file
diff --git a/app/src/main/java/it/reyboz/bustorino/map/Styles.kt b/app/src/main/java/it/reyboz/bustorino/map/Styles.kt
new file mode 100644
--- /dev/null
+++ b/app/src/main/java/it/reyboz/bustorino/map/Styles.kt
@@ -0,0 +1,56 @@
+package it.reyboz.bustorino.map
+import android.content.Context
+import it.reyboz.bustorino.util.ViewUtils
+import org.maplibre.android.maps.Style
+
+object Styles {
+ const val DEMOTILES = "https://demotiles.maplibre.org/style.json"
+
+ const val VERSATILES = "https://tiles.versatiles.org/assets/styles/colorful.json"
+
+ const val AMERICANA = "https://americanamap.org/style.json"
+
+ const val OPENFREEMAP_LIBERTY = "https://tiles.openfreemap.org/styles/liberty"
+
+ const val OPENFREEMAP_BRIGHT = "https://tiles.openfreemap.org/styles/bright"
+
+ const val BASIC_V8 = "mapbox://styles/mapbox/streets-v8"
+
+
+ const val AWS_OPEN_DATA_STANDARD_LIGHT =
+ "https://maps.geo.us-east-2.amazonaws.com/maps/v0/maps/OpenDataStyle/style-descriptor?key=v1.public.eyJqdGkiOiI1NjY5ZTU4My0yNWQwLTQ5MjctODhkMS03OGUxOTY4Y2RhMzgifR_7GLT66TNRXhZJ4KyJ-GK1TPYD9DaWuc5o6YyVmlikVwMaLvEs_iqkCIydspe_vjmgUVsIQstkGoInXV_nd5CcmqRMMa-_wb66SxDdbeRDvmmkpy2Ow_LX9GJDgL2bbiCws0wupJPFDwWCWFLwpK9ICmzGvNcrPbX5uczOQL0N8V9iUvziA52a1WWkZucIf6MUViFRf3XoFkyAT15Ll0NDypAzY63Bnj8_zS8bOaCvJaQqcXM9lrbTusy8Ftq8cEbbK5aMFapXRjug7qcrzUiQ5sr0g23qdMvnKJQFfo7JuQn8vwAksxrQm6A0ByceEXSfyaBoVpFcTzEclxUomhY.NjAyMWJkZWUtMGMyOS00NmRkLThjZTMtODEyOTkzZTUyMTBi"
+
+ private fun protomaps(style: String): String {
+ return "https://api.protomaps.com/styles/v2/${style}.json?key=e761cc7daedf832a"
+ }
+
+ private fun makeStyleMapBoxUrl(dark: Boolean) =
+ if (dark)
+ "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json"
+ else //"https://basemaps.cartocdn.com/gl/positron-gl-style/style.json"
+ "https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json"
+
+ val PROTOMAPS_LIGHT = protomaps("light")
+
+ val PROTOMAPS_DARK = protomaps("dark")
+
+ val PROTOMAPS_GRAYSCALE = protomaps("grayscale")
+
+ val PROTOMAPS_WHITE = protomaps("white")
+
+ val PROTOMAPS_BLACK = protomaps("black")
+
+ val CARTO_DARK = makeStyleMapBoxUrl(true)
+
+ val CARTO_VOYAGER = makeStyleMapBoxUrl(false)
+
+ fun getPredefinedStyleWithFallback(name: String): String {
+ try {
+ val style = Style.getPredefinedStyle(name)
+ return style
+ } catch (e: Exception) {
+ return OPENFREEMAP_LIBERTY
+ }
+ }
+ fun getJsonStyleFromAsset(context: Context, filename: String) = ViewUtils.loadJsonFromAsset(context,filename)
+}
\ No newline at end of file
diff --git a/app/src/main/java/it/reyboz/bustorino/util/ViewUtils.kt b/app/src/main/java/it/reyboz/bustorino/util/ViewUtils.kt
--- a/app/src/main/java/it/reyboz/bustorino/util/ViewUtils.kt
+++ b/app/src/main/java/it/reyboz/bustorino/util/ViewUtils.kt
@@ -1,17 +1,24 @@
package it.reyboz.bustorino.util
-import android.R
import android.content.Context
+import android.content.Intent
import android.content.res.Resources.Theme
import android.graphics.Rect
+import android.net.Uri
+import android.os.Bundle
import android.util.Log
import android.util.TypedValue
import android.view.View
import android.view.WindowManager
import android.view.animation.Animation
import android.view.animation.Transformation
-import androidx.annotation.ColorInt
+import android.widget.Toast
import androidx.core.widget.NestedScrollView
+import it.reyboz.bustorino.R
+import it.reyboz.bustorino.backend.Stop
+import it.reyboz.bustorino.fragments.LinesDetailFragment
+import it.reyboz.bustorino.fragments.LinesDetailFragment.Companion
+import java.io.IOException
class ViewUtils {
@@ -105,5 +112,46 @@
return color
}
+ fun loadJsonFromAsset(context: Context, fileName: String): String? {
+ return try {
+ context.assets.open(fileName).bufferedReader().use { it.readText() }
+ } catch (e: IOException) {
+ e.printStackTrace()
+ null
+ }
+ }
+ @JvmStatic
+ fun insertSpaces(input: String): String {
+ return input.replace(Regex("(?<=[A-Za-z])(?=[0-9])|(?<=[0-9])(?=[A-Za-z])"), " ")
+ }
+
+ @JvmStatic
+ fun mergeBundles(bundle1: Bundle?, bundle2: Bundle?): Bundle {
+ if (bundle1 == null) {
+ return if (bundle2 == null) Bundle() else Bundle(bundle2) // Return a copy to avoid modification
+ }
+ if (bundle2 != null) {
+ bundle1.putAll(bundle2)
+ }
+ return bundle1
+ }
+ @JvmStatic
+ fun openStopInOutsideApp(stop: Stop, context: Context?){
+ if(stop.latitude==null || stop.longitude==null){
+ Log.e(DEBUG_TAG, "Navigate to stop but longitude and/or latitude are null")
+ }else{
+ val stopName = stop.stopUserName ?: stop.stopDefaultName
+
+ val uri = "geo:?q=${stop.latitude},${stop.longitude}(${stop.ID} - $stopName)"
+ val intent = Intent(Intent.ACTION_VIEW, Uri.parse(uri))
+ context?.run{
+ if(intent.resolveActivity(packageManager)!=null){
+ startActivity(intent)
+ } else{
+ Toast.makeText(this, R.string.no_map_app_to_show_stop, Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/it/reyboz/bustorino/viewmodels/LinesViewModel.kt b/app/src/main/java/it/reyboz/bustorino/viewmodels/LinesViewModel.kt
--- a/app/src/main/java/it/reyboz/bustorino/viewmodels/LinesViewModel.kt
+++ b/app/src/main/java/it/reyboz/bustorino/viewmodels/LinesViewModel.kt
@@ -12,6 +12,7 @@
import it.reyboz.bustorino.data.gtfs.GtfsRoute
import it.reyboz.bustorino.data.gtfs.MatoPatternWithStops
import it.reyboz.bustorino.data.gtfs.PatternStop
+import org.maplibre.android.geometry.LatLng
import java.util.concurrent.Executors
class LinesViewModel(application: Application) : AndroidViewModel(application) {
@@ -99,6 +100,26 @@
requestStopsForGTFSIDs(gtfsIDs)
}
+ fun getStopByID(id:String) : Stop?{
+ //var stop : Stop? = null
+ val stop = stopsForPatternLiveData.value?.let { stops ->
+ for (s in stops){
+ if(s.ID == id)
+ return@let s
+ }
+ return@let null
+ }
+ return stop
+ }
+
+ private var lastMapPos: Pair<LatLng, Float>? = null
+
+ fun saveMapPos(latLng: LatLng, zoom: Float){
+ lastMapPos = Pair(latLng, zoom)
+ }
+
+ fun getLastMapPos(): Pair<LatLng,Float>? = lastMapPos
+
/*fun getLinesGTT(): MutableLiveData<List<GtfsRoute>> {
val routesData = MutableLiveData<List<GtfsRoute>>()
diff --git a/app/src/main/java/it/reyboz/bustorino/viewmodels/LivePositionsViewModel.kt b/app/src/main/java/it/reyboz/bustorino/viewmodels/LivePositionsViewModel.kt
--- a/app/src/main/java/it/reyboz/bustorino/viewmodels/LivePositionsViewModel.kt
+++ b/app/src/main/java/it/reyboz/bustorino/viewmodels/LivePositionsViewModel.kt
@@ -26,11 +26,13 @@
import com.android.volley.Response
import it.reyboz.bustorino.backend.NetworkVolleyManager
import it.reyboz.bustorino.backend.gtfs.GtfsRtPositionsRequest
+import it.reyboz.bustorino.backend.gtfs.GtfsUtils
import it.reyboz.bustorino.backend.gtfs.LivePositionUpdate
import it.reyboz.bustorino.backend.mato.MQTTMatoClient
import it.reyboz.bustorino.data.GtfsRepository
import it.reyboz.bustorino.data.MatoPatternsDownloadWorker
import it.reyboz.bustorino.data.MatoTripsDownloadWorker
+import it.reyboz.bustorino.data.gtfs.MatoPattern
import it.reyboz.bustorino.data.gtfs.TripAndPatternWithStops
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@@ -39,13 +41,15 @@
import kotlin.collections.HashMap
import kotlin.collections.HashSet
+typealias FullPositionUpdatesMap = HashMap<String, Pair<LivePositionUpdate, TripAndPatternWithStops?>>
+typealias FullPositionUpdate = Pair<LivePositionUpdate, TripAndPatternWithStops?>
class LivePositionsViewModel(application: Application): AndroidViewModel(application) {
private val gtfsRepo = GtfsRepository(application)
//private val updates = UpdatesMap()
- private val updatesLiveData = MutableLiveData<ArrayList<LivePositionUpdate>>()
+ private val positionsToBeMatchedLiveData = MutableLiveData<ArrayList<LivePositionUpdate>>()
private val netVolleyManager = NetworkVolleyManager.getInstance(application)
@@ -61,6 +65,13 @@
private var lastRequestedDownloadTrips = MutableLiveData<List<String>>()
+ //INPUT FILTER FOR LINE
+ private var gtfsLineToFilterPos = MutableLiveData<Pair<String,MatoPattern?>>()
+
+ fun setGtfsLineToFilterPos(line: String, pattern: MatoPattern?){
+ gtfsLineToFilterPos.value = Pair(line, pattern)
+ }
+
var isLastWorkResultGood = workManager
.getWorkInfosForUniqueWorkLiveData(MatoTripsDownloadWorker.TAG_TRIPS).map { it ->
if (it.isEmpty()) return@map false
@@ -99,14 +110,14 @@
}
val time = System.currentTimeMillis()
if(lastTimeReceived == (0.toLong()) || (time-lastTimeReceived)>500){
- updatesLiveData.postValue(mupds)
+ positionsToBeMatchedLiveData.postValue(mupds)
lastTimeReceived = time
}
}
//find the trip IDs in the updates
- private val tripsIDsInUpdates = updatesLiveData.map { it ->
+ private val tripsIDsInUpdates = positionsToBeMatchedLiveData.map { it ->
//Log.d(DEBUG_TI, "Updates map has keys ${upMap.keys}")
it.map { pos -> "gtt:"+pos.tripID }
@@ -132,11 +143,11 @@
// unify trips with updates
val updatesWithTripAndPatterns = gtfsTripsPatternsInDB.map { tripPatterns->
Log.i(DEBUG_TI, "Mapping trips and patterns")
- val mdict = HashMap<String,Pair<LivePositionUpdate, TripAndPatternWithStops?>>()
+ val mdict = HashMap<String,FullPositionUpdate>()
//missing patterns
val routesToDownload = HashSet<String>()
- if(updatesLiveData.value!=null)
- for(update in updatesLiveData.value!!){
+ if(positionsToBeMatchedLiveData.value!=null)
+ for(update in positionsToBeMatchedLiveData.value!!){
val trID:String = update.tripID
var found = false
@@ -169,6 +180,63 @@
return@map mdict
}
+ //OBSERVE THIS TO GET THE LOCATION UPDATES FILTERED
+ val filteredLocationUpdates = MediatorLiveData<Pair<FullPositionUpdatesMap, List<String>>>()
+ init {
+ filteredLocationUpdates.addSource(updatesWithTripAndPatterns){
+ filteredLocationUpdates.value = filterUpdatesForGtfsLine(it, gtfsLineToFilterPos.value!!)
+ }
+
+ filteredLocationUpdates.addSource(gtfsLineToFilterPos){
+ updatesWithTripAndPatterns.value?.let{ ups-> filteredLocationUpdates.value = filterUpdatesForGtfsLine(ups, it)}
+ }
+
+ }
+
+ private fun filterUpdatesForGtfsLine(updates: FullPositionUpdatesMap,
+ linePatt: Pair<String, MatoPattern?>):
+ Pair<HashMap<String,FullPositionUpdate>, List<String>>{
+ val gtfsLineId = linePatt.first
+ val pattern = linePatt.second
+
+
+ val filtdLineID = GtfsUtils.stripGtfsPrefix(gtfsLineId)
+ //filter buses with direction, show those only with the same direction
+ val updsForTripId = HashMap<String, Pair<LivePositionUpdate, TripAndPatternWithStops?>>()
+ val directionId = pattern?.directionId ?: -100
+ val numUpds = updates.entries.size
+ Log.d(DEBUG_TI, "Got $numUpds updates, current pattern is: ${pattern?.name}, directionID: ${pattern?.directionId}")
+ // cannot understand where this is used
+ //val patternsDirections = HashMap<String,Int>()
+ val vehicleOnWrongDirection = mutableListOf<String>()
+ for((tripId, pair) in updates.entries){
+ //remove trips with wrong line
+ val posUp = pair.first
+ val vehicle = pair.first.vehicle
+ if(pair.first.routeID!=filtdLineID)
+ continue
+
+ if(directionId!=-100 && pair.second!=null && pair.second?.pattern !=null){
+ val dir = pair.second!!.pattern!!.directionId
+
+ if(dir == directionId){
+ //add the trip
+ updsForTripId[tripId] = pair
+ } else{
+ vehicleOnWrongDirection.add(vehicle)
+ }
+ //patternsDirections[tripId] = dir ?: -10
+ } else{
+ updsForTripId[tripId] = pair
+ //Log.d(DEBUG_TAG, "No pattern for tripID: $tripId")
+ //patternsDirections[tripId] = -10
+ }
+ }
+ Log.d(DEBUG_TI, " Filtered updates are ${updsForTripId.keys.size}") // Original updates directs: $patternsDirections\n
+
+ return Pair(updsForTripId, vehicleOnWrongDirection)
+ }
+
fun requestMatoPosUpdates(line: String){
lineListening = line
@@ -191,8 +259,8 @@
}
fun retriggerPositionUpdate(){
- if(updatesLiveData.value!=null){
- updatesLiveData.postValue(updatesLiveData.value)
+ if(positionsToBeMatchedLiveData.value!=null){
+ positionsToBeMatchedLiveData.postValue(positionsToBeMatchedLiveData.value)
}
}
//Gtfs Real time
@@ -206,7 +274,7 @@
}
else {
//Log.i(DEBUG_TI, "Posting value to positionsLiveData")
- viewModelScope.launch { updatesLiveData.postValue(it) }
+ viewModelScope.launch { positionsToBeMatchedLiveData.postValue(it) }
}
}
diff --git a/app/src/main/java/it/reyboz/bustorino/viewmodels/StopsMapViewModel.kt b/app/src/main/java/it/reyboz/bustorino/viewmodels/StopsMapViewModel.kt
--- a/app/src/main/java/it/reyboz/bustorino/viewmodels/StopsMapViewModel.kt
+++ b/app/src/main/java/it/reyboz/bustorino/viewmodels/StopsMapViewModel.kt
@@ -1,20 +1,17 @@
package it.reyboz.bustorino.viewmodels
import android.app.Application
+import android.os.Bundle
import android.util.Log
import androidx.lifecycle.AndroidViewModel
-import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.map
-import it.reyboz.bustorino.backend.Result
import it.reyboz.bustorino.backend.Stop
-import it.reyboz.bustorino.data.GtfsRepository
import it.reyboz.bustorino.data.NextGenDB
import it.reyboz.bustorino.data.OldDataRepository
-import it.reyboz.bustorino.data.gtfs.GtfsDatabase
+import org.maplibre.android.geometry.LatLngBounds
import org.osmdroid.util.BoundingBox
-import java.util.ArrayList
import java.util.concurrent.Executors
+import kotlin.collections.ArrayList
class StopsMapViewModel(application: Application): AndroidViewModel(application) {
@@ -22,6 +19,10 @@
private val executor = Executors.newFixedThreadPool(2)
private val oldRepo = OldDataRepository(executor, NextGenDB.getInstance(application))
+ val stopsToShow = MutableLiveData(ArrayList<Stop>())
+ private var stopsShownIDs = HashSet<String>()
+ private var allStopsLoaded = HashMap<String,Stop>()
+
val stopsInBoundingBox = MutableLiveData<ArrayList<Stop>>()
@@ -33,13 +34,55 @@
}
}
+ private val addStopsCallback =
+ OldDataRepository.Callback<ArrayList<Stop>> { res ->
+ if(res.isSuccess) res.result?.let{ newStops ->
+ val stopsAdd = stopsToShow.value ?: ArrayList()
+ for (s in newStops){
+ if (s.ID !in stopsShownIDs){
+ stopsShownIDs.add(s.ID)
+ stopsAdd.add(s)
+ allStopsLoaded[s.ID] = s
+ }
+ }
+
+ stopsToShow.postValue(stopsAdd)
+ //Log.d(DEBUG_TAG, "Loaded ${stopsAdd.size} stops in total")
+ }
+ }
+
+ fun getStopByID(id: String): Stop? {
+ if (id in allStopsLoaded) return allStopsLoaded[id]
+ else return null
+ }
+
+ fun getAllStopsLoaded(): ArrayList<Stop>{
+ return ArrayList(allStopsLoaded.values)
+ }
+
fun requestStopsInBoundingBox(bb: BoundingBox) {
bb.let {
Log.d(DEBUG_TAG, "Launching stop request")
oldRepo.requestStopsInArea(it.latSouth, it.latNorth, it.lonWest, it.lonEast, callback)
}
}
+ fun requestStopsInLatLng(bb: LatLngBounds) {
+ bb.let {
+ Log.d(DEBUG_TAG, "Launching stop request")
+ oldRepo.requestStopsInArea(it.latitudeSouth, it.latitudeNorth, it.longitudeWest, it.longitudeEast, callback)
+ }
+ }
+ fun loadStopsInLatLngBounds(bb: LatLngBounds?){
+ bb?.let {
+ Log.d(DEBUG_TAG, "Launching stop request")
+ oldRepo.requestStopsInArea(it.latitudeSouth, it.latitudeNorth, it.longitudeWest, it.longitudeEast,
+ addStopsCallback)
+ }
+ }
+
+ var savedState: Bundle? = null
+
companion object{
private const val DEBUG_TAG = "BusTOStopMapViewModel"
}
}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/arrow_up_box_fill.xml b/app/src/main/res/drawable/arrow_up_box_fill.xml
new file mode 100644
--- /dev/null
+++ b/app/src/main/res/drawable/arrow_up_box_fill.xml
@@ -0,0 +1,7 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
+
+ <path android:fillColor="@color/white" android:pathData="M4.439,4.19l15.637,0l0,15.383l-15.637,0z" android:strokeColor="#00000000" android:strokeLineJoin="round" android:strokeWidth="1.261"/>
+
+ <path android:fillColor="@color/line_drawn_poly" android:pathData="M3.396,2.32C2.802,2.32 2.32,2.802 2.32,3.396l0,17.209c0,0.594 0.482,1.076 1.076,1.076l17.209,0c0.594,0 1.076,-0.482 1.076,-1.076L21.68,3.396C21.68,2.802 21.198,2.32 20.604,2.32ZM12,5.916 L18.399,12.315l-5.324,0l0,5.769l-2.151,0L10.924,12.315L5.6,12.315Z"/>
+
+</vector>
diff --git a/app/src/main/res/drawable/bottom_sheet_background.xml b/app/src/main/res/drawable/bottom_sheet_background.xml
new file mode 100644
--- /dev/null
+++ b/app/src/main/res/drawable/bottom_sheet_background.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@android:color/white" />
+ <corners
+ android:topLeftRadius="14sp"
+ android:topRightRadius="14sp" />
+</shape>
\ No newline at end of file
diff --git a/app/src/main/res/drawable/bus_stop_new.xml b/app/src/main/res/drawable/bus_stop_new.xml
new file mode 100644
--- /dev/null
+++ b/app/src/main/res/drawable/bus_stop_new.xml
@@ -0,0 +1,21 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="28dp"
+ android:height="28dp"
+ android:viewportWidth="7.4083"
+ android:viewportHeight="7.4087">
+ <path
+ android:pathData="M0.9452,0L6.4632,0A0.9452,0.9452 0,0 1,7.4083 0.9452L7.4083,6.4635A0.9452,0.9452 0,0 1,6.4632 7.4087L0.9452,7.4087A0.9452,0.9452 0,0 1,0 6.4635L0,0.9452A0.9452,0.9452 0,0 1,0.9452 0z"
+ android:strokeLineJoin="round"
+ android:strokeWidth="0.0936408"
+ android:fillColor="@color/red_orange"
+ android:strokeColor="#00000000"/>
+ <path
+ android:pathData="m3.5412,1.164c-0.732,0 -1.2681,0.085 -1.6077,0.2548 -0.3395,0.1698 -0.509,0.4376 -0.509,0.8036v2.5135c0,0.1279 0.0238,0.2459 0.0723,0.354 0.0485,0.108 0.1129,0.2064 0.1922,0.2946v0.5421c0,0.075 0.0253,0.1379 0.076,0.1886 0.0507,0.0507 0.1137,0.076 0.1886,0.076h0.2646c0.075,0 0.1374,-0.0253 0.1881,-0.076 0.0507,-0.0507 0.0765,-0.1136 0.0765,-0.1886L2.4829,5.662L3.7474,5.662L3.642,5.5565C3.5147,5.4293 3.4139,5.2859 3.3391,5.1328L2.4829,5.1328c-0.1455,0 -0.2705,-0.0519 -0.3741,-0.1555 -0.1036,-0.1036 -0.155,-0.2281 -0.155,-0.3736v-0.7937h1.3565c0.0768,-0.1767 0.1873,-0.3423 0.3318,-0.4868 0.0145,-0.0145 0.0295,-0.0285 0.0444,-0.0424h-0.1452,-1.5875v-0.7937h3.175v0.4175c0.1863,0.0447 0.366,0.124 0.5292,0.2372v-0.9193c0,-0.3792 -0.1636,-0.6502 -0.4899,-0.8134C4.8417,1.2458 4.2997,1.164 3.5412,1.164ZM3.5541,1.6932c0.4851,0 0.837,0.0253 1.0552,0.076 0.2183,0.0507 0.3602,0.1137 0.4263,0.1886L2.0726,1.9578c0.0794,-0.0662 0.2282,-0.1268 0.4465,-0.1819 0.2183,-0.0551 0.5632,-0.0827 1.0351,-0.0827zM2.6152,4.0745c-0.1102,0 -0.2039,0.0386 -0.2811,0.1158 -0.0772,0.0772 -0.1158,0.1709 -0.1158,0.2811 0,0.1102 0.0386,0.2039 0.1158,0.2811 0.0772,0.0772 0.1709,0.1158 0.2811,0.1158 0.1102,0 0.2039,-0.0386 0.2811,-0.1158 0.0772,-0.0772 0.1158,-0.1709 0.1158,-0.2811 0,-0.1102 -0.0386,-0.204 -0.1158,-0.2811 -0.0772,-0.0772 -0.1709,-0.1158 -0.2811,-0.1158z"
+ android:strokeWidth="0.00661458"
+ android:fillColor="?colorOnPrimary"/>
+ <path
+ android:pathData="m4.7541,3.2766c-0.3147,0 -0.6292,0.1199 -0.8693,0.3599 -0.4802,0.4802 -0.4802,1.2587 0,1.7389l0.8693,0.8693 0.8696,-0.8693c0.4801,-0.4802 0.4801,-1.2587 0,-1.7389 -0.2401,-0.2401 -0.5549,-0.3599 -0.8696,-0.3599zM4.7541,3.9596c0.3018,0 0.5464,0.2447 0.5464,0.5464 0,0.3018 -0.2447,0.5464 -0.5464,0.5464 -0.3018,0 -0.5464,-0.2447 -0.5464,-0.5464 0,-0.3018 0.2447,-0.5464 0.5464,-0.5464z"
+ android:strokeWidth="0.136612"
+ android:fillColor="?colorOnPrimary"
+ android:strokeColor="#00000000"/>
+</vector>
diff --git a/app/src/main/res/drawable/bus_stop_new_highlight.xml b/app/src/main/res/drawable/bus_stop_new_highlight.xml
new file mode 100644
--- /dev/null
+++ b/app/src/main/res/drawable/bus_stop_new_highlight.xml
@@ -0,0 +1,21 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="28dp"
+ android:height="28dp"
+ android:viewportWidth="7.4083"
+ android:viewportHeight="7.4087">
+ <path
+ android:pathData="M0.9452,0L6.4632,0A0.9452,0.9452 0,0 1,7.4083 0.9452L7.4083,6.4635A0.9452,0.9452 0,0 1,6.4632 7.4087L0.9452,7.4087A0.9452,0.9452 0,0 1,0 6.4635L0,0.9452A0.9452,0.9452 0,0 1,0.9452 0z"
+ android:strokeLineJoin="round"
+ android:strokeWidth="0.0936408"
+ android:fillColor="?colorAccent"
+ android:strokeColor="#00000000"/>
+ <path
+ android:pathData="m3.5412,1.164c-0.732,0 -1.2681,0.085 -1.6077,0.2548 -0.3395,0.1698 -0.509,0.4376 -0.509,0.8036v2.5135c0,0.1279 0.0238,0.2459 0.0723,0.354 0.0485,0.108 0.1129,0.2064 0.1922,0.2946v0.5421c0,0.075 0.0253,0.1379 0.076,0.1886 0.0507,0.0507 0.1137,0.076 0.1886,0.076h0.2646c0.075,0 0.1374,-0.0253 0.1881,-0.076 0.0507,-0.0507 0.0765,-0.1136 0.0765,-0.1886L2.4829,5.662L3.7474,5.662L3.642,5.5565C3.5147,5.4293 3.4139,5.2859 3.3391,5.1328L2.4829,5.1328c-0.1455,0 -0.2705,-0.0519 -0.3741,-0.1555 -0.1036,-0.1036 -0.155,-0.2281 -0.155,-0.3736v-0.7937h1.3565c0.0768,-0.1767 0.1873,-0.3423 0.3318,-0.4868 0.0145,-0.0145 0.0295,-0.0285 0.0444,-0.0424h-0.1452,-1.5875v-0.7937h3.175v0.4175c0.1863,0.0447 0.366,0.124 0.5292,0.2372v-0.9193c0,-0.3792 -0.1636,-0.6502 -0.4899,-0.8134C4.8417,1.2458 4.2997,1.164 3.5412,1.164ZM3.5541,1.6932c0.4851,0 0.837,0.0253 1.0552,0.076 0.2183,0.0507 0.3602,0.1137 0.4263,0.1886L2.0726,1.9578c0.0794,-0.0662 0.2282,-0.1268 0.4465,-0.1819 0.2183,-0.0551 0.5632,-0.0827 1.0351,-0.0827zM2.6152,4.0745c-0.1102,0 -0.2039,0.0386 -0.2811,0.1158 -0.0772,0.0772 -0.1158,0.1709 -0.1158,0.2811 0,0.1102 0.0386,0.2039 0.1158,0.2811 0.0772,0.0772 0.1709,0.1158 0.2811,0.1158 0.1102,0 0.2039,-0.0386 0.2811,-0.1158 0.0772,-0.0772 0.1158,-0.1709 0.1158,-0.2811 0,-0.1102 -0.0386,-0.204 -0.1158,-0.2811 -0.0772,-0.0772 -0.1709,-0.1158 -0.2811,-0.1158z"
+ android:strokeWidth="0.00661458"
+ android:fillColor="?colorOnPrimary"/>
+ <path
+ android:pathData="m4.7541,3.2766c-0.3147,0 -0.6292,0.1199 -0.8693,0.3599 -0.4802,0.4802 -0.4802,1.2587 0,1.7389l0.8693,0.8693 0.8696,-0.8693c0.4801,-0.4802 0.4801,-1.2587 0,-1.7389 -0.2401,-0.2401 -0.5549,-0.3599 -0.8696,-0.3599zM4.7541,3.9596c0.3018,0 0.5464,0.2447 0.5464,0.5464 0,0.3018 -0.2447,0.5464 -0.5464,0.5464 -0.3018,0 -0.5464,-0.2447 -0.5464,-0.5464 0,-0.3018 0.2447,-0.5464 0.5464,-0.5464z"
+ android:strokeWidth="0.136612"
+ android:fillColor="?colorOnPrimary"
+ android:strokeColor="#00000000"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_baseline_departure_board_24.xml b/app/src/main/res/drawable/ic_baseline_departure_board_24.xml
--- a/app/src/main/res/drawable/ic_baseline_departure_board_24.xml
+++ b/app/src/main/res/drawable/ic_baseline_departure_board_24.xml
@@ -1,5 +1,6 @@
-<vector android:height="24dp" android:tint="#FFFFFF"
+<vector android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
- android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
- <path android:fillColor="@android:color/white" android:pathData="M16,1c-2.4,0 -4.52,1.21 -5.78,3.05 0.01,-0.01 0.01,-0.02 0.02,-0.03C9.84,4 9.42,4 9,4c-4.42,0 -8,0.5 -8,4v10c0,0.88 0.39,1.67 1,2.22L2,22c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-1h8v1c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-1.78c0.61,-0.55 1,-1.34 1,-2.22v-3.08c3.39,-0.49 6,-3.39 6,-6.92 0,-3.87 -3.13,-7 -7,-7zM4.5,19c-0.83,0 -1.5,-0.67 -1.5,-1.5S3.67,16 4.5,16s1.5,0.67 1.5,1.5S5.33,19 4.5,19zM3,13L3,8h6c0,1.96 0.81,3.73 2.11,5L3,13zM13.5,19c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM16,13c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM16.5,4L15,4v5l3.62,2.16 0.75,-1.23 -2.87,-1.68z"/>
+ android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"
+ android:tint="?attr/colorOnPrimary">
+ <path android:fillColor="@color/black" android:pathData="M16,1c-2.4,0 -4.52,1.21 -5.78,3.05 0.01,-0.01 0.01,-0.02 0.02,-0.03C9.84,4 9.42,4 9,4c-4.42,0 -8,0.5 -8,4v10c0,0.88 0.39,1.67 1,2.22L2,22c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-1h8v1c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-1.78c0.61,-0.55 1,-1.34 1,-2.22v-3.08c3.39,-0.49 6,-3.39 6,-6.92 0,-3.87 -3.13,-7 -7,-7zM4.5,19c-0.83,0 -1.5,-0.67 -1.5,-1.5S3.67,16 4.5,16s1.5,0.67 1.5,1.5S5.33,19 4.5,19zM3,13L3,8h6c0,1.96 0.81,3.73 2.11,5L3,13zM13.5,19c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM16,13c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM16.5,4L15,4v5l3.62,2.16 0.75,-1.23 -2.87,-1.68z"/>
</vector>
diff --git a/app/src/main/res/drawable/ic_magnifying_glass.xml b/app/src/main/res/drawable/ic_magnifying_glass.xml
new file mode 100644
--- /dev/null
+++ b/app/src/main/res/drawable/ic_magnifying_glass.xml
@@ -0,0 +1,14 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:autoMirrored="true" android:height="20dp" android:viewportHeight="24"
+ android:viewportWidth="24" android:width="20dp">
+
+ <path android:fillColor="#00000000"
+ android:pathData="M9.14,9.14m-7.64,0a7.64,7.64 0,1 1,15.28 0a7.64,7.64 0,1 1,-15.28 0"
+ android:strokeColor="?attr/colorOnPrimary" android:strokeWidth="1.91"/>
+
+ <path android:fillColor="#00000000"
+
+ android:pathData="M22.5,22.5L14.39,14.39"
+ android:strokeColor="?attr/colorOnPrimary" android:strokeWidth="1.91"/>
+
+</vector>
diff --git a/app/src/main/res/drawable/navigation_right.xml b/app/src/main/res/drawable/navigation_right.xml
new file mode 100644
--- /dev/null
+++ b/app/src/main/res/drawable/navigation_right.xml
@@ -0,0 +1,21 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="24"
+ android:tint="?attr/colorOnPrimary"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M6,21L6,12C6,9.24 8.24,7 11,7L17,7"
+ android:strokeLineJoin="round"
+ android:strokeWidth="2"
+ android:fillColor="#00000000"
+ android:strokeColor="#000"
+ android:strokeLineCap="round"/>
+ <path
+ android:pathData="M18,7 L14,3m4,4 l-4,4"
+ android:strokeLineJoin="round"
+ android:strokeWidth="2"
+ android:fillColor="#00000000"
+ android:strokeColor="#000"
+ android:strokeLineCap="round"/>
+</vector>
diff --git a/app/src/main/res/layout/fragment_lines_detail.xml b/app/src/main/res/layout/fragment_lines_detail.xml
--- a/app/src/main/res/layout/fragment_lines_detail.xml
+++ b/app/src/main/res/layout/fragment_lines_detail.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<FrameLayout
+<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
@@ -82,7 +82,7 @@
app:layout_constraintTop_toTopOf="@+id/patternsSpinner"
app:layout_constraintBottom_toBottomOf="@+id/patternsSpinner"
/>
- <org.osmdroid.views.MapView android:id="@+id/lineMap"
+ <org.maplibre.android.maps.MapView android:id="@+id/lineMap"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_marginTop="10dp"
@@ -90,14 +90,14 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
+
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/locationEnableIcon"
android:src="@drawable/location_circlew_red"
- android:layout_marginTop="8dp"
- android:layout_marginRight="8dp"
+ android:layout_marginTop="54dp"
android:layout_marginEnd="8dp"
android:background="#00ffffff"
android:contentDescription="@string/enable_position"
@@ -151,5 +151,130 @@
/>
</androidx.constraintlayout.widget.ConstraintLayout>
+ <!-- Bottom Sheet for details -->
+ <RelativeLayout
+ android:id="@+id/bottom_sheet"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="5dp"
+ android:layout_marginEnd="5dp"
+ android:paddingTop="3dp"
+ android:orientation="vertical"
+ android:background="@drawable/bottom_sheet_background"
+ android:elevation="8dp"
+ android:padding="13dp"
+ app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
+ app:behavior_hideable="true"
+ app:behavior_peekHeight="4dp"
+ android:clickable="true"
+ android:focusable="true">
+
+ <TextView
+ android:id="@+id/stopNumberTextView"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textSize="18sp"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentStart="true"
+ android:layout_toStartOf="@id/arrivalsCardButton"
+ android:layout_marginStart="10dp"
+ android:layout_marginEnd="10dp"
+ android:layout_marginBottom="2dp"
+ android:fontFamily="@font/pitagon_semibold"
-</FrameLayout>
\ No newline at end of file
+ />
+ <TextView
+ android:id="@+id/stopTitleTextView"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textSize="18sp"
+ android:textStyle="bold"
+ android:layout_below="@id/stopNumberTextView"
+ android:layout_alignParentStart="true"
+ android:layout_toStartOf="@id/arrivalsCardButton"
+ android:layout_marginBottom="5dp"
+ android:layout_marginStart="10dp"
+ android:layout_marginEnd="10dp"
+ android:fontFamily="@font/pitagon_regular"
+ />
+ <TextView
+ android:id="@+id/linesPassingTextView"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textSize="14sp"
+ android:layout_below="@id/stopTitleTextView"
+ android:layout_alignParentStart="true"
+ android:layout_toStartOf="@id/arrivalsCardButton"
+ android:layout_marginBottom="5dp"
+ android:layout_marginStart="10dp"
+ android:layout_marginEnd="10dp"
+ />
+ <androidx.cardview.widget.CardView
+ android:id="@+id/arrivalsCardButton"
+ android:layout_width="50sp"
+ android:layout_height="50sp"
+ android:layout_marginStart="5sp"
+ android:layout_marginEnd="5sp"
+ app:cardCornerRadius="25sp"
+ app:cardElevation="2dp"
+ android:clickable="true"
+ android:focusable="true"
+ android:layout_alignParentTop="true"
+ android:layout_toStartOf="@id/directionsCardButton"
+ android:backgroundTint="?android:attr/colorAccent"
+ android:foreground="?selectableItemBackground">
+
+ <ImageView
+ android:layout_width="30sp"
+ android:layout_height="30sp"
+ android:layout_gravity="center"
+ android:tint="?colorOnPrimary"
+ app:srcCompat="@drawable/ic_baseline_departure_board_24" />
+
+ </androidx.cardview.widget.CardView>
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/directionsCardButton"
+ android:layout_width="50sp"
+ android:layout_height="50sp"
+ android:layout_marginStart="5sp"
+ android:layout_marginEnd="5sp"
+ app:cardCornerRadius="25sp"
+ app:cardElevation="2dp"
+ android:clickable="true"
+ android:focusable="true"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentEnd="true"
+ android:foreground="?selectableItemBackground"
+ android:backgroundTint="?android:attr/colorAccent"
+ >
+
+ <ImageView
+ android:id="@+id/rightmostImageView"
+ android:layout_width="30sp"
+ android:layout_height="30sp"
+ android:layout_gravity="center"
+ app:srcCompat="@drawable/navigation_right" />
+
+ </androidx.cardview.widget.CardView>
+
+
+ <!-- Additional details -->
+
+ <!-- Close button -->
+ <ImageView
+ android:layout_width="30sp"
+ android:layout_height="30sp"
+ app:srcCompat="@drawable/baseline_close_16"
+ android:id="@+id/btnClose"
+ android:layout_marginTop="15dp"
+ android:layout_marginEnd="15dp"
+ android:layout_marginBottom="5dp"
+ android:layout_marginStart="10dp"
+ android:layout_below="@id/directionsCardButton"
+ android:layout_alignParentEnd="true"
+ app:layout_constraintHorizontal_bias="0.5"
+ android:foreground="?selectableItemBackground"
+ />
+ </RelativeLayout>
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_map.xml b/app/src/main/res/layout/fragment_map.xml
--- a/app/src/main/res/layout/fragment_map.xml
+++ b/app/src/main/res/layout/fragment_map.xml
@@ -19,7 +19,7 @@
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:id="@+id/icon_center_map"
+ android:id="@+id/centerMapImageButton"
android:src="@drawable/ic_center_map"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
@@ -33,13 +33,13 @@
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:id="@+id/icon_follow"
+ android:id="@+id/followUserImageButton"
android:src="@drawable/ic_follow_me"
android:background="#00ffffff"
android:contentDescription="@string/bt_follow_me_description"
android:cropToPadding="true"
- android:layout_below="@+id/icon_center_map"
- android:layout_alignLeft="@+id/icon_center_map"
- android:layout_alignStart="@+id/icon_center_map"
+ android:layout_below="@+id/centerMapImageButton"
+ android:layout_alignLeft="@+id/centerMapImageButton"
+ android:layout_alignStart="@+id/centerMapImageButton"
android:layout_marginTop="10dp" />
</RelativeLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_map_libre.xml b/app/src/main/res/layout/fragment_map_libre.xml
new file mode 100644
--- /dev/null
+++ b/app/src/main/res/layout/fragment_map_libre.xml
@@ -0,0 +1,195 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.coordinatorlayout.widget.CoordinatorLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:theme="@style/MapTheme"
+ tools:context=".fragments.MapLibreFragment">
+
+ <org.maplibre.android.maps.MapView
+ android:id="@+id/libreMapView"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ />
+
+ <!-- Bottom Sheet for details -->
+ <RelativeLayout
+ android:id="@+id/bottom_sheet"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="10dp"
+ android:layout_marginEnd="10dp"
+ android:orientation="vertical"
+ android:background="@drawable/bottom_sheet_background"
+ android:elevation="8dp"
+ android:padding="10dp"
+ app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
+ app:behavior_hideable="true"
+ app:behavior_peekHeight="4dp"
+ android:clickable="true"
+ android:focusable="true">
+
+ <TextView
+ android:id="@+id/stopNumberTextView"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textSize="18sp"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentStart="true"
+ android:layout_toStartOf="@id/arrivalsCardButton"
+ android:layout_marginTop="8dp"
+ android:layout_marginStart="10dp"
+ android:layout_marginEnd="10dp"
+ android:layout_marginBottom="2dp"
+ android:fontFamily="@font/pitagon_semibold"
+
+ />
+ <TextView
+ android:id="@+id/stopTitleTextView"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textSize="18sp"
+ android:textStyle="bold"
+ android:layout_below="@id/stopNumberTextView"
+ android:layout_alignParentStart="true"
+ android:layout_toStartOf="@id/arrivalsCardButton"
+ android:layout_marginBottom="5dp"
+ android:layout_marginStart="10dp"
+ android:layout_marginEnd="10dp"
+ android:fontFamily="@font/pitagon_regular"
+ />
+ <TextView
+ android:id="@+id/linesPassingTextView"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textSize="14sp"
+ android:layout_below="@id/stopTitleTextView"
+ android:layout_alignParentStart="true"
+ android:layout_toStartOf="@id/arrivalsCardButton"
+ android:layout_marginBottom="5dp"
+ android:layout_marginStart="10dp"
+ android:layout_marginEnd="10dp"
+ />
+ <androidx.cardview.widget.CardView
+ android:id="@+id/arrivalsCardButton"
+ android:layout_width="50sp"
+ android:layout_height="50sp"
+ android:layout_margin="5sp"
+ app:cardCornerRadius="25sp"
+ app:cardElevation="2dp"
+ android:clickable="true"
+ android:focusable="true"
+ android:layout_alignParentTop="true"
+ android:layout_toStartOf="@id/directionsCardButton"
+ android:backgroundTint="?android:attr/colorAccent"
+ android:foreground="?selectableItemBackground">
+
+ <ImageView
+ android:layout_width="30sp"
+ android:layout_height="30sp"
+ android:layout_gravity="center"
+ android:tint="?colorOnPrimary"
+ app:srcCompat="@drawable/ic_baseline_departure_board_24" />
+
+ </androidx.cardview.widget.CardView>
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/directionsCardButton"
+ android:layout_width="50sp"
+ android:layout_height="50sp"
+ android:layout_margin="5sp"
+ app:cardCornerRadius="25sp"
+ app:cardElevation="2dp"
+ android:clickable="true"
+ android:focusable="true"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentEnd="true"
+ android:foreground="?selectableItemBackground"
+ android:backgroundTint="?android:attr/colorAccent"
+ >
+
+ <ImageView
+ android:layout_width="30sp"
+ android:layout_height="30sp"
+ android:layout_gravity="center"
+ app:srcCompat="@drawable/navigation_right" />
+
+ </androidx.cardview.widget.CardView>
+
+
+ <!-- Additional details -->
+
+ <!-- Close button -->
+ <ImageView
+ android:layout_width="30sp"
+ android:layout_height="30sp"
+ app:srcCompat="@drawable/baseline_close_16"
+ android:tint="@color/red_darker"
+ android:id="@+id/btnClose"
+ android:layout_marginTop="10dp"
+ android:layout_marginEnd="10dp"
+ android:layout_marginBottom="10dp"
+ android:layout_marginStart="10dp"
+ android:layout_below="@id/directionsCardButton"
+ android:layout_alignParentEnd="true"
+ app:layout_constraintHorizontal_bias="0.5"
+ android:foreground="?selectableItemBackground"
+ />
+ </RelativeLayout>
+
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:pointerIcon="none">
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="end"
+ android:layout_marginTop="60dp"
+ android:layout_marginEnd="6dp"
+
+ >
+
+ <ImageButton
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/locationEnableIcon"
+ android:src="@drawable/location_circlew_grey"
+
+ android:background="#00ffffff"
+ android:contentDescription="@string/enable_position"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentEnd="true"
+ android:layout_marginEnd="4dp"
+
+ android:cropToPadding="true"/>
+ <ImageButton
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/centerMapImageButton"
+ android:src="@drawable/ic_center_map"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentEnd="true"
+ android:layout_below="@id/locationEnableIcon"
+ android:layout_marginTop="6dp"
+ android:background="#00ffffff"
+ android:contentDescription="@string/bt_center_map_description"
+ android:cropToPadding="true" />
+
+ <ImageButton
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/followUserImageButton"
+ android:src="@drawable/ic_follow_me"
+ android:background="#00ffffff"
+ android:contentDescription="@string/bt_follow_me_description"
+ android:cropToPadding="true"
+ android:layout_below="@+id/centerMapImageButton"
+ android:layout_alignStart="@+id/centerMapImageButton"
+ android:layout_marginTop="6dp" />
+
+ </RelativeLayout>
+ </FrameLayout>
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -258,4 +258,7 @@
<string name="message_check_at_least_one">Seleziona almeno un elemento da importare!</string>
<string name="load_file_favorites">Importa preferiti dal backup</string>
<string name="load_preferences">Importa preferenze dal backup</string>
-</resources>
\ No newline at end of file
+
+ <string name="no_map_app_to_show_stop">Nessuna app disponibile per mostrare la fermata!</string>
+ <string name="destination_unknown">Destinazione sconosciuta</string>
+</resources>
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -2,6 +2,7 @@
<resources>
<color name="orange_500">#ff9800</color>
+ <color name="orange_600">#E77D13</color>
<color name="orange_700">#F57C00</color>
<color name="orange_700_40light">#cc6600</color>
<color name="orange_700_30light">#994d00</color>
diff --git a/app/src/main/res/values/keys.xml b/app/src/main/res/values/keys.xml
--- a/app/src/main/res/values/keys.xml
+++ b/app/src/main/res/values/keys.xml
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:ignore="MissingTranslation">
- <string name="pref_layout">layout_pref</string>
- <string name="pref_update_db_now">pref_update_db_now</string>
-
+ <string name="pref_layout" translatable="false">layout_pref</string>
+ <string name="pref_update_db_now" translatable="false">pref_update_db_now</string>
+
<array name="first_screen_values">
<item>arrivals</item>
<item>favorites</item>
@@ -30,4 +30,10 @@
<item>gtfsrt</item>
</array>
+ <!-- MapLibre Preferences -->
+ <array name="map_style_pref_values">
+ <item>versatiles_c</item>
+ <item>osm_legacy</item>
+ </array>
+
</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -292,6 +292,13 @@
</array>
<string name="positions_source_mato_descr">MaTO (updated more frequently, might be offline)</string>
<string name="positions_source_gtfsrt_descr">GTFS RT (more stable, less frequently updated)</string>
+ <string name="map_style_pref_title">Style of the map</string>
+ <string name="map_style_versatiles">Versatiles (vector)</string>
+ <string name="map_style_legacy_raster">OSM legacy (raster, lighter)</string>
+ <array name="map_style_pref_categories">
+ <item>@string/map_style_versatiles</item>
+ <item>@string/map_style_legacy_raster</item>
+ </array>
<string name="remove_all_trips">Remove trips data (free up space)</string>
<string name="all_trips_removed">All GTFS trips have been removed from the database</string>
@@ -342,5 +349,12 @@
<string name="message_check_at_least_one">Check at least one item to import!</string>
<string name="load_file_favorites">Import favorites from backup</string>
<string name="load_preferences">Import preferences from backup</string>
+ <!-- TODO: Remove or change this placeholder text -->
+ <string name="hello_blank_fragment">Hello blank fragment</string>
+
+ <string name="no_map_app_to_show_stop">No map app present to show the stop!</string>
+ <string name="showing_same_direction">Direction is already shown</string>
+ <string name="destination_loading">Loading destination…</string>
+ <string name="destination_unknown">Destination unknown</string>
</resources>
diff --git a/app/src/main/res/values/theme.xml b/app/src/main/res/values/theme.xml
--- a/app/src/main/res/values/theme.xml
+++ b/app/src/main/res/values/theme.xml
@@ -19,6 +19,8 @@
<item name="colorPrimary">@color/orange_500</item>
<item name="colorPrimaryDark">@color/orange_700</item>
<item name="colorAccent">@color/teal_500</item>
+ <item name="colorOnPrimary">@color/white</item>
+
</style>
<style name="preferenceTheme" parent="PreferenceThemeOverlay"></style>
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -11,12 +11,27 @@
android:summary="%s"
/>
+ </androidx.preference.PreferenceCategory>
+
+ <androidx.preference.PreferenceCategory
+ android:title="@string/map"
+ >
+ <androidx.preference.ListPreference
+ android:key="libremap_style_1"
+ android:title="@string/map_style_pref_title"
+ android:entries="@array/map_style_pref_categories"
+ android:entryValues="@array/map_style_pref_values"
+ android:summary="%s"
+ />
<androidx.preference.ListPreference
android:key="@string/pref_positions_source"
android:entries="@array/positions_source_sel"
android:entryValues="@array/positions_source_values"
android:title="@string/positions_source_pref_title"
- />
+ android:summary="%s"
+
+ />
+
</androidx.preference.PreferenceCategory>
<androidx.preference.PreferenceCategory
diff --git a/build.gradle b/build.gradle
--- a/build.gradle
+++ b/build.gradle
@@ -10,11 +10,11 @@
}
//kotlin
- ext.kotlin_version = '1.9.0'
+ ext.kotlin_version = '1.9.21'
ext.coroutines_version = "1.8.0"
dependencies {
- classpath 'com.android.tools.build:gradle:8.1.4'
+ classpath 'com.android.tools.build:gradle:8.5.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
diff --git a/gradle.properties b/gradle.properties
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,4 +1,5 @@
android.defaults.buildfeatures.buildconfig=true
android.nonFinalResIds=false
android.nonTransitiveRClass=false
-android.useAndroidX=true
\ No newline at end of file
+android.useAndroidX=true
+org.gradle.jvmargs=-Xms200M -Xmx1G
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 758de960ec7947253b058ff79c88ce51f3abe08a..d64cd4917707c1f8861d8cb53dd15194d4248596
GIT binary patch
literal 43462
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
ZFT-s5TKEK5e+aW<;r)2_Da-!3^<OsxR+0b!
literal 54417
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
Sx4&U{`_X;#0j%ENzWqNgAOXGr
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,7 @@
-#Sat Apr 24 16:03:07 CEST 2021
distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip
distributionPath=wrapper/dists
-zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
--- a/gradlew
+++ b/gradlew
@@ -1,78 +1,127 @@
-#!/usr/bin/env sh
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
##############################################################################
-##
-## Gradle start up script for UN*X
-##
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
##############################################################################
# Attempt to set APP_HOME
+
# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG=`dirname "$PRG"`"/$link"
- fi
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >/dev/null
-APP_HOME="`pwd -P`"
-cd "$SAVED" >/dev/null
-
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
+MAX_FD=maximum
warn () {
echo "$*"
-}
+} >&2
die () {
echo
echo "$*"
echo
exit 1
-}
+} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
-case "`uname`" in
- CYGWIN* )
- cygwin=true
- ;;
- Darwin* )
- darwin=true
- ;;
- MINGW* )
- msys=true
- ;;
- NONSTOP* )
- nonstop=true
- ;;
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
- JAVACMD="$JAVA_HOME/jre/sh/java"
+ JAVACMD=$JAVA_HOME/jre/sh/java
else
- JAVACMD="$JAVA_HOME/bin/java"
+ JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -81,92 +130,120 @@
location of your Java installation."
fi
else
- JAVACMD="java"
- which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
+ fi
fi
# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
- MAX_FD_LIMIT=`ulimit -H -n`
- if [ $? -eq 0 ] ; then
- if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
- MAX_FD="$MAX_FD_LIMIT"
- fi
- ulimit -n $MAX_FD
- if [ $? -ne 0 ] ; then
- warn "Could not set maximum file descriptor limit: $MAX_FD"
- fi
- else
- warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
- fi
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
fi
-# For Darwin, add options to specify how the application appears in the dock
-if $darwin; then
- GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
-fi
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin ; then
- APP_HOME=`cygpath --path --mixed "$APP_HOME"`
- CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
- JAVACMD=`cygpath --unix "$JAVACMD"`
-
- # We build the pattern for arguments to be converted via cygpath
- ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
- SEP=""
- for dir in $ROOTDIRSRAW ; do
- ROOTDIRS="$ROOTDIRS$SEP$dir"
- SEP="|"
- done
- OURCYGPATTERN="(^($ROOTDIRS))"
- # Add a user-defined pattern to the cygpath arguments
- if [ "$GRADLE_CYGPATTERN" != "" ] ; then
- OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
- fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
- i=0
- for arg in "$@" ; do
- CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
- CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
-
- if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
- eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
- else
- eval `echo args$i`="\"$arg\""
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
fi
- i=$((i+1))
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
done
- case $i in
- (0) set -- ;;
- (1) set -- "$args0" ;;
- (2) set -- "$args0" "$args1" ;;
- (3) set -- "$args0" "$args1" "$args2" ;;
- (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
- (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
- (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
- (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
- (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
- (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
- esac
fi
-# Escape application args
-save () {
- for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
- echo " "
-}
-APP_ARGS=$(save "$@")
-
-# Collect all arguments for the java command, following the shell quoting and substitution rules
-eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
-# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
-if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
- cd "$(dirname "$0")"
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
fi
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -1,4 +1,20 @@
-@if "%DEBUG%" == "" @echo off
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -9,19 +25,23 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS=
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto init
+if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -35,7 +55,7 @@
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-if exist "%JAVA_EXE%" goto init
+if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@@ -45,38 +65,26 @@
goto fail
-:init
-@rem Get command-line arguments, handling Windows variants
-
-if not "%OS%" == "Windows_NT" goto win9xME_args
-
-:win9xME_args
-@rem Slurp the command line arguments.
-set CMD_LINE_ARGS=
-set _SKIP=2
-
-:win9xME_args_slurp
-if "x%~1" == "x" goto execute
-
-set CMD_LINE_ARGS=%*
-
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
+if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal

File Metadata

Mime Type
text/plain
Expires
Tue, Apr 28, 00:58 (5 h, 58 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1877629
Default Alt Text
D184.1777330736.diff (705 KB)

Event Timeline