Page MenuHomeGitPull.it

D184.1773463273.diff
No OneTemporary

Size
597 KB
Referenced Files
None
Subscribers
None

D184.1773463273.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/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -21,12 +21,14 @@
<queries>
<intent>
<action android:name="android.intent.action.VIEW"/>
-
<data android:scheme="http"/>
</intent>
<intent>
<action android:name="android.intent.action.VIEW"/>
-
+ <data android:scheme="geo"/>
+ </intent>
+ <intent>
+ <action android:name="android.intent.action.VIEW"/>
<data android:scheme="https"/>
</intent>
<intent>
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)
+
+ // 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)
+
+ symbolManager = SymbolManager(mapView,mapReady,style)
+ symbolManager.iconAllowOverlap = true
+ symbolManager.textAllowOverlap = false
+
+ symbolManager.addClickListener{ _ ->
+ if (stopActiveSymbol!=null){
+ hideStopBottomSheet()
+
+ return@addClickListener true
+ } else
+ return@addClickListener false
+ }
+
+ 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,48 @@
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)"
+ //This below is the full URI, in the correct format. However it seems that apps accept the above one
+ //val uri_real = "geo:${stop.latitude},${stop.longitude}?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
@@ -213,6 +213,9 @@
<string name="favorites_lines">Preferite</string>
<!-- lines -->
<string name="long_press_stop_4_options">Tocca a lungo la fermata per le opzioni</string>
+ <string name="map_style_pref_title">Stile della mappa</string>
+ <string name="map_style_versatiles">Versatiles (vettoriale)</string>
+ <string name="map_style_legacy_raster">OSM Legacy (raster, più leggera)</string>
<string name="remove_all_trips">Rimuovi i dati dei trip (libera spazio)</string>
<string name="all_trips_removed">Tutti i trip GTFS sono rimossi dal database</string>
<!-- tutorial -->
@@ -258,4 +261,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,15 @@
</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="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="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 +351,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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
literal 0
Hc$@<O00001
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
Sat, Mar 14, 05:41 (22 h, 22 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1744851
Default Alt Text
D184.1773463273.diff (597 KB)

Event Timeline