diff --git a/app/build.gradle b/app/build.gradle --- a/app/build.gradle +++ b/app/build.gradle @@ -98,7 +98,13 @@ implementation 'com.readystatesoftware.sqliteasset:sqliteassethelper:2.0.1' implementation 'com.android.volley:volley:1.2.1' - implementation 'org.osmdroid:osmdroid-android:6.1.10' + implementation 'org.osmdroid:osmdroid-android:6.1.18' + //maplibre + implementation 'org.maplibre.gl:android-sdk:11.8.6' + implementation 'org.maplibre.gl:android-sdk-turf:6.0.1' + + implementation 'org.maplibre.gl:android-plugin-annotation-v9:3.0.2' + // remember to enable maven repo jitpack.io when wanting to use osmbonuspack //implementation 'com.github.MKergall:osmbonuspack:6.9.0' // ACRA diff --git a/app/src/main/assets/map_style_good.json b/app/src/main/assets/map_style_good.json new file mode 100644 --- /dev/null +++ b/app/src/main/assets/map_style_good.json @@ -0,0 +1,3147 @@ +{ + "version": 8, + "metadata": {"maputnik:renderer": "mlgljs"}, + "sources": { + "ne2_shaded": { + "maxzoom": 6, + "tileSize": 256, + "tiles": [ + "https://tiles.openfreemap.org/natural_earth/ne2sr/{z}/{x}/{y}.png" + ], + "type": "raster" + }, + "openmaptiles": { + "type": "vector", + "url": "https://tiles.openfreemap.org/planet" + } + }, + "sprite": "https://tiles.openfreemap.org/sprites/ofm_f384/ofm", + "glyphs": "https://tiles.openfreemap.org/fonts/{fontstack}/{range}.pbf", + "layers": [ + { + "id": "background", + "type": "background", + "paint": {"background-color": "#f8f4f0"} + }, + { + "id": "landcover-glacier", + "type": "fill", + "source": "openmaptiles", + "source-layer": "landcover", + "filter": ["==", ["get", "subclass"], "glacier"], + "paint": { + "fill-color": "#fff", + "fill-opacity": ["interpolate", ["linear"], ["zoom"], 0, 0.9, 10, 0.3] + } + }, + { + "id": "landuse-residential", + "type": "fill", + "source": "openmaptiles", + "source-layer": "landuse", + "filter": [ + "match", + ["get", "class"], + ["neighbourhood", "residential"], + true, + false + ], + "paint": { + "fill-color": [ + "interpolate", + ["linear"], + ["zoom"], + 12, + "hsla(30,19%,90%,0.4)", + 16, + "hsla(30,19%,90%,0.2)" + ] + } + }, + { + "id": "landuse-suburb", + "type": "fill", + "source": "openmaptiles", + "source-layer": "landuse", + "maxzoom": 10, + "filter": ["==", ["get", "class"], "suburb"], + "paint": { + "fill-color": [ + "interpolate", + ["linear"], + ["zoom"], + 8, + "hsla(30,19%,90%,0.4)", + 10, + "hsla(30,19%,90%,0.0)" + ] + } + }, + { + "id": "landuse-commercial", + "type": "fill", + "source": "openmaptiles", + "source-layer": "landuse", + "filter": [ + "all", + ["match", ["geometry-type"], ["MultiPolygon", "Polygon"], true, false], + ["==", ["get", "class"], "commercial"] + ], + "paint": {"fill-color": "hsla(0,60%,87%,0.23)"} + }, + { + "id": "landuse-industrial", + "type": "fill", + "source": "openmaptiles", + "source-layer": "landuse", + "filter": [ + "all", + ["match", ["geometry-type"], ["MultiPolygon", "Polygon"], true, false], + [ + "match", + ["get", "class"], + ["dam", "garages", "industrial"], + true, + false + ] + ], + "paint": {"fill-color": "hsla(49,100%,88%,0.34)"} + }, + { + "id": "landuse-cemetery", + "type": "fill", + "source": "openmaptiles", + "source-layer": "landuse", + "filter": ["==", ["get", "class"], "cemetery"], + "paint": {"fill-color": "#e0e4dd"} + }, + { + "id": "landuse-hospital", + "type": "fill", + "source": "openmaptiles", + "source-layer": "landuse", + "filter": ["==", ["get", "class"], "hospital"], + "paint": {"fill-color": "#fde"} + }, + { + "id": "landuse-school", + "type": "fill", + "source": "openmaptiles", + "source-layer": "landuse", + "filter": ["==", ["get", "class"], "school"], + "paint": {"fill-color": "#f0e8f8"} + }, + { + "id": "landuse-railway", + "type": "fill", + "source": "openmaptiles", + "source-layer": "landuse", + "filter": ["==", ["get", "class"], "railway"], + "paint": {"fill-color": "hsla(30,19%,90%,0.4)"} + }, + { + "id": "park", + "type": "fill", + "source": "openmaptiles", + "source-layer": "park", + "filter": [ + "match", + ["geometry-type"], + ["MultiPolygon", "Polygon"], + true, + false + ], + "paint": { + "fill-color": "#d8e8c8", + "fill-opacity": [ + "interpolate", + ["exponential", 1.8], + ["zoom"], + 9, + 0.5, + 12, + 0.2 + ] + } + }, + { + "id": "landcover-wood", + "type": "fill", + "source": "openmaptiles", + "source-layer": "landcover", + "filter": ["==", ["get", "class"], "wood"], + "paint": { + "fill-antialias": ["step", ["zoom"], false, 9, true], + "fill-color": "#6a4", + "fill-opacity": 0.1, + "fill-outline-color": "hsla(0,0%,0%,0.03)" + } + }, + { + "id": "landcover-grass", + "type": "fill", + "source": "openmaptiles", + "source-layer": "landcover", + "filter": ["==", ["get", "class"], "grass"], + "paint": {"fill-color": "#d8e8c8", "fill-opacity": 1} + }, + { + "id": "landcover-grass-park", + "type": "fill", + "source": "openmaptiles", + "source-layer": "park", + "filter": ["==", ["get", "class"], "public_park"], + "paint": {"fill-color": "#d8e8c8", "fill-opacity": 0.8} + }, + { + "id": "waterway_tunnel", + "type": "line", + "source": "openmaptiles", + "source-layer": "waterway", + "minzoom": 14, + "filter": [ + "all", + ["match", ["get", "class"], ["canal", "river", "stream"], true, false], + ["==", ["get", "brunnel"], "tunnel"] + ], + "layout": {"line-cap": "round"}, + "paint": { + "line-color": "#a0c8f0", + "line-dasharray": [2, 4], + "line-width": [ + "interpolate", + ["exponential", 1.3], + ["zoom"], + 13, + 0.5, + 20, + 6 + ] + } + }, + { + "id": "waterway-other", + "type": "line", + "source": "openmaptiles", + "source-layer": "waterway", + "filter": [ + "all", + ["match", ["get", "class"], ["canal", "river", "stream"], false, true], + ["==", ["get", "intermittent"], 0] + ], + "layout": {"line-cap": "round"}, + "paint": { + "line-color": "#a0c8f0", + "line-width": [ + "interpolate", + ["exponential", 1.3], + ["zoom"], + 13, + 0.5, + 20, + 2 + ] + } + }, + { + "id": "waterway-other-intermittent", + "type": "line", + "source": "openmaptiles", + "source-layer": "waterway", + "filter": [ + "all", + ["match", ["get", "class"], ["canal", "river", "stream"], false, true], + ["==", ["get", "intermittent"], 1] + ], + "layout": {"line-cap": "round"}, + "paint": { + "line-color": "#a0c8f0", + "line-dasharray": [4, 3], + "line-width": [ + "interpolate", + ["exponential", 1.3], + ["zoom"], + 13, + 0.5, + 20, + 2 + ] + } + }, + { + "id": "waterway-stream-canal", + "type": "line", + "source": "openmaptiles", + "source-layer": "waterway", + "filter": [ + "all", + ["match", ["get", "class"], ["canal", "stream"], true, false], + ["!=", ["get", "brunnel"], "tunnel"], + ["==", ["get", "intermittent"], 0] + ], + "layout": {"line-cap": "round"}, + "paint": { + "line-color": "#a0c8f0", + "line-width": [ + "interpolate", + ["exponential", 1.3], + ["zoom"], + 13, + 0.5, + 20, + 6 + ] + } + }, + { + "id": "waterway-stream-canal-intermittent", + "type": "line", + "source": "openmaptiles", + "source-layer": "waterway", + "filter": [ + "all", + ["match", ["get", "class"], ["canal", "stream"], true, false], + ["!=", ["get", "brunnel"], "tunnel"], + ["==", ["get", "intermittent"], 1] + ], + "layout": {"line-cap": "round"}, + "paint": { + "line-color": "#a0c8f0", + "line-dasharray": [4, 3], + "line-width": [ + "interpolate", + ["exponential", 1.3], + ["zoom"], + 13, + 0.5, + 20, + 6 + ] + } + }, + { + "id": "waterway-river", + "type": "line", + "source": "openmaptiles", + "source-layer": "waterway", + "filter": [ + "all", + ["==", ["get", "class"], "river"], + ["!=", ["get", "brunnel"], "tunnel"], + ["!=", ["get", "intermittent"], 1] + ], + "layout": {"line-cap": "round"}, + "paint": { + "line-color": "#a0c8f0", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 10, + 0.8, + 20, + 6 + ] + } + }, + { + "id": "waterway-river-intermittent", + "type": "line", + "source": "openmaptiles", + "source-layer": "waterway", + "filter": [ + "all", + ["==", ["get", "class"], "river"], + ["!=", ["get", "brunnel"], "tunnel"], + ["==", ["get", "intermittent"], 1] + ], + "layout": {"line-cap": "round"}, + "paint": { + "line-color": "#a0c8f0", + "line-dasharray": [3, 2.5], + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 10, + 0.8, + 20, + 6 + ] + } + }, + { + "id": "water", + "type": "fill", + "source": "openmaptiles", + "source-layer": "water", + "filter": [ + "all", + ["!=", ["get", "intermittent"], 1], + ["!=", ["get", "brunnel"], "tunnel"] + ], + "paint": {"fill-color": "#AECFE2"} + }, + { + "id": "water-intermittent", + "type": "fill", + "source": "openmaptiles", + "source-layer": "water", + "filter": ["==", ["get", "intermittent"], 1], + "paint": {"fill-color": "hsl(210,67%,85%)", "fill-opacity": 0.7} + }, + { + "id": "landcover-ice-shelf", + "type": "fill", + "source": "openmaptiles", + "source-layer": "landcover", + "filter": ["==", ["get", "subclass"], "ice_shelf"], + "paint": { + "fill-color": "#fff", + "fill-opacity": ["interpolate", ["linear"], ["zoom"], 0, 0.9, 10, 0.3] + } + }, + { + "id": "landcover-sand", + "type": "fill", + "source": "openmaptiles", + "source-layer": "landcover", + "filter": ["==", ["get", "class"], "sand"], + "paint": {"fill-color": "rgba(245, 238, 188, 1)", "fill-opacity": 1} + }, + { + "id": "building", + "type": "fill", + "source": "openmaptiles", + "source-layer": "building", + "paint": { + "fill-antialias": true, + "fill-color": [ + "interpolate", + ["linear"], + ["zoom"], + 15.5, + "#f2eae2", + 16, + "#dfdbd7" + ] + } + }, + { + "id": "building-top", + "type": "fill", + "source": "openmaptiles", + "source-layer": "building", + "paint": { + "fill-color": "#f2eae2", + "fill-opacity": ["interpolate", ["linear"], ["zoom"], 13, 0, 16, 1], + "fill-outline-color": "#dfdbd7", + "fill-translate": [ + "interpolate", + ["linear"], + ["zoom"], + 14, + ["literal", [0, 0]], + 16, + ["literal", [-2, -2]] + ] + } + }, + { + "id": "tunnel-service-track-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "tunnel"], + ["match", ["get", "class"], ["service", "track"], true, false] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#cfcdca", + "line-dasharray": [0.5, 0.25], + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 15, + 1, + 16, + 4, + 20, + 11 + ] + } + }, + { + "id": "tunnel-motorway-link-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "tunnel"], + ["==", ["get", "class"], "motorway"], + ["==", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "rgba(200, 147, 102, 1)", + "line-dasharray": [0.5, 0.25], + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 12, + 1, + 13, + 3, + 14, + 4, + 20, + 15 + ] + } + }, + { + "id": "tunnel-minor-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "tunnel"], + ["==", ["get", "class"], "minor"] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#cfcdca", + "line-dasharray": [0.5, 0.25], + "line-opacity": ["interpolate", ["linear"], ["zoom"], 12, 0, 12.5, 1], + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 12, + 0.5, + 13, + 1, + 14, + 4, + 20, + 15 + ] + } + }, + { + "id": "tunnel-link-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "tunnel"], + [ + "match", + ["get", "class"], + ["primary", "secondary", "tertiary", "trunk"], + true, + false + ], + ["==", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#e9ac77", + "line-dasharray": [0.5, 0.25], + "line-opacity": 1, + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 12, + 1, + 13, + 3, + 14, + 4, + 20, + 15 + ] + } + }, + { + "id": "tunnel-secondary-tertiary-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "tunnel"], + ["match", ["get", "class"], ["secondary", "tertiary"], true, false], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#e9ac77", + "line-dasharray": [0.5, 0.25], + "line-opacity": 1, + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 8, + 1.5, + 20, + 17 + ] + } + }, + { + "id": "tunnel-trunk-primary-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "tunnel"], + ["match", ["get", "class"], ["primary", "trunk"], true, false], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#e9ac77", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 5, + 0.4, + 6, + 0.6, + 7, + 1.5, + 20, + 22 + ] + } + }, + { + "id": "tunnel-motorway-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "tunnel"], + ["==", ["get", "class"], "motorway"], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#e9ac77", + "line-dasharray": [0.5, 0.25], + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 5, + 0.4, + 6, + 0.6, + 7, + 1.5, + 20, + 22 + ] + } + }, + { + "id": "tunnel-path", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["==", ["get", "brunnel"], "tunnel"], + ["==", ["get", "class"], "path"] + ], + "paint": { + "line-color": "#cba", + "line-dasharray": [1.5, 0.75], + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 15, + 1.2, + 20, + 4 + ] + } + }, + { + "id": "tunnel-motorway-link", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "tunnel"], + ["==", ["get", "class"], "motorway"], + ["==", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "rgba(244, 209, 158, 1)", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 12.5, + 0, + 13, + 1.5, + 14, + 2.5, + 20, + 11.5 + ] + } + }, + { + "id": "tunnel-service-track", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "tunnel"], + ["match", ["get", "class"], ["service", "track"], true, false] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#fff", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 15.5, + 0, + 16, + 2, + 20, + 7.5 + ] + } + }, + { + "id": "tunnel-link", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "tunnel"], + [ + "match", + ["get", "class"], + ["primary", "secondary", "tertiary", "trunk"], + true, + false + ], + ["==", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#fff4c6", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 12.5, + 0, + 13, + 1.5, + 14, + 2.5, + 20, + 11.5 + ] + } + }, + { + "id": "tunnel-minor", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "tunnel"], + ["==", ["get", "class"], "minor"] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#fff", + "line-opacity": 1, + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 13.5, + 0, + 14, + 2.5, + 20, + 11.5 + ] + } + }, + { + "id": "tunnel-secondary-tertiary", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "tunnel"], + ["match", ["get", "class"], ["secondary", "tertiary"], true, false], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#fff4c6", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 6.5, + 0, + 7, + 0.5, + 20, + 10 + ] + } + }, + { + "id": "tunnel-trunk-primary", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "tunnel"], + ["match", ["get", "class"], ["primary", "trunk"], true, false], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#fff4c6", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 6.5, + 0, + 7, + 0.5, + 20, + 18 + ] + } + }, + { + "id": "tunnel-motorway", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "tunnel"], + ["==", ["get", "class"], "motorway"], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#ffdaa6", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 6.5, + 0, + 7, + 0.5, + 20, + 18 + ] + } + }, + { + "id": "tunnel-railway", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "tunnel"], + ["==", ["get", "class"], "rail"] + ], + "paint": { + "line-color": "#bbb", + "line-dasharray": [2, 2], + "line-width": [ + "interpolate", + ["exponential", 1.4], + ["zoom"], + 14, + 0.4, + 15, + 0.75, + 20, + 2 + ] + } + }, + { + "id": "ferry", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": ["match", ["get", "class"], ["ferry"], true, false], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "rgba(108, 159, 182, 1)", + "line-dasharray": [2, 2], + "line-width": 1.1 + } + }, + { + "id": "aeroway-taxiway-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "aeroway", + "minzoom": 12, + "filter": ["match", ["get", "class"], ["taxiway"], true, false], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "rgba(153, 153, 153, 1)", + "line-opacity": 1, + "line-width": [ + "interpolate", + ["exponential", 1.5], + ["zoom"], + 11, + 2, + 17, + 12 + ] + } + }, + { + "id": "aeroway-runway-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "aeroway", + "minzoom": 12, + "filter": ["match", ["get", "class"], ["runway"], true, false], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "rgba(153, 153, 153, 1)", + "line-opacity": 1, + "line-width": [ + "interpolate", + ["exponential", 1.5], + ["zoom"], + 11, + 5, + 17, + 55 + ] + } + }, + { + "id": "aeroway-area", + "type": "fill", + "source": "openmaptiles", + "source-layer": "aeroway", + "minzoom": 4, + "filter": [ + "all", + ["match", ["geometry-type"], ["MultiPolygon", "Polygon"], true, false], + ["match", ["get", "class"], ["runway", "taxiway"], true, false] + ], + "paint": { + "fill-color": "rgba(255, 255, 255, 1)", + "fill-opacity": ["interpolate", ["linear"], ["zoom"], 13, 0, 14, 1] + } + }, + { + "id": "aeroway-taxiway", + "type": "line", + "source": "openmaptiles", + "source-layer": "aeroway", + "minzoom": 4, + "filter": [ + "all", + ["match", ["get", "class"], ["taxiway"], true, false], + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ] + ], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "rgba(255, 255, 255, 1)", + "line-opacity": ["interpolate", ["linear"], ["zoom"], 11, 0, 12, 1], + "line-width": [ + "interpolate", + ["exponential", 1.5], + ["zoom"], + 11, + 1, + 17, + 10 + ] + } + }, + { + "id": "aeroway-runway", + "type": "line", + "source": "openmaptiles", + "source-layer": "aeroway", + "minzoom": 4, + "filter": [ + "all", + ["match", ["get", "class"], ["runway"], true, false], + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ] + ], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "rgba(255, 255, 255, 1)", + "line-opacity": ["interpolate", ["linear"], ["zoom"], 11, 0, 12, 1], + "line-width": [ + "interpolate", + ["exponential", 1.5], + ["zoom"], + 11, + 4, + 17, + 50 + ] + } + }, + { + "id": "road_area_pier", + "type": "fill", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["match", ["geometry-type"], ["MultiPolygon", "Polygon"], true, false], + ["==", ["get", "class"], "pier"] + ], + "paint": {"fill-antialias": true, "fill-color": "#f8f4f0"} + }, + { + "id": "road_pier", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["match", ["get", "class"], ["pier"], true, false] + ], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "#f8f4f0", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 15, + 1, + 17, + 4 + ] + } + }, + { + "id": "highway-area", + "type": "fill", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["match", ["geometry-type"], ["MultiPolygon", "Polygon"], true, false], + ["match", ["get", "class"], ["pier"], false, true] + ], + "paint": { + "fill-antialias": false, + "fill-color": "hsla(0,0%,89%,0.56)", + "fill-opacity": 0.9, + "fill-outline-color": "#cfcdca" + } + }, + { + "id": "highway-motorway-link-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true], + ["==", ["get", "class"], "motorway"], + ["==", ["get", "ramp"], 1] + ], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "#e9ac77", + "line-opacity": 1, + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 12, + 1, + 13, + 3, + 14, + 4, + 20, + 15 + ] + } + }, + { + "id": "highway-link-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "minzoom": 13, + "filter": [ + "all", + ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true], + [ + "match", + ["get", "class"], + ["primary", "secondary", "tertiary", "trunk"], + true, + false + ], + ["==", ["get", "ramp"], 1] + ], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "#e9ac77", + "line-opacity": 1, + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 12, + 1, + 13, + 3, + 14, + 4, + 20, + 15 + ] + } + }, + { + "id": "highway-minor-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["!=", ["get", "brunnel"], "tunnel"], + ["match", ["get", "class"], ["minor", "service", "track"], true, false] + ], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "#cfcdca", + "line-opacity": ["interpolate", ["linear"], ["zoom"], 12, 0, 12.5, 1], + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 12, + 0.5, + 13, + 1, + 14, + 4, + 20, + 15 + ] + } + }, + { + "id": "highway-secondary-tertiary-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true], + ["match", ["get", "class"], ["secondary", "tertiary"], true, false], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-cap": "butt", "line-join": "round"}, + "paint": { + "line-color": "#e9ac77", + "line-opacity": 1, + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 8, + 1.5, + 20, + 17 + ] + } + }, + { + "id": "highway-primary-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "minzoom": 5, + "filter": [ + "all", + ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true], + ["match", ["get", "class"], ["primary"], true, false], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-cap": "butt", "line-join": "round"}, + "paint": { + "line-color": "#e9ac77", + "line-opacity": ["interpolate", ["linear"], ["zoom"], 7, 0, 8, 1], + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 7, + 0, + 8, + 0.6, + 9, + 1.5, + 20, + 22 + ] + } + }, + { + "id": "highway-trunk-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "minzoom": 5, + "filter": [ + "all", + ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true], + ["match", ["get", "class"], ["trunk"], true, false], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-cap": "butt", "line-join": "round"}, + "paint": { + "line-color": "#e9ac77", + "line-opacity": ["interpolate", ["linear"], ["zoom"], 5, 0, 6, 1], + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 5, + 0, + 6, + 0.6, + 7, + 1.5, + 20, + 22 + ] + } + }, + { + "id": "highway-motorway-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "minzoom": 4, + "filter": [ + "all", + ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true], + ["==", ["get", "class"], "motorway"], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-cap": "butt", "line-join": "round"}, + "paint": { + "line-color": "#e9ac77", + "line-opacity": ["interpolate", ["linear"], ["zoom"], 4, 0, 5, 1], + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 4, + 0, + 5, + 0.4, + 6, + 0.6, + 7, + 1.5, + 20, + 22 + ] + } + }, + { + "id": "highway-path", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true], + ["==", ["get", "class"], "path"] + ], + "paint": { + "line-color": "#cba", + "line-dasharray": [1.5, 0.75], + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 15, + 1.2, + 20, + 4 + ] + } + }, + { + "id": "highway-motorway-link", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "minzoom": 12, + "filter": [ + "all", + ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true], + ["==", ["get", "class"], "motorway"], + ["==", ["get", "ramp"], 1] + ], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "#fc8", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 12.5, + 0, + 13, + 1.5, + 14, + 2.5, + 20, + 11.5 + ] + } + }, + { + "id": "highway-link", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "minzoom": 13, + "filter": [ + "all", + ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true], + [ + "match", + ["get", "class"], + ["primary", "secondary", "tertiary", "trunk"], + true, + false + ], + ["==", ["get", "ramp"], 1] + ], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "#fea", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 12.5, + 0, + 13, + 1.5, + 14, + 2.5, + 20, + 11.5 + ] + } + }, + { + "id": "highway-minor", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["!=", ["get", "brunnel"], "tunnel"], + ["match", ["get", "class"], ["minor", "service", "track"], true, false] + ], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "#fff", + "line-opacity": 1, + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 13.5, + 0, + 14, + 2.5, + 20, + 11.5 + ] + } + }, + { + "id": "highway-secondary-tertiary", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true], + ["match", ["get", "class"], ["secondary", "tertiary"], true, false], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "#fea", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 6.5, + 0, + 8, + 0.5, + 20, + 13 + ] + } + }, + { + "id": "highway-primary", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true], + ["match", ["get", "class"], ["primary"], true, false], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "#fea", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 8.5, + 0, + 9, + 0.5, + 20, + 18 + ] + } + }, + { + "id": "highway-trunk", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true], + ["match", ["get", "class"], ["trunk"], true, false], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "#fea", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 6.5, + 0, + 7, + 0.5, + 20, + 18 + ] + } + }, + { + "id": "highway-motorway", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "minzoom": 5, + "filter": [ + "all", + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true], + ["==", ["get", "class"], "motorway"], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "#fc8", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 6.5, + 0, + 7, + 0.5, + 20, + 18 + ] + } + }, + { + "id": "railway-transit", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["==", ["get", "class"], "transit"], + ["match", ["get", "brunnel"], ["tunnel"], false, true] + ], + "paint": { + "line-color": "hsla(0,0%,73%,0.77)", + "line-width": [ + "interpolate", + ["exponential", 1.4], + ["zoom"], + 14, + 0.4, + 20, + 1 + ] + } + }, + { + "id": "railway-transit-hatching", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["==", ["get", "class"], "transit"], + ["match", ["get", "brunnel"], ["tunnel"], false, true] + ], + "paint": { + "line-color": "hsla(0,0%,73%,0.68)", + "line-dasharray": [0.2, 8], + "line-width": [ + "interpolate", + ["exponential", 1.4], + ["zoom"], + 14.5, + 0, + 15, + 2, + 20, + 6 + ] + } + }, + { + "id": "railway-service", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["==", ["get", "class"], "rail"], + ["has", "service"] + ], + "paint": { + "line-color": "hsla(0,0%,73%,0.77)", + "line-width": [ + "interpolate", + ["exponential", 1.4], + ["zoom"], + 14, + 0.4, + 20, + 1 + ] + } + }, + { + "id": "railway-service-hatching", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["==", ["get", "class"], "rail"], + ["has", "service"] + ], + "paint": { + "line-color": "hsla(0,0%,73%,0.68)", + "line-dasharray": [0.2, 8], + "line-width": [ + "interpolate", + ["exponential", 1.4], + ["zoom"], + 14.5, + 0, + 15, + 2, + 20, + 6 + ] + } + }, + { + "id": "railway", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["!", ["has", "service"]], + ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true], + ["==", ["get", "class"], "rail"] + ], + "paint": { + "line-color": "#bbb", + "line-width": [ + "interpolate", + ["exponential", 1.4], + ["zoom"], + 14, + 0.4, + 15, + 0.75, + 20, + 2 + ] + } + }, + { + "id": "railway-hatching", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["!", ["has", "service"]], + ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true], + ["==", ["get", "class"], "rail"] + ], + "paint": { + "line-color": "#bbb", + "line-dasharray": [0.2, 8], + "line-width": [ + "interpolate", + ["exponential", 1.4], + ["zoom"], + 14.5, + 0, + 15, + 3, + 20, + 8 + ] + } + }, + { + "id": "bridge-motorway-link-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "bridge"], + ["==", ["get", "class"], "motorway"], + ["==", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#e9ac77", + "line-opacity": 1, + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 12, + 1, + 13, + 3, + 14, + 4, + 20, + 19 + ] + } + }, + { + "id": "bridge-link-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "bridge"], + [ + "match", + ["get", "class"], + ["primary", "secondary", "tertiary", "trunk"], + true, + false + ], + ["==", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#e9ac77", + "line-opacity": 1, + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 12, + 1, + 13, + 3, + 14, + 4, + 20, + 19 + ] + } + }, + { + "id": "bridge-secondary-tertiary-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "bridge"], + ["match", ["get", "class"], ["secondary", "tertiary"], true, false], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#e9ac77", + "line-opacity": 1, + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 5, + 0.4, + 7, + 0.6, + 8, + 1.5, + 20, + 21 + ] + } + }, + { + "id": "bridge-trunk-primary-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "bridge"], + ["match", ["get", "class"], ["primary", "trunk"], true, false], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "hsl(28,76%,67%)", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 5, + 0.4, + 6, + 0.6, + 7, + 1.5, + 20, + 26 + ] + } + }, + { + "id": "bridge-motorway-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "bridge"], + ["==", ["get", "class"], "motorway"], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#e9ac77", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 5, + 0.4, + 6, + 0.6, + 7, + 1.5, + 20, + 26 + ] + } + }, + { + "id": "bridge-minor-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["==", ["get", "brunnel"], "bridge"], + ["match", ["get", "class"], ["minor", "service", "track"], true, false] + ], + "layout": {"line-cap": "butt", "line-join": "round"}, + "paint": { + "line-color": "#cfcdca", + "line-opacity": ["interpolate", ["linear"], ["zoom"], 12, 0, 12.5, 1], + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 12, + 0.5, + 13, + 1, + 14, + 6, + 20, + 24 + ] + } + }, + { + "id": "bridge-path-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["==", ["get", "brunnel"], "bridge"], + ["==", ["get", "class"], "path"] + ], + "paint": { + "line-color": "#f8f4f0", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 15, + 1.2, + 20, + 18 + ] + } + }, + { + "id": "bridge-path", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["==", ["get", "brunnel"], "bridge"], + ["==", ["get", "class"], "path"] + ], + "paint": { + "line-color": "#cba", + "line-dasharray": [1.5, 0.75], + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 15, + 1.2, + 20, + 4 + ] + } + }, + { + "id": "bridge-motorway-link", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "bridge"], + ["==", ["get", "class"], "motorway"], + ["==", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#fc8", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 12.5, + 0, + 13, + 1.5, + 14, + 2.5, + 20, + 11.5 + ] + } + }, + { + "id": "bridge-link", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "bridge"], + [ + "match", + ["get", "class"], + ["primary", "secondary", "tertiary", "trunk"], + true, + false + ], + ["==", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#fea", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 12.5, + 0, + 13, + 1.5, + 14, + 2.5, + 20, + 11.5 + ] + } + }, + { + "id": "bridge-minor", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["==", ["get", "brunnel"], "bridge"], + ["match", ["get", "class"], ["minor", "service", "track"], true, false] + ], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "#fff", + "line-opacity": 1, + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 13.5, + 0, + 14, + 2.5, + 20, + 11.5 + ] + } + }, + { + "id": "bridge-secondary-tertiary", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "bridge"], + ["match", ["get", "class"], ["secondary", "tertiary"], true, false], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#fea", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 6.5, + 0, + 8, + 0.5, + 20, + 13 + ] + } + }, + { + "id": "bridge-trunk-primary", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "bridge"], + ["match", ["get", "class"], ["primary", "trunk"], true, false], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#fea", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 6.5, + 0, + 7, + 0.5, + 20, + 18 + ] + } + }, + { + "id": "bridge-motorway", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "bridge"], + ["==", ["get", "class"], "motorway"], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#fc8", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 6.5, + 0, + 7, + 0.5, + 20, + 18 + ] + } + }, + { + "id": "bridge-railway", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "bridge"], + ["==", ["get", "class"], "rail"] + ], + "paint": { + "line-color": "#bbb", + "line-width": [ + "interpolate", + ["exponential", 1.4], + ["zoom"], + 14, + 0.4, + 15, + 0.75, + 20, + 2 + ] + } + }, + { + "id": "bridge-railway-hatching", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "bridge"], + ["==", ["get", "class"], "rail"] + ], + "paint": { + "line-color": "#bbb", + "line-dasharray": [0.2, 8], + "line-width": [ + "interpolate", + ["exponential", 1.4], + ["zoom"], + 14.5, + 0, + 15, + 3, + 20, + 8 + ] + } + }, + { + "id": "cablecar", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "minzoom": 13, + "filter": ["==", ["get", "subclass"], "cable_car"], + "layout": {"line-cap": "round"}, + "paint": { + "line-color": "hsl(0,0%,70%)", + "line-width": ["interpolate", ["linear"], ["zoom"], 11, 1, 19, 2.5] + } + }, + { + "id": "cablecar-dash", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "minzoom": 13, + "filter": ["==", ["get", "subclass"], "cable_car"], + "layout": {"line-cap": "round"}, + "paint": { + "line-color": "hsl(0,0%,70%)", + "line-dasharray": [2, 3], + "line-width": ["interpolate", ["linear"], ["zoom"], 11, 3, 19, 5.5] + } + }, + { + "id": "boundary_3", + "type": "line", + "source": "openmaptiles", + "source-layer": "boundary", + "minzoom": 5, + "filter": [ + "all", + [">=", ["get", "admin_level"], 3], + ["<=", ["get", "admin_level"], 6], + ["!=", ["get", "maritime"], 1], + ["!=", ["get", "disputed"], 1], + ["!", ["has", "claimed_by"]] + ], + "paint": { + "line-color": "hsl(0,0%,70%)", + "line-dasharray": [1, 1], + "line-width": ["interpolate", ["linear", 1], ["zoom"], 7, 1, 11, 2] + } + }, + { + "id": "boundary_2", + "type": "line", + "source": "openmaptiles", + "source-layer": "boundary", + "filter": [ + "all", + ["==", ["get", "admin_level"], 2], + ["!=", ["get", "maritime"], 1], + ["!=", ["get", "disputed"], 1], + ["!", ["has", "claimed_by"]] + ], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "hsl(248,7%,66%)", + "line-opacity": ["interpolate", ["linear"], ["zoom"], 0, 0.4, 4, 1], + "line-width": ["interpolate", ["linear"], ["zoom"], 3, 1, 5, 1.2, 12, 3] + } + }, + { + "id": "boundary_disputed", + "type": "line", + "source": "openmaptiles", + "source-layer": "boundary", + "filter": [ + "all", + ["!=", ["get", "maritime"], 1], + ["==", ["get", "disputed"], 1] + ], + "paint": { + "line-color": "hsl(248,7%,66%)", + "line-dasharray": [1, 2], + "line-width": ["interpolate", ["linear"], ["zoom"], 3, 1, 5, 1.2, 12, 3] + } + }, + { + "id": "road_oneway", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "transportation", + "minzoom": 15, + "filter": [ + "all", + ["==", ["get", "oneway"], 1], + [ + "match", + ["get", "class"], + [ + "minor", + "motorway", + "primary", + "secondary", + "service", + "tertiary", + "trunk" + ], + true, + false + ] + ], + "layout": { + "icon-image": "oneway", + "icon-padding": 2, + "icon-rotate": 90, + "icon-rotation-alignment": "map", + "icon-size": ["interpolate", ["linear"], ["zoom"], 15, 0.5, 19, 1], + "symbol-placement": "line", + "symbol-spacing": 75 + }, + "paint": {"icon-opacity": 0.5} + }, + { + "id": "road_oneway_opposite", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "transportation", + "minzoom": 15, + "filter": [ + "all", + ["==", ["get", "oneway"], -1], + [ + "match", + ["get", "class"], + [ + "minor", + "motorway", + "primary", + "secondary", + "service", + "tertiary", + "trunk" + ], + true, + false + ] + ], + "layout": { + "icon-image": "oneway", + "icon-padding": 2, + "icon-rotate": -90, + "icon-rotation-alignment": "map", + "icon-size": ["interpolate", ["linear"], ["zoom"], 15, 0.5, 19, 1], + "symbol-placement": "line", + "symbol-spacing": 75 + }, + "paint": {"icon-opacity": 0.5} + }, + { + "id": "waterway_line_label", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "waterway", + "minzoom": 10, + "filter": [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + "layout": { + "symbol-placement": "line", + "symbol-spacing": 350, + "text-field": [ + "case", + ["has", "name:nonlatin"], + ["concat", ["get", "name:latin"], " ", ["get", "name:nonlatin"]], + ["coalesce", ["get", "name_en"], ["get", "name"]] + ], + "text-font": ["Noto Sans Italic"], + "text-letter-spacing": 0.2, + "text-max-width": 5, + "text-size": 14 + }, + "paint": { + "text-color": "#74aee9", + "text-halo-color": "rgba(255,255,255,0.7)", + "text-halo-width": 1.5 + } + }, + { + "id": "water_name_point_label", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "water_name", + "filter": [ + "match", + ["geometry-type"], + ["MultiPoint", "Point"], + true, + false + ], + "layout": { + "text-field": [ + "case", + ["has", "name:nonlatin"], + ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]], + ["coalesce", ["get", "name_en"], ["get", "name"]] + ], + "text-font": ["Noto Sans Italic"], + "text-letter-spacing": 0.2, + "text-max-width": 5, + "text-size": ["interpolate", ["linear"], ["zoom"], 0, 10, 8, 14] + }, + "paint": { + "text-color": "#495e91", + "text-halo-color": "rgba(255,255,255,0.7)", + "text-halo-width": 1.5 + } + }, + { + "id": "water_name_line_label", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "water_name", + "filter": [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + "layout": { + "symbol-placement": "line", + "symbol-spacing": 350, + "text-field": [ + "case", + ["has", "name:nonlatin"], + ["concat", ["get", "name:latin"], " ", ["get", "name:nonlatin"]], + ["coalesce", ["get", "name_en"], ["get", "name"]] + ], + "text-font": ["Noto Sans Italic"], + "text-letter-spacing": 0.2, + "text-max-width": 5, + "text-size": 14 + }, + "paint": { + "text-color": "#495e91", + "text-halo-color": "rgba(255,255,255,0.7)", + "text-halo-width": 1.5 + } + }, + { + "id": "poi_hos", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "poi", + "minzoom": 15, + "filter": [ + "all", + ["==", "$type", "Point"], + ["has", "name"], + ["==", "class", "railway"], + ["==", "subclass", "station"] + ], + "layout": { + "icon-image": [ + "match", + ["get", "subclass"], + ["florist", "furniture"], + ["get", "subclass"], + ["get", "class"] + ], + "text-anchor": "top", + "text-field": [ + "case", + ["has", "name:nonlatin"], + ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]], + ["coalesce", ["get", "name_en"], ["get", "name"]] + ], + "text-font": ["Noto Sans Italic"], + "text-max-width": 9, + "text-offset": [0, 0.6], + "text-size": 12, + "visibility": "none" + }, + "paint": { + "text-color": "#666", + "text-halo-blur": 0.5, + "text-halo-color": "#ffffff", + "text-halo-width": 1 + } + }, + { + "id": "poi_r1", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "poi", + "minzoom": 15, + "filter": [ + "all", + ["match", ["geometry-type"], ["MultiPoint", "Point"], true, false], + [">=", ["get", "rank"], 1], + ["<=", ["get", "rank"], 6], + ["!=", ["get", "subclass"], "bus_stop"], + ["!=", ["get", "subclass"], "tram_stop"] + ], + "layout": { + "icon-image": [ + "match", + ["get", "subclass"], + ["florist", "furniture"], + ["get", "subclass"], + ["get", "class"] + ], + "text-anchor": "top", + "text-field": [ + "case", + ["has", "name:nonlatin"], + ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]], + ["coalesce", ["get", "name_en"], ["get", "name"]] + ], + "text-font": ["Noto Sans Italic"], + "text-max-width": 9, + "text-offset": [0, 0.6], + "text-size": 12, + "visibility": "visible" + }, + "paint": { + "text-color": "#666", + "text-halo-blur": 0.5, + "text-halo-color": "#ffffff", + "text-halo-width": 1 + } + }, + { + "id": "highway-name-path", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "transportation_name", + "minzoom": 15.5, + "filter": ["==", ["get", "class"], "path"], + "layout": { + "symbol-placement": "line", + "text-field": [ + "case", + ["has", "name:nonlatin"], + ["concat", ["get", "name:latin"], " ", ["get", "name:nonlatin"]], + ["coalesce", ["get", "name_en"], ["get", "name"]] + ], + "text-font": ["Noto Sans Regular"], + "text-rotation-alignment": "map", + "text-size": ["interpolate", ["linear"], ["zoom"], 13, 12, 14, 13] + }, + "paint": { + "text-color": "hsl(30,23%,62%)", + "text-halo-color": "#f8f4f0", + "text-halo-width": 0.5 + } + }, + { + "id": "highway-name-minor", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "transportation_name", + "minzoom": 15, + "filter": [ + "all", + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["match", ["get", "class"], ["minor", "service", "track"], true, false] + ], + "layout": { + "symbol-placement": "line", + "text-field": [ + "case", + ["has", "name:nonlatin"], + ["concat", ["get", "name:latin"], " ", ["get", "name:nonlatin"]], + ["coalesce", ["get", "name_en"], ["get", "name"]] + ], + "text-font": ["Noto Sans Regular"], + "text-rotation-alignment": "map", + "text-size": ["interpolate", ["linear"], ["zoom"], 13, 12, 14, 13] + }, + "paint": { + "text-color": "#666", + "text-halo-blur": 0.5, + "text-halo-width": 1 + } + }, + { + "id": "highway-name-major", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "transportation_name", + "minzoom": 12.2, + "filter": [ + "match", + ["get", "class"], + ["primary", "secondary", "tertiary", "trunk"], + true, + false + ], + "layout": { + "symbol-placement": "line", + "text-field": [ + "case", + ["has", "name:nonlatin"], + ["concat", ["get", "name:latin"], " ", ["get", "name:nonlatin"]], + ["coalesce", ["get", "name_en"], ["get", "name"]] + ], + "text-font": ["Noto Sans Regular"], + "text-rotation-alignment": "map", + "text-size": ["interpolate", ["linear"], ["zoom"], 13, 12, 14, 13] + }, + "paint": { + "text-color": "#666", + "text-halo-blur": 0.5, + "text-halo-width": 1 + } + }, + { + "id": "highway-shield-non-us", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "transportation_name", + "minzoom": 8, + "filter": [ + "all", + ["<=", ["get", "ref_length"], 6], + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + [ + "match", + ["get", "network"], + ["us-highway", "us-interstate", "us-state"], + false, + true + ] + ], + "layout": { + "icon-image": ["concat", "road_", ["get", "ref_length"]], + "icon-rotation-alignment": "viewport", + "icon-size": 1, + "symbol-placement": ["step", ["zoom"], "point", 11, "line"], + "symbol-spacing": 200, + "text-field": ["to-string", ["get", "ref"]], + "text-font": ["Noto Sans Regular"], + "text-rotation-alignment": "viewport", + "text-size": 10 + } + }, + { + "id": "highway-shield-us-interstate", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "transportation_name", + "minzoom": 7, + "filter": [ + "all", + ["<=", ["get", "ref_length"], 6], + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["match", ["get", "network"], ["us-interstate"], true, false] + ], + "layout": { + "icon-image": [ + "concat", + ["get", "network"], + "_", + ["get", "ref_length"] + ], + "icon-rotation-alignment": "viewport", + "icon-size": 1, + "symbol-placement": ["step", ["zoom"], "point", 7, "line", 8, "line"], + "symbol-spacing": 200, + "text-field": ["to-string", ["get", "ref"]], + "text-font": ["Noto Sans Regular"], + "text-rotation-alignment": "viewport", + "text-size": 10 + } + }, + { + "id": "road_shield_us", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "transportation_name", + "minzoom": 9, + "filter": [ + "all", + ["<=", ["get", "ref_length"], 6], + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["match", ["get", "network"], ["us-highway", "us-state"], true, false] + ], + "layout": { + "icon-image": [ + "concat", + ["get", "network"], + "_", + ["get", "ref_length"] + ], + "icon-rotation-alignment": "viewport", + "icon-size": 1, + "symbol-placement": ["step", ["zoom"], "point", 11, "line"], + "symbol-spacing": 200, + "text-field": ["to-string", ["get", "ref"]], + "text-font": ["Noto Sans Regular"], + "text-rotation-alignment": "viewport", + "text-size": 10 + } + }, + { + "id": "airport", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "aerodrome_label", + "minzoom": 10, + "filter": ["all", ["has", "iata"]], + "layout": { + "icon-image": "airport_11", + "icon-size": 1, + "text-anchor": "top", + "text-field": [ + "case", + ["has", "name:nonlatin"], + ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]], + ["coalesce", ["get", "name_en"], ["get", "name"]] + ], + "text-font": ["Noto Sans Regular"], + "text-max-width": 9, + "text-offset": [0, 0.6], + "text-optional": true, + "text-padding": 2, + "text-size": 12 + }, + "paint": { + "text-color": "#666", + "text-halo-blur": 0.5, + "text-halo-color": "#ffffff", + "text-halo-width": 1 + } + }, + { + "id": "label_other", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "place", + "minzoom": 8, + "filter": [ + "match", + ["get", "class"], + ["city", "continent", "country", "state", "town", "village"], + false, + true + ], + "layout": { + "text-field": [ + "case", + ["has", "name:nonlatin"], + ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]], + ["coalesce", ["get", "name_en"], ["get", "name"]] + ], + "text-font": ["Noto Sans Italic"], + "text-letter-spacing": 0.1, + "text-max-width": 9, + "text-size": ["interpolate", ["linear"], ["zoom"], 8, 9, 12, 10], + "text-transform": "uppercase" + }, + "paint": { + "text-color": "#333", + "text-halo-blur": 1, + "text-halo-color": "#fff", + "text-halo-width": 1 + } + }, + { + "id": "label_village", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "place", + "minzoom": 9, + "filter": ["==", ["get", "class"], "village"], + "layout": { + "icon-allow-overlap": true, + "icon-image": ["step", ["zoom"], "circle_11_black", 10, ""], + "icon-optional": false, + "icon-size": 0.2, + "text-anchor": "bottom", + "text-field": [ + "case", + ["has", "name:nonlatin"], + ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]], + ["coalesce", ["get", "name_en"], ["get", "name"]] + ], + "text-font": ["Noto Sans Regular"], + "text-max-width": 8, + "text-size": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 7, + 10, + 11, + 12 + ] + }, + "paint": { + "text-color": "#000", + "text-halo-blur": 1, + "text-halo-color": "#fff", + "text-halo-width": 1 + } + }, + { + "id": "label_town", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "place", + "minzoom": 6, + "filter": ["==", ["get", "class"], "town"], + "layout": { + "icon-allow-overlap": true, + "icon-image": ["step", ["zoom"], "circle_11_black", 10, ""], + "icon-optional": false, + "icon-size": 0.2, + "text-anchor": "bottom", + "text-field": [ + "case", + ["has", "name:nonlatin"], + ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]], + ["coalesce", ["get", "name_en"], ["get", "name"]] + ], + "text-font": ["Noto Sans Regular"], + "text-max-width": 8, + "text-size": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 7, + 12, + 11, + 14 + ] + }, + "paint": { + "text-color": "#000", + "text-halo-blur": 1, + "text-halo-color": "#fff", + "text-halo-width": 1 + } + }, + { + "id": "label_state", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "place", + "minzoom": 5, + "maxzoom": 8, + "filter": ["==", ["get", "class"], "state"], + "layout": { + "text-field": [ + "case", + ["has", "name:nonlatin"], + ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]], + ["coalesce", ["get", "name_en"], ["get", "name"]] + ], + "text-font": ["Noto Sans Italic"], + "text-letter-spacing": 0.2, + "text-max-width": 9, + "text-size": ["interpolate", ["linear"], ["zoom"], 5, 10, 8, 14], + "text-transform": "uppercase" + }, + "paint": { + "text-color": "#333", + "text-halo-blur": 1, + "text-halo-color": "#fff", + "text-halo-width": 1 + } + }, + { + "id": "label_city", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "place", + "minzoom": 3, + "filter": [ + "all", + ["==", ["get", "class"], "city"], + ["!=", ["get", "capital"], 2] + ], + "layout": { + "icon-allow-overlap": true, + "icon-image": ["step", ["zoom"], "circle_11_black", 9, ""], + "icon-optional": false, + "icon-size": 0.4, + "text-anchor": "bottom", + "text-field": [ + "case", + ["has", "name:nonlatin"], + ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]], + ["coalesce", ["get", "name_en"], ["get", "name"]] + ], + "text-font": ["Noto Sans Regular"], + "text-max-width": 8, + "text-offset": [0, -0.1], + "text-size": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 4, + 11, + 7, + 13, + 11, + 18 + ] + }, + "paint": { + "text-color": "#000", + "text-halo-blur": 1, + "text-halo-color": "#fff", + "text-halo-width": 1 + } + }, + { + "id": "label_city_capital", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "place", + "minzoom": 3, + "filter": [ + "all", + ["==", ["get", "class"], "city"], + ["==", ["get", "capital"], 2] + ], + "layout": { + "icon-allow-overlap": true, + "icon-image": ["step", ["zoom"], "circle_11_black", 9, ""], + "icon-optional": false, + "icon-size": 0.5, + "text-anchor": "bottom", + "text-field": [ + "case", + ["has", "name:nonlatin"], + ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]], + ["coalesce", ["get", "name_en"], ["get", "name"]] + ], + "text-font": ["Noto Sans Bold"], + "text-max-width": 8, + "text-offset": [0, -0.2], + "text-size": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 4, + 12, + 7, + 14, + 11, + 20 + ] + }, + "paint": { + "text-color": "#000", + "text-halo-blur": 1, + "text-halo-color": "#fff", + "text-halo-width": 1 + } + }, + { + "id": "label_country_3", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "place", + "minzoom": 2, + "maxzoom": 9, + "filter": [ + "all", + ["==", ["get", "class"], "country"], + [">=", ["get", "rank"], 3] + ], + "layout": { + "text-field": [ + "case", + ["has", "name:nonlatin"], + ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]], + ["coalesce", ["get", "name_en"], ["get", "name"]] + ], + "text-font": ["Noto Sans Bold"], + "text-max-width": 6.25, + "text-size": ["interpolate", ["linear"], ["zoom"], 3, 9, 7, 17] + }, + "paint": { + "text-color": "#000", + "text-halo-blur": 1, + "text-halo-color": "#fff", + "text-halo-width": 1 + } + }, + { + "id": "label_country_2", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "place", + "maxzoom": 9, + "filter": [ + "all", + ["==", ["get", "class"], "country"], + ["==", ["get", "rank"], 2] + ], + "layout": { + "text-field": [ + "case", + ["has", "name:nonlatin"], + ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]], + ["coalesce", ["get", "name_en"], ["get", "name"]] + ], + "text-font": ["Noto Sans Bold"], + "text-max-width": 6.25, + "text-size": ["interpolate", ["linear"], ["zoom"], 2, 9, 5, 17] + }, + "paint": { + "text-color": "#000", + "text-halo-blur": 1, + "text-halo-color": "#fff", + "text-halo-width": 1 + } + }, + { + "id": "label_country_1", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "place", + "maxzoom": 9, + "filter": [ + "all", + ["==", ["get", "class"], "country"], + ["==", ["get", "rank"], 1] + ], + "layout": { + "text-field": [ + "case", + ["has", "name:nonlatin"], + ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]], + ["coalesce", ["get", "name_en"], ["get", "name"]] + ], + "text-font": ["Noto Sans Bold"], + "text-max-width": 6.25, + "text-size": ["interpolate", ["linear"], ["zoom"], 1, 9, 4, 17] + }, + "paint": { + "text-color": "#000", + "text-halo-blur": 1, + "text-halo-color": "#fff", + "text-halo-width": 1 + } + } + ], + "id": "94huyrm" +} diff --git a/app/src/main/assets/map_style_good_noshops.json b/app/src/main/assets/map_style_good_noshops.json new file mode 100644 --- /dev/null +++ b/app/src/main/assets/map_style_good_noshops.json @@ -0,0 +1,3107 @@ +{ + "version": 8, + "metadata": {"maputnik:renderer": "mlgljs"}, + "sources": { + "ne2_shaded": { + "maxzoom": 6, + "tileSize": 256, + "tiles": [ + "https://tiles.openfreemap.org/natural_earth/ne2sr/{z}/{x}/{y}.png" + ], + "type": "raster" + }, + "openmaptiles": { + "type": "vector", + "url": "https://tiles.openfreemap.org/planet" + } + }, + "sprite": "https://tiles.openfreemap.org/sprites/ofm_f384/ofm", + "glyphs": "https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf", + "layers": [ + { + "id": "background", + "type": "background", + "paint": {"background-color": "#f8f4f0"} + }, + { + "id": "landcover-glacier", + "type": "fill", + "source": "openmaptiles", + "source-layer": "landcover", + "filter": ["==", ["get", "subclass"], "glacier"], + "paint": { + "fill-color": "#fff", + "fill-opacity": ["interpolate", ["linear"], ["zoom"], 0, 0.9, 10, 0.3] + } + }, + { + "id": "landuse-residential", + "type": "fill", + "source": "openmaptiles", + "source-layer": "landuse", + "filter": [ + "match", + ["get", "class"], + ["neighbourhood", "residential"], + true, + false + ], + "paint": { + "fill-color": [ + "interpolate", + ["linear"], + ["zoom"], + 12, + "hsla(30,19%,90%,0.4)", + 16, + "hsla(30,19%,90%,0.2)" + ] + } + }, + { + "id": "landuse-suburb", + "type": "fill", + "source": "openmaptiles", + "source-layer": "landuse", + "maxzoom": 10, + "filter": ["==", ["get", "class"], "suburb"], + "paint": { + "fill-color": [ + "interpolate", + ["linear"], + ["zoom"], + 8, + "hsla(30,19%,90%,0.4)", + 10, + "hsla(30,19%,90%,0.0)" + ] + } + }, + { + "id": "landuse-commercial", + "type": "fill", + "source": "openmaptiles", + "source-layer": "landuse", + "filter": [ + "all", + ["match", ["geometry-type"], ["MultiPolygon", "Polygon"], true, false], + ["==", ["get", "class"], "commercial"] + ], + "paint": {"fill-color": "hsla(0,60%,87%,0.23)"} + }, + { + "id": "landuse-industrial", + "type": "fill", + "source": "openmaptiles", + "source-layer": "landuse", + "filter": [ + "all", + ["match", ["geometry-type"], ["MultiPolygon", "Polygon"], true, false], + [ + "match", + ["get", "class"], + ["dam", "garages", "industrial"], + true, + false + ] + ], + "paint": {"fill-color": "hsla(49,100%,88%,0.34)"} + }, + { + "id": "landuse-cemetery", + "type": "fill", + "source": "openmaptiles", + "source-layer": "landuse", + "filter": ["==", ["get", "class"], "cemetery"], + "paint": {"fill-color": "#e0e4dd"} + }, + { + "id": "landuse-hospital", + "type": "fill", + "source": "openmaptiles", + "source-layer": "landuse", + "filter": ["==", ["get", "class"], "hospital"], + "paint": {"fill-color": "#fde"} + }, + { + "id": "landuse-school", + "type": "fill", + "source": "openmaptiles", + "source-layer": "landuse", + "filter": ["==", ["get", "class"], "school"], + "paint": {"fill-color": "#f0e8f8"} + }, + { + "id": "landuse-railway", + "type": "fill", + "source": "openmaptiles", + "source-layer": "landuse", + "filter": ["==", ["get", "class"], "railway"], + "paint": {"fill-color": "hsla(30,19%,90%,0.4)"} + }, + { + "id": "park", + "type": "fill", + "source": "openmaptiles", + "source-layer": "park", + "filter": [ + "match", + ["geometry-type"], + ["MultiPolygon", "Polygon"], + true, + false + ], + "paint": { + "fill-color": "#d8e8c8", + "fill-opacity": [ + "interpolate", + ["exponential", 1.8], + ["zoom"], + 9, + 0.5, + 12, + 0.2 + ] + } + }, + { + "id": "landcover-wood", + "type": "fill", + "source": "openmaptiles", + "source-layer": "landcover", + "filter": ["==", ["get", "class"], "wood"], + "paint": { + "fill-antialias": ["step", ["zoom"], false, 9, true], + "fill-color": "#6a4", + "fill-opacity": 0.1, + "fill-outline-color": "hsla(0,0%,0%,0.03)" + } + }, + { + "id": "landcover-grass", + "type": "fill", + "source": "openmaptiles", + "source-layer": "landcover", + "filter": ["==", ["get", "class"], "grass"], + "paint": {"fill-color": "#d8e8c8", "fill-opacity": 1} + }, + { + "id": "landcover-grass-park", + "type": "fill", + "source": "openmaptiles", + "source-layer": "park", + "filter": ["==", ["get", "class"], "public_park"], + "paint": {"fill-color": "#d8e8c8", "fill-opacity": 0.8} + }, + { + "id": "waterway_tunnel", + "type": "line", + "source": "openmaptiles", + "source-layer": "waterway", + "minzoom": 14, + "filter": [ + "all", + ["match", ["get", "class"], ["canal", "river", "stream"], true, false], + ["==", ["get", "brunnel"], "tunnel"] + ], + "layout": {"line-cap": "round"}, + "paint": { + "line-color": "#a0c8f0", + "line-dasharray": [2, 4], + "line-width": [ + "interpolate", + ["exponential", 1.3], + ["zoom"], + 13, + 0.5, + 20, + 6 + ] + } + }, + { + "id": "waterway-other", + "type": "line", + "source": "openmaptiles", + "source-layer": "waterway", + "filter": [ + "all", + ["match", ["get", "class"], ["canal", "river", "stream"], false, true], + ["==", ["get", "intermittent"], 0] + ], + "layout": {"line-cap": "round"}, + "paint": { + "line-color": "#a0c8f0", + "line-width": [ + "interpolate", + ["exponential", 1.3], + ["zoom"], + 13, + 0.5, + 20, + 2 + ] + } + }, + { + "id": "waterway-other-intermittent", + "type": "line", + "source": "openmaptiles", + "source-layer": "waterway", + "filter": [ + "all", + ["match", ["get", "class"], ["canal", "river", "stream"], false, true], + ["==", ["get", "intermittent"], 1] + ], + "layout": {"line-cap": "round"}, + "paint": { + "line-color": "#a0c8f0", + "line-dasharray": [4, 3], + "line-width": [ + "interpolate", + ["exponential", 1.3], + ["zoom"], + 13, + 0.5, + 20, + 2 + ] + } + }, + { + "id": "waterway-stream-canal", + "type": "line", + "source": "openmaptiles", + "source-layer": "waterway", + "filter": [ + "all", + ["match", ["get", "class"], ["canal", "stream"], true, false], + ["!=", ["get", "brunnel"], "tunnel"], + ["==", ["get", "intermittent"], 0] + ], + "layout": {"line-cap": "round"}, + "paint": { + "line-color": "#a0c8f0", + "line-width": [ + "interpolate", + ["exponential", 1.3], + ["zoom"], + 13, + 0.5, + 20, + 6 + ] + } + }, + { + "id": "waterway-stream-canal-intermittent", + "type": "line", + "source": "openmaptiles", + "source-layer": "waterway", + "filter": [ + "all", + ["match", ["get", "class"], ["canal", "stream"], true, false], + ["!=", ["get", "brunnel"], "tunnel"], + ["==", ["get", "intermittent"], 1] + ], + "layout": {"line-cap": "round"}, + "paint": { + "line-color": "#a0c8f0", + "line-dasharray": [4, 3], + "line-width": [ + "interpolate", + ["exponential", 1.3], + ["zoom"], + 13, + 0.5, + 20, + 6 + ] + } + }, + { + "id": "waterway-river", + "type": "line", + "source": "openmaptiles", + "source-layer": "waterway", + "filter": [ + "all", + ["==", ["get", "class"], "river"], + ["!=", ["get", "brunnel"], "tunnel"], + ["!=", ["get", "intermittent"], 1] + ], + "layout": {"line-cap": "round"}, + "paint": { + "line-color": "#a0c8f0", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 10, + 0.8, + 20, + 6 + ] + } + }, + { + "id": "waterway-river-intermittent", + "type": "line", + "source": "openmaptiles", + "source-layer": "waterway", + "filter": [ + "all", + ["==", ["get", "class"], "river"], + ["!=", ["get", "brunnel"], "tunnel"], + ["==", ["get", "intermittent"], 1] + ], + "layout": {"line-cap": "round"}, + "paint": { + "line-color": "#a0c8f0", + "line-dasharray": [3, 2.5], + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 10, + 0.8, + 20, + 6 + ] + } + }, + { + "id": "water", + "type": "fill", + "source": "openmaptiles", + "source-layer": "water", + "filter": [ + "all", + ["!=", ["get", "intermittent"], 1], + ["!=", ["get", "brunnel"], "tunnel"] + ], + "paint": {"fill-color": "#AECFE2"} + }, + { + "id": "water-intermittent", + "type": "fill", + "source": "openmaptiles", + "source-layer": "water", + "filter": ["==", ["get", "intermittent"], 1], + "paint": {"fill-color": "hsl(210,67%,85%)", "fill-opacity": 0.7} + }, + { + "id": "landcover-ice-shelf", + "type": "fill", + "source": "openmaptiles", + "source-layer": "landcover", + "filter": ["==", ["get", "subclass"], "ice_shelf"], + "paint": { + "fill-color": "#fff", + "fill-opacity": ["interpolate", ["linear"], ["zoom"], 0, 0.9, 10, 0.3] + } + }, + { + "id": "landcover-sand", + "type": "fill", + "source": "openmaptiles", + "source-layer": "landcover", + "filter": ["==", ["get", "class"], "sand"], + "paint": {"fill-color": "rgba(245, 238, 188, 1)", "fill-opacity": 1} + }, + { + "id": "building", + "type": "fill", + "source": "openmaptiles", + "source-layer": "building", + "paint": { + "fill-antialias": true, + "fill-color": [ + "interpolate", + ["linear"], + ["zoom"], + 15.5, + "#f2eae2", + 16, + "#dfdbd7" + ] + } + }, + { + "id": "building-top", + "type": "fill", + "source": "openmaptiles", + "source-layer": "building", + "paint": { + "fill-color": "#f2eae2", + "fill-opacity": ["interpolate", ["linear"], ["zoom"], 13, 0, 16, 1], + "fill-outline-color": "#dfdbd7", + "fill-translate": [ + "interpolate", + ["linear"], + ["zoom"], + 14, + ["literal", [0, 0]], + 16, + ["literal", [-2, -2]] + ] + } + }, + { + "id": "tunnel-service-track-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "tunnel"], + ["match", ["get", "class"], ["service", "track"], true, false] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#cfcdca", + "line-dasharray": [0.5, 0.25], + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 15, + 1, + 16, + 4, + 20, + 11 + ] + } + }, + { + "id": "tunnel-motorway-link-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "tunnel"], + ["==", ["get", "class"], "motorway"], + ["==", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "rgba(200, 147, 102, 1)", + "line-dasharray": [0.5, 0.25], + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 12, + 1, + 13, + 3, + 14, + 4, + 20, + 15 + ] + } + }, + { + "id": "tunnel-minor-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "tunnel"], + ["==", ["get", "class"], "minor"] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#cfcdca", + "line-dasharray": [0.5, 0.25], + "line-opacity": ["interpolate", ["linear"], ["zoom"], 12, 0, 12.5, 1], + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 12, + 0.5, + 13, + 1, + 14, + 4, + 20, + 15 + ] + } + }, + { + "id": "tunnel-link-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "tunnel"], + [ + "match", + ["get", "class"], + ["primary", "secondary", "tertiary", "trunk"], + true, + false + ], + ["==", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#e9ac77", + "line-dasharray": [0.5, 0.25], + "line-opacity": 1, + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 12, + 1, + 13, + 3, + 14, + 4, + 20, + 15 + ] + } + }, + { + "id": "tunnel-secondary-tertiary-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "tunnel"], + ["match", ["get", "class"], ["secondary", "tertiary"], true, false], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#e9ac77", + "line-dasharray": [0.5, 0.25], + "line-opacity": 1, + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 8, + 1.5, + 20, + 17 + ] + } + }, + { + "id": "tunnel-trunk-primary-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "tunnel"], + ["match", ["get", "class"], ["primary", "trunk"], true, false], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#e9ac77", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 5, + 0.4, + 6, + 0.6, + 7, + 1.5, + 20, + 22 + ] + } + }, + { + "id": "tunnel-motorway-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "tunnel"], + ["==", ["get", "class"], "motorway"], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#e9ac77", + "line-dasharray": [0.5, 0.25], + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 5, + 0.4, + 6, + 0.6, + 7, + 1.5, + 20, + 22 + ] + } + }, + { + "id": "tunnel-path", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["==", ["get", "brunnel"], "tunnel"], + ["==", ["get", "class"], "path"] + ], + "paint": { + "line-color": "#cba", + "line-dasharray": [1.5, 0.75], + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 15, + 1.2, + 20, + 4 + ] + } + }, + { + "id": "tunnel-motorway-link", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "tunnel"], + ["==", ["get", "class"], "motorway"], + ["==", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "rgba(244, 209, 158, 1)", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 12.5, + 0, + 13, + 1.5, + 14, + 2.5, + 20, + 11.5 + ] + } + }, + { + "id": "tunnel-service-track", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "tunnel"], + ["match", ["get", "class"], ["service", "track"], true, false] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#fff", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 15.5, + 0, + 16, + 2, + 20, + 7.5 + ] + } + }, + { + "id": "tunnel-link", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "tunnel"], + [ + "match", + ["get", "class"], + ["primary", "secondary", "tertiary", "trunk"], + true, + false + ], + ["==", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#fff4c6", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 12.5, + 0, + 13, + 1.5, + 14, + 2.5, + 20, + 11.5 + ] + } + }, + { + "id": "tunnel-minor", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "tunnel"], + ["==", ["get", "class"], "minor"] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#fff", + "line-opacity": 1, + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 13.5, + 0, + 14, + 2.5, + 20, + 11.5 + ] + } + }, + { + "id": "tunnel-secondary-tertiary", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "tunnel"], + ["match", ["get", "class"], ["secondary", "tertiary"], true, false], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#fff4c6", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 6.5, + 0, + 7, + 0.5, + 20, + 10 + ] + } + }, + { + "id": "tunnel-trunk-primary", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "tunnel"], + ["match", ["get", "class"], ["primary", "trunk"], true, false], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#fff4c6", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 6.5, + 0, + 7, + 0.5, + 20, + 18 + ] + } + }, + { + "id": "tunnel-motorway", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "tunnel"], + ["==", ["get", "class"], "motorway"], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#ffdaa6", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 6.5, + 0, + 7, + 0.5, + 20, + 18 + ] + } + }, + { + "id": "tunnel-railway", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "tunnel"], + ["==", ["get", "class"], "rail"] + ], + "paint": { + "line-color": "#bbb", + "line-dasharray": [2, 2], + "line-width": [ + "interpolate", + ["exponential", 1.4], + ["zoom"], + 14, + 0.4, + 15, + 0.75, + 20, + 2 + ] + } + }, + { + "id": "ferry", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": ["match", ["get", "class"], ["ferry"], true, false], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "rgba(108, 159, 182, 1)", + "line-dasharray": [2, 2], + "line-width": 1.1 + } + }, + { + "id": "aeroway-taxiway-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "aeroway", + "minzoom": 12, + "filter": ["match", ["get", "class"], ["taxiway"], true, false], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "rgba(153, 153, 153, 1)", + "line-opacity": 1, + "line-width": [ + "interpolate", + ["exponential", 1.5], + ["zoom"], + 11, + 2, + 17, + 12 + ] + } + }, + { + "id": "aeroway-runway-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "aeroway", + "minzoom": 12, + "filter": ["match", ["get", "class"], ["runway"], true, false], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "rgba(153, 153, 153, 1)", + "line-opacity": 1, + "line-width": [ + "interpolate", + ["exponential", 1.5], + ["zoom"], + 11, + 5, + 17, + 55 + ] + } + }, + { + "id": "aeroway-area", + "type": "fill", + "source": "openmaptiles", + "source-layer": "aeroway", + "minzoom": 4, + "filter": [ + "all", + ["match", ["geometry-type"], ["MultiPolygon", "Polygon"], true, false], + ["match", ["get", "class"], ["runway", "taxiway"], true, false] + ], + "paint": { + "fill-color": "rgba(255, 255, 255, 1)", + "fill-opacity": ["interpolate", ["linear"], ["zoom"], 13, 0, 14, 1] + } + }, + { + "id": "aeroway-taxiway", + "type": "line", + "source": "openmaptiles", + "source-layer": "aeroway", + "minzoom": 4, + "filter": [ + "all", + ["match", ["get", "class"], ["taxiway"], true, false], + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ] + ], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "rgba(255, 255, 255, 1)", + "line-opacity": ["interpolate", ["linear"], ["zoom"], 11, 0, 12, 1], + "line-width": [ + "interpolate", + ["exponential", 1.5], + ["zoom"], + 11, + 1, + 17, + 10 + ] + } + }, + { + "id": "aeroway-runway", + "type": "line", + "source": "openmaptiles", + "source-layer": "aeroway", + "minzoom": 4, + "filter": [ + "all", + ["match", ["get", "class"], ["runway"], true, false], + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ] + ], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "rgba(255, 255, 255, 1)", + "line-opacity": ["interpolate", ["linear"], ["zoom"], 11, 0, 12, 1], + "line-width": [ + "interpolate", + ["exponential", 1.5], + ["zoom"], + 11, + 4, + 17, + 50 + ] + } + }, + { + "id": "road_area_pier", + "type": "fill", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["match", ["geometry-type"], ["MultiPolygon", "Polygon"], true, false], + ["==", ["get", "class"], "pier"] + ], + "paint": {"fill-antialias": true, "fill-color": "#f8f4f0"} + }, + { + "id": "road_pier", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["match", ["get", "class"], ["pier"], true, false] + ], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "#f8f4f0", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 15, + 1, + 17, + 4 + ] + } + }, + { + "id": "highway-area", + "type": "fill", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["match", ["geometry-type"], ["MultiPolygon", "Polygon"], true, false], + ["match", ["get", "class"], ["pier"], false, true] + ], + "paint": { + "fill-antialias": false, + "fill-color": "hsla(0,0%,89%,0.56)", + "fill-opacity": 0.9, + "fill-outline-color": "#cfcdca" + } + }, + { + "id": "highway-motorway-link-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true], + ["==", ["get", "class"], "motorway"], + ["==", ["get", "ramp"], 1] + ], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "#e9ac77", + "line-opacity": 1, + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 12, + 1, + 13, + 3, + 14, + 4, + 20, + 15 + ] + } + }, + { + "id": "highway-link-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "minzoom": 13, + "filter": [ + "all", + ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true], + [ + "match", + ["get", "class"], + ["primary", "secondary", "tertiary", "trunk"], + true, + false + ], + ["==", ["get", "ramp"], 1] + ], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "#e9ac77", + "line-opacity": 1, + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 12, + 1, + 13, + 3, + 14, + 4, + 20, + 15 + ] + } + }, + { + "id": "highway-minor-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["!=", ["get", "brunnel"], "tunnel"], + ["match", ["get", "class"], ["minor", "service", "track"], true, false] + ], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "#cfcdca", + "line-opacity": ["interpolate", ["linear"], ["zoom"], 12, 0, 12.5, 1], + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 12, + 0.5, + 13, + 1, + 14, + 4, + 20, + 15 + ] + } + }, + { + "id": "highway-secondary-tertiary-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true], + ["match", ["get", "class"], ["secondary", "tertiary"], true, false], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-cap": "butt", "line-join": "round"}, + "paint": { + "line-color": "#e9ac77", + "line-opacity": 1, + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 8, + 1.5, + 20, + 17 + ] + } + }, + { + "id": "highway-primary-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "minzoom": 5, + "filter": [ + "all", + ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true], + ["match", ["get", "class"], ["primary"], true, false], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-cap": "butt", "line-join": "round"}, + "paint": { + "line-color": "#e9ac77", + "line-opacity": ["interpolate", ["linear"], ["zoom"], 7, 0, 8, 1], + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 7, + 0, + 8, + 0.6, + 9, + 1.5, + 20, + 22 + ] + } + }, + { + "id": "highway-trunk-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "minzoom": 5, + "filter": [ + "all", + ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true], + ["match", ["get", "class"], ["trunk"], true, false], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-cap": "butt", "line-join": "round"}, + "paint": { + "line-color": "#e9ac77", + "line-opacity": ["interpolate", ["linear"], ["zoom"], 5, 0, 6, 1], + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 5, + 0, + 6, + 0.6, + 7, + 1.5, + 20, + 22 + ] + } + }, + { + "id": "highway-motorway-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "minzoom": 4, + "filter": [ + "all", + ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true], + ["==", ["get", "class"], "motorway"], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-cap": "butt", "line-join": "round"}, + "paint": { + "line-color": "#e9ac77", + "line-opacity": ["interpolate", ["linear"], ["zoom"], 4, 0, 5, 1], + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 4, + 0, + 5, + 0.4, + 6, + 0.6, + 7, + 1.5, + 20, + 22 + ] + } + }, + { + "id": "highway-path", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true], + ["==", ["get", "class"], "path"] + ], + "paint": { + "line-color": "#cba", + "line-dasharray": [1.5, 0.75], + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 15, + 1.2, + 20, + 4 + ] + } + }, + { + "id": "highway-motorway-link", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "minzoom": 12, + "filter": [ + "all", + ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true], + ["==", ["get", "class"], "motorway"], + ["==", ["get", "ramp"], 1] + ], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "#fc8", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 12.5, + 0, + 13, + 1.5, + 14, + 2.5, + 20, + 11.5 + ] + } + }, + { + "id": "highway-link", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "minzoom": 13, + "filter": [ + "all", + ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true], + [ + "match", + ["get", "class"], + ["primary", "secondary", "tertiary", "trunk"], + true, + false + ], + ["==", ["get", "ramp"], 1] + ], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "#fea", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 12.5, + 0, + 13, + 1.5, + 14, + 2.5, + 20, + 11.5 + ] + } + }, + { + "id": "highway-minor", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["!=", ["get", "brunnel"], "tunnel"], + ["match", ["get", "class"], ["minor", "service", "track"], true, false] + ], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "#fff", + "line-opacity": 1, + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 13.5, + 0, + 14, + 2.5, + 20, + 11.5 + ] + } + }, + { + "id": "highway-secondary-tertiary", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true], + ["match", ["get", "class"], ["secondary", "tertiary"], true, false], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "#fea", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 6.5, + 0, + 8, + 0.5, + 20, + 13 + ] + } + }, + { + "id": "highway-primary", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true], + ["match", ["get", "class"], ["primary"], true, false], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "#fea", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 8.5, + 0, + 9, + 0.5, + 20, + 18 + ] + } + }, + { + "id": "highway-trunk", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true], + ["match", ["get", "class"], ["trunk"], true, false], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "#fea", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 6.5, + 0, + 7, + 0.5, + 20, + 18 + ] + } + }, + { + "id": "highway-motorway", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "minzoom": 5, + "filter": [ + "all", + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true], + ["==", ["get", "class"], "motorway"], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "#fc8", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 6.5, + 0, + 7, + 0.5, + 20, + 18 + ] + } + }, + { + "id": "railway-transit", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["==", ["get", "class"], "transit"], + ["match", ["get", "brunnel"], ["tunnel"], false, true] + ], + "paint": { + "line-color": "hsla(0,0%,73%,0.77)", + "line-width": [ + "interpolate", + ["exponential", 1.4], + ["zoom"], + 14, + 0.4, + 20, + 1 + ] + } + }, + { + "id": "railway-transit-hatching", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["==", ["get", "class"], "transit"], + ["match", ["get", "brunnel"], ["tunnel"], false, true] + ], + "paint": { + "line-color": "hsla(0,0%,73%,0.68)", + "line-dasharray": [0.2, 8], + "line-width": [ + "interpolate", + ["exponential", 1.4], + ["zoom"], + 14.5, + 0, + 15, + 2, + 20, + 6 + ] + } + }, + { + "id": "railway-service", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["==", ["get", "class"], "rail"], + ["has", "service"] + ], + "paint": { + "line-color": "hsla(0,0%,73%,0.77)", + "line-width": [ + "interpolate", + ["exponential", 1.4], + ["zoom"], + 14, + 0.4, + 20, + 1 + ] + } + }, + { + "id": "railway-service-hatching", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["==", ["get", "class"], "rail"], + ["has", "service"] + ], + "paint": { + "line-color": "hsla(0,0%,73%,0.68)", + "line-dasharray": [0.2, 8], + "line-width": [ + "interpolate", + ["exponential", 1.4], + ["zoom"], + 14.5, + 0, + 15, + 2, + 20, + 6 + ] + } + }, + { + "id": "railway", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["!", ["has", "service"]], + ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true], + ["==", ["get", "class"], "rail"] + ], + "paint": { + "line-color": "#bbb", + "line-width": [ + "interpolate", + ["exponential", 1.4], + ["zoom"], + 14, + 0.4, + 15, + 0.75, + 20, + 2 + ] + } + }, + { + "id": "railway-hatching", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["!", ["has", "service"]], + ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true], + ["==", ["get", "class"], "rail"] + ], + "paint": { + "line-color": "#bbb", + "line-dasharray": [0.2, 8], + "line-width": [ + "interpolate", + ["exponential", 1.4], + ["zoom"], + 14.5, + 0, + 15, + 3, + 20, + 8 + ] + } + }, + { + "id": "bridge-motorway-link-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "bridge"], + ["==", ["get", "class"], "motorway"], + ["==", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#e9ac77", + "line-opacity": 1, + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 12, + 1, + 13, + 3, + 14, + 4, + 20, + 19 + ] + } + }, + { + "id": "bridge-link-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "bridge"], + [ + "match", + ["get", "class"], + ["primary", "secondary", "tertiary", "trunk"], + true, + false + ], + ["==", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#e9ac77", + "line-opacity": 1, + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 12, + 1, + 13, + 3, + 14, + 4, + 20, + 19 + ] + } + }, + { + "id": "bridge-secondary-tertiary-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "bridge"], + ["match", ["get", "class"], ["secondary", "tertiary"], true, false], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#e9ac77", + "line-opacity": 1, + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 5, + 0.4, + 7, + 0.6, + 8, + 1.5, + 20, + 21 + ] + } + }, + { + "id": "bridge-trunk-primary-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "bridge"], + ["match", ["get", "class"], ["primary", "trunk"], true, false], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "hsl(28,76%,67%)", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 5, + 0.4, + 6, + 0.6, + 7, + 1.5, + 20, + 26 + ] + } + }, + { + "id": "bridge-motorway-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "bridge"], + ["==", ["get", "class"], "motorway"], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#e9ac77", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 5, + 0.4, + 6, + 0.6, + 7, + 1.5, + 20, + 26 + ] + } + }, + { + "id": "bridge-minor-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["==", ["get", "brunnel"], "bridge"], + ["match", ["get", "class"], ["minor", "service", "track"], true, false] + ], + "layout": {"line-cap": "butt", "line-join": "round"}, + "paint": { + "line-color": "#cfcdca", + "line-opacity": ["interpolate", ["linear"], ["zoom"], 12, 0, 12.5, 1], + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 12, + 0.5, + 13, + 1, + 14, + 6, + 20, + 24 + ] + } + }, + { + "id": "bridge-path-casing", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["==", ["get", "brunnel"], "bridge"], + ["==", ["get", "class"], "path"] + ], + "paint": { + "line-color": "#f8f4f0", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 15, + 1.2, + 20, + 18 + ] + } + }, + { + "id": "bridge-path", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["==", ["get", "brunnel"], "bridge"], + ["==", ["get", "class"], "path"] + ], + "paint": { + "line-color": "#cba", + "line-dasharray": [1.5, 0.75], + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 15, + 1.2, + 20, + 4 + ] + } + }, + { + "id": "bridge-motorway-link", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "bridge"], + ["==", ["get", "class"], "motorway"], + ["==", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#fc8", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 12.5, + 0, + 13, + 1.5, + 14, + 2.5, + 20, + 11.5 + ] + } + }, + { + "id": "bridge-link", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "bridge"], + [ + "match", + ["get", "class"], + ["primary", "secondary", "tertiary", "trunk"], + true, + false + ], + ["==", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#fea", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 12.5, + 0, + 13, + 1.5, + 14, + 2.5, + 20, + 11.5 + ] + } + }, + { + "id": "bridge-minor", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["==", ["get", "brunnel"], "bridge"], + ["match", ["get", "class"], ["minor", "service", "track"], true, false] + ], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "#fff", + "line-opacity": 1, + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 13.5, + 0, + 14, + 2.5, + 20, + 11.5 + ] + } + }, + { + "id": "bridge-secondary-tertiary", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "bridge"], + ["match", ["get", "class"], ["secondary", "tertiary"], true, false], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#fea", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 6.5, + 0, + 8, + 0.5, + 20, + 13 + ] + } + }, + { + "id": "bridge-trunk-primary", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "bridge"], + ["match", ["get", "class"], ["primary", "trunk"], true, false], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#fea", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 6.5, + 0, + 7, + 0.5, + 20, + 18 + ] + } + }, + { + "id": "bridge-motorway", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "bridge"], + ["==", ["get", "class"], "motorway"], + ["!=", ["get", "ramp"], 1] + ], + "layout": {"line-join": "round"}, + "paint": { + "line-color": "#fc8", + "line-width": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 6.5, + 0, + 7, + 0.5, + 20, + 18 + ] + } + }, + { + "id": "bridge-railway", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "bridge"], + ["==", ["get", "class"], "rail"] + ], + "paint": { + "line-color": "#bbb", + "line-width": [ + "interpolate", + ["exponential", 1.4], + ["zoom"], + 14, + 0.4, + 15, + 0.75, + 20, + 2 + ] + } + }, + { + "id": "bridge-railway-hatching", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", ["get", "brunnel"], "bridge"], + ["==", ["get", "class"], "rail"] + ], + "paint": { + "line-color": "#bbb", + "line-dasharray": [0.2, 8], + "line-width": [ + "interpolate", + ["exponential", 1.4], + ["zoom"], + 14.5, + 0, + 15, + 3, + 20, + 8 + ] + } + }, + { + "id": "cablecar", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "minzoom": 13, + "filter": ["==", ["get", "subclass"], "cable_car"], + "layout": {"line-cap": "round"}, + "paint": { + "line-color": "hsl(0,0%,70%)", + "line-width": ["interpolate", ["linear"], ["zoom"], 11, 1, 19, 2.5] + } + }, + { + "id": "cablecar-dash", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "minzoom": 13, + "filter": ["==", ["get", "subclass"], "cable_car"], + "layout": {"line-cap": "round"}, + "paint": { + "line-color": "hsl(0,0%,70%)", + "line-dasharray": [2, 3], + "line-width": ["interpolate", ["linear"], ["zoom"], 11, 3, 19, 5.5] + } + }, + { + "id": "boundary_3", + "type": "line", + "source": "openmaptiles", + "source-layer": "boundary", + "minzoom": 5, + "filter": [ + "all", + [">=", ["get", "admin_level"], 3], + ["<=", ["get", "admin_level"], 6], + ["!=", ["get", "maritime"], 1], + ["!=", ["get", "disputed"], 1], + ["!", ["has", "claimed_by"]] + ], + "paint": { + "line-color": "hsl(0,0%,70%)", + "line-dasharray": [1, 1], + "line-width": ["interpolate", ["linear", 1], ["zoom"], 7, 1, 11, 2] + } + }, + { + "id": "boundary_2", + "type": "line", + "source": "openmaptiles", + "source-layer": "boundary", + "filter": [ + "all", + ["==", ["get", "admin_level"], 2], + ["!=", ["get", "maritime"], 1], + ["!=", ["get", "disputed"], 1], + ["!", ["has", "claimed_by"]] + ], + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "hsl(248,7%,66%)", + "line-opacity": ["interpolate", ["linear"], ["zoom"], 0, 0.4, 4, 1], + "line-width": ["interpolate", ["linear"], ["zoom"], 3, 1, 5, 1.2, 12, 3] + } + }, + { + "id": "boundary_disputed", + "type": "line", + "source": "openmaptiles", + "source-layer": "boundary", + "filter": [ + "all", + ["!=", ["get", "maritime"], 1], + ["==", ["get", "disputed"], 1] + ], + "paint": { + "line-color": "hsl(248,7%,66%)", + "line-dasharray": [1, 2], + "line-width": ["interpolate", ["linear"], ["zoom"], 3, 1, 5, 1.2, 12, 3] + } + }, + { + "id": "road_oneway", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "transportation", + "minzoom": 15, + "filter": [ + "all", + ["==", ["get", "oneway"], 1], + [ + "match", + ["get", "class"], + [ + "minor", + "motorway", + "primary", + "secondary", + "service", + "tertiary", + "trunk" + ], + true, + false + ] + ], + "layout": { + "icon-image": "oneway", + "icon-padding": 2, + "icon-rotate": 90, + "icon-rotation-alignment": "map", + "icon-size": ["interpolate", ["linear"], ["zoom"], 15, 0.5, 19, 1], + "symbol-placement": "line", + "symbol-spacing": 75 + }, + "paint": {"icon-opacity": 0.5} + }, + { + "id": "road_oneway_opposite", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "transportation", + "minzoom": 15, + "filter": [ + "all", + ["==", ["get", "oneway"], -1], + [ + "match", + ["get", "class"], + [ + "minor", + "motorway", + "primary", + "secondary", + "service", + "tertiary", + "trunk" + ], + true, + false + ] + ], + "layout": { + "icon-image": "oneway", + "icon-padding": 2, + "icon-rotate": -90, + "icon-rotation-alignment": "map", + "icon-size": ["interpolate", ["linear"], ["zoom"], 15, 0.5, 19, 1], + "symbol-placement": "line", + "symbol-spacing": 75 + }, + "paint": {"icon-opacity": 0.5} + }, + { + "id": "waterway_line_label", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "waterway", + "minzoom": 10, + "filter": [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + "layout": { + "symbol-placement": "line", + "symbol-spacing": 350, + "text-field": [ + "case", + ["has", "name:nonlatin"], + ["concat", ["get", "name:latin"], " ", ["get", "name:nonlatin"]], + ["coalesce", ["get", "name_en"], ["get", "name"]] + ], + "text-font": ["Noto Sans Italic"], + "text-letter-spacing": 0.2, + "text-max-width": 5, + "text-size": 14 + }, + "paint": { + "text-color": "#74aee9", + "text-halo-color": "rgba(255,255,255,0.7)", + "text-halo-width": 1.5 + } + }, + { + "id": "water_name_point_label", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "water_name", + "filter": [ + "match", + ["geometry-type"], + ["MultiPoint", "Point"], + true, + false + ], + "layout": { + "text-field": [ + "case", + ["has", "name:nonlatin"], + ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]], + ["coalesce", ["get", "name_en"], ["get", "name"]] + ], + "text-font": ["Noto Sans Italic"], + "text-letter-spacing": 0.2, + "text-max-width": 5, + "text-size": ["interpolate", ["linear"], ["zoom"], 0, 10, 8, 14] + }, + "paint": { + "text-color": "#495e91", + "text-halo-color": "rgba(255,255,255,0.7)", + "text-halo-width": 1.5 + } + }, + { + "id": "water_name_line_label", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "water_name", + "filter": [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + "layout": { + "symbol-placement": "line", + "symbol-spacing": 350, + "text-field": [ + "case", + ["has", "name:nonlatin"], + ["concat", ["get", "name:latin"], " ", ["get", "name:nonlatin"]], + ["coalesce", ["get", "name_en"], ["get", "name"]] + ], + "text-font": ["Noto Sans Italic"], + "text-letter-spacing": 0.2, + "text-max-width": 5, + "text-size": 14 + }, + "paint": { + "text-color": "#495e91", + "text-halo-color": "rgba(255,255,255,0.7)", + "text-halo-width": 1.5 + } + }, + { + "id": "poi_r1", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "poi", + "minzoom": 15, + "filter": [ + "all", + ["match", ["geometry-type"], ["MultiPoint", "Point"], true, false], + [">=", ["get", "rank"], 1], + ["<=", ["get", "rank"], 6], + ["!=", ["get", "subclass"], "bus_stop"], + ["!=", ["get", "subclass"], "tram_stop"], + ["!=", ["get", "class"], "shop"] + ], + "layout": { + "icon-image": [ + "match", + ["get", "subclass"], + ["florist", "furniture"], + ["get", "subclass"], + ["get", "class"] + ], + "text-anchor": "top", + "text-field": [ + "case", + ["has", "name:nonlatin"], + ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]], + ["coalesce", ["get", "name_en"], ["get", "name"]] + ], + "text-font": ["Noto Sans Italic"], + "text-max-width": 9, + "text-offset": [0, 0.6], + "text-size": 12, + "visibility": "visible" + }, + "paint": { + "text-color": "#666", + "text-halo-blur": 0.5, + "text-halo-color": "#ffffff", + "text-halo-width": 1 + } + }, + { + "id": "highway-name-path", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "transportation_name", + "minzoom": 15.5, + "filter": ["==", ["get", "class"], "path"], + "layout": { + "symbol-placement": "line", + "text-field": [ + "case", + ["has", "name:nonlatin"], + ["concat", ["get", "name:latin"], " ", ["get", "name:nonlatin"]], + ["coalesce", ["get", "name_en"], ["get", "name"]] + ], + "text-font": ["Noto Sans Regular"], + "text-rotation-alignment": "map", + "text-size": ["interpolate", ["linear"], ["zoom"], 13, 12, 14, 13] + }, + "paint": { + "text-color": "hsl(30,23%,62%)", + "text-halo-color": "#f8f4f0", + "text-halo-width": 0.5 + } + }, + { + "id": "highway-name-minor", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "transportation_name", + "minzoom": 15, + "filter": [ + "all", + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["match", ["get", "class"], ["minor", "service", "track"], true, false] + ], + "layout": { + "symbol-placement": "line", + "text-field": [ + "case", + ["has", "name:nonlatin"], + ["concat", ["get", "name:latin"], " ", ["get", "name:nonlatin"]], + ["coalesce", ["get", "name_en"], ["get", "name"]] + ], + "text-font": ["Noto Sans Regular"], + "text-rotation-alignment": "map", + "text-size": ["interpolate", ["linear"], ["zoom"], 13, 12, 14, 13] + }, + "paint": { + "text-color": "#666", + "text-halo-blur": 0.5, + "text-halo-width": 1 + } + }, + { + "id": "highway-name-major", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "transportation_name", + "minzoom": 12.2, + "filter": [ + "match", + ["get", "class"], + ["primary", "secondary", "tertiary", "trunk"], + true, + false + ], + "layout": { + "symbol-placement": "line", + "text-field": [ + "case", + ["has", "name:nonlatin"], + ["concat", ["get", "name:latin"], " ", ["get", "name:nonlatin"]], + ["coalesce", ["get", "name_en"], ["get", "name"]] + ], + "text-font": ["Noto Sans Regular"], + "text-rotation-alignment": "map", + "text-size": ["interpolate", ["linear"], ["zoom"], 13, 12, 14, 13] + }, + "paint": { + "text-color": "#666", + "text-halo-blur": 0.5, + "text-halo-width": 1 + } + }, + { + "id": "highway-shield-non-us", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "transportation_name", + "minzoom": 8, + "filter": [ + "all", + ["<=", ["get", "ref_length"], 6], + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + [ + "match", + ["get", "network"], + ["us-highway", "us-interstate", "us-state"], + false, + true + ] + ], + "layout": { + "icon-image": ["concat", "road_", ["get", "ref_length"]], + "icon-rotation-alignment": "viewport", + "icon-size": 1, + "symbol-placement": ["step", ["zoom"], "point", 11, "line"], + "symbol-spacing": 200, + "text-field": ["to-string", ["get", "ref"]], + "text-font": ["Noto Sans Regular"], + "text-rotation-alignment": "viewport", + "text-size": 10 + } + }, + { + "id": "highway-shield-us-interstate", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "transportation_name", + "minzoom": 7, + "filter": [ + "all", + ["<=", ["get", "ref_length"], 6], + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["match", ["get", "network"], ["us-interstate"], true, false] + ], + "layout": { + "icon-image": [ + "concat", + ["get", "network"], + "_", + ["get", "ref_length"] + ], + "icon-rotation-alignment": "viewport", + "icon-size": 1, + "symbol-placement": ["step", ["zoom"], "point", 7, "line", 8, "line"], + "symbol-spacing": 200, + "text-field": ["to-string", ["get", "ref"]], + "text-font": ["Noto Sans Regular"], + "text-rotation-alignment": "viewport", + "text-size": 10 + } + }, + { + "id": "road_shield_us", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "transportation_name", + "minzoom": 9, + "filter": [ + "all", + ["<=", ["get", "ref_length"], 6], + [ + "match", + ["geometry-type"], + ["LineString", "MultiLineString"], + true, + false + ], + ["match", ["get", "network"], ["us-highway", "us-state"], true, false] + ], + "layout": { + "icon-image": [ + "concat", + ["get", "network"], + "_", + ["get", "ref_length"] + ], + "icon-rotation-alignment": "viewport", + "icon-size": 1, + "symbol-placement": ["step", ["zoom"], "point", 11, "line"], + "symbol-spacing": 200, + "text-field": ["to-string", ["get", "ref"]], + "text-font": ["Noto Sans Regular"], + "text-rotation-alignment": "viewport", + "text-size": 10 + } + }, + { + "id": "airport", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "aerodrome_label", + "minzoom": 10, + "filter": ["all", ["has", "iata"]], + "layout": { + "icon-image": "airport_11", + "icon-size": 1, + "text-anchor": "top", + "text-field": [ + "case", + ["has", "name:nonlatin"], + ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]], + ["coalesce", ["get", "name_en"], ["get", "name"]] + ], + "text-font": ["Noto Sans Regular"], + "text-max-width": 9, + "text-offset": [0, 0.6], + "text-optional": true, + "text-padding": 2, + "text-size": 12 + }, + "paint": { + "text-color": "#666", + "text-halo-blur": 0.5, + "text-halo-color": "#ffffff", + "text-halo-width": 1 + } + }, + { + "id": "label_other", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "place", + "minzoom": 8, + "filter": [ + "match", + ["get", "class"], + ["city", "continent", "country", "state", "town", "village"], + false, + true + ], + "layout": { + "text-field": [ + "case", + ["has", "name:nonlatin"], + ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]], + ["coalesce", ["get", "name_en"], ["get", "name"]] + ], + "text-font": ["Noto Sans Italic"], + "text-letter-spacing": 0.1, + "text-max-width": 9, + "text-size": ["interpolate", ["linear"], ["zoom"], 8, 9, 12, 10], + "text-transform": "uppercase" + }, + "paint": { + "text-color": "#333", + "text-halo-blur": 1, + "text-halo-color": "#fff", + "text-halo-width": 1 + } + }, + { + "id": "label_village", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "place", + "minzoom": 9, + "filter": ["==", ["get", "class"], "village"], + "layout": { + "icon-allow-overlap": true, + "icon-image": ["step", ["zoom"], "circle_11_black", 10, ""], + "icon-optional": false, + "icon-size": 0.2, + "text-anchor": "bottom", + "text-field": [ + "case", + ["has", "name:nonlatin"], + ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]], + ["coalesce", ["get", "name_en"], ["get", "name"]] + ], + "text-font": ["Noto Sans Regular"], + "text-max-width": 8, + "text-size": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 7, + 10, + 11, + 12 + ] + }, + "paint": { + "text-color": "#000", + "text-halo-blur": 1, + "text-halo-color": "#fff", + "text-halo-width": 1 + } + }, + { + "id": "label_town", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "place", + "minzoom": 6, + "filter": ["==", ["get", "class"], "town"], + "layout": { + "icon-allow-overlap": true, + "icon-image": ["step", ["zoom"], "circle_11_black", 10, ""], + "icon-optional": false, + "icon-size": 0.2, + "text-anchor": "bottom", + "text-field": [ + "case", + ["has", "name:nonlatin"], + ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]], + ["coalesce", ["get", "name_en"], ["get", "name"]] + ], + "text-font": ["Noto Sans Regular"], + "text-max-width": 8, + "text-size": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 7, + 12, + 11, + 14 + ] + }, + "paint": { + "text-color": "#000", + "text-halo-blur": 1, + "text-halo-color": "#fff", + "text-halo-width": 1 + } + }, + { + "id": "label_state", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "place", + "minzoom": 5, + "maxzoom": 8, + "filter": ["==", ["get", "class"], "state"], + "layout": { + "text-field": [ + "case", + ["has", "name:nonlatin"], + ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]], + ["coalesce", ["get", "name_en"], ["get", "name"]] + ], + "text-font": ["Noto Sans Italic"], + "text-letter-spacing": 0.2, + "text-max-width": 9, + "text-size": ["interpolate", ["linear"], ["zoom"], 5, 10, 8, 14], + "text-transform": "uppercase" + }, + "paint": { + "text-color": "#333", + "text-halo-blur": 1, + "text-halo-color": "#fff", + "text-halo-width": 1 + } + }, + { + "id": "label_city", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "place", + "minzoom": 3, + "filter": [ + "all", + ["==", ["get", "class"], "city"], + ["!=", ["get", "capital"], 2] + ], + "layout": { + "icon-allow-overlap": true, + "icon-image": ["step", ["zoom"], "circle_11_black", 9, ""], + "icon-optional": false, + "icon-size": 0.4, + "text-anchor": "bottom", + "text-field": [ + "case", + ["has", "name:nonlatin"], + ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]], + ["coalesce", ["get", "name_en"], ["get", "name"]] + ], + "text-font": ["Noto Sans Regular"], + "text-max-width": 8, + "text-offset": [0, -0.1], + "text-size": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 4, + 11, + 7, + 13, + 11, + 18 + ] + }, + "paint": { + "text-color": "#000", + "text-halo-blur": 1, + "text-halo-color": "#fff", + "text-halo-width": 1 + } + }, + { + "id": "label_city_capital", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "place", + "minzoom": 3, + "filter": [ + "all", + ["==", ["get", "class"], "city"], + ["==", ["get", "capital"], 2] + ], + "layout": { + "icon-allow-overlap": true, + "icon-image": ["step", ["zoom"], "circle_11_black", 9, ""], + "icon-optional": false, + "icon-size": 0.5, + "text-anchor": "bottom", + "text-field": [ + "case", + ["has", "name:nonlatin"], + ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]], + ["coalesce", ["get", "name_en"], ["get", "name"]] + ], + "text-font": ["Noto Sans Bold"], + "text-max-width": 8, + "text-offset": [0, -0.2], + "text-size": [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 4, + 12, + 7, + 14, + 11, + 20 + ] + }, + "paint": { + "text-color": "#000", + "text-halo-blur": 1, + "text-halo-color": "#fff", + "text-halo-width": 1 + } + }, + { + "id": "label_country_3", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "place", + "minzoom": 2, + "maxzoom": 9, + "filter": [ + "all", + ["==", ["get", "class"], "country"], + [">=", ["get", "rank"], 3] + ], + "layout": { + "text-field": [ + "case", + ["has", "name:nonlatin"], + ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]], + ["coalesce", ["get", "name_en"], ["get", "name"]] + ], + "text-font": ["Noto Sans Bold"], + "text-max-width": 6.25, + "text-size": ["interpolate", ["linear"], ["zoom"], 3, 9, 7, 17] + }, + "paint": { + "text-color": "#000", + "text-halo-blur": 1, + "text-halo-color": "#fff", + "text-halo-width": 1 + } + }, + { + "id": "label_country_2", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "place", + "maxzoom": 9, + "filter": [ + "all", + ["==", ["get", "class"], "country"], + ["==", ["get", "rank"], 2] + ], + "layout": { + "text-field": [ + "case", + ["has", "name:nonlatin"], + ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]], + ["coalesce", ["get", "name_en"], ["get", "name"]] + ], + "text-font": ["Noto Sans Bold"], + "text-max-width": 6.25, + "text-size": ["interpolate", ["linear"], ["zoom"], 2, 9, 5, 17] + }, + "paint": { + "text-color": "#000", + "text-halo-blur": 1, + "text-halo-color": "#fff", + "text-halo-width": 1 + } + }, + { + "id": "label_country_1", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "place", + "maxzoom": 9, + "filter": [ + "all", + ["==", ["get", "class"], "country"], + ["==", ["get", "rank"], 1] + ], + "layout": { + "text-field": [ + "case", + ["has", "name:nonlatin"], + ["concat", ["get", "name:latin"], "\n", ["get", "name:nonlatin"]], + ["coalesce", ["get", "name_en"], ["get", "name"]] + ], + "text-font": ["Noto Sans Bold"], + "text-max-width": 6.25, + "text-size": ["interpolate", ["linear"], ["zoom"], 1, 9, 4, 17] + }, + "paint": { + "text-color": "#000", + "text-halo-blur": 1, + "text-halo-color": "#fff", + "text-halo-width": 1 + } + } + ], + "id": "94huyrm" +} \ No newline at end of file diff --git a/app/src/main/assets/openstreetmap_raster.json b/app/src/main/assets/openstreetmap_raster.json new file mode 100644 --- /dev/null +++ b/app/src/main/assets/openstreetmap_raster.json @@ -0,0 +1,21 @@ +{ + "version": 8, + "sources": { + "osm": { + "type": "raster", + "tiles": ["https://a.tile.openstreetmap.org/{z}/{x}/{y}.png","https://b.tile.openstreetmap.org/{z}/{x}/{y}.png"], + "tileSize": 256, + "attribution": "© 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": "© OpenStreetMap contributors", + "tiles": ["https://tiles.versatiles.org/tiles/osm/{z}/{x}/{y}"], + "type": "vector", + "scheme": "xyz", + "bounds": [-180, -85.0511287798066, 180, 85.0511287798066], + "minzoom": 0, + "maxzoom": 14 + } + }, + "sprite": [ + { + "id": "basics", + "url": "https://tiles.versatiles.org/assets/sprites/basics/sprites" + } + ], + "glyphs": "https://tiles.versatiles.org/assets/glyphs/{fontstack}/{range}.pbf", + "layers": [ + { + "id": "background", + "type": "background", + "paint": {"background-color": "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 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 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 decodePolyline(String encodedPolyline, int initial_capacity) { - ArrayList points = new ArrayList<>(initial_capacity); + public static ArrayList decodePolyline(String encodedPolyline, int initial_capacity) { + ArrayList 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 getFavoritesLinesGtfsIDs(Context con){ + public static HashSet getFavoritesLinesGtfsIDs(@NonNull Context con){ final SharedPreferences pref = getMainSharedPreferences(con); return new HashSet<>(pref.getStringSet(PREF_FAVORITE_LINES, new HashSet<>())); } + + public static String getMapLibreStyleFile(Context con){ + final SharedPreferences pref = getAppPreferences(con); + final String mapStyle_val = pref.getString(SettingsFragment.LIBREMAP_STYLE_PREF_KEY, ""); + return switch (mapStyle_val) { + //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? = null + //Bottom sheet behavior + private lateinit var bottomSheetBehavior: BottomSheetBehavior + 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 - 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(5) + private val animatorsByVeh = HashMap() + + 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() - 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) - 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() - private var busPositionsOverlay = FolderOverlay() private val tripMarkersAnimators = HashMap() 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(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(R.id.btnClose).setOnClickListener { + hideStopBottomSheet() + } + val titleTextView = rootView.findViewById(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()) 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>() - 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() - 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()), null, null) + /*if(!stopsLayerStarted) { + Log.d(DEBUG_TAG, "Stop layer is not started yet") + initStopsPolyLineLayers(style, FeatureCollection.fromFeatures(ArrayList()), 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(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){ - 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 - 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){ - 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, 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() + 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() + 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){ + private fun showStopsInRecyclerView(stops: List){ 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){ + 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> - ) { - //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() - 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>){ + 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() + 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()//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{ + private fun findOptimalPosition(stop: Stop, pointsList: MutableList): 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() private val durations = HashMap() @@ -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 . + */ +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? = 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() + private var busPositionsOverlay: FolderOverlay? = null + private val tripMarkersAnimators = HashMap() + 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, Map>( + 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 -> + 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> -> + 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? -> + 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>) { + Log.d(DEBUG_TAG, "Updating positions of the buses") + //if(busPositionsOverlay == null) busPositionsOverlay = new FolderOverlay(); + val noPatternsTrips = ArrayList() + 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?) { + 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(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 + 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, Map>( + 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, Map>( + 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(5) + private val animatorsByVeh = HashMap() + private var lastUpdateTime : Long = -1 + //private var busLabelSymbolsByVeh = HashMap() + private val symbolsToUpdate = ArrayList() + + 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(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(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())) + 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())) + 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(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() + 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 -> + 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?) { + 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()//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())) + + 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> -> + 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>){ + val vehsNew = HashSet(incomingData.values.map { up -> up.first.vehicle }) + val vehsOld = HashSet(positionsByVehDict.keys) + + val symbolsToUpdate = ArrayList() + 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()//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, 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, 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, stops: List, distanceIcon: Double): List{ + val closestIndices = findIndicesClosestPointsForStops(polyPoints, stops) + Log.d(DEBUG_TAG, "idcs: $closestIndices") + + val distancesSec = mutableListOf() + var pi = closestIndices[0] + val cumulativeDist = mutableListOf() + var sum = 0.0 + + val pointsOutput = mutableListOf() + var nPoints = 0 + var distFromLastPoint = 0.0 + for(i in 1..= 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() + var prevk = 0 + val cumulativeDist = mutableListOf() + var sum = 0.0 + + val pointsOutput = mutableListOf() + var nPoints = 0 + //for(i in 1..= 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, distMax: Double): List{ + val outList = mutableListOf(points[0]) + var oldP = points[0] + for(i in 1.. 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, stops:List): List { + + 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 { + private val latLng = LatLng() + override fun evaluate(fraction: Float, startValue: LatLng, endValue: LatLng): LatLng { + latLng.latitude = startValue.latitude + (endValue.latitude - startValue.latitude) * fraction + latLng.longitude = startValue.longitude + (endValue.longitude - startValue.longitude) * fraction + return latLng + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/it/reyboz/bustorino/map/MapViewModel.kt b/app/src/main/java/it/reyboz/bustorino/map/MapViewModel.kt --- a/app/src/main/java/it/reyboz/bustorino/map/MapViewModel.kt +++ b/app/src/main/java/it/reyboz/bustorino/map/MapViewModel.kt @@ -2,6 +2,7 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import it.reyboz.bustorino.backend.Stop class MapViewModel : ViewModel() { @@ -9,7 +10,9 @@ val currentLong = MutableLiveData(INVALID) val currentZoom = MutableLiveData(-10.0) + var stopShowing: Stop? = null + companion object{ const val INVALID = -1000.0 } } \ No newline at end of file diff --git a/app/src/main/java/it/reyboz/bustorino/map/Styles.kt b/app/src/main/java/it/reyboz/bustorino/map/Styles.kt new file mode 100644 --- /dev/null +++ b/app/src/main/java/it/reyboz/bustorino/map/Styles.kt @@ -0,0 +1,56 @@ +package it.reyboz.bustorino.map +import android.content.Context +import it.reyboz.bustorino.util.ViewUtils +import org.maplibre.android.maps.Style + +object Styles { + const val DEMOTILES = "https://demotiles.maplibre.org/style.json" + + const val VERSATILES = "https://tiles.versatiles.org/assets/styles/colorful.json" + + const val AMERICANA = "https://americanamap.org/style.json" + + const val OPENFREEMAP_LIBERTY = "https://tiles.openfreemap.org/styles/liberty" + + const val OPENFREEMAP_BRIGHT = "https://tiles.openfreemap.org/styles/bright" + + const val BASIC_V8 = "mapbox://styles/mapbox/streets-v8" + + + const val AWS_OPEN_DATA_STANDARD_LIGHT = + "https://maps.geo.us-east-2.amazonaws.com/maps/v0/maps/OpenDataStyle/style-descriptor?key=v1.public.eyJqdGkiOiI1NjY5ZTU4My0yNWQwLTQ5MjctODhkMS03OGUxOTY4Y2RhMzgifR_7GLT66TNRXhZJ4KyJ-GK1TPYD9DaWuc5o6YyVmlikVwMaLvEs_iqkCIydspe_vjmgUVsIQstkGoInXV_nd5CcmqRMMa-_wb66SxDdbeRDvmmkpy2Ow_LX9GJDgL2bbiCws0wupJPFDwWCWFLwpK9ICmzGvNcrPbX5uczOQL0N8V9iUvziA52a1WWkZucIf6MUViFRf3XoFkyAT15Ll0NDypAzY63Bnj8_zS8bOaCvJaQqcXM9lrbTusy8Ftq8cEbbK5aMFapXRjug7qcrzUiQ5sr0g23qdMvnKJQFfo7JuQn8vwAksxrQm6A0ByceEXSfyaBoVpFcTzEclxUomhY.NjAyMWJkZWUtMGMyOS00NmRkLThjZTMtODEyOTkzZTUyMTBi" + + private fun protomaps(style: String): String { + return "https://api.protomaps.com/styles/v2/${style}.json?key=e761cc7daedf832a" + } + + private fun makeStyleMapBoxUrl(dark: Boolean) = + if (dark) + "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json" + else //"https://basemaps.cartocdn.com/gl/positron-gl-style/style.json" + "https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json" + + val PROTOMAPS_LIGHT = protomaps("light") + + val PROTOMAPS_DARK = protomaps("dark") + + val PROTOMAPS_GRAYSCALE = protomaps("grayscale") + + val PROTOMAPS_WHITE = protomaps("white") + + val PROTOMAPS_BLACK = protomaps("black") + + val CARTO_DARK = makeStyleMapBoxUrl(true) + + val CARTO_VOYAGER = makeStyleMapBoxUrl(false) + + fun getPredefinedStyleWithFallback(name: String): String { + try { + val style = Style.getPredefinedStyle(name) + return style + } catch (e: Exception) { + return OPENFREEMAP_LIBERTY + } + } + fun getJsonStyleFromAsset(context: Context, filename: String) = ViewUtils.loadJsonFromAsset(context,filename) +} \ No newline at end of file diff --git a/app/src/main/java/it/reyboz/bustorino/util/ViewUtils.kt b/app/src/main/java/it/reyboz/bustorino/util/ViewUtils.kt --- a/app/src/main/java/it/reyboz/bustorino/util/ViewUtils.kt +++ b/app/src/main/java/it/reyboz/bustorino/util/ViewUtils.kt @@ -1,17 +1,24 @@ package it.reyboz.bustorino.util -import android.R import android.content.Context +import android.content.Intent import android.content.res.Resources.Theme import android.graphics.Rect +import android.net.Uri +import android.os.Bundle import android.util.Log import android.util.TypedValue import android.view.View import android.view.WindowManager import android.view.animation.Animation import android.view.animation.Transformation -import androidx.annotation.ColorInt +import android.widget.Toast import androidx.core.widget.NestedScrollView +import it.reyboz.bustorino.R +import it.reyboz.bustorino.backend.Stop +import it.reyboz.bustorino.fragments.LinesDetailFragment +import it.reyboz.bustorino.fragments.LinesDetailFragment.Companion +import java.io.IOException class ViewUtils { @@ -105,5 +112,46 @@ return color } + fun loadJsonFromAsset(context: Context, fileName: String): String? { + return try { + context.assets.open(fileName).bufferedReader().use { it.readText() } + } catch (e: IOException) { + e.printStackTrace() + null + } + } + @JvmStatic + fun insertSpaces(input: String): String { + return input.replace(Regex("(?<=[A-Za-z])(?=[0-9])|(?<=[0-9])(?=[A-Za-z])"), " ") + } + + @JvmStatic + fun mergeBundles(bundle1: Bundle?, bundle2: Bundle?): Bundle { + if (bundle1 == null) { + return if (bundle2 == null) Bundle() else Bundle(bundle2) // Return a copy to avoid modification + } + if (bundle2 != null) { + bundle1.putAll(bundle2) + } + return bundle1 + } + @JvmStatic + fun openStopInOutsideApp(stop: Stop, context: Context?){ + if(stop.latitude==null || stop.longitude==null){ + Log.e(DEBUG_TAG, "Navigate to stop but longitude and/or latitude are null") + }else{ + val stopName = stop.stopUserName ?: stop.stopDefaultName + + val uri = "geo:?q=${stop.latitude},${stop.longitude}(${stop.ID} - $stopName)" + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(uri)) + context?.run{ + if(intent.resolveActivity(packageManager)!=null){ + startActivity(intent) + } else{ + Toast.makeText(this, R.string.no_map_app_to_show_stop, Toast.LENGTH_SHORT).show() + } + } + } + } } } \ No newline at end of file diff --git a/app/src/main/java/it/reyboz/bustorino/viewmodels/LinesViewModel.kt b/app/src/main/java/it/reyboz/bustorino/viewmodels/LinesViewModel.kt --- a/app/src/main/java/it/reyboz/bustorino/viewmodels/LinesViewModel.kt +++ b/app/src/main/java/it/reyboz/bustorino/viewmodels/LinesViewModel.kt @@ -12,6 +12,7 @@ import it.reyboz.bustorino.data.gtfs.GtfsRoute import it.reyboz.bustorino.data.gtfs.MatoPatternWithStops import it.reyboz.bustorino.data.gtfs.PatternStop +import org.maplibre.android.geometry.LatLng import java.util.concurrent.Executors class LinesViewModel(application: Application) : AndroidViewModel(application) { @@ -99,6 +100,26 @@ requestStopsForGTFSIDs(gtfsIDs) } + fun getStopByID(id:String) : Stop?{ + //var stop : Stop? = null + val stop = stopsForPatternLiveData.value?.let { stops -> + for (s in stops){ + if(s.ID == id) + return@let s + } + return@let null + } + return stop + } + + private var lastMapPos: Pair? = null + + fun saveMapPos(latLng: LatLng, zoom: Float){ + lastMapPos = Pair(latLng, zoom) + } + + fun getLastMapPos(): Pair? = lastMapPos + /*fun getLinesGTT(): MutableLiveData> { val routesData = MutableLiveData>() 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> +typealias FullPositionUpdate = Pair class LivePositionsViewModel(application: Application): AndroidViewModel(application) { private val gtfsRepo = GtfsRepository(application) //private val updates = UpdatesMap() - private val updatesLiveData = MutableLiveData>() + private val positionsToBeMatchedLiveData = MutableLiveData>() private val netVolleyManager = NetworkVolleyManager.getInstance(application) @@ -61,6 +65,13 @@ private var lastRequestedDownloadTrips = MutableLiveData>() + //INPUT FILTER FOR LINE + private var gtfsLineToFilterPos = MutableLiveData>() + + 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>() + val mdict = HashMap() //missing patterns val routesToDownload = HashSet() - 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>>() + 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): + Pair, List>{ + 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>() + 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() + val vehicleOnWrongDirection = mutableListOf() + 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()) + private var stopsShownIDs = HashSet() + private var allStopsLoaded = HashMap() + val stopsInBoundingBox = MutableLiveData>() @@ -33,13 +34,55 @@ } } + private val addStopsCallback = + OldDataRepository.Callback> { 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{ + 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 @@ + + + + + + + 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 @@ + + + + + \ 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 @@ + + + + + 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 @@ + + + + + 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 @@ - - + android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android" + android:tint="?attr/colorOnPrimary"> + 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 @@ + + + + + + + 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 @@ + + + + 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 @@ - - + + + + + \ No newline at end of file + /> + + + + + + + + + + + + + + + + + + + + + \ 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 @@ \ 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -258,4 +258,7 @@ Seleziona almeno un elemento da importare! Importa preferiti dal backup Importa preferenze dal backup - \ No newline at end of file + + Nessuna app disponibile per mostrare la fermata! + Destinazione sconosciuta + 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 @@ #ff9800 + #E77D13 #F57C00 #cc6600 #994d00 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 @@ - layout_pref - pref_update_db_now - + layout_pref + pref_update_db_now + arrivals favorites @@ -30,4 +30,10 @@ gtfsrt + + + versatiles_c + osm_legacy + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -292,6 +292,13 @@ MaTO (updated more frequently, might be offline) GTFS RT (more stable, less frequently updated) + Style of the map + Versatiles (vector) + OSM legacy (raster, lighter) + + @string/map_style_versatiles + @string/map_style_legacy_raster + Remove trips data (free up space) All GTFS trips have been removed from the database @@ -342,5 +349,12 @@ Check at least one item to import! Import favorites from backup Import preferences from backup + + Hello blank fragment + + No map app present to show the stop! + Direction is already shown + Loading destination… + Destination unknown 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 @@ @color/orange_500 @color/orange_700 @color/teal_500 + @color/white + 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" /> + + + + + android:summary="%s" + + /> + Ywk3*=IXH)H+qP}nwr$N}+qP}nwr$&fYwewRU*3H(*E{#!?@yE-@s%jO z^jd3cy&)pxB!PgD0Dk}e4FCXu5AeVK-~hk@q=l9Esl{bPXr;sjg=G|lm6bpMe*JxM zOGDFfg9XKBrl!}-#PkwfM7-nby4hR7utb?Pi7~z_jYoipOkWUzice$t`<)wglWa`p z*_k_SIX^5Q!0~O`73NXG>&$d+!l7mmOuKx#OqI6tjn+7eCF|AYyBV%$%qH=ftlRtJ zxx*CoDkSgcZYpD7k1nNgYIAifBe=!ER%fsOW8>)tvrcU*)YY{jOfBAO01=XGXp735rl1sqS27C_WnNW~sxr9}WL4gFy zJ@jac$>!A%Z2#gd|A1#PmgW+4i3hr8v6}gN!BWQ;-ejJl${&eZIapEr5q;arZ`iAo zBP|X3%OgC!g{-97+KoBMZJ0Db3sOvb$^4l_G)^u&^O`^^afw;LNq`#%CEDr^B~!vInml66A}x}#SihH5&O9z+!K4yC;MG>g`&Lc+kAsW6(S*E zxKo{p=-5MHl-^U4Q~Yayo2Gq|rGeM4F9J{^i3pj|H}2%0r1gK0t1b^^@*eDTS(80$ z)bL?Eg-5_{7P{k&PLkQpB1vK$iue&7Vm2;th?m+yEuY7e$==UDT727zub@&xpmBkt z0x)~ePl4e9Tz0^u4$C;ioSM779Ls|im2`)ny&M8j6Osxs*4aV4GV8@uyhFMA1|DBVA97(7glYUlsCFk0 z&J>!FB?545zXtmKsDrS9N7kJLleHpGq0$N_!Yc%8h-@Q#-LJ)V>D)Hc3y{go2mNQV z@mxNEl0LkTiAOdN5E2uU$M5&eF;_+>U8Tb~e_`wsv6Tsi1cX|#YkPq9w6{<6$gh%f z@pshyMXzlSw5e@ZqHR^^3XL*DpK=|IaHK{^>;h#KX3^1rYesY%$T@%e`7sr3uo6oE z0?mo|aG|)RQL{zHqFIjd#j%SUxN+~Op3}PptWffdD^%!HM4H9%hG?WS#5rCa=8I-^Un#VCwTB6n-TWkF>1hfN*!GfS;QeC z3mLED9C%msrZT7QyWihCFX>F~rfn^<<&4!(${Jzmp((TOrkW`>kDSc6quK%$T zA7_uOdgaJW1Z-?W;sZTzhQtMlEXBcN1^!u(EI($n7L^db&!QR7Q!?)4+{-1qpY)Rh zhtp^mm0Z?_Z*b(E!p_!ftcXFHgrdk^YP#T%frY z$2-V*ip^(jE+0J=14ak?r#g?iw6S?AQDYEg1tU;|-}t$Vb!__}d@y^7y|*6Be~blQ ze?2lU@e{6`O1-3W-Rus&-Z9tWbPNoSVHq=G>8_~skwOn%&n5moG+o+!@om*VZPo)h zREx3q3U&gY{;-F%>43->fvTpf494=)GZ zOZ$TgH?-=9jcDJCXgmV}Zqh$C3w&8nTXFdTx*@&bw=gyys;$#hABS-{bz>3&L>V8u z*$&UjIjVC~xRjXH1o8-u9W|f#d=%z;RGkOSl68*^zYB{dZuHc6>BP?l=i1u6?*A@z zUp)Rs4RabUcX4c>Uhrc`dk9cQS6rY7+GU7*D6^jfe&}`JlLVo1xV+5vk`5I-LN*op z8O9rMQe<}67v<4gz+y2X3_yjFD4V$pjI0aDRt{d-`cUN(uSjx&HBjfzt6@-%7hJZ= zgaBj=F;zN~IBQmj^*~HpOHmDznt$2_M>!ADuhKUF=QY=k2mLV^p10f4rdHv_z*JJD z=`JHa%T?Zt?kY0V*Xj#Hr6MqYO*&I{(Q3bd%u?0A{&CCuH)Wp4cWfH~A#TtYC#*A7FkB;S`vBYC^_=kr;owlLNZM-q%_v&2QxbWqGSpnjdgXhwLXlw2qb`PS!}8?Zfpj-uF5&fq2* zcL0<^HG_~@-uN#4TxO=di&#*EtbI6!+nJNTJ>>SxyZPs|nqmTHw3Z6*qSq#54x|3A13`)9ClBaw~jssl|uzL2&!mq?x z=YDjmv<{_TGe?-3nVRaL)hW_#Ubc395>MjAaDIPaVzWR1t)DPp-6Olt?0CSS84)0f z^T4}y98rT`oo$XMcZ6_SlcEKAPVp5U!l8`Iqi3iEDi4fUa3_K*DL%IhdJ;s9NQg_X zsUMls3iXCo5w3De3VM0pq4J`oxXTy<+yJ-D;3{0^@4M#BZ}x5gzd_~DeuoOG zO}aT5|7Hg(-du}rS}G3L8~8XQ#WE_ds29YH+}ymJUui_)rH~5WP*u{UstO5 z#%k)}?)Sg$B~_arE0TMaj#ybJg;qm%69or*tPYh1ld+gNZotp*-|=E@l>=N=@Xd+9 zyef6>m)y|s0{jMdVIL+6Ew_o;>{U34qE_+$bx*)dYYw486!Ny|D!xRyh#Uf1LT&NQ z5=~1w-8I~X!uM@e2R|_-_1U>f#oE`Jll;=N&Ak~R8}Z~(*RH#5Gq}aklicG;i&DWm z$eU7unai1O`zOba<9uG6X-^_4N?Z|iE>j6KlS3duvN%6@G+U4g?ZL(0h;Vky}c60=hs;LCGWX`u>eYtl}Y4I1?}leue2Z04+&SqQaT;4TG@H6!J|agKdg z$ZZo~WgWhrQ}KM2gt7F)Qzd=)7`5tTifUw=_xP4Q6$#aQ)}aEkolDo?2$|2f-S1Hk z{kAkupY-&1mJ5poIIB7wpCAvz>0aRjP^jv1d>W}DwOTXnOSX|yuevPCB|kG0cSy5V4IT+Zn5AJ36&0;o72M$|y8)ALI^j zpC$m*_9jkeodX;jT8P-JESSBH2v+gLx%>jL`BhK%FZA6gxbDr}AxF?pI@AaROF#{L zJlg9{u^jfw7E-J6PcIw($2U{pve)D0YeXmPcwTSyF z3>cA@Y2Y#^1-6Y&(k_??>@7VgdHB`7k$mZTiI2p^2{!Tw`6c4=D4;$hk=zN@=sEOz z>^oyD&W+MV7G|n$Aan=k{ij;%$+2Xy7iL3=(*;du&6BdxzY_8&M@*7JN{8My-8>(S z2(eE?QN&oHjJ{)q4Mi{uXs~))DljW6Qo2E3&U@q}!G71a6yTge0|0cB0szSUALB)& z|21Be=9du{5mr>9krw&CV#WvQ$q6Y*YMMD{NotDe$(aTPx<$s_qx(tfQSmA1DN1pw zUl8ZPM#+bT8sx30#Knh&6oIJ3C`g4UcMf+BzX1N8igg_iH{W0P;syApsnGv56Etn11v0Z1V!>0e?X0G;gTi526ZG&lP!19>u@3OYiUttig;By2 zvl8qH30h9{wx^{HWC%Q9F7NtQ634Oh(PQ`+DXA1`fD zo6)g(z+u8;?kRCmYUfdZFJwTatFE8zr}ypMJ73@#_8N z1g_9aEL6Bc;P`_<%=ggv0_s`A8_ltVvDowLZzLqCZ@dWpS}(g_|AYj|KO!OfzgvD| z1x;B*dAM&Fq~vBwx!m76_FYXylGLbGdS3WCV&!O(cxbvZuCNOm*zw}Tfp>AHDvsJgvoNX?DuPmZ48%k1I z1fMV!O$na0wI8JU?|0XNzyfo#xaDF(>t6y>lg^^Fv|@+mFE=`cusWmLr#_gt>o?1& z^~_K2Sgf&t_=B!Y973$bSHpEXQHNhc@ZwR2tC}2}P_&b~G_?TfL6xPi+(X~Vt^4bb zKUBpZIWC|O4Q`f}kp}_s~v_WCjJqa4#@piZ*l9 z0in$vO7clZ+uyp96KQA`iFA2v-`FMHff!k+VJn3~gs*&{S)46TDgD62q1cWq?N9p9`1oD3gvU|3+~@k z&be$9umJ=BXaM>rDl`5gmF4su9E}}__)VOQ9sW&g$N%7UlH!#NB0m!M0-$6|(VyS1 z)YW*_`sU&3N)}SFgM)>?2*b&JC!Ji|sV=8k8Z;Bp+~IQiaMN>%(q93167Os@0R<9< zw~VeQHyy9J9Z#ljczJz(sfqaQE$r=0+=CTNS8?N88xGZC4Cm{E_$)lR9fHn30%^{6 zBV_O9;hn(1N?VGmdFoHVMkRs12-KM|VK1CWFGLxx!d}h~9C7F?zXk4?x#L&|a&A)1 zDTs|E36QCBGi^NNqb)LIA;bZP7KAdtKrc;nZO#X@$T?xRbqxW4z+d2DqG z&yd$@knqXSCyM>qZT8&tpqr8GREwACWemz=6qaft;aRkgW~Igi^obQSee`r`8=MEBv@9v=OdW)lv?} zRobE}H?o6lQEUxE4-UftfCd$e-*|2VT`j$Vpv(sy)kAUQxZcNMUc~5P2%b|L^q8oD zdCSGL$2D zYrDwkJ*u|NMj#rC{vK4SvJ0#WY6&vb`kJ3kE3pr?YR~$?sCE?(vRSsu6Nx0;RDX?r zV=ckT>I`6#jWfQw1tqB_%+mr1fnD+L@y&99CobzQm^cxz7E4A;u{?1 z^9*K|Z$PBeo%UJ~S#lAHC-%0V-sC;=k?EN2=q~H~`S}Ra`}-J8huhbO^FfK)&Rcy| z*jZpHWihdwQ)P&*&6cg%gk6~5K{eIi)1PHxbK$XmHl?fls0>kZ^HE5mDv>Edm5H!c zo=X#phgAWuGUKhPM~PV)!OgfDdO=~8cmSoJ`4T}~HSi*iHr_N?B!odjzOr;iqZjQk zQ_y5MA{~0o}E|B+NHvXE?$8f9@Bx3-n?TG zqD$=5AoM1BrVFq5i=o~A4?fC~NI!nCx6D)u>lYDI|135}^?Pty_JPhDXpIpvR^OZo z)-ZkWH1!683>{N);LL1K+^N7`+^o060!o~}V(1uKST?qMuzRdsx|;ohlgiEdM00~h zh(nAYHN~l*Z!gs}9ZuS+^m_(}W2}D7H&~Cfr_B`!hwEPS06ALrc#!3{T(c&1yV9G78ZhZvg5!Spno2`yK4@6^LuZ@snuVmBbtf zJo%lN(Q(J~=4123=It!Uwi~DxT$iEK;dJ)8nNuLZ!h@H!NzRju8!0?+UWKIuv%Esn zgvru8^N@wL_1umFOC`(9Bemp(^Q{um0#^Y#L{y+nimBo_zx_7PdX7b>(LC1asyY8l zZktYQ7ph_|>Tzr;(#FB&!X&tmaMR_KCcdS~#>P6r!i&hM7Bmt+wej91UAG7`a}Qx+ zdXS;H9Gxqf*UJFllU5sYo^_ zRK&r@B0O`lt25!U`Np}LmaA4?tum{#E9O94F?%q#XK8~dON(9l2RfiMXZnXRlb$>r z0tzElC0t5i5tOoYw}BztM6K4adufd@v&wiWf0Y}jt_Ym-zH}V^0oEN#d>z0^cnf4H z19ii~^kKuM6MB2P7bx`7h%YwXRD1sl-^9Q2fYZMpS5cp>;hC>o20{$lod`~wCnNQ9*n#>ULG`c(fbu=&nj z`4e*Zw^<@5Myo3(XWR$F1LU8TE2tmbNnhz5olj*0U)Ve}s&Vmpz851yFf zW;|#z*UJfp{qWxHf%vEFUNDx@%!}P(o_HAwBhRmE^X*BkFR~lA5Lb3Zf{RKPHj`GM zqc#w1AofkDO6oKXMrY;7ZCeD8wVB0?bB`SwsnPS1H@=}#;vk{5HdzPd4m5_M>PyQE z(x3!U+doc|*`sv8gVD4DnIRjzN)w~h!P$cyy$Q0a>j=t&h$;Q*88hV^fyaXtBR2TN z%V{~2=>&#^$Bfi;&DjzojIesu;0ju^q)Z0XvHV;Wh7PT+p7padvglFc$hZtbvf1__zOu)kbr z4(RF8Q}*sOum;EXN1v#^tRIjDnuWIQoH2qHoUh0VHjhoYJ+t$|=d^7qs*M?`$W0|Wb}pI04p>|YB?DEnCHMN zt)Zm!vTEV7mkkB`zTwno3Qn?b*_I;--$7WqLa3<|%s3<8?JP1gr3@~5iK^Lr0JX{I zHM$sYsjH=iD*DHQD>Sc zvn02J@+*%i!3Gg8J1y0zblOS%l8S$OluE{Ukv?c=k(wL9d?qOuzU1}~F4v6rgHhn| z%TT=XBj03*b}H|B7hPlq#i$^SLpKu_&M!NP@)N(5MR9{D3B7KzXTS|f!n?>$wm&3H zXgvVKk|OSZbe46(dhv}CJ<;#&_zJ5!aEr&Gw1LLrxBR6uupmnRgQlW7I@U0h`MRqc z+2R$Yir%DZ7`VM{^!iKFW7Jsb@N;!+$2;amYQM{T%B{6@O$u%0SxD>!NWhVooS2|{ zeoQw%F2Ca$pby2~$Lsab7Wjwp&L=bTM`C(q%4lvdynY{UYEEWRS32IuB1Yc=E@0jX zlUXc#{eZ_J7EJuX-Xiv-lv}5;u}jj}C2{>gk*Xh8Et0p4=eu9wGs@VC9b=7RJ-2IH zTiNM?OX&;pZ(pkBp9;GQ3IJdL^Z$CUD{b!RXl`TrZ*R))VEVsxrHal;N^Vv%vU`?j|MBpismuRx#j!wc1n zW@2#bhuNO8IVbETCz&4)D=)YJ$$E5Nt2@4PUL{R!ZIp#YRc0ol73Re^&e$oU(X`Mr zEx>O;dk{Df@2vxZ1Do*2HmeT;)m3g2z{`4FnLUmHKaf2oJqQ=xUTQO zi`&4_yiH?%YjeesHK?#JTym5LbI8Kr2Woz<5L}Gt;)2`|RJeQQ5PPsc?N9s;kI;AQ zA5zNY`8#z8nt89+_*`vOAtqebr=L(Cy6W(W)QJOx>Qc5CNc%YEU~pZVSBKY)7?_Il zyM7|`d8TJBd_v@vI=GQe{wc9~Tn1dp2>b%lDBS|h0Xp;!Ru3IbU|RPLWVOO$N+(zJ z;sf>EEwrnL)Z8YC5_BUv0hj!HjtA(Ha$Wi-^gBjrTY8Wwcp^~prTsJ>6J8PzW{n0T z^?d0evA%}FRD8`+uRbMQ)ruybYHtbVle*Y`iLzrisEX%2SbSrpZ$5)7Bms#U(6l-s zAKjmN(1=`Z-XBm4&~9yf91%UBZ5WoQ z()t*9sksg7Fzvm2kD&h0MvD}PK98Jb>$XU@c=kgMbv{q+2ia~fZIYfhyi?cNo#(mg z+7EJxKGF249?Tjx{y4suaAOdfPvdXFL>Gt{@e58+5eZ_SevtqnRRb*@%SQ=Lsq}FI z+R#jr>gpaSyROaBWchPft2p5XJ#jEIWf4@d$&Jsag&cw-Y~L>DxG!Q4f4$&TfVHXm z^f=-=>9yixkW$6#i>p+fJLz0?c3>6{#(T_ho{GKyiJPJltNl>Zg@lxtSEHWGmje`8 zmhe;ACEKDL-{g7P9_;;qW-`l^3f z!p7TMpLu51otm3m%3jR*tvi;ch%HnK7$ew*sXNopA3j(^@%4e_rF1-5=w2fk6%Ra8 zpDSFxQ*V}ng|kIkegf>6e3apFCvEJ5lqR^0jD(*QDZ4~AE03P;NK%G>)LExixHY9% zCJ$EzdA|=1Q2I_!1{~Zk&X>E2)_f(34&Ml8byF%UpWZ3@_>o+r2fuaSOz?yF0o+`9 zPEnlMHFIwc)x$Wos3|zR4|~LrAZd(}2qX3bltp&d!~c5`T>+ES>@#{Vic!kybMWvO zAZ?od;VDM})m(YOGtlcoJTEE!0xe%o?p0N2+E{?~;wG4|)*LKoXR4m*V z{wJi27}o2rPqIb(^zuzOo>%wn4EOA`zeMy6_~(}u$^;_sQ5$Wh=qJl{4`yIuMF{NE zf`A0UWaNi8ggmhTzC|8_0ct7qm+)t8%MRu{JxyngvmO*iLgg%cHh35mXqt?_(1V%* zoH%@@iTSuP7eNVRHS~Gb!{(D0t`t>^S^z^p6ua&%pF(8ZL{Ha)S3Tg$V0%&>P{k*|@OpHp5k-}- zYi6>oe^n+xUj>v4|3b_g@c(r>_urIIbaMD>hwvY4a8}Tk{X3m2XxXy3G!Ll4FRYr_ z>|`FE&ZOrRJ4i|(1XbLfdUox^S*x42^*l8CGt0YMK_Z7B{rT&g>|m>TFlJeb!SiI2 z+w_{-)A#f99lMuADmaFels4lglBFd&$QDyGc!}O37}+h)$(;&l=Mhj7t~!v?jp(#~ zf^gv+;)3nmJ+5wP&xXiC`TNV6L2yfu=N-K@2oFk($zt3NY(lZ=AF=s<5!PmN5qSeec+U0v&=;xQt) zIsspPQW>i&**-$lNiwrZC)$( zX+eQBx9+t$+EY!M==wsf*8ri4p6L!zIyaV0 zR387%I+U(lyu>?8W4&{BfJj9@jklkI55Kkb(IhP2Ac? zXv(Vd4TCeRVe(@zH#)vILyE&^YS=JdI|C4_?0t8BcNc3J^YZi z{h|%4`#H$7>=Sa;zXL_a6(G`>?b@t%tRK>j3AnLkk;$ptNj0byVUh-0#y4f3QzUQ15W$UENasludQ zVP!cUO2aes?gMMhFx zYdF0l8$__cidnLPrRkuh(ooCo~i8}^xj)nF3lnihL zIxyB|SI$XxG}J@BQ>$H(0<(E_f%oMPz0q=id*dMD3L+G&z*%baVKb@JgyYoF?2Ege zYUmkAN?hzhO~4kO7jETp;$y+8#-w4KNW6YG;U|<9t008}jbPb)TrPC`XzcF1K3e7F zEw_yam)&HOnzU3I&js*AZjOn~{i^jR@R`pksLf|6|KYdkrIa1d-J5f;(Y3mdwjeK! zNvw#9AgFhjn7&v*FyuJq+-!i(Q55|nCxOp+0rAh^Yn2g=fs9vC6LeIx1U=3-i=*(tb#Wf4Ye|r03YhU0Q4JSnhxV7{SnJ zaP$`0dS6|0!#V;ds?L4tBdt{LEh$|=5UaGBlO&O0?5{K_xq~jO&#D*B#(Ymd3f1yP zATTkZBYA9N^FeKTI0NsB{p>;X=rRxxm|&!3tOTXy1i%IM|7G|9B2Bd&|04OwKSneE z+zIe+QvU~^qm;Eh5rvU_LV#JI<<}Eo1A?mK)|sp^HvAKlVFk$)Hb{!d^~lspIu?vJ zrfgQX0Fd|kM=WzfBwHVYHIbz%xbj!F-S%W}eM3!IzUpzgu7|EC(_E)BAF>&5zVBxK zvVxBV!?%xf1!a9;ckI>F5Rx&lLrrgPvRVz4$^(bl`kH0oRgm?Z{Yab0jcUg>5|ohE zb5+Wlud`gN+H;6C$qL9i(&+OcJ_XKGk+DTzwVuK&;Xq z+su}8^*8Eo!#Kg-rqC0&(~Q6f+AD-?e~#&p8tEFX9`bUTBLmFB@cdiSYf(ec1x(4G zf&+js(@dlxRQMzJYH73v;5|&JU;RhA(l2mb8U0=7P=@8PBJd)==8ZW8HTRc;YHXBo zjif46+}Rp z*rfXaoO3ViM;vX@blf9qnqo&+$i0w;#WP75C^ic=_UNFjhD|0m$wN7r_00{yBuFqa zLgN|H;uE26dfzC-`r4ihH-FQN`e%=fK;1+4_>61)Q zPdqDCUswcKH8l)eDYaENSWe}S%4FZ@ip zJO|r_NwC>+BH<3CVV|b2 zaP`e*?=+6(N*5Ehb{C!-WWP(0%6&(jC+HHtoNFMyDNnG$=;lkRwbjI5_<(=e7{TTp176W!0Bm8A_$UBt*` zCjp72qE^s6o49S}g`Ev$O53KHivDoKOQ)hCd`AF_Fq~jZ`~(;E`Qs(vW{SNL&CS!y z+};v>&{vZq7&WXK>lSSU^oh20st)oa{OhXd#^I&H(eGhoy?Y>K?Aq{4un(0D0do0n zPf;rhq~OaLibwcel&I*=3OkL|?46`HyX*UbxPEB2MlRgYPpuXXTfAD&3H_kQy6(iK zPS$3%Bz!3B8riEkG7UAgN-)y(ClVx>lr6mw-u&){Ev=wef!mpl^{<ua!{Vz80zgWW!7er;WZA&Ctv4A1^qpw(r9*+Sv`MLupR6PI~&Qbv1|d5{U>39ws`Ly*>?CJP{wGA;ML zdyvjY;mV)sUkC|B#|F>5KVCoPzB}7*S2ei4pmfj)dt(P9t_iw9j^)iG)ZARS%lCU~ zY7G&@14`Tj&HKWdCx@=BW6CKDBiKt(mYiA}juXPHTsf-JDI@(w0-dGF`@y4mGv|tq z3+(+IVazs$cT3yH5Yx?xu1ljQo|HS(l&HCd--uq(qi@PTEc%tI1PmBkCYWUfa0lo+ zh=%P|ag4d-llFwlL8&~0(^|{gInA3Id+F31S|_M&8(?k%jay0eU})Drw0uD@4`I1Q zwOt^N2!s<1TZwJf>=#AJ2aw~uBZ7_HR?uXkT*tWVWBOQm>}C7tp~hn2jL^mWuug100lmm29enEh&Vo<&kl@@j3C&DhJx7KfciCX5PiND zed?B9bfgLWwl%|Ln^ z-7D=Jza!w81q{vzmGUD=Abpt`NonAC1n4{UWVi80S_X;8UU``6cwvJ(aEDd+8ufYB=3fvOgGs&hF0_4dZQMtSW~z@(^1?oy~QGfWJrF-dhHxKZcyfD zp+$j3Dhmww3K-zFz7Hm{|o!rM;`NukJ-Yv*2 zICFMOR=z}}*46xLfN#m6vgpel-!MWmxpo(9@KE)~?GKV@%{Dc6>$Q_)Z`0nz&u zTT7|=TPdeMeKGj+U4Vjk-08V&6mCBq-x>vULjn3%Zl`x@I7}wF#ono54NIhoPXJ+y?<;!p!#pvrT=Z$ zrnTIZ!~h@M_p*heuHBHtJp!@JcFY7{*bxY zB<4I3bsbymG9)Mxlr@g#OCfDjQ88zDr&=@@^Th9%{^dLVn6Bclm+ltr^CmFG#~fMb)*-L}*hA z`L#Aaeb<|Ae+HFtJ308(*O42&sz_%g3Nn3nbjeb1DwkKCMl`n15x}N2mYYGUs0+{n zYou17^QaJV72djUJ7(dgyXbdJI#<+In%vue6}rYLp!pU5THwlm?6UojT$Z_wld*%1 zz7?&B`ClVBeJ3*|2YnY~2S`c2?AOTo6Ix#>^0-FaGnV5y=c0S&YD- zOguN0C`bWOqjw<19G#5T(w!ReQQu_+96k6G_7mvTG6XEe&#(JM)VUR5ATUOWG2UgS z>t)mNW&7);C&d6NX$VpmExb1h1XM|4IGQ8z9B4T9T{5U8B9BP3Y-68|}d){G|+N*qdkd*tS>)4_0 zNxQ4$eP+l=lvZXe=s1+;^#k+u2p9C1<<$qlEa)(LdAO@Ej$;z(pr==H+tKoTTj|l- zOkT|Dwmv8Ylt`gULie^F+0^8ARercO^5q~Z)Ff+NNVr_OWR;C*`-q)hhY^yozAjEv zPX2XTsu9`)7oskGDu1#CPEq*YY)D-cP73v%9o{@25|r+N+_YV))8>R>PoA2EMO+KS z2lfw=I(H0%mnbB!E2j!xp;Nj7ZCYwkIKxqq6BCJym)2wOJORC&;VL?Wx3-Bl{*wqY z4++KX?U(0`U$j9_8VDB(C`xJajs}#tw zqR9K@Pvr28veqCia^^q{)Dh?wN${3*JC`b$xaO#4Cw9a=l1ne&H=R^(VaguERW>}2 z$d9yZ;Fch8b(3o#kl?0s6>Y>2^a=-Ce;oes&tRjaH>W@2V7MhVb+7OWhYnt~>mi&v211gtqD_r?`jN zxS7(3m@YW0yoj0}aeZ{&Bg)@WTTM3i`2_?3kok{cEYJU4YR*pPR{vkUsXFM}*%>W3%Z6=i+tORXs{t}0yGwBz$yVm3m>pG*;B`{_h?{+CCws6I@Er@3Sc{Tmo| z>T(nRt|ye^+%32rteNaMIJ;HjCMsy;qB-~uBo`v&g*M8C35ym&UKroieidTB(?@m6 zHt0b;*{Ga{>VS(w=@%#9&2c3oC5nvlkqK(pqkGMA&4~9P1val#H^a>Z?VQ6hbNlk4$AL`i=UJcQrg{ZZj`!W<7xyjskmj)Nn#w z=QIUy3`{!a1-kdmntcoYYzIY9I#2FV3>*bYt#n&j1kfJ*qz4dN z-b!vyks|=XcO9&^s%4wm&{cl%&YtnLoSW^k3^a~eL4DXvLGo*F4t0e)<>WP6_p~&J zo3oWBPx+~LpyRBTM`09So@MD(uXsM3qOA<8eFhI9GI_vi8g6n8XVI%fYlxbjhkJu7 z^8TyZAcx`0-TxP!1^<}lqy4|r#`WJsp%t*Tb^41d`gYR#<~E8>`hT;8i2gratNerw zB0pRuehl+49}bb4PrzT%;RaXFDZpHQ{-rhF)O5B9j&^NevA{*>CqZ-O9oAPog zGZK}j{W)2rvW?mBbBqwDr#~wn@Agt7Pi(YM#l@ZTQE}cHI)6U(h_qN=t8ix`;d^Dt z&Lk(AXOq{=HGgxMo*1X>25c{E_)V(ZxbuWBm*}E8r>rP4f+gBR9nnn;Nbit==}eC(1mPW zZLDndjfjMdP4u0uoc`xn&cW8y!PwDJ>M!$QZ1Z2UVT$s$3o1z8*Yk^2C&`H{a;10` zqUs3g!6;{8vHFM z9$XGUJ&Vv*{PyHreS-WM*f`0R(X}iCt(fVO*4IG>%a`m$G&f0FNdr9AGdb#J@G2_X z*|sCuqJFglk`IkY-3RXiVk@p>>!=JwG~uepGE>SD&8HbH!M$jYB5;zDS}xpua?pi} zE<`Qd^4sy9D~R=8O&F!dPOuPOsiQ(b*IN^|zo2_aMvk|KKz9B3^7p7$H%6;B2Zo6$jeWoc{N9luTk6Y3@@5Sk@TK!le&EmRvSa#*^M|;AD)Z9c>LvU8*#mU6{#&|vG}hb$#Uo>AG&K2b{@}{5;41h# zGME%jCNO=*!Usyqpr<}aX(HHx(++y=BYDvQ`26$x355=SuzL;sOGmByWZuQdnj} z%>5Fz*W}V`;?~q$_X$KHhLHMb1Q`TzpEtr%kRXmByH{)j>Sw|h%I+@nuQBY0s@Rj_`rMgfEaSql%VxSN^HB#(p`bbjK&0HcW~ulK?>@7vyy zWUt>8`ZbjG$4Mz@G{?oRcHiJn>_G=+`^wNB3VGQR})02HWambS<=djZec4=d+U06jauw44}Q=vV^%999Vwgs zS`cXpECmU?7BZI2xWj&u;nI_3u(7n{bl;SHGz__y`d({8b^g`9hZ3GV zcZI zW73*L$;7lMK~hrf6VX==jGcH}s+5StytG(546Qd3(9Td-Gj9m8Z2hvAs}-iDFAvG6 zIC`h)Yorgqe~D6$+>fAn5PT4L;)cNS2?D|356WT6yTFA6%+5~ErXhXUPBY$P$%M3>m&z?in_Hb6lM!i098bzRSQH1+5w9qCm2HQ1qhj)>ajJ( zX8XqLv-!>vWUi=;})!zs6WK_LY1LZ*n-L%$A&gV!%!Y5yyytFWW?Hn zX>Lo9^3GfagV!yB3S?P z`yGwLMWsW%-byjn#ejcsN$5fue#9tL*hO(;%7rpJ97<2F2#vsRBtoJ!h6^94br9}$ zOWN+Y>f+db>-emau)+W{0_s!nu!6cP&xaP8kMb6^>Tg0c>p{9|MMRhSdAoqUTaqa2 zBM`biJ_Q+irytQ|44b(^FV>_J2LXxhemT@L?{tVgmTO@8=#IrP?)3+zCU?%_fYg4e z-&JOc0;vVJNZjZaOpgMSNf7Gzxc8ORbYd$K1tAeljpdpmzRbCx`Eko2_3QGIICyAN zkKO_cpc*`U-c=MYD9706zd<^~ljpwSOST81O~y~j6Z%)?8l%5!LQUVZt?}fChngoE z6XQN3>oB}V+)SRqL<&52ZLWKze}ud@4j)yej7w*`eEO%Vg<0u(ieC?Ss;mbKv%WdQ z9?iCY=&UrH?a#|9nze^%mu$gW5g+zU$?{inx>wPDLjvfdg< zk{kx#Ppjk1I-M_1ye2jvURogq<-+L2m~rZQl7TTJKtdj#mT#yaxtvS~S=c(z27^GZ zsWBWZ-z`6X-y65oO}>Yr$rBoBdmo|}Jv%iFxyfJdwP3)6!(4?0mI6G1 zRjGKtuP`2>TtLK1+v@3^8K`l^Z2S(k7!jNxRZzl?IZrF10^=mOd?<{gA3DJazKgt( zWf3XzE^a)7TMufxHk&2iDTQ*LaoCIfb?{JDn11!*vD3C~b*w7V5cH=5ow|E{pgJ^9a8t#!TEL z=KFBuY|`9r650N>pyYL~dzc|ChrpB>LW$c1$!a;~rS(BMEyazS0S2lU19A$qWaN12 zJwQ;{6)Hon$+6!1bT4Bei@QC6%3Fdz5ojng8u51YcjYLoNRp%}DwF%xGxOqqA|)m6 z2$UFJPiiDf4Pwb2+TT)*HNCrFQ}*r^lnR3Tiq;N|+1K3B`Qk6+XrGzssIrbe%B%K; z5Cjfm*jhMVTVX2(w?!&RGN`$&!ag%8SMuUFo77H%0-qXFRzH%2(m!quMEMZW(-8Ws z%F}G;y(IWMGUf>K9Y6-s(?J+o_%=5zKZ1;rdE9Ev;5S>>!EjL`YE*)ge+bKnt@K;n z@EtR~){lo&tX}#e6!Mz3LWtR9y`1T^gxa`EhZjIe&kPST7M9_Tk};Y-qF}WN(6>&D z|748g-7qR?7I;fgBn@|GKWFwhkOk1O2_2;jB;@Q_1c{EuIBaQ6fu(<-VkC-u7lLQC}V> z7LyGMC$v@a?IBSm$VVm_03R%B26-{+_W1mn}hiY;x$HTp_T40XC z62KJ4VwN9ym0NZj!M06x7UqacvI8s2?D8xzNOvM0;&LO@7ItLUSg;@<(df(QYL(Q& zAodrJpa+H|n*Ll)J1Uv&LbgRD*p}hWE4JQ?x`D%l4a<3wX$wPavJ7a4y>S}9(uT6( z{KeH8Vztj&Y|fysrb8BU`s_w~{j@LB8MD`{o#ENO(a|#DO+udJ`uKdEX#F$M0EBnp zL^u&nQjtDZu-JWg8aX2jKUDvQkC@Q8vJFCNC3-2H(H=X zZwk^xOm>REBW^6Q-Bc&T5s4N6*l^v`NgO^Iz;-+YG-||1NkqXe)czXmjDtmL2+1Jf z7Ru3Rmn>~m`c0c=XiMmv(?6gibpTI6UGGqVQgjxN@DaFh)4_(FaesRbIPWr(kW9O? z8vq_|R6yv54voPOX@$Ttg5NsoG*Z-Db>$BPriA zyP7LMYGFX2ySOI;;zqP>Y?cN8X8N5To1mO1-`oSze$I|%M4aLYmy=ke^yKU?E5~>w zM(!E?ppUjb2^m8?oA^8Y7<>!LTq54{7E?k`zy!tjgo;AMX*7U0bZmgh@cUEajB#c^z z>QUiBh7_!Ij?%`vviM9YAFL4a$e_Eh#)1B1Ew)$+bG}iuHc(~ax8Wk;@|X05fGw z9%FzdsnxacYS=~nW(xvIwA&I>KRiBuz&DJDC!sd_W-WO@3UdcTC`$(;niWl1g{>O8 z9mu9%ym+Z7XE8n29q9#0oW~-Hf&?u{0GEP|AaC+Ut-p>WV0G^hOQL-lbj6uIP3|p0 zRKI#abLog%z@;pVW8jbTZx5e67R;d9c+-8%RNHtlQ;s&H#ycg64I z1827`!Z>U^up~GwgNfFyWP3nYk#_4(Y4*-FXNaX=?{Oveszj}>2)1Lpdg_j#u3)1e zV2o^=-oV-KP}^JBT+l1P-cKJp4Xgn#xWO?-*3Z6l0Wt5q%lNOHUpc9RfjOHj=wVD9 zpNi;Q=RZz&18#T$Bk5b;3xGeJ5w~J>Kalzapqr7&MNrv9u{DS)4v~`IiKUrN)$n6581xcD9a4qsRi!-{g#i& zj)7VeZ~y>ig#QQihyS^I6LvGSa&|O#F%~hmGUhilG6{jjyyNq3fhV&A0^%CuOHpx3j&zzEAA+T?oyMQ z3`E#$Qdzznal>=nxZd=w)po&VEmfS!Zu;z3bQ8%3zpgO?!FNvyw;B zLZ~FJkKKz*tgL_mTddBZ(*qi@ad|18s#f1I&$mjsWfZG@EE*ZB0M(E=$v1#+ z;+@SL+7Oto$#G`e$+qLoWXnxX58!HVeQ(FXfy+VEcFJ-npytkEm0x$UW*%-^w$vsa z-RCUHF|s?6;r>KkB($a2GMUVm+76Z&Z80;q1C*{vaK=$B?&<|B>pNBjM$cow;zMd^ z$h?}VnBM`Y&^R=f$w;9=^^jMAPtP`;6~=`v^KU-|QmG{2j%rar$~*@tQSlAJho*RW zq4CX(UK=S3-p4ZJ-~uKJJX$(WvXS{mquo@nFTwhFz+w;-m-LQ}t>uVq9Z%1arAued zDHq$HU3JCW_4le%qO=B4!1d&#Hopuml+h&>U5Lpa>_;MJ?hK`0$bM&4;#MFh@-+IO zsKDXmnia5X8Nm_0t>;)n;BO#W2xOdzT&nml30UA%E7Kh6+>*+6=&BN_16-)n!SJhq z!@lOe9>++k_SB5+qgm-s3v(8&ePrOWf*{8hKU6-DEsRGBAQ<7onFH8;EwwO7TzR`5&c|zw_Y#+108J?uB`j)AiEz@p>#-!~IDHUlnH{<+^1jxDw2|_;@}y zAVQ&nSRVGgw74QPb*RUcXL?{lk;-HUy1XFLKg$Tp0`@3H$Dt<#Yw9p2C0!~jYZUpO z-uJDifD1JY61BR%dM>&A2*}cv7rK)oNmLt)J{wSRXHocmKt3QgqWGo8ZBWR_26=&r zL1VKD7l|%vv0aG3Xuf4agU#GLj0n2mz__)unzL*OUT|lofs4g7AGJ}r;{B`{f+I^# z#5mQZ!kf=Z@~Gr{P`zy#Wu}6Unvgljv+&vLcRT3gszO&+_AF7%j#xnr&Yd7HW_bUM zxwd7_2uYwaKF{@Rq&Q=OCUQcYo z;kyox*ny`btP@3HjJzll<5;BYu?>o$&PFjZA3tI`^lx&#@6x&Tr?i3d%! zh%ao(iY-WmzAf^?;g-JmoX@RXoptH@MUESUxx4^$b^Msfh6N%$s^jSrAkqy!0SKzk z7qCbG$v7Pfc6oVr2&1aFNlJO)1z7_uxxV-+?!e<$sM@b*&~-Kb3v#7JNqx~g$uy)r zNxc2M39CC=uqw%eGPmb?^8F~!qLB1b^@6EIg9_)CY!(G|*d}nm4D$-0daQ1>-;T&2UWT% zE>?nMwk`iL?&g7w{of$&rxNRAF3}bF*%uujx=^mSZ+CXi;D4zOlSnl*-8vTJx5WYinV!r=F1VJ)Z);`jiQX?&@_q&r5dOi+N`+d(4tLazoPj|Z3r(~etm(& zpxtM9e_#ID+p#we;nib$WPJ^&8pE&aU}tv{l0A%E2TiIT0jDQKlpcY6FQYhtvMpo< z=dmRlu5w~7}*_P|VJ z<=5}XM*q_a(FLTw)ofAhK5Bv-jX)E0ISXdLS@kHhNA$xJ1=o!H4*Oe=9GIqwjxKEj zg)y!+fjxAS|7QNwJ13;7ZJo#T!W^H6y)Y^ zw&axO*Nb`a`^r+v*Xi`Rx}6X2CvzF>6Na0tsVg#9rxgPzXHF3qv&SEv5h9QPiYz0D z?rVE#Z@Ajn7$gk%Uv||TzG4OP9%vfips-q9Z*o}@IU|w^Gx@D1T#u!@#nCc(Ohyh} zY5tz7VCxem7qqhnfbS8bohIc%>t82ofI6y58M{PZ`>Uj9jdE*>QkLJ&UP0OXER?mB zpZVm_;It5#K(s|#UTN7&r;?Aza*$ftWz?sRjsp})-JnUsiu4Do)jV$Ki7VH6wzG}` zsyj&Cn>+~|J=A>Jq6rqo=@90bWo?-!pB0-vAv#E#KByr5`zmynckkXtHAkh8`I!S* z8VBg`zVxtPvo!X+0_8M%z_8$fIY=LZ5jn*KCYl`7fY&7$Us8$F;iYNg`S4ShOu<}_ zkaKcdGCf8bbjpdxT3qmmPcJKEJiq_!m_5duNo(c*$m9isPK5IUp^LqHz6apli5|vO zn%jDu%I3$phkZjC=85uYP?fI;`WATxnS7g=V_RTJp{YN@)T|zV>DtBjTE$`4-h)BC z!M^kA7yX1pnv^e4J%ZqH5HfuN1m?D(WaYE3E6~GA=9jqr!7idMQ|3T+LE%WbE4HxU zPhDL*)DKdQ(!NB22+F7BM!iW*h9+5ue9on~r7DJ;ffn6k7yG?O4$>y4ce5+OI&)NR zaG(W|d&?D7AzXbtMM0a2Pa3YK4+ZBO`O9BvcyIBK@i6#@W-CnusD|S?gARN#xFpBZ{OVAwUeo8PZAjTI2e*7 zNrnmd%cCFSwb@X>!F@mxhdQNQ&@4HckBx zF8!y6#V9Dqp^4KGBg~{{99VO~4Rprw-ezt;nh;8Jv$gd)=JrBERYe)|=5j+zM^%OC zM|Cs{ENfoV0re)=HXCc`a9f{6X4_IWJ>)aGzh$-}#Gm<=dlq=i4StvNG>V%)QT)qd z*mriTpo5nR>TJIAVgnti$siIVwdf+S-GD%#uJlzD1!$bzq?=ZpfaHEkBc7L%f66`< zWUHuGRPU2IWe>W%kTv=lN`j}pee6elhuC#*k-{-NYZ2Qbcwev5-6!n7rR+yUo)HE9|+z!rnXEu%KHX1KxT7 zD@e6SY3qTiPiT-$utz0CwwX)kW-c~Iu_X0bw1|`_czeatcFcH+Xn$dYW zx=gTnj(fxHOxhxvxN}A>mfll~L8R(7T)F5T?YXWC11=yFvg)TRFnHwDv0|HQFbp(J zvYm0hY76wCm75q(6<;SmKMNG}-ZWRsqXi@u6g(j$?SR2jbwOXyF`#$tm_@x{<`-hO zBqSLpCbk-Day!;j$O3(O{kd6wr5UCw^jCS1Q)$h_D&?NN+=IOf3ChIfCprQ(6uVAc z0Rl@GmVYPnmCE(*R9l`MhGk+I`PfiY9~l2Nz!$C_;`W-53$*()tdP6aA3}Y-@T&9O zcp8I)+H;L6jj-=^ZM492fv!{pCOpi7?igO*VYw3BoXky;lL=_PC3F`G%?>eoaS*`r zJn+1p+GR4pZBWAvLCYE~jMygi8R|^EffOacjqL$}At1$){7})14xc+U@(O9wx_TgW zGK0l4p-TA)toUA;Cx6a*ghnn$*`dnPvgW~Cb(ip&Xl-+$0g9Yw1Vz2Mbj$cj9EJ4F zu$hMs&t1uI46S-^2UXA((qm$JS2F-|aJsp03Ny*15q@&%Jc)54Eq6IyR6pvGpb=ne zcx_Ja7?!GhU0@D0eT3V1J@Z*d!0elo_Dni?s^8OE_gL`>xPWE1wU|MV*jA~(aW8z4 zAKO!L_mH0=r|2<`#J+5i?&|gm_9-ZD>w8FaXDai60(RGeLn(tG=Zb1kkk&|TzGZVi zLkC|HQ~n7V|~7ecMiY9X`ckA%A)skQS0ShlDzI`av$^>%t3v!f$>Cn^8|w zpN4v_P~r?w3H{I;UZwe}SciSBg3Yc>2?$7H0#8iDtDPT%HGiINI&p_X;Bo9eZ!iWVsD;hVFYt|>~iuZXQBXk{A)0REH*tX;gJ;; zBiXbUzZQdrIA&o~dZP|RUq?Ox3io^w0UiZ06t6Yn7z2K{FZ!}{OO=YR{M0wLwDz}K zc`H{N*Sl)|q_0wp=`W+Bk2{acl`5#?nRJzRSig3(0wH^^T9bpjs9MC0Byb6=MB{`I3HG%q6S?U0<6h`A*@ima{>9d_Ox@zr$t`+@-=4h zQfC9JCJ`*G#+0*qltT`xA-bocguBGO7zkHPp|ScwDk8=o81AbZ$qaGd#Ypt7*ebI! z?|qVEs!CyC%Z~_ZgFM+&oc>l{tNdvRu!tN>YK*MnqlYSy9Tmou@U<6{`K2LBL`V|^ z1`#H8uRp>|$SHJLZALY5+k|epMTbN~6s4i;3U@I@Is`8{cNP9(Pb%yWOr)|)?iz_D zMR9i7Dk@9IWp}Oj23us>_i)w*5U`3%-Sq2NXQdMh$?z6cur=kB-L?Zg*vfn5(_GF` zv)f$S#y`+8bKj$V0a=7jVm#vQ5Afv?LRiXkHf`gl(~6zt*Fgr1qQADR0vrl~kUXR+}mh$C+YJVg|=gN5Vt7$DVr*qA&IRLi(y3x*?OU`!z6 z?)@c?hi7M8-pq$sJdU`}JvLXYpB zK7K^9f8LQ+fLE`<&PH741OR?wKrS(g!|C7EjRJjqD1I~X1uKbNDLy<_95BhwBJ@Ob zPC=uXg=_+jTl04fonRHD5b(IlF4>Bny1IL?%lg85$_k!xeSNXHNBFN>bv{SJ($)lL zPCHvPod>k*beuFL@R&AH=n@iDsZh%cmJ6#;<6AO5Sp3H0VFm^qFqP?(@gQ%g30KT# zyjKP=LqT&lx%14D1y!Vqsb6LnX$g3MuPz-wa(@PYK6d{OY%1#-T-MP^JrRy`KHpLz zKXrGv&rZ4iYh^NL5Qa+F%9Zh>2y_+cnwq}YbFjd}2~|~ugRY z*$*qj4Y&~;)VV5?Z4r3%dn%K7B_IPg_NL-ap7cQF8Ks8NY$M&=7k=w{jDNeQy}seK zTs}B#khL6CE+|n8KoYR~9XRs)KzO*HkhxCRa*k-&`-lIY(i7@jc6WdI?Hc0%%n%Ba z(_j(5v_ zNQQfbzIQS58Wjg4w2ig{5I@}x@8f6}xC8Mjb^`7bk}Ip(Tbp7#^GFFB>2=PB(SmhP4!KoTdkCB=R=R)x%oo@%B~l*(Wv%t-J4e{~1Q#jVeP_ zGjLH-u1xos);96*`R7OzLWdV}20?UTg1b2;2uYvo2{;D;`E*GFFntEy0d_#N)}RRF zjN2LCb++Vv(_cXjj-wAYY5_qXKa@YuKm7yPmj_`!d~uVL=nbVV>n_T){S5=4|kC{{~36kDm_J^UF( z<%Dy`o7EckAV;)v_uZJA?NvG3*V271hagLK4k3`;tkb_I>G5%@;B%eMoYPz4glwb&KbS$-9Jxmfj0YBUY=oHrn^-FN@GDT=gJP4kz|oUegv_UXho?JT4l( zET4yVD$FUfxa}!s9G9eOyRO(UHFHzmH~l$MJSi?fpHJzi=p5z^UIWX1(KSXQ;(mS~ zevFrbdSvWZO(O*@vtA1Uf{R;GM0?k~MWwDkG6-cwJGyPg^HI?R59Ol?^^McwFRO`i z`7-5FD~yLt_wrIhv=K_{rpSkd&c*8_7YND@Nu_QIwZ&z&blHnL32YacM~My+y^&v= zi~yHNa5`t<%7pXTiV1AhU?Zw1?AZik%0teO=&4{bb6ky(4azM}p(t|>5Mtc-U-#li z>f$M)N3Xu_C2KkK*^U-b@kKC2Nn&>4GzJTFa&J6{QmIOzuS? z=Gji+}}2oZI}8*gJ(7 zBRghkm$^sv0+fgBPI+3wlt%y{haTlgEAtfY+)HE9O0>Gd{BLl~qw*Gsw#5;LF>@B~ zsmr4;3q$vijVj-a+5|lU7l$imU5@0|cGdY&(|AFdn&t_NQYjoYMN`vI+Q(c1DV#fk znwc;12^I0_+LmQQ&ZJH4+oIOhC*~FN5*vw{<{kIQ7OFah*y9tk$j8p7k_03F?lG>o z;lTSr0sx32|EK&a!T&zLDsJQGq;F;QU)qYJR3W`IjgfzR#TPt1Gq~5QH|D$QZ33#T z*Sj=C&|<29$P*csqvEXJe6VFGV6P5~Y+GGxyXiUFxDOF#xyfG)A-@3P0qq`v`MC=t==#sW2 zuIZtqr!R>kxV}(J{z5i~%E4`7@J+zLgDXGm!PC+onK4Zc=(42N^`Ni*#1no*f6;pD zMJpkv0Sr6dH7hr%yU=-C+~cJq)hqO!zBfc9bzGwkcui4EIe-NX@d@}BgYS0|%Y z48@k+9i!yvboB&U_A2MtW6#W_rx8r3pZ3|XYV?1V0~cePtR34;S4pJLh3Zq5F3Sz< zBO9!gk3`;K6viMn0Gy|)bGxT|!^QjtE|2DNBeE7js#A+3rm`kS*EO}4pTo=LP5WD7FHkjz2BQ$d4`_U4MBsUx(SrhF)@eT~=6~rbnYwDMed_kSa7> zeJQ0jS7=x{mxV*CQc}gKox6WZp0{i0g#To>DkdjFlb?B6{XPoy?yKSjeD?`uwJ#vp zZ8@xtF+I2jID$(U=kF-C@z}>$g~qJ08+CN-AS^ECL*GKFVGYC_)vPqDomepzn0Cd%P6QW%8j(Mc0ncf z{3s;CF#$PB0V82C(7%u`4*tTqxdjg7jmxJNlWmK4m)7e zOddfRyTIDfB9E^>Mo~e|*P6MRUB~NJ3DSXwS6sNE7R_d;d#4@Z z8@+|ID<+YdNH^yq0jv1>RBKTv){k+$2XhH*Q4()FHL?$~?h(U$pRsAkpPSN5q)m}* z{5YM>>5-n`nuL)BaB@%mu~sBK-7P;(_S2oh{j3aG9j}azK9kL)s`E4{`CIJr^XR7f zsDIak5-VD{3l`Zrcv;&{sK&~4AbO|%wesFP#}nTmn&>;26MhT!>29?W?}hvsgyc|( zQW0~`CVdiuJbo0Xn%A>$i?Q9;VWg9T9;4d0dORJbU;$LhJ@F*FI>1A?ffblM-zfg%}+U9fbTkWi^c6arS4TD&wc zzd~{ljm{{ugTmr*L`IlwVSvustiA?eB=p%FMBh9h2BShL}Y3xJiWQ%Wit)RJ!gVjY&QKq5_*`)9!0PzbYp_C)JD`o zVwe@&70oTwG$lxg?T(T?M*O)B_}haqR!0M2o>Nl5BV|rU|CT* zFuRVn@Ao2TPn2pJDe@7#Oc2kh_*{KJh(Y6BF}3~sh_j$Y;Du;UcG%Vi;r1mMMTv=SZT_xWJZ`?6Poe?>~Hg}4$oK>88*@oeanA0{3$pM73G)iFxsO2gB8}0zJ$d~hw=!gh4^DxbMIq}Dp24NTgEe>(L&+L)3TQIVFa@+pEiI zY9}B_t{)kP!Lija+uek!q3loMB0zh-a$=7#*!vfHcS~iaf)iaC!gm4Mz@vsHm_hVt z_Gd8-FklUSA@BryBV3pGMeaqosrRqk1_#>iVT13W`r|*TN%w;rqz57!R0k&Q3xUg` zSQ1Bu>D*!IqefT~cgkqd?m_RXfZHMtAn=gkq}@Z#whp0t}U5&P@Vq1EyCN&FQ=1k=%1#cI>PN2pq~ z;(uMMY1K(#6CB=g>Y}s{j>L8P`=55@!O}|TtObBv9huTgP1rV8Lch9}xgUk72AP%g zZT-VLN)3E|N125WS-1(al7ym>v}4eXIkS*-Eawe2{VJ-4ls|T$8}g1+`s<$S8Twvmnby=2>{TJ{QpPe&i{-J{>SlOPlx|!Z&uvK-*21? z{!wevP_^XVa%+LeZl`$y5FWzhM?&J~zh&6UvME>t8f(`1*Bj@8e(U`G!5TJAWO($^ z$#}Q;IMj(r#URmEWR_-Djmh-m8Ly5CX2@p3F3^@lAqsp3iZHdS zxH>PCi>arnr(#rvnJ!#ExKgb`ZN%BUd7|3H^wF3|DCV~o5fgOuAGW%-MUOj9TqIOV zLpi;BjpxD|V}v);DocoahTydu%TKbXHQW8dnbJktPgiv_$)b*6nK2D?dV1&k3x_z= z7Gn!HSz?MER5i74%UfKXHY76Y8Z8bxI(dF{WiUcC>>)S{7z#AXa&x~vARl6;R`Q1# zs9Y5}ttE)yhFv?l=?f~?2QqtrrKO4rRH@U?;p|p&xa+{AiWe%oO!{-lfB?!H_!-h!VK(Ui6@TX>FB}Bti=qdaE9Q@`2?zq zEI<66YCLa{ca8G4M4r!^tF7Bn+JmV!_N^p4>isSgQJyX8jkP;$v^z{&nAKaHYpOkY z72bJsh%}px=dC|q&o*y9rd_)qvL1M2W@@}I5>~>@AWu?hvN>JNMiQt6J&qIgjWydf z83w4YsL?rjO^%&r8mo-9r!i7pBQx_lKv6`US<}#&8A>(zkvFv_kS8|HsIhC#FjQ3xwRUtuEiVnPNmcKrmn7+M zr$z3ye>h31mN+f1cbfJ=!>PK~n5S*i6pI?R%-$w*ENO6+dKPe3TONiutZqNkCD1NE z+odMYJ4E>}_9lMnnqw4_M5)LLruH`$W&<`dNa2^K)NJ$@&KbOEK&PBptOQGEu+T>> zb}Foyb1IJO7jKb=B#DlD2V~~Kn9FOr}1=}0pwcwmXD?gumq7gX)n z4i$H7IOPUjMTVGbiceh4YRmvOPt`4M0<4m^tnt5$e%llmH8?}fcS-RtY!RY@FVI+M zOTEl}NyYG%O5sfTkI{!fIGmjhtGF66Sz_8O)_0i=H*;+@=C?D$G{?&n@3{!QWMU8W zj*4(|a#(4smD;P!xV7D#9-DXHMZ-N!LafC11T2E7LT$+Cgh=0nO@R&8VX$V-AQy8# z{t@Xo<1dcd!T0xHoN45FP7YQ2#gsdzBSHVB{QxfLSIjH*arP(m25;Yu{~@XBxS!31 z>BqNe3m*%-F=FIn<5-hOD&#D*lmI`6F+|`IrVsqlxeKvBMdp&bq=czQu(=kM~th)?uRg3f~`Sg$!k76bWkq^JX*TF(|TS1B;$WOa#u2u0DR|GhH3JboF=H0;kT=d~i0bf=>N_)sduI19QDuWjuB*izQ}w zG_LL_fZuAJj$ghgNR$>wlo>^&u`?_OSl!|d?f&$9tJyv9DkJ*_bN{2Z0=$+7#HD%> zEii%PQ}CJgqs`veb@;L}>os+1TcuLa&C+QnK#_iM!OlIDGJQ8}@xx_EWJ3}60~{xI zEiWH$>D3wag8d)*Wf(V9xgX#Q!eg}^XM_fPkK#C1?{f+cKFQ|jwF``g z>uvs~V$v`Rjs*JLUqlb51Avx{1sYYQ+**_X`2!rWv0V*I7ESY~!4~(2Pj{!KKI@?{ z=)uTOk6Z$n?uU_QQ7(-d-p$Nb9B-wp<({wf`Ir@~u+itg1N#Jg44o$H>gF(G2rx#D zDi!L*k`shQ-Lc0Y-oQChY5Z5>CIbaxlc?pl{49>>g8CNikFMtQN2UqKxl48>k{y+8 zSMX6IuB*GJ;7M1ot(`TiBsxVEUB*O;&d#{h&jdS^ZTwQrWQPM}kEsJsOJ-Z9+#clm z?k79_Y(~%!+xXwp*g>3B`g3MaK|J7tIuCV__Av6^fA^D=tot3GVen#d(9Vqv-r>S7 z%*@SLnx*jg;Zdo{riNZ|S}bR$anV(v_C-&NPSz>>I0zS1#AEvhABkh@hfi9FKC1RTzNJGwY#9fAeh6KOaVN6$0+(Bp>f8I${5^MmO!c@885rtLE#G z>LIx737_Paz2KH}@CJvABd)j0qIg| zBp&)Cd1qI}Rmmf+PrZJR_Q#d@M)6iDSUGJuGIEl4ep4*1Z@sulGP3v3w-HlQbG`Sb{P(`|=afE#wulEtZ&kq`*O_Q?g!AreW-W z+2!$-@+GMLQgka?_TztN?7e~-M}+!DRRHCn z`ouTyzDlX8Q-jTqp3XahMqY%K{=CEKZ(aG;620R zK&I?GH*Wv|R=sNymEm@DX}s0hmt8G>VBo&fF|o@7bDI&oZ%iZx(b0VfzGXY{QIGK9zkKDeAS!KF;E) zv6Zh8(JF4*}vwYKXbWg7w7Wm|17AmwOBKAbRm@RUSu{uw+v(^y$=qSl9wSy;(uH3LSfb5W4%qj;U!JR7#Q5 z#H#KnEc&!Qyl{p*vj1&!494LN@2f>@&o>!HF>x+`uO2zI;51j*oXG75|H|OP2x-|J zMwJ+>l!D_YMU{kBk=Tc_hNF64N%r!5`#gycoaQ+nD(Sv(`-52=iw==>csvlIlW)FQ z#sW}{!q(uF?O@H^vXe#pb$9nf%ht}N?h98{1nWmjVF!@#c{`s5fqzVv=*)-v{_#ba z-|NI9OEDzUlW)5J*)QD>Jwscsa2$+Q5zhtkXxaI0@OQY@KL9^P7R`O`R)+RF6HZR> z9$8PRx;Zi>`%!1&9>C)|xoQGgzNY&a*r*`5L$7yE=g1n#t=GQM=ZM|Zh=(Vt6)|Y( zu7e27p^Ttmvb{o+CAXL%T^D)>0gb<77;&?L>$V{&c_Y@q)qb2`k22^^q;HoLgX@h8 zmi4u{UfP~j$9&uD-NGF{YT}ZvpGis1vY;o<6!Wh625C_^6Sj3;sF+Czf9X1E$7N^! zX*vU%a*m)0TkDCBe2s9b7TXE*-{Ug{tOXEOO_=0*yO2bzq)Gn5?dwHVBPcQNA*0O_&`$!53 zbQRz{);F9J{VIv#fe-!hc?n%K1g(~vrn11YX+=HWFw633lhM4QUv}FMBPS=<@M_HRQF!lDL4%7?TsIn+Qs-dt@?z8;x+))Og7ch8g8TWv00m+2K~b?}_vFuF$BDzq(k91RTdO7U?e zW#QxCj1mdHkXVMv$2<5YUdz$_- zPn3Mw4D79zRID4iE9VHa>dHAa>z9WHKPj&9WJ-l575O+Poa4V#HYPl;I&Yg>m(=hT z$*eC}NHE zi7ny)?qQm{nNsG}UIB?}Zbn)`VM;i=o@bi_S_RLj=7iTue5oE(g1qIm&nwHoI*iw}0r!YCLOL0v zk0d8#UFmiVcY;M#Swf5T3)1)Ql20i!^bc@w*4Xe!H+aq;-iLEL&iA9Nnz74TsiovD z`9Z!ulq`IvcjrmP%1C)rzIh(%rff#tPbYL8rnqh(1C`!$dskCX_0H_OxG!HUmpW?l zYnc&gFof~NRi2w4m&}r$8GMOG>Bz?6x@$4guozC&VH?a5kg1XLMUF+y4i9&g8Y7C7 zzitzSDiob9N&pL6#^KE)x<&7+kF#X3a*Vk_>_?Ql^3)LzTH14Dl8bn7y8?!hK!HIK zL6Ja~z*ZIO*F|cKs(9gKhG~@8RX~xWUQ*;-eT%hwaScdBUl8V)(D4NzF<0Gf?MkiO zy&X+=2}iu+z;{wLf5!lV3_C9&*Gat3=#Wkf*~wU9b8~8P2yCO)Xin`Q?TxXe-Nu2) z;xUron3>}@vPFTl9V>5}H`@EYV7{Y9H(EWIXb?hXNFrF0vi$Ob6_rSZk`Ty)_{FD} z0+l0a6UbE;(|X2bn|*~Va&v8Tk_Qeshw`I9#VcH<^CfPZeAL~;`vb7Tprrg-616aw z5=HA|R3pC_DvJGEziqaEh}zS-PSWmV)c1CkX+rL1C0So>n-`^gUgzsSd26!(w@D?_ z^sXOj*M&-3+B_EM^$xXTo_^{5QJ9I^^kYzZvvoq-x_1>6;DK9o>3v-rEv=q@BY-SB zxbRz07*&}?=N_dIIjPv zPiCj1&i8gRaIV4_L_kNmG)x~%(VFyt_jA*jb`nf7t1@dO|Xl-rH?fk0I6cWN1C^aZf{p76MwJ32B;9nl#+qQF!;v-|;dHLAfp1 z#FMpZUoq#t3A-sZ1!bJe2N=BsUer-da@W3c?rUHFWizVl8APh~gAZS^nL!QnU20+{ zIao;b!VTi5GUX3j=)G7XQ&N1MNQg=CyD}D;a55s)1YS|Xe0WM$hc5Qo#XKh^B{)1& zMVm*X z>u0m3RIq&yu!mT?Nc%g%6|dK+DKb+T)r;j(>j&o!gfnwi;@f?_%=%4-_TS9rba}6D z+-g3eX^~(AWa;Gy(n%T`yOog?r}7s@`|;<`&!q>j(E8QU)Osdd>EWey6&Q? zOkWz*uvDyh!|jkG_gaQoQmuqm=OfE<9m|aJ#3z@LTNSmiEUf0%XMB`lc4n~@IUmzQ z%ZdAopAre{Ud8sJ)<>zw~dGzAR3*?A+^|Xon037^f@wE#+Cai=sE6d zp0LtBC2c85kJVgLgH3KnvU zxaoIts8250ICJrF>}O(Z<|?l$-ce+40!6ARGC}BLrg60)U^47?)%PC-0*lhq+`;34 z>I`^7#u`l0P7N`Mu(PbC`uesJ`wiLY(KE|(JFZy*4~R>HT3)tTRpE^1RDGUK_bADo z_N2+3xrE3HYFKX8wGp(G6T2%cBJc!mSOFV9!;s!>5Nk&UeY_WG57D*e2&_W_rA6ZK zFQ$S$9x94c$6Y_l&2yV|>G=o^Z_l||zlLW5>6H^x(Yi`Wvc4)(Q&F=5dtKgR+^Dv4 ze#N&u%>quu!?&cqZ10jb-*c&I$BY_vq^0HW)l^}Nk1|Y}n~_#=T9Y5I5(2o!mwkL|$e?#9Nyw7!=cIDA56Sv<6v3;fD7039Eo^yNkyQ#yZ z1KXs4qd!)a`jzQ!X5(UE=@R@0GaG-cCsl^KIXFAs@US_-vN$jarbnen(dC#oU>G+< zOYxB3BO>s&p*c(0ZC*Wo;z(zFTz2*klRJLbUtaJYO>~+I*<%REyBBj$_y{YAch+@7 zSIhrBx8zgl@2J)F-t`KUd=QQT!E>U37mdA8`3QV78OKb{@`#Q^DIVI=l+1iX$m450 zn#Q(L72+udAvTrTG4@rxkqfFXZ6gG@N*G;r%X)f9o^a9RD4Qh@X=UJC`%;ETTP}&D zQqACgD`lTq<`lgY$vx{DvP4iGwmr5Vw(n|aOS)A+PK14{$!s)gwLV`AXRt)5*#5pI zzn0};`3={$bYw*CY)&?7`zCGFL3YD4p*RI=kbSW}LD0eq4bJ4QX5XuJS*>#| zG{PkZ`{sphB{!0=#<)aA9+YqxB*ALAE)5t*vrsKsK_ogXcm~czv66`36a;Oy=^$T4 zAv<>_-z+Hx@pvD2d>@V_@5J9k#9%Lm5aWdCKHmEVU`5g@R0ezF6PZrRVuuUrQL_=T zuTRji0T4)k1?z6>`JI%ZFx$&6U6LO|u*>x*=u<0}pWsBd&$x9kT|l-MRJp;9Dy5$p z558d{a=>xcfW%F*|o?s#2?fBW$4wqs(g^IW{56s^0BX zA3omAR9M`6u>*{uvu@jqYp0T)ca|~V8)u7I_%w5-i>}=~m}Po54~qYRZ2W@t2lfxP z+vc?9aTg+3CMAyCW}L8}Qq~N%4-WS9c+;OjT~p!=C?-xqX3b1`@iMx$MHdqfRrq8a%hEyORbTZ3R z{niN5+H+C6l+CgOhlD>bDd(AC(8kY2+P}P{cz=0GfqkHMH%_p^Guij1GN%5PqmxC^ zHl%-ZfF6Hb!udTYiv+)BD25=C4@WmC&3^HZtZ~{_OGS4pMGFH8Fi5kQMkQ#IDQGk9 zlMhOJh!eIvot=&J-I&-ALIrL#?e;fIe#fb#tz{6(mYewCCnm+lMNpp!a3kY3Y`rd$ z2lcGx$fzz-tu{-1+xUh?h#{L+Jn^iCy^rlcw`0uIeDXtj8K&Ms;1xc)gs_;ogSkvn z7ee*RaL^3I7o0~71cFBf;!B;ce6&a#mgpmMpJ20+K#f%V37b$Q$7!o1(Ksx14)VAi?qMkj@rf&0SpC<;t52 zYSq^~EK`zg=btz4BT@XW5Ac$pucQ|NzMDjdMz2Deswo7wA_Vt>crUfY#b{O@ z7RKUmDcC?tFTCSIs|@U=Z>LcjV7oM(mRKfsjU$;kiE=TaH=0non466@;QbosX1fvm zjbDS^7rsyCZs$`IM%>=hzjl;ev^JIK`P^PYZW;U4EnH_;MdQ0f_9{*G=R^TJv&)Cs z`nzv6F0wD!zO$%isW&uPnBE)j;|{-GUNfp>li7KkoxoQ0&D8K~nw0>j$4#u4cELtJ%`xhdSp#O-&~~ z$A>K@ISVXaz1T8&e3#lh(QX?i{AQYYxn$UlSaK3rjc->Ir?n~e`gscZW**(fY=NzP z3cp)Gn|_wQL={w?Dy^?rN>4}V$`xaBIY-kgPmGP^t*nUBti)V5hH#V<6Bq8-jP>FX z?7KHaVgtLVJT&KfZkL%nk#yHD+Z_$Qw-!7BU*b0u*r_2ziPpi$C-???$4d#k7%rSR@#>IzJa9d z<~4m`p`e2jy3r=kl7_+%DF>zJ;&<8>q$v(v-?s^-Yh+={TcUgLJa(21M#PbUAXCST zf#9>vDPN1oP>!v~@@-*VTa{s*x@T+-EL>~~no|hO+tdVQ9P;{_qg3^1RsE0dODq$! zKCYs8IRZ2l$>nrlL!Iq@l+^CZzdYlKhzx0RuT+0q~O-PrY`g% z%ya9_{B+~V7-VCHpTy{H#%)K@xCzzw>l`0pH^ z2G4;U9%iO5icLaUHy&+SMb|5dewXY^M$ijQuyZ1EKW}geeEBSkO7)7NTO}k(@Qt|f zVGb`p_95iv3j&gH&_huE*4@;DW|VkrX&w+qBG#(H*eBB&PTU1)BH6iQ1E;Q%T9j*?$7=~DV zx1rp3>t~NuPBA_O`O#Z-0DksZ*f`W!fA?9HwFN9oq*z!@JXly1Kl&`pq60Qk1Zn81 z3Tta9gLNU`|LLiY%fPDSWEHS}M)6_&*o!VBLWU-zqo}K)3WgX7>8Spzlon_sfaAw6 z*MEFOF{B!PNd0^KnWBf|4H(qT3+!VDJtfO|kyojp9gAUACP`u#UZ^yGM14CGCQQy{fYlf!tNi5~Zjd6;c%r(m8Yq5e1$ zJr)`BFz*0Qfjm{J_;DtB?yeLDvzPnS%+ur0^HwpNDT?>hxD%s&kCV`oJjF0bCIY7* zo&J!}lN9AKKn|c&fKC|`dYp=$ScZ8s(G&SS^*7?^u|t^RmgoN={<{dYR?mD%gpDe_I`S zTp0buT+Fl6=le_HU%XoVkAIJ7)1Yh^j4c0OF@7V9K9|LcA)63+qU?z?s>fB)CulHl z?7)~4Rev=rc3d2N=vM?o{9){g;-{POIgUXe4#TX};=~g%rx;Z}&Om>F;l*Gar2LBU ZFT-s5TKEK5e+aW<;r)2_Da-!3^ZaSQ?4^gIwir zL;0_S()=>wBEpJFw9+CE(vuTXk~Fk)u#z;C(~~m|3iOLiyGQqvG^65E(o zR>uD;De^y)x;p6F*%>?hw?l3`G*WK7dL}yg#^j`Z3w}VhceXd0#cti z&Pcq4-u?oAyPndWjGmFK=<&YpJI5!$rCN_CSMaA`u4z&GsXKjExgb_-BiRJSL_6Gs6pcA+iu0~i#gF^3 z2@wsygO)B`IJ~GrUj28;L|35(iuW?JRkJafbCZ%pnng4s!==TqNf|9&@uj*}hq!zf zF(8i%FD~Ni^oNwxF8yN=m?cOx>Y9eiEIW5%j@8uGD``6SuPKumsY&~Z2Tk>6bIW4+ z_*{94`}8=;GGn5SK5FnuyG9DxsnMa%VxrYw-bc3prJCpe>45#LX${rH2j$ zRTC=#to@nQ{PMPiOwC6F7$NA%RJ zZQ8ZU)m8iKAQRXU?KE%Yrd0j`f@4_lnFZ*^*T7P$*CyDAvc(wB2IB~<`|B@Q8*NJX z?!il=j5c}{@30P;tm&pcdNk`S2^s*)*-mx)=BR0n2$@S`WaFO*H|8mpgKqbj?b|aC zHX9=5*0MN*D&lh2tk6WX8Iu)1{g*x%EDzN6$_{0BjX5r{v1?7#hH-Zn+Wy&J^5Npb*qXX#^qlk4&5YMhYJrArxu+mGo@em5BN~=v!3l zh7AV7?Gj|BfGb!=BV_ag5Xb=`;qizsOkb-PLIttU8fYSPprGeeL@gpahm_$6f7}q$ zUI3{VM?x^uDuyuZ;89bA*plq@M_FLXI(yKP*tt9zI^~7)ZW}*bEOEgos2jg2QDw~V z&+~*VLuwk#RfT~s=w4Bosbja|{vWqf(?}`UP%<|#%3n}8rMtUCApPIEc;WqYKi>~1 zA&hzYbnvqD3c`moS^Y5an%RFK0MkjuM$Wv#@$E|;4_WrUEn>UDJf5CmsgFER} z^Kt-*FWBGv%Rs=ZnJs0)yNpD?y{T$W1?56w6I8#Msja!2`J6nXg60=#V32b-6&Vzb z`-PCUKYQzZ!!1|A$982U|CHerG2$V;d)PLwzUPe^(MoirThF z{3txYi9^Qb_R6)(64sVM<JiFd0aNE}#j4rt3rt>%U}SddnvwN{&&tj%^C zA}H?A8)(<`gE?<`H91T|4?#S^YSOHr0}5O4Ll@h7esZ?2{J#-fXtB6g560O(?CFa) zd{Sz0NMgX2vTY*dK47bh4D57*^5mNSxOVun7qu(V)D*cfA?WDd$%vBQ1YL&s9ZpR( zfKtLt(B!$2chM<30zDXy*2d8-?mWP6LI*vYBv7JVGyc)%;cIbiGXRUhPWt6=f`Dwh|*ELmI2*yuMwAGiide1QtwAr^J0?~D z4U7m95J)_N264TfRZJt1xjqZg%ruaLCR?=NKC|>@qdl*T0hx@(i|%DGDx}P1F^$c_ z@)w(Rg3mnbSG)PUG@tqW!?sVOhBZn_^V6~Swf9T6=Sa)s67U|s(0>dpXr5f&TF5+ej9h` z!8?n$nqcX-8({sfIKVv-I|&m#bQyfJcc8$&p?vXEcoWAFVBLXy47Vk(OlzO+u)^Nz zBf7s{B|npbYffJ+fi>^g_(y#)0dagScW-WAnqI*>ZFceQlyR6Gj&lxWbQ73#7j9=1 z(tP8ldd81;j2>?4-{9B0v{`>BP~Yireyn#vzC}maoIY!UZ829vQk=XZbo&lS17o`= z*Ts~u*a?B!AutJvnYB;TOXC!SX)T=fE!N1ITs8i7TXdwOc{KS{-?&RqOuq2|dMYp+fscV5G=P7`!@%Pp#RmgDf7d3v(v0TQ zFJg3QYApC|z4MwU2y6&H3?>V&1YDH~RkWFgk&BABvxg}^} zU-M`9E|kSoF(}V*8MK1Sn1gAHVMmgaNsG0WTi^4n@B)H)+VeWnv}8eWtkIUs;bB@L z1J8+$lVU|8GC9t%XSPe6?Cb{p)KXixDk;4(bB%!j;nWp*u~VP?Y?UajO?-pPT)KEs zeUpNUMc-YVV_FvMbB$QK%bX>tEWe}NWM|5L=aj)F-68AjP-?hgLOyp=XNxlIQAj7U zcdDw*8++ph7Y}RlCf%t6oaMqjg4ttYo~asUY7`rJQ=p zND6htsg`*!m+6X;LrTNk^Q9WlRATRAc^Rs;45ccHsX52+KG@oKx#);Ban}9WDSW=& z(Xx=uT39Q0$wAQ8otB-~Jz8ph#NoR7VEfNeQG9%PTVOdv2GCwMQh)3{5!ZzTyV5n5 z0`#H!fLqphA@vR}UHV?~__msli7~6FF}-&-myQz;E=i{q7pRn9Qx&Q!{o!Wd6|vED z)fDVKhV9h6f<5hFu_I)|uyl&dvxnkf1EM3-b|Otd7(K0CtK_xeJ|S+>REG5Afj%5%fVFRR^`o%>(m4{KFi7=oMPi1hhMca<4ki>3;0o}i?mZ}zM_+P{su}* zL{@RD;HLRLWarQz_+2A~OISy$50v~|iNLZ<;xJr5HM3wMtvF(baf2wbYsNnAoZ&u^ z+wdT?TU>DLxhv&_r9lnFHR^{{o$jV-IB>oyvUl+*Iyei1Ubx_XEk(50PeA`01HDycy+ zJH@ax13|Eqy#(eKbx<0`b|AVmV#<{HoMkvuy=#xvbp2j8eCjL4XXS6XcS`GU@F78< z6xpZfD41yM=;#v2iqp6RGSLD`exVf8&{EI7=loxE|&M;|8&NFi@F4Y4+PMqzj= zUs2>v`OZTlk$ER!QAO5;03%5<4DLB8dA-Vr)P#L4x;HUiyecYDFJ00!g}z_oWn@)? zRz5H@X$A_}4idyt`Vr>G$YUGAb!FAONpZ{!hT-acQs&e}n51|0rL037_PjBf3eVQU z(eGO29B0Q);}Mk7?CD}@1~RuGeRl~VoRIXYlXL}CwXDjc0zmQ{o@E#FTIH6EXTXZIWf9kCC$;vrNMqVc3(} z+G!6TQq%B`9ux&_*jtzj+Rm!A)|po|S*^>Yn(aN?Jv}+s+j+WFzgh|kEunOc#Oftv z40OtBIa?b$&6@(76Eaf3jIH|asw7FO$%p!YE@(*0H3Z_rVn1G?&+pZg|Fr+n3}+md zuqCqo0KYP)O4#qJt2@Ni5Gi@4mwX^PkhRcvFC2@)f=-!Mh(b;0$`SK0S6<5^?eV*c_Ru1^6qI<}-AjhG$=5R1U7mm|}Z4>T9b5u1y|5MBRGNRN+N7xMh#q{oZu)-v{) zkhV^H*9@B<@tTY=%xyVC7 ztqJMQOlJ^TE3RkL+D21IS@T0#sl{1GIF|;gHC#_~L^-fEmu74WChAH>Vq^HLxd6At zfW}4;q-r$~M+(inL)ejesDOFbzEe5Otk<|RD$~}KuQ!e1Ifopg0+S5wP}a@4^3cQoYGpQcC_Q`D zIq+a*4y9zbDZ&glEU@0}46?CF6evPXRI=t~j?mAJIN!LA0BO{~P}WYGN@w2FiKE@T zMZTe=*+f>G_}5&+D$0(w;uH08wZ0=7n=ir?9U z(_*=;wd9q?Ca3Ddw(0Elv$#@-X2Ytzg!H|u~-`d2UvGtsb0L!nuE z97caKdu-W+b7XeEoMKC#?8O|SC(F7UOO=MSrVVgkFrgThDSdH-QX=K z?vQ4laTo81YxYHNbufzZs{zQK@9hlez)L9QBT8<0m7v28u|?%h-S~KaPf+_wr_gTsKR8o6n2bW>@q(t99?0DjQ>_@EPb<;%vTTg0qeVWXP z3Z;}FhIq6xuX0Nt>ElRa6=A(F2vZj|ehsZ|=tg#Zxg`ge5x#7_mP!zTR<$@J%Wi97 zB7TsQ3?`Aee}7ZM!MO&4*R!MWYJFi~j~Un%5_u^_$AGd&tFq~n53gnaF2V1!LnGqp z6T?S3Jh$s?=kel>kVlH_^hZ`5#Z0Iqn@}SHln?79v%@*KyA6zdP^427&l<%(=wUeS z98tP=6T(^3`%tOwhP`kvwJA=-&9{wwjX<GWb}2jnvoZ0qo%GD~^UbXzi&)6wh|wkBou<~W7S@KAog=y_pu91S z{{{&7I78CCp{8t5%!_+rTEBBVe`mxHP))34zVarMDWBCFfo%rFucA^;6^Sm8C~3Nin>W3*@0e3LkoUR_kO~>=U_&)E-z& zVqE1FY|1VYz&mRCV%YYYL2Sl5!Fh#rP9)+8x)+7qLurUgSKIm8!4 z|9*T=N(Z+RNpyh(fzwD7G8?qO`-thX>+>(uGd`j8h&o^Z07uXO08IZgzrt>YR?d#* zF2*9}R>u5>hJRU>*0<3&{SS_lR5TrtPcVFV)#ukQSXklM5l2{Ng&0@;AAeJJ5R3FF z;XBsb9|Q_4&xdrmHC0rr{E?Iums~Hj_K;W)c38+bCR|7}Aw@)(3Djz0>ar>6$xUfH zlQ#8`fA{LBkArVTo_Xc!4mt7OIq_8W{kjpw17;80;LPrCgX0c^WRbF9uOW)2fYn`y z8{}wm=dUW>xXVx1RUkuOyme;LQW_wErE3{bn83i{AwOeQUdG?BS07O!=|*kfJl3no zpyE8U!rjoD2xtH_8szvF1|wW5Y;wN~XtDH|2ezqR0Xs-5KM5L9D2Cwah&@qJ=Pxn!B zmjsG0akKT>N}IAPa|z2FZHEprKkvF+v#qf>TDhe0S_{K@+}PM2(c+3ap*pOUL$Fd> zt*JIu>Bk}-UfN`p^kdwNQu(NOFi`^!Re(67{iX}{P)f5?aMHR~9h!;J_MOGO)mF0K zfYTGtBchIwFc}?}gruh5$a0)MGqTPW9m_@A$J`~B!Z=v4-9WDoHa&ePFp!9)8CZ9a zEi`xcdk{|5hK%|AN{moKV{h5cK`h5|qSc63warzMnP+AJlj#48d9de5Ku4r7r3T$StxE~p=Fo|EP z?^G|=Yh7^8uUCp~X1T%E;2exs4j+P94vZ$Sg$}E7HJ>q4DR7> zd})^JcHa&{@Z~r(rX|t7;oKDk7RU z?^<{;u?KF8h^o@H{2UDO&_k z=lM-@(aXTzv!crmVV$-I30iDF!f`HbdP9FjoTk~FKid=|6?vvO;dRhCK*mWjCei@o z;kztwR)%SbmLJglQa1Ho*KDzOL)0t*_x!ltz0)F0GTRN)Vob8aM}^{j^`vx1-%x;r zW1e$)pl$B?*hMxrs9k!3dsm{xTwUvkxcH*d!)^`P0^;Bq0%l?sQ_7PD@;b#Y`k`29 z>y|qC%A6xK+cf?D36m9Ob6llit-vrwL{78o(Vd!XEMKv#L=_9-DfA=kcdVQcRv9#y zn@B9J_h4DRR8~2WUOutL9=7(v=n;kU?WNxyfWTbT7$oPx9VlUs_Rag_+u;2JPId<% zj5uJPw+ADSjqXhzdc{uQQrC8Kq#f)EZclnR3d&didig* zSl#VUbU1~(?v-q6Ef#@Gh@pRp_RY^NI+oA)9n775GBanW#For5QtI9*elxRILEsDh zc_LaO{!}u+mHS2e-l8Xa6f=P>8+>viI4MHBFj!I{E2sEFeWLcN^c`94=ebIDOS?bx z;*MRShrT6p{2mLTtAb5;_ep3=d%M=TY0!;TVy7WIBSe8<`tvUl6Ob~GEiE7b0C|xA zW2{;9{~anj8as&DS{qB*+FCl>{d=rgQBxL^ALUylPJ8)Dp_06**+LUUb4}w)d0}v( zJWL4!A5zvR2(p^C!${rgQ?-lO!eV}e_feFEXF!2!G@xUwZOXOV@py9bhL6t&NUguz zUw(4TEq|9HTRfBkLQh9T>op8Pq!xd#%|8~P3&BHPM8QnDUlLLhCPDN4`JJCqcYUnd zw&-!5UuGuRWg*8Y*zSClorQU#wnAgtQM#jjoPH~c%DDb4GJ6VL($gbO-)x}{D_h?a zbil%4zb9zr1)7(+)nvW+B`%LIo17kmH4BO)vQR4HhRrf$a|$a24c(jFc!2U(HN~e3 z{Vq-#Y_QO{eTBvn3nkhLZ-+?Nw6=B05iWCU$Hk)CxV=oevxRr-I-=>XD6&tQ^yp@5 zjZFG`jWr9`qQlH$&0_;Ug7pX{ig;xqdVVcl_j>WrLqvY!PKCNiRIn!#SU_Lt+v~bg zw*XYs7X*{k)9eqCg+|IvV+ieq3bzQ?#hT9g}+49sv8_*#{xNu17o< zhpD&TzTD>*;F7l8@<8u}6aw zE93p4;3mfDXU1(xkkv4HWFGKNIi&_EdO{d} z06MFnxWLuG#X8tuMp#yc24VtmZ?(e2S#dIvCeFk-;u+0Oq+3klWa64@Q3GKtTx(Nm0#f*O$LQR*uVdJUXA$lndjojL!=JX;%@s{l>Qa103 zqvi~oe!f-~zGdR&sNRCUy$QR&CUSqq8$BlC=_W^Z?~kaWdP%Q4v}ko-kKo+DVEK3{ z4z=7`gYMiNbj1Ia)!i|8qemf>K7n`@uaG|FyQY+2caDDRIA2#;an*t!VD0blSC3cq;?;JRF#~wF-5IW*NVQL(-%)J?1gz4=@Drm4vp}SXQ@j&dw+A z%3S21uP?4KC;3yR{Lv7TY@$dEms$PZo?r!my5SrH=gSG}3gULK4n!zB2ez+WU3fgG#S6S3zoDqB`P8iH zX|!dd;&q!w>i(QNY!{V)P~t?UVr=6^U~yMr%cgNR8i;5JfFvfGYh*F(Cr4kDHTK0U zy?KZn)Y*lkiJguinqT5#g+z|$o7}Twi2F-ILxRZ^(Gb;i>n?9w98=5 zMH(adMxlRyuc^&nX_FNUH*ze!kBa5NgQzUeL=RtP!Bn1y+B56O|Jj!NH4#+J`o@U1 zh@SU$ASGU=MvD_4jJmzDGsVQuF<}Y_E<-}|WiVLC-= zmWM~BDBjAg>ZkO;&K*3g`DWQ3N_JUazc$o&*`DreORoa_i}OS`c1*)AHyqa#@>pwG zOf5J&85uJ-dW$68w`uc6F~#%gvYIESHSwyvBGOHd)+x}UJHGBzc5-uT+#N=((}o(s zAa}8bkH@T5NWF0}jYRVU>fVA-82M6Qe<&26GO}@Nmi3`VFjtdEE_aSlX};FHxfQzO z*fI7#GYo)S@MXi<8r z*!cyQ0251{6gB$lavoViF)Aj*X*38j0ZP=x*&{m-7NPf|W&FOsen)(Uq722%{hWn8gW!f`uvKDg1XB7n zk`h4zc3vW8O>%V@i$N9Xau}!I#OS~kOJ3oYmB5)j5hfQBlZZgwA;)VAdA<%ZY-{w@ zT0s}R1CB|sQv(n46|$;M1aq@IqI9|%UAlU-ztuo`D@Y5KK|p1U=2@U4E3$Z(ybkB0n&J+9N<_<|(^J=`BR4yIME4o5M|Sh#2_qfe!opSAoK(M<2D$>x zi0U+ms6jy*3bduQ{00rHh>knS^CE+)CHIIwH)F@y9VI49<)^0o*khD3H#zw=Hj&*E zHf=aJ?Pa7jET|`kka4BU+7r-IP%E>`Q;;Ym<%Gtju3H1wmWAV-7=6i_QL{1Vi)P(Q zSIBfwP(5VFcV}c5ZP3~9jXP(>$OaI~K(l`_-s3pDEaG(6f%~yD-KqoaGxije_Uvh2 z{cZNvm{QopT}e8J+a5+Od%)%9l42zn*|Bk3S0^=Wg=)$`UNYnGK|8jDf;p~LS&rhb zuHokPwz&DvatyPOPIhc8^Q@tYKRCGyPTMpUB^5MNt3?LOWp)H`L)Gkj!3W@P;|{@e zytCo0)0y!*@s3$_DRGL#Y``a^MkfwYjnXq9*V!~`s42I;@u5kBMQfmZ~YBj z2{VNokQ=|9CTn+ahwCD7OM1|b61Y^&GzS0)YY&k*58Q%8>ES$P-9R1Uz^k+cU4YaL zWm?}Og73lT+8trec}>kU6>i*Vi;`vc<<`&um?ek+D zA*?81wF0r(En$P4vQO?X3NZ*!BSkErsLHG!B&4F7rw@MO(splCB*Mt%HZGw*8s}ZY zzf0f-`MUY!TIsE#%YoOE&j@4ys*2eaf!yQ>pi-bWCWA;4U-jqKFhu;eJR>gGOv)87 zJ43MuttXEdQeOmfuP}5l~cP z!G4t0djg@&?#Fb{$QWwJ=x`*p_ zTbtB^Lzb8x=f^{o^I$CCCuFl!kS)y%|MdIcOmJm|2EWP5=Y&(NTB0;AYx|{@fSy<6 zH^`N{1GW89+TE39T%@Oam_XN-vQo5Yo)WM5+Wg3A?lHaGlR9nJHaKPsVl^KvR* z*TWYHn2BTeuuK>dgW8y2@~|JO;}5Ybv}@aDOY0_83B6b`Y9hU#cMyiNB5sOAN`&5= zcqRG|{6AZ}CzooD<$rq{e;eWdZ0(Bw^Aa54s6MEEdYIs!$+Ke=#7-!kCxg4r3MgQH zxksHAMpg-~b0^*maJ=3SB(5Y3h_RH6;pVg2~Tf^y3&_G{>D%BsSCk=wT#4~U^0c;@!Nca zU>3~&TSK}wiM8j%h*sjr?}B5+kl%K4;BA3uS`*fi&R46CRyV!A!2istPFR4UAUFWP z75x91QyWJoeJiW~Sg)6*`r)diiux^cY#%ZPDxguSm@91^I5um9j}m)}kUq=m0>Mlm zL~dbgLYn~1_7d%SeOqc4fJqfS#h$lkgw3#u zNqP`^5D0_)=3q!J_5&B^Hjh$tS41(y5UpsRIh^)uI{c6zu9GR)bu8X>kiko9FlO)$(u*-#bZCv8mt@Kib9(6Z>=!TcfT`iD z7Pj_lrxZ8sayP;WSTQqq-d+S|cj2tdcrBMTo8_u#RsUA9H6`^#CcO-X`GoX@TZll# z>0xC)pPoAu+oDJ|i_SgG3dYmrnsD@4iD(FTn^{n)vGz%85}kYSDTR}<7G2J?r^qzx zLJ3cS9DUcL+U(0pg@bqg+|+2`T!-<6n7Kk8kDUpY3@?uWDA^3Cyl+3Hi;a@n!sJA* z;v?(^T31%G;adkPt_*E4J0HZ9NK=>2Lor(ZHD@KlwX0*~`+ek5s$kKW{Q{R!q$KV- zQ+)D3u<2MUCCmgZYHlt(CmheJNS&F2!vtZ*+2){khz;>_>Y@gRrQvOFH!qnPWCph)-e#xeTqqN-{8;CBCQM{QmGrO(t%n#KM3xmu%>Pw8tktA^iy zZ|vmN5~2LqsNWs3i3&FjM%{h%UJAFyR)<{PH2hi?jeGXl>zU86${%J&uKncBN{m3E z8k0iN#neY{nPr!V!hWBH96LwM?)-y_*YxNuw}fb#)pElOX4^p*9PiaULo%1M)qt7> zl`blro!$&o8;inKZIX%UwCrfxeNE2ps2lnLo@;TO6XQj~ygjD|@XzqGRr(luoe}INcL6PvnEa(gmlS-prq5YwJ08>Crt9f&7rm=|eIw z)@;W*`d9g5HGY-UWx(zaOJA|L>NCur<8gAU+l|C5G^Efn_T&^xM&{d(@R&eU_?g3Z zu?*&{^WN1~;npD~4P0x5yfg^PZ-YS$JFr4fEo2!>Bf|nK7O3DBW!1gL9HAOR04EOn zn_N0hSMj#E%%6~8!=olsgjDY$%a4=JE9ayesS!)(q{GMOiKHwRiw$Z@2@jIqss}Xa zQ;-o;9E}+u)(_0fv#ed9Og6l_kQbfIN|w_G=3-h)zYMK9Ca1?u!g#pB+f?yyI(D79gavw#{tJoFr5E^6waKu(IkIjcH$Pt?j+Voq|c z5zSt7fg3%JwI;-CI94g)Tt0VZD#IzRZTYM*-r5D5ui6bMlNiVw1&_@U^8(=aro2S9 z!pt-?HX8vxte3K1tcy;PG|${GR6KMhK7$bUe{e64&O2V65ol&tYb32xgC({YqLP(f zm<7s&+%Wf)2pOZqC=s(nn4KZl7Z$Df`8LQ`fUy{QRcgFy26#82-8b)i^3keVK4B5- zs&zX2SK}6i8x!jeq4q^-^H_eJ8B<&94i`rrrMx=jGb;^;Q&euriF}}_$ma_ElG>yX zz@h`8+Q|8X#v5a2hCZ@~QHia=6qFG0uFi5%LYE1VV!x28meY4pUVa5h>6Bv6lr*k} zBUO#~5O!-^2}^(oSGmQg62KCm`9ayx+4w;iHfp?COdq6u|Ku@*E>uakzI*7xDK6BO z0wBF4PskZCUH90Zcd+-KL(KZ%XS9=MsR5l z=K*_}<^oN~X_cuWHgwT0ki9Kh9^Q+DkV$Oea=1?D5Ge6nb-*IAXHEcBhFxu{8C~4L zk*h6@fG8#|ii6SKUyECmlvjpUhZtFx7+se@^m{t#r~i*egtrCHwF<>%#k+kD<}km; zbScB7YK(yxPZ01q222GyeZI~x!4oB%YO8Bz5BJR6viG4+YBKBWg-ZPcUglN|fsolA zAU6ICQU3s?Xu`SprYz6U$R-AiVW8Jk84w(zYiR0snEX2Y!Uj~NJe_}wk~{7sPF_tx zVjzWkt~DKoFSQ=`eB!8iC_Q=xEwzIdp)z@c$!d2~{ts(s*E}CY0V7*9_4S@=rbbuk z=NI}vtKigRRCP#X0Dwvg008{|RR#aYYF|h%rK2W3GNv>x3|dY6-r8s}V$apMKW1Y7 zLV~}627ja4nApTaHw<#Q;`3K5`>S0UY-u#oItx-M*Tv`BS3oyaTQ_AiHAUQX@qVnI zY`kw@Tu^=Gc$$(X91`O9=yrchwPZf|PP=x;tb9K|Itnr8_+sVaPNxi$MG;2nruzdi z1DcDB$+G+|;?~$>z}F*GQP=fJ`CO(OD4v|&@0SM@w?^D$h?>w8)jB0oARQB zf_rO*35YwvlmZ)SR*9FWx8Y3+BaKM9uHO{Y3@rxkR*M!=jwWS%hS7YysO;BJl2(yj zQ_cwmx2_5`#H^MM`9Z{Ir&_n9&}I>%tIHqK2S&b?QCD}snq5br&iEPFT-WUWCO{1+ zom5Q{=`7|afjoZl%IAkS#(I%Gks!L;haexDc)un<&X|lMb@bgQueg3J6q~r~VE~r< zVt}f>DX11Ovnn~=0rZbHX&r3MRG|9Z9R=u7e9JYy)8wrbiPW#$LX@59Ma zSeUBT>#MER?i=sxQI@Gt87^iWjpwCkH{#=h$|V2buL?*rTPlou@IJXQ|acTH;=h1=Zw2;XVsZQ zY;)v=@PX3W4UZl~<_t8BOSwyL{dqVdrsIp$M2NaN=j>ZzN)gh7sC%34#q)}!pizW+ zwwPBGZ(U=Nj8XQZBhD2b3&A4P6yw_S^F=gjorNT*hM0J$OBar$sNF~d(gPuBXjzNn zjv|*pro!wiSSBi^{8`EwZ)gRYJ_E_UVcRqt2u`YfG^sEp`SiBYy$zDV3KY%}xUwnR z1A}1Sn0sSz8D2Ut1PlY}*^2`d3$-2PyWHf7Q1Tu8v_hyl=zceWckORb3}+_0dXD*uNRXHle#4OaO1+{s?kv=e+cLRbNoU1nXvyNB5HKkg z?@v_E@f(Ot&`5i_^^k+WcvPQ{BScM*8+D-}Y~3*8DC*QfgWQub%=TXF+B`HGmaN^= z(E&kGIn2_=A!WP|v+}5J?l3I;2q@ipj^p8Y%Fx1rr=Ia-!@MNFGuwTBGR341zv8Wb z{L5*w)`91yURXMK7Kos6{}J*CjR!*mT2|(wu#H5}GH$k<8%IMEjEydN3O%d;cwwr- zod+7cjT(`5LUv=TZ7!am>9MgXX^O^$(-onnctv@PF-a#sMfn}oJ9i%OT=A^rktqr6 z6eEdObfwe@Rl91w;4xL=+o?(-r=$x?x8VN7y=MI5RU_zSBIZKr z8%Ve29@dK|`UIJZ#k3pYBY#|XpH47H3l?#T#?pdHzL5MAHOcm{hOpcb{cw;nban!2 zr|^EtD`LFm-kQ=kswaAJUx$g@>-W*d8(dOPpKG}n`VP$$52ddnbF1pn$LR<=C>Wcq z*bog-v1tR#!t;Z5kU(3IfSHZGgAIB7Y(kz}KUCglBQp{1{C=03$$qW07;6O-OQGy` zY!ZYseniM4F82J~_Uz|n`!?m`uYk{$rkmc{@s|f+gIaK9-ceUZZ3h8@`OXBrIEW$M zED%b*LHG(M^ghm=eNiP|kP5pLZ4(TAF2E2Z90E2BPG)v;2NC;D_HD4p_UbV6t~^@n z6kLmyC%4gggDnTW5Em>JRb~54+R-Vs(ers~W)HJ!%QhaiMQhU9xpUaQ`L6y6ehp(x zbLBJlQk?jF79(PPY(=L)L&AEJ!ZZY$tf@bU8QLD6r2|rf0ve-?isE%Ciocb^4DvQ5 zm;%$wNtH6GqfO@x#K!s49D*Cr;7cq!FgN7!k;|K)8xfHyeKluaOUGmG4xsQ<-XQHi zU0=nqmjcWimYJJ$#+oIs$wvWL&S@_|etE*YC# zeOjrKe?m6o!G4` zgc7O3jFhp1GRA8ju%LR%?lnJ~$N6^g3~X8y56V7?d_<32F}kT8XsndpV}0{)WtG3v zTE9;|{~BpTQ1nT@fZ+EZK#^wy-pw{*#;hc4JI69%9X=G>A4w8;b9gA^(Ev5ltD_+L!8;O_Rdr8(SuVOH@D=8)~ zUO=_&)f(ZXq3wbVL08wHg;SEW7^Rit&0WS`S8X%#r9wzFlA71VmB<(mTW15E!lxUO z)pdn64sot2+CHM3L&!FEILBaq=?BSUw+3Nov~{&WCkQ12R}imH_F{aJektT@?a7EJ zt=>MkFpFG^KEnHX+9w|G*`^C`4n|jwtQD*uZS#hjBudmZQpMlA?~mrkG{gO7h^7`9AmgmTjVPLh=A4+WwC~W>@<1YIyPiDBa=2SD z{E8UhKyMx#gp7$_3rq%nm*D6yF(Pm^RHx@;<@D?g%8N(8A4UZ87%!B0N3lzy;1s z;zDGfKl|%2u46bU#2-rFMt#NJ-_D{DuRA{aLwm>9pQKF$=lxaTkkc>rd_LY1xp*64 z$#8TMV6wDz+px$Nj*q^9p`tf(8Y`KudBNQj77oa8kV(q98a9}lAq&C}dCN@P9ZNb? z{=t=~;lc^*Nsa>XNHLbSgevr`;WbHARUuC+bB-=iv+--_Yvp^NG4ckaa!Yn4?+eIB z8x~5(>m>0>k$(*UbR9}GszmieoJXKwVmu~RLB&4M%Q#8YY*IgC;K{5c9{$Se5@t?_ z6Pa-`FHI?OTiprRWE?yFQTQDxZVNeW^*T(+-9`5BUhi?@5j8TDS(M^8R5#VchuNhD z^FjJVb|J%QWX&B|m$#By0$mOy3vXl$+&h?=fu{N_`eY4x&1FeGZ~7yUw2xP6X3=aN zOd=J&C5dzar5Nu_vSZW$4b_Q6dt5hE*u6Q%mJnNj#2>Udn%Ww0&xuo&cTVOEFdq2X zD>ask;su=3w^OXutD$MGI`9;EQ9PSLH2|g$s1od=-F3k9b(L+|g>)Xt&V!LTrtkDm zLfh>v<}Sw69npmpw}?(xoS#<0)Ef%_cqTa9pW6HwljYwN>egQ<L9RJ*Z0WrEas-73SRQ zlK9U>%*oAwm>YWnQ6}J_-8_HBshh3f-@XoKgoHL!6unf5ldN8`l{cil4R%9VRoc2a@(wfNVZMO8mAQJ z91{Or-=8pO`YAcMdbB&dHl9$Fj~6ii;pPBXk7@>Id;w}q0p?Ye!vV5JIGs|L&al?2 z-SWh~xUY4D#VIEHeZ%!j*_vu}G~w6S(iy~4k+TOH8bUQ}(nx7_WqwY%WoMHAyL-!m z0DysyXzye}muS!=ERQ_9+!}lGGen{tp5lUKLtFB^~{WwOHi_zwCO)=tqzrcb6M;E*Xa; z%s&CxMLwZ5gBn)=G@!{d$-6o(f6(?77qs3sk!noEeGv2abfA>cj_6A7K)t;iKLN^6 z`F9i>31c)~j)BMUlwjUXiluJ>7Ta;U+X5tdJ#bfP1%I@1>5+Fyet&qo#L5&psJ(Dp z<^W~#*%sJfdc6aAyb)@t^sP7IedUsFPfU2sV7Ma@?KDY$5Ptu)lCqq}Mf-{X4o3cz zI>N=i+yY43P|<0v$(~dn9RqUKOubMqHM>Kp_BjVR+hKpI?##==%J^tTeS)oI`TkLX zpG|KDw6IFpL4RBb`dTDT)1#?e(JBaaUv64@jhQ>lRcPJzbC}(k7e#L(v=!^MHRBaa zz^=!d!#Q$dV*^$x8qFRDX!2hk3bOWY6qXB=#k0ODm>D(NBRRzmwh{g_?!0K=Yg;UB zPH&wjZie^A881*6w?FTmUWHo#8nKEcbVWIIPDSX+ z1F%vPu#gKNp#w0{bT>nF7xhk)?axZMm}gLQU;c)DqgRX$XbWvR{|@-%=We5)PQl7v zA7*>(b}rjq-$mUsjR%%t%X$~yYAsLl(>X)0Tqh^s_GcXI2I1&SH0%bqdyoWPxaO@S zK2Ou_d_m(CKR!LHXOSHcgV8`Fu&KR+;`DD&10~@rkaSyTJqKGmg593#6ZQ4B$^y;o1KtunkatJ$ z1#AVaX-wmROwKhjApI!I3Q)9ew+xDa-BaQljFtwo?*A6kyEB%{rYF^_z`Y$Yp4Zli zgetg!bc4fOoIhWU?t;VpE!u0qX0V2-Q~m|M&L?>FBhq(|j$-`-p7)a;1QKc@OdXsL zZjBgm79%GtQ>*^mb_u9QD){8-D2#m)!p|!cvHFK4zg9sd+HitT&FC%ZWeh~a2-G8-TNJLggNOs{NMRpm(1!-1@d6B>Jd_0#l}%c0?Bk@W}u%htnD zniJ>sl5oJg1^K$WQiWHPYW58}@=9RD^OX7QGwGYp`uM3fniz*~GO~QdaWyGkG5xh6 z@iX@@bIoUUjice~u_e!x^%dLH!PzvMD?`@hJDE;m}X`(+W2 z3)r+^zvK7Tu<#?xe$T%|vWnd4+g1LN=m!)40PlaEMw7BN{r}eZ2PJ&i&dZ^U^rVbX z)-Vj)Nu?rZK?$n{+cJ;_8$lxCL7I?$R3&CqOPc4QME!*O24M~j;(Y>t7DS#j!y>6Q z#AatZ-f-kRQ2Ks#GngFHPQTc6@lFA2A3u%XP32Y|W;q%AM>V?LXfVpC< zLJq7m4KQw=z|@_KU#03;d-h_~YY*(#lnn5?ZkKr{$gp-a7pISu8$xigN4Av=2j(p{ z;b_m{IO{na6%<5^Y|6JcS38yrA+T6y<;h)Tm*FW5DbZ-3wMFZn(ZtDN-j4|_b-_@< zDdqf82M!)K@cK}4_VD69N*>m13W`9_ZXuT;UM4jRHc-PBWJ+8+3sg8jR&^eHzl5V;o#n__l%*lETEmE{+q>5MYY(Y^B@d$Ap&`D+m=8;6~er zJc7((92oxjw0$?#DgbJkrV6vD4zHtoec_eX;w^;sl#mDQK^{=qo`=%X08tHD6#LFJ zT;CQ`D;~<#YRp01-y2%c4WpPA?Q#l&^8SLH%{^N~IVkiM$$W%a7B?B=6bSVBt?&&= zn&N|hpHvCKDppM-anES5N-BZKF>~J;mJH4RrkTeMkqhuu8DRQtHxWL%C#8ovk1DWRhTsGGbQ3#k09E3;U;6 zBA1FMkZE6oO^}ZGk?51V&ExCEQ|4w0o-al4T1|8OpmkiG=t1`d=p3rxnAEF+fR~ki zoW!2-?tGLdeYIv8jZw;>-#HD>AyiTCtU-G7*(|4$zP&eODZNBs#Z0>%#GzRai}W?0 zyOG54MSQ#4*2DLl60#U}$5Sf5j|uELf4mc)8?$BTkyeocL<<9BoQjkz4w5Eohx<74Zn zPeO)LZWzC3X03e4*}LOY{B6cv|<|SmDbdTTzX>0Qpf~=1+>KHS~k{41;>3GU|os`L)I_26B7jF2cbXlr}?l z*qRJh6TT<6b>zIb17x>jzj%Yth2I;)XGTBQe#sm-GJ5F@S)=NP?nf{X-zuQ!A|269 z+@dl(D#vxB4Xqe|i=ga8?rSo5VeKHE(23q{k$01k>8jj$qw>kwS+zG-qmxOGG)*;y zTP9P3YAn~oc;=UKMemWbJG=W>Ie1t&o9kQnJdIn~SUBt3^Kv<=^LxDq`zX$!I9J4~ z=K@yHm+#&RjX7v-Iix%p49VFU+4ww%m4ywxml1(#&Y(w;jMUa7vI-Bd874EvWC#b4 z62_z_pXn&fW@BrqAf%HYmAN>rH%6w7AE)cnom&bEenP(*Z?PKVSOsswCX{}ZNV*Ul zbc}D-KRK+`GC8()FbtQnmYFZi)gAh*>|dGn*}Xi}rCS+aQ23fOu1pCGEepFk-O_Zy zb%xpPS$Wr>g3?SiI+fzL7jq3q(h7+@FkeLAxRedjTlp4^<7t7;D>qc?vA!K&b_K9& z8tGK64pFr?@P}c)&kNFYHgbEpE!bgq=}D!74%nkUzDY#a>C{QPawTkhussb}Zd5Co zZZz40LK$+`jhrtox68x5hOuWulPfgT?6tgRh3{Ct0)Ln8 z$-XxD)55QkP8knR2`oEA`RBezkVo#<_?wCL-Jzwa%r2~E)u>gsnP0v&xaKQS93!iY zNn6#@BNr=Iw?g(Xiwr(jM5gxd+`7WL-lYeZ3wfz-IP*+PqaTiPW8)2)0LB);!MuQoS-$pGy~HHT5#e!OF!nG0Sp>% zKy5;$v!|-%zP6>$(z8t7?)~Qxqle6>>%}TdZ5jvr%`d9F;SB^bE_=77M$SPFQ>{>8 z`hu1xFkxjF)QA`lAe6Y_dpgXh*|=Vz>fO!I)P8_=D?Vi|qAMVV^I!~1vteb(D8f!` zjG(06rN=T`%0%-c2^S9~_)b8Wj#K*ivmOVhvpvV(dW415Tj%5wso}&155Ne+^i|m@ zTUYPubeO0W0QBah99F!VuhukcKgK)OA<@$+0vqq(yekuRdQS;zBLCt}2#C{;YB7s16h_CFTgl-o-abiCbd{TOh z)lWK}vP|2OdF93xgz=(a3y7J*-~|>T+F+Nw>QUV}Ot>q;W(7fTRSndT15tLT5OyW| z#^C9Qy^r>h=d134q~5mkk)y=yU8ZLTLf4}`r`Bk}-t2@zQW=~=cUCfe9!#oAW@ZG0Bw!h1*sgyO6Lw|IrXbgxVx#&NA6 zcKE{3+OBY9mds3Zq5S~7=e`?el3Q$K*X@Did+WtIST}YQ+4TGP(mlXKZY%41FwpIr z+ssVtu$=Y~a0jPV9ws4Mo(}ddB*Oe{%y&-kayL7&mWV-#PZ*}9pJ3BB7{%{u@69NC zY*BE-Yoq#)vo~sQ=DkeFK`@VB%Ppt{VW&QKD%^f=CUL~75|jYAUnSYAnM zik51pPb*a5n5S)i_O1k*o2vaSSe<~)NDXdcy}0oWKa42)lrsrF`(g$b(%joYIVvRjt$ z+4|U4_UwMjH3rN&gm-l6d#>WUKP1`6BBTpB`$e>DM~3ildEBV}dEL_6^Qx(*X+`Gy{Ue4Curt)w`*f(#p91fk{%pWm ziYoQ4k0{FaEmu!PhESm+Z`T^s5$f36tiKRlo`Y7bj(kWOPl;OGtrBsJTl;F=O4ECUJgJ zi2QgpI1K`tSz6s>cuvYxSkl87ey-d7-6R9tKfS2sqS2rD37KgGTyAXYiQe>3Z^?9vCc!1@)3$LMi&XX(C@E zeGjtg5}X0XGsmq3R}8TRzMflYQz*;-<5s-e>B}y71ofU+B zg;0XSHZ}4Ne%A+sDfPvMR%ayB)#HlNCQ*yQHqctB&DImwTQcV+w&U}xrx!fcnm5|V z4$4EM*Wz&sb)br*MrC3LM4-*#Fxa5#s4saHtHW?~BFCY*x&GRycn8R+Ir~hgIR}2> zbhkzRHA-Glwjf}#+X_H5NhxZ-X^Nt(ZBS~cHa=M|`{ z0>+%A3`BvJUJP=_m55oi{OFoD8g6w)C{x|IfR=s}f>|2L8K}#wFE?Liczr)zey3iK z9;9r$r5_HcnZ(ve*PX3J7~jT^?WKN|K| zKfSybb$399*lvs9V%60E3tMWyhp1aHE?&Q&;MXTXB8$kgNQkI|+3rdUxbr3rYR|_Q zu17(L+zQ;B&|_KiF}B99e3p>rIPXS2jOn}sfGnW7IN(e)x4;Xfc97vMA6Gcj^-m5b zzW&>}!!~S>VICj&rkq)h!K7-bp^YFM(ehYiH2O#l;fSk^y!Zw17*F8e`fs2$yQ-1L z;#vlcyiQ1{hqU^S2(D@Cf=Q^)jJBd{Z3uI>3ornuQ&+T44m z1TZ7axs7I@t@&;*RoH5^L-cFy&3qVPN1!6Brk+Y*g0|no&op6*N=pnmtqa6iN@!lX zm;!)<^R|^9dNXy^tXyJ#&Ffwsm?6dFZ!a=!)CG_PyEQdW#i}{Klx5qlMn5%sF4lLx zm{cHdUtOnVSuZrasS_jj`CTzBVpgxPu4lLH)J4u@yL(x=56c{?YM&HrU3-w45^Q>H zKrPdDcFI+a+0J}8DAsw@g)}4=$FC)3=bL#GkT+FUR(*u5o04Rjsg`o_+ zUggn2=%REJdvQDh>t*I#(r3Al&y2=g@bx|_T!PqlC9?A~Mk?xOexr@kCE$p}TY*A` zJ0w!*CFV0k%Be(v$(uxS51?wHru;f|rz!IlJ-%40O+?6yNHE>E*FxSWIgh@x@qPzM zb4=7q&BC9Wvg@~7Bq6;OgWegp!(l_gx5qG2fMn!9K>gb*6v6C*hxnoY+}D(dErek# z0BNfN6CcNSEKIurRzSc{kch2>sm}#zD*ziC$Bz+zD1;Z2|06}gufRC!NFt^T8z0Y~ zEZ#5=UP%1gLL#;iMnTp8)Z*`Fy-fOw_wR8aBhJOmFX(^9_WE}S;K<)@A~FB~`TvS- zVK-w#=l{%ysv72r{MXa1ilcT{hZ+4<%MbC5KduqV*b=T9~4bO20Zy4N-cZLh^a4DqUE%^lg;Wmyw```_zA?IXC zbl~;Z4Bp8evc6aRiR|6&83Hfzu<6>LnYz~#I0}_<3xLwNLz6;j?D~Q-193;jB6b1Uw9bjShBFN z#U8=Iq9j7^GUX*p)0ycx=Te%***To}`r<8dbQ%`hoW;iy>4D3Xf2sw~aNz}5n4HaC zOGDb)!_#;@%)Cafc@ECvd>|`^Jrobl_WJU&HH6?xz+`AsMfjO$w5cT2%rkWAAGJ)! zx5yr_s1!sWvDbFYB+o_27Kr6@8156^69Z|ygE^KsjLX+Dm)(bzpq;88T6tWd)8flC z+T%(Dr4xe1J!C8}E^2M&%~BhXT!zZ~ZUR>wRVETofBAv7M^URcH_Faf0vDx0PGPH% ztuBk@#K}93C@Gq*uNn2=bfGBL4**Inv1|Cs&0H)4BeAZi9pRPJj1Y@CRjws~@R%}b z^xd)RNnfCxK6f~p8}}_jm6zcxMmzAjlnn$K`aKGQ~jRg^JR;~hHLcFWaYUI7zO%jK3ZPgwp^ z7v@q{r6xf9@VzG}gxnbmL#{(^Nshx)pi5DvJ(;**go(IRg#M!REKE-}3CDseJw|z3 zJ1hu@oV6B%w02b>*B=Um|F(weT|sHb)VwTZ5agvRzU5f?tz1jtQem(8d8wSao{1xe zVO%rQ)*(D@kzhgL+8}z>K~>10TUuL|MJX?CT82GL3?jP`JC!&SZ2ojHL^!6LnY2LY zeig4NuLz-uq1*rdqs@5e-y%XXXmMy=KS0o;(w^?m+$~-H6`t-+cSm2jYphl9t!3|4 zn%MG)F;ztmDLztc2FlGTJ}ks$OKlTv0!#Q1jJhW{Y@Qb=||u|(TcO3@mP1qFrw<1?6EPOMPQ zQnfomeFw#q(KY$lC}Atp&Rnod#wsG-C#x>yu0&UOM#!u=Fq8W6Kp71C32pKjk0H(L z9q+$to#>#BNNyuD$HdFP5`}@dP=#HlOYzH8*LSM7r_UDkGg`F=oMdO-O8H~5uCD}T za({8k%ig0%Z`SZ^ia>V5kjGc4y`egx6E>_H2cN*%p_e-r}7RnIeSX;TwT^HeY2YTbZu+*Lr6J^ zhowiD0rx@}2o01&B`LU>Tzs~Gy+GsJyLW@*s`JT~V#-H5a*>W^nHP7$aOnn3l4XgT zAyZlD?^9Hk7UtX2fSB&lG``weVyU;uMy_zvOqMDce)O-Bq4j{wFbv?r&IsLUYl4%_ z7{>UPL6kScPjxcUIJoW(+<|x}9MyCQaO4A_)HRr@c3Yorfx*eOgwX&SYCsJPWfFd;RqEvahpB_Df$fz;e55+PpL@`2ynPt&D)^i&(G&L{D*dHISg zwa2?Sz3^rD8aMw9joTX_)D>RJX+`uN8=@oZloFyN=+qL@9dbH=>%lX}gX=*=xS4B? zvxiG#a=y+?N3i96N;U?V-!3mpm1_(*e+qOiau6qM41$J$O;gYvIh*+ht8fl&GY};z z+#8|>c^3AU10bG&*lqlsdtlDUIVf|a$YHOf8h1N+b~jW}B*}`J`lhTX5!&1@vPh8i z*}b_Wr%eF9=w{k8{4r27Q(7GnqCFatPGi+t?(9x~F>ZTMX-LL>plPyr)jC3-GoPEf!eL@4YM0-B+m@G zeCOY8JwwmDecP^yh&?W0mofY5+##t4uIR~gtx=t8zxe=L3_)GwoLu7YWkq?B%|2nqFM_;llf`NCI{;aCkx zU`@>740f^mvBh4qA))5J5$wlwv@aOI6V}F!HoIH(0CM22sSHOSK(BI8CU=alYG;ss z6*>V(7uLsk?Z@188I`s)4u zEt(%x{k~h2?#3rjH>uA-f=_f^FWUx*<`sJy!LE0}^4mk1T7kSMTmR7B#PKs`I69W_i9t?C@aWUP-{ zR!I&8oP=Dt|M|-=xz~BI1>`t>1yO3cs_Rd%oBzGDO!=v%tXi-7y?3(2yL|YN;bGr_ znzZ_AFj?47B3blRHO)WX(KMZ$QA~usn{y7Mm)y4hW!~XYV`2S`I(fqq1MDza|K6d&}X0&$y z-$Vu#`S^^4G%ZE5G@XRhnD~wi1IAV;kVy%|~BUs?uzmreLJU5898^gG*9xIpJ_^5xf&Ea{| z=6Jll#pmn$4Q3Zg1Ppn3y2^sk7Y&Frlk_6s&Vl2O1BxRY>^AhIrEVO0d|v{iv2|k? zycB$Km!MxWB|Kr_3MtO!wQ-)U(XadgSvodz{U>knHpq~(|JhIL+B6O?$#|vIQkyZGvgF z;~AsHm;*0ZEP@1kzP>rszgm3Wl6HxQdNk^VhGExV-%_V{vTp~R0j29SF$trRBIts{ zubJH3h+fCL&Rw^?n{Bpk#IE9uGn*vDzxqYz5<;#a$XR4KH`0v=RG zkBc&1gX)vD?vi+auXXUG^GcEsDnjsLCu#5%iBQnQq|j0z)1|$v zOojk#9^&UAC%>SJD$?L1EW#QYA|4ro~4~^>-@b-vXyQ zhy;>Tc`V``nCc>|Dy;w?{4yx6W>MgJm{g6VBY`5z)GL>+I? zaWUwj*(ef3W z|8sut{|w416-`wvWt49jMt!|hhp4m!krwEGGkMtosBFoexp*1B5R2KP4voNQ=dg&rF+cXhr7HjDyl_(44Imyk-@;p2w zMV17Z1LG&5Q?mvW%iw8x#4r>1L_rdH`9}vr(~NW-(mC;%Q|%>{#&8XT)a$!R%5;s1 zeYv^Wmqv#z;?ccP^IDwQW|Rd&Ep9|$mX}HT7Yp}zR*mZEcpE|@^SV_jzyTb1%oU?D zL8>(=k*#IQFz2G=$n5+w2bO^FUPh(DlJmJ{g|Z6EsPr<8WN3<2stjK1Y{o5pNp#$L&{H1l}WAqc|_J}R?lTsH4_iERO;!gUmOw?=JDtV<02x0Ex^W(<) z>Qx(OU$-l-=QCChnc9~RutN(QG`Pk5tVj=an4+KadY!H$PIMi%E{*~lv?Wi>8u`i| z8J)vsqs}n#;JO7D^Sm!6R{)wPWvoF|3D z_EUTR)&lxL?sHpNct?|mTrXooRC@)zq!G!*V+o|^L^W$rwnk7BN23bCU16w{23Wo!8)$xE8$aN0?BeQVB z*nb#DlxnVFNoXgl|87dmCV@;YupS8yAHTo-LwEz+cw)F&6F>ky&k&5kT8$%U@``Bb z)Y^Ls)UDZHh=6TR-;NCd>eKI*Ia+5J`BC@4R2+IA0tDxuAV7}KZGS6rF~`?4oD&D$ z6Y&mJgpwFVZ)^}(e@k#gApPnBxn|fuM5IO_6cIW{Cv)1b*3Xg;8o3an2XxbRceHVL zbmn*u%y!{k-z;>eZM)*lL9Fb!mHvrok4+cG?VNF`2|9Y9h)YApd>_X;h=4#C4! z(5x`=Aeqsfyo6%Y@iE@>&qC^y2=-nS5CA|D=zr9g|3xJ6JD56K8{0SuyBQkW{g+i6 zaZ?h&3@Aae?@R?v%^KEJ=l$1>%HS(W{-OT#t+hoTPMF1UX+n%l=ay}ht+#+)5fe?haFno0*mMXdyJFxP27EkV^2Ty)D}@Z6m?H3 z^;WmtZM;vcmH{Tp2O0_leIB`+;G@EgH92}{Q$JLNUDZ4rHtQZdd}%I0?0HOS8J8VcdXc0EVa5yo9PlGDsXmcgO-O7 zBSW6||B{Zd@oE8Z;ID9MGW(A*~lR zerxcPw^-1LSyBlHnlly$EJ&qFwT6bjT+r2YtZI+-I~4%F5XBLCy0~A z(c*cS z&quv#+aTezzR$vBTXaYhZDy{HDhe{?u1RuCzK)HHh3J{SX#bEA~f`nZ;25X zEB4wWvzk&!fw6Yd(-CmnoU|lV4os`Aq&8p9TtyC?%CwlQNTxl$HW+lOL7xBUir|95 zk=tk#Be0^onEcK*MXsL78j%!`^|mYCCYim&rJ+nwe^HMMQR8ngT@GxZU_pqT?jCMJ z(?6K7cnhq+fFvsWqX)K}kU|8=M^NtE_FS^;EOmExn8fAwqv+FTquycLWki&$QvIt_ zU03hLNSN(DU9MN8NohX(jiO3R1-ep$b?N@rfIgI5v@pEaMn@g^41bR^IK;p7VnWW} zl}G>Av9vGHx&p9OZ`JlPifvg6%#^FpoOtfCcBahhyK3IWn#M;o$Wwham`ovNA=$h= z`4e9)w)#vuDpg6=obbaVhzu;G5-7NRKz@xddr5K%hH#(EWxTy&AI+t5ttbqz!BMmS zi@oSDI27+-qJNB?s(`8?gOOL|F0$A7{8BF26XQ^V6#+QtN3CujhgUIv07`;K*>kqG ziXE6KDt4*O;Hzw6F zEbPo(&MVxvLMT2#Bj|ES1Hy`+fX;Sjj<1rxVZ-JpY2bb57xNF}v`^PYd^A^Bju4s0T!8gq7j(g)Q}8l~EcXnudG;^i%%0%q1*V}abOtFO zqo5ReAogV$Y{G|uMpT~)D$&l5E~yK0`SO-=2hwRPghgC-@Ol}fp?zL5GgPAhtXwF@ z;U-nPNl^CyU48)>Qz24Zx0i5^31BA~E;*hhLtNFhDygUnwX>I2T-W~2arr=wNmTn{ zo-T~Lrr!j*xdiQ|-kBR)zma5^LDyDcj8^wnU=E3gwPU#|100_zIMp7f#Z781;K?!W zyd$Nd&Gku;GII1H3|+`P%uH~J)S+9F)WM*Ujm0_D<8^eL=^QzQs+){pJN61tjwmuo z{bJq9wFp-@kn=A$eBj0VUwmTJiY;FZA8CxhK;uGmT5(kX10j%!WkNx68gwiSrBy-h z&gV~pk(N2msSnkVqtA)oJFkQJuIao~@sku`IJWX{^Ff0iN!`n#To$XO&>v9s6~6#D zyzus#b1;?M`&2V&YuFd2?9~qOGcQ=EIfPsR z_Mr%>S%XsVYz}N^g$_{fr7@NI--@TVqNXRLGll8cJTVW4OZv>-n6>OUeU?Gzw1S91 zA@GK)yxs%TbcR^do5*SdbaREwbxJg-b1dRZoEksK%}lK&sY{DgnZnlzwYj@m;l1!v??K!p2{2VF{O z%z?z8>}a|FgdizuwcGJN@Zp(kUwpX%jUt3V2nq$BTG=%3T34mkj!JJxXkS}Sk(uLc zI1}B()9;)LV5nZ4_iimE5|{)Q)g_fyQlmx)EbX7TCn5e0b=7E?N^*+EAvlW@5Z4Yn zDB8;>D*O=>Kj#4UGCc*|0gChj`{kMQ9`ynlzz#4nEM?eTPFT#o;IETQG-|Z=pxD=w$;>p4owT&9G#)-*-!CwH zKV!(M*bMV9NwWazIIGbR{Y_*B!hPPANO1%P+I_!JNJw5OiP&4QK|@4}V3CU^6{sN$ zkA-0MSm@$<9i<~L`Gk_FDjH#3SwasraYBV#{UX0jr*lw;r@+x2}F#j@tg+ zEY@Y^U{>1EORK7~V@KmEvH0l5IMr~f@UlB^_0ywkMyFyO^>U7aG!m+o29;o=+nI}Y z3mNnz1#yxJc~v|?vOT%X?oZtbWCcYWNWqA_6Y8PH`i*3hQ^I15b)4Oy9HYHnL_5ii=y zA0b-kW$*2`C!ls?vja|sFmOgd%oLGWJ^tA!Zi+o>El0W(v0*EJ3rFeA90=W<(koHm z%&kaIE`P5pvxx!UtUZ7|i$HoY{f6knHejK$jVr*81+fI7@1@*Yz3ZU_IJi-SnJI=- z9kqbfnhV*bmeZjORLh*CjomezJrF34D1_@Cq+Q~7eS|txR1V5j>e@~!)|)=7A|NfU zU&Jqbb`wW^E{kblgr)|}1_m(upi>m%+$0^XE#fxu$7EFjJb94xQz4*&UD+y-_`(-N zZ-{R(Nxg9@JwLN`s;_^s^W8o!)(ih(qQpbOv8cMR3U64Z=`Dwwr zI0eHZ51oPb{|%j(mmkW2=mjw*glvSUld9FsS-w?u7QQcf+wm%Zlq7`A_Xls>%|=z! zSQBcP`Hbge)6w@N({sk!_xt@B_&1^?rjnF}m|=MevG!17FtG_sYO12V62nYvS?d!g z`cga%b?{vL#nNQ@NJX@<3igKAcvrBpHpip7f!J1E(AAFdubfEN97-l))lJGi$NAc3 z&7I~>Q?Ctz3B9o@yOK#B&GS5lvzt}e;LEO*?js)egN^Fg^Kyjbmi=3i0m3D2Ob#R_ zv3{(G>XOO_=Wm}4jhD6)wkR0Egd){X8a5nSi{tj2wftu0rlHtf9cj`P*0I@>a0r1R zmSDf)%}9v^-lf_lzfA%aFGOprwjY~R-(ghC>Q_bS;~JP`t|c@)I!y5)XE6Fo+=JAE zv7ubl#4e6EO2$YWQ^uV3xi#!@l!`3NN6|^*AF!i_gIAJmgt178=?uJ)J7v)dwD(YMGomX*bEqhcES%qS zQFLOL3FWyt+h1a{<0q&38MCWs4n|lu?L;w(zwyPX3r?7W1x|sY<|W0wtm+hEQ*MO= z^BR9asZWDMB4)y4#F&E$i(YfZX9Ry^K&I)>lo0hw08le)YxZhEB+i4zqr}tgJA~@M z{S}l;ui=S4VoQfG8~o{OaY{{S>Xz{j^|y1et~p*tpNTfuf3EPx?13{2(nscWl^8E%*F{7Rh^-BNt12f6}`j^6=ip8vF=YKVk z7c>9>_x}y(1&mE>9gP1Es>oUqSrx^{rryrJ!AuM`soAWMAD=D|fKprzda(s5%sP>^ z)S69SS~Wr2w4v-f=sWNmnri`0W4L*TbAj#)?+Y<2y8}y*-VZjK>B~WP+WVSg#`}4* z;icxUQiA=B32&S^IL~0g$k?y8KROm$OHC9Z38*!U8ZA3$6lAE}%bP@SKsnMxpmowp za6rnNz!2#i-5H6%$_|Cyf&q4Q^C;pJtxssHv81%5&|;}kuCSnJY0_1Vx#7MwYg3^j z*x(STvkbe{J!$8?j`dC2&tU7i>%f!&GoRj8#gK^*9A(l~sqVVy5JjEWsV;AsQC<|` zK0yxI4zs6xm1l0g;#8rJo^g~c3wBhjZ?>!Y-QQ$JMO(Jy}V3>MY)k?tMs^?hG~1T!wVU_$3i*C zN&EYBBMmNF$N~HsNE-U1xXcH*1t-QyOxbLRojOBHD!O14Ab8}4IH^;;B+Kc6Cj(hp zgZTkw&gYq7RJg%hnOs^3F4{%kBxn;#MqjFreEXzzAeK8m(%?ZGG~+e?fC`kvib1H8 zSji6l1%oj=?iw=&_MvQ!2tyX9v2d4~6r{rT*FHG7T3z{6yUD6*j&kF23EzeThIx^2 zy+~_lhGSgW*zh8?pdB3_^pU8>(DAE^UwD0}ntto4d*Kr>9P_#-|7+2^-QLwXKVRTx zES0RX8O7DDBkTk;dr(?@Vi5db5t? zmO@VGe%s9Npvv+!m=jL(_OyEs+Y<8_ei8cA%aA@a@H33nl56-bC3o4fS4;0PXpP|Ymeva)!gSgV)f6xk$)Ddxzc8PSqbV|Q4v(HT-EKv`u!y#K? zK)OJ3xCCuSRKR@+r>JFg2UAEPTQj8R#lZtw5d?$acxN7p8=xs55UaO4w*pm(Ejo(Q zG?y&r+6^3M(KDdW!woRVo->&??w*o}M6lm@#Xv{LM6e5fQJTBPXAhK&Ar<*3Ak7?6 z?>|z?$j9s=3_7~Q?lWW#(ewwE{b=3q0Y2$U-uwQu4C!fxEa&;FIF~^H07U+8WQd@? zqp`S+qp^*nxs$nzv7(cMxsB;xGCO^T|EyPCq_|IkXHe8Y%3+5faQy%N1=$eA>q<9Kv zA~H7WO{`6j<60eGyQKPY63`?jMr=(kS_G!jG3-nDM+wG%*sE}J?(PpDflpbxhajw` zr}wDbCpzU-B2%} z6mSSVhC@mjOr@b9c+~yfPxs``DiJ>nRs;&eB~1?6fJZ0hh|P$DmTc)eOa}C_5Y5mz z%v5bhWOi}hf~c_iV;V_b8OPjIZiaX=D?r&emznL@Tx=0jmH#DEb-b+*KqUcu6BTnm zySvaS@tRkYi=U9?2du(|;t^R1Q*oz2EuC0H&1kJH{c22X6O!?8<@kmWL1p5I^F8x< zUoY&Y5aCOph0PEP9w9#;zGj(^P`SttbYY3FIX70)sGpMU2yx7)MV&>nnlk~=Zhs z&A(Z8|MrcdlfIMjzu|IA?d%^Dk3U_kSAkBwNaSjAk@NGcThRUxDw4uwjes=%s4WB- z3w=(-(^BjHRFv0Tp@~bN!dm>+lAtRAt=K=XGk33|r7zP`^CW(aUXK$QFV`I#A6uR~ z-eWgEUJp2bL?XfDK#jp*H-I3BC<)O1z*8#X9uiCovcY4*Z>0SYKtl3)5T6242P6K8 z)XC<#3{eMxW@RD)F8z&1_d#+Ap~s7RcQ6NFajOOpa{#&R&iF^$EgU1~`2eH%lD5CW zEjr|cq_cN}?*3U)*bSqDq_aQ2M#4?H{{pCf&eAe?+dLy3H|3U8o;9*ywqANgxq&55 zE!hHcu?5Sdlh=l_E>Htc+U%)HL^dXWWSvEOt28y)A~`BWmqhMV1EKYpFuxkc9$k1O zR9y%gxz667SV3tKaK0KkOA4WM;_8Ck$E8VDocJwq=xQU0w}{u=GVI-Ds@~jI zqy%jRmCHPSzg~Q!V#`@z?%-*=EkiwPt4iG%7M-u{0?@H$*=foKja@e_!#iDmG|I~+ zw*=f|$plch0+FbZ)-YMD-ZaC)MxmJ`6v>J$U&5flz@qaxlvhMjZ`)(*bVa*{ROqkd z0k4lhY6cXvWjGkaRc--YRx$~$uxKyaSDkQ$wr$(CZQHihv28o) z*hY73cWhhTani}3b20j!-1E*m@7{lA)X4XZkv&(X_S&dbwdS&#Q?d$REFY?AS9T0g z4R)wTT>i#DxWqtkVH_IR`4R5^=`}XHwG}f9X#&%1GcPWL1Sk1>O=O7W15TYlhjIcWMkUHZOqU@Q90kE}90_AD z?6nv}^e_4=F3b|yol!qe*K;EQ*~n_zgkej$J8z>K)IRIh3Un4vm-K&AJLK<$5~kH_ zlj*@n53v^#0Xi|p5)O%Uox}7H$PMPtjjz{e)0;HBY7_{AU?2()RpcP}VLI z9VMb$K=B`=Qilaj=h&cslO-P473AfxF?cZ)VAiW{%LZ}n}Opl$b+cUfXbS5NsEE`gG01I(~_q%2xU3L zMi%4@zqB^-j{nI*2aT+^vHxYuIqq1Yf{GI*3a|8#{@TM>WyOdT1&lfs%k4|-aEhih z;ZnQs4pA0&w^7N+M@CXOu|6fw$gXSqQCgI1q~#3wd|Y1rM|HsDMGCBrb-_ROsE?BR(#?y>mzPDR7mLV>s8st`itX`*vAu=+8!)q_9I z$EZ{QXQDr49gHph@G>3fddfui)a6@W^Vgi^k$~|f;)=`%RC5DyP_-+*&>TA+f%Szh z{lP?o$$xb>*PEKP3}a$5+fn0z*k2ww-RI2PdrZy$m^*7!Eia^K(t z_F$JvhcmJs;En&xhf#V$-Xu ze$+l^1pKQaLm3xj_kEx*+FXRv(eiRkT7)^pH%JUw*D=FSv)|(q6ATbP4CB3a^TFgB zWQNdK6sgN+bf3(sZE9#Qo`-Am!{`Q17nZk*w>us#VZHq z+xK&u^g6=vn(q}oV4DjjOxIrJIoNUA^_k$s?$Uzf`c`4YIyZOU5EaFM(1tY26ug%+ z6NsBX?0g1{6L!I*BV5MHpKSjDg;s#%D4q>u>o|Q`LD#$+tglpDetAbt``~*~g2aHf zQg%<8vpX=sj!iY6k<|@^&;YlL-{q;!>OtaM{Vj^`hRtQ>AB@+`9WMCUOVz7jF-x%^e-zHgkVpJNX?<|7GFK z`e$&&6?1JK8J#RNM#{eq)CejLKR0Y51dTLZkFHx4b$%@U9K{xL`hI}*(cB9^EV>WX z2Y@&6YPD$$IvFaNO=qP2;6{sii3s&_l)|&m{vu1Yk zc+K5#*ZWn$g63%1UqfyM!Z-A~jTwoV7@-4$#lf(!`SxoM; zY`2U91haZWcQ(ro>qZWRusc)IOJ(&1@$0&}>@__sDnu~2jG3t1^nQh#Ic_rJ8$<^* zj>J^!!3IpRTl&gCvW+)y;Op5(=a`PdIF{4p2bIvd(oN^>zXcQ}qjtUpbM*4n)k-la zZ8Y0M-{}i-JG7!3RGQAj&=e>Yc%uZN1qP9M=x6ZhVLEeJv4|Jynr=4+j@SWHLEgl3AYi+5sK^nh#T4PoAPuqP! z^@gz)A95GDA_s)p_r+W85+!T8_yjg`tvHnVbY+^qIHefHvH)@maPGl?YU@hqPcX{d_w zKPxE3`V%BIXY>J~4vH+mjFwyicU%G>01FZGe2Nhmla>}s!b=HHi8S3GV^838Jj3Pv z;tg68+73=cU&=2k!y|2e?I_*L^c@3&PHD_cj=sb|NAM#g1&VSaTD)`f+_p@C6+4Mw z(`mR#ExB7SON8?Z1H!!1cb)T6K52t$=3n71S`fvTwYt6RrOcl5qy=~jyc~Sn|4wS zX7{sA5p0MXMpyMn9;=T~d=(jo5nu3w^{I5%cWB+G_hstUu7dWM79eEN^0k%W^ zg!{{)nJXBnf+%dcZlr6C!6&O5Ecw&*o#fQY)(lcPaSpZ~roLY5s5(qlQRmnNL)E`J zBvYKzebVRfYIKaqnv2q0zvz|;Jmcu4vt>H}atmDT&~E)=oL2Tz$@`eeQKvd1J4I6? zH-ghSvYagC`oXugZjd|UjHfZd(YV)s1gI0=*~3+YjzSentYmLSkq9E_)+~_3Gq{5R zKi_C}V2mfVy9>`??40nC{xk5LLbh>Ut$3jtj3LsfT(redHYkUz0--=ADjg18bv&&+ zz2QD%;+=LE+OEc)r<;LxHX6+~!X7$}A;IQSuNIDep;Rdt$M_Z-+lxl zl7SB{9BjfYnM#jAaC?v;-CcYkzKQS!$`?%c`J)Izujy-){1=Q#ZOlw71*xWW$4U1I z#|iF()!UQmNnHS>D*=8CMu^A$0@!+neI&6|)uARlL-~Pt|H4JmS}H@~uQ@|%Nwp+` zribbWEO)GE+wRUO{1og@B)xn=EBh|>wWe*hk=zkXg2*K%YVEdkv~Di$OgS1{%V#ZXDgqgsb`=9ms?gN{#W?m4eqX5`x>`4WG-ZY}yre(T{x z%@V3n$|2os(zToaV2HhS0OSmT4pn@KgvpRiU!hfh#ZSE=YF5$Ce|cbJRa_Hvz~q^- z(p`g&UT5THvUDr8vctp6z-cHegr|Te8n&gQGzUa8D))SG^m?Mle}E>^)))=K?m_k0 z*?AV$f65tatlZjAj@kR?NIei0RKeL5b%X=N;WpEY(ydofM(5MlBZ~tgoi@I$v*T}{ zjmKs+D{^e606GDQ`}&5aZ9-izDxF$mQ+b3>3Bl>1X6r@CmRKWWGa&%b`lUlH#i${& zc;VFj=l9YO`zG-^jZ4J51SZ*c&z4~3(w%|}7w=F8iV(>LQKui_Nw+W1OnB+~eVX!V zfXDQe@Yj0IR7gf!1miEgKDDRfZIE*;a>o~rp%}aJj@blJ^+jA?kFh_R9QpcxyFwO~ z0_|qlPJt9xF^>wm(O)}ZL;;{-klw_{iPPRCzI6Xw>5Rc@mQ|QuB#&23H4Ge{L?rEp z5mzKD^C2egC-m<}MHZn`L&*mKfZ~6bRr<40@o$Sjl7jYcvP#_b>h&(t(hcd`u94&- z?t}GwmAg_KMi4U<3J}p(6$)q>^KULBHk{*!UqMl-6&u zx+kAuX@0R&XxTL!sK0ePJfyZ}p9QwYusT(}+NdDFN{~(DPc}HN7Q+A8ofd(gHJ+dF z{I2zQiKn1#gfot#*QdnqGk5wPT~R-nO+s#tM3+mk{YFjVnIa z{-Op+@~$1MMR3Pd{&~TmEwe{uIwT$Kwll798t4aSj1;ty1W|o5X@XW_j~<%SlpsS> z0#)ud;6NjKlUv{J2XViEr9>m|S3g*!0}-J6d$5)U7OKLQP+4*sefIwRaa1ZtV=5nk z!^r-nFZ~gOJz`u@Dz(YUl_k&67O*46hgSL%H*us?a6TkL`nSNp?|f*dNP)z+DKQiH zFFqLjd*}Z>toe_AS3HwJ{H@=um+C5vsde+sAXLno6N^G~@GvZ;B7vmn(4JWvEY<7Q ztX)tGVzv?QQ4#!v5OKe{&bXPj;Z2bQ@w%ROG9Iiuo{v4hJnf)upQnSA?9T*2Cf29u87r}u&D55=;_DX-27<+aT1vux3Dcyx!vJ=80O_**% zWH_{}({G0gs^*@ADWicE%y6kh<~37``m~_EQq~%+e3K!4J|`T}%msFa=ZhixkpY_1 z<0z0i#aDe#`j}CR%mpz$`njok8y*mCT}#Z=mIs*~a_>hb9kTvD`ghcBdZu_ z{Z%{jXMBT-R3|NA;2=qsIc+OC$K)9Ngjp<5+8Zbgy=&&s((aJ?jzfCv_>1S!;Z~1>n$Y3 zAm5#}?z83@K~rP+LYO9voqs_|;v=xeC7==bGtsmGOW-N^YA4E%5z~`2^HGSpSOUkR z-DWHxtOV|^!3^1x{0H2ldyNl7rbPfWJ_~BVf*1~Xh()qVbnau2~^C;>yY3CBti?r#1llM=!iTp zM+%|TU53w-@2kHKXMlQMeGDTRsP|308gI&1Xc*|7c%t9%yvSg+y*+7;$@!32Yhegi z&tlc7My8fpuWz738$(TXXdNFNBn)XByc_&osbN?TDZCxZWNsq`iqLs)_@J_Y8@&U` zlg3@-#7D5Doj`8+oMZfuF9>SsY)9;2T?UD)TKy&_S{RRjS5It?OBO^~H8788)ng4d zU4{|vsmckX$^p!isKZN`#2ZQ=BA!7MHU_Y!0#7XE_3riJFm>rC&^O0!2Lb?K_$!Y6?~AtL z#&5cvFEZpvkdDC3rH=Bea(Y(Z9!%)c#hB>vR}jWoynW`n;0K?AGRY%c( zMbRg%my7~{%xAK3?=mmE4l-9Ze0^R%+5s$xpm4G{EDje~?0{!$eAYCBbD;G;8mtCO z^VkX0bLq(wRXcl};p{-W&Vlp^#0KBG$g)8=OQ~o zwg);8tF=?@!Qq=tCc<|gC-iuDi$e8@K6n)Y_8=4e6gi0-%MRHCq%=hrdrrp1u{^ku zUOAn3Z^r7Jq64In(%5DPvpjKtJQZCi=c)M>79L(grB~8)y}KSr=F9a5k1I?@dg?Vf zc$Duz28h?0M+;Ss=_?h>K(#gBG}aK)$K;%wLbw4u&O{UK98G|vXj;5NCB!4|A!u|t$e*u%u5`0VqBff`d8N0#{%?|qxVe@G*unP;eYLOW+Vc_$B%u$LDoR|Fad-kL=M*`I3MVhVkXvTE**;^BguzRpH{9G&1lm>IrZzC)^_}d5 zHVD(&)|*?|fC2#U{uQ@Uwz2$^r%Ij5qYA%m5V(hl^cncT;I4u~$%f&;w(PV_e)6S8Lxw}f(7+R4W0#}a&ocO7kO zC;13<47f7CW*m_+>XD}ZcKZ{Rx%EB2=l~))P18k!*alTqO0#-f{gibPiq|@GMKGFbKDIW-U9vXTRw@de5j#T=)qJ zd3n-I@j1_aMly}gE>cV5gtp>ofZn2~MZ6fG?CC+%j~Snqy|n0`DVfxXb_l828^(w0 z-a0xCQ4-?K+zPB&OSv_Mioqw9$1Cgcv7QWB=FPIl(PprF2rs^^4I(G(OCZ&S!>ePFj<|Y zQo6<~72r<^*M}0R&D|zJDQM}ZyPKTtrkg`r0r)nHmuxL+^ti=>8m`Z@X2BSo4*fxp zlosW}#*;?(Kqct*XVgIBhq+oxdLi5%m`AnXSS08?Wrc>JyKltb;l=X3nQDJFP)^fG zb?F*{#x~S)2R5AI^eo@w>|%4o#u8-sOEJE{GduwWd5f?<`SaW&mmEr!Bj^E`0{9Zs z7`;enkv~w%ONHi}+S9afNa`4gZ!I)>5)kxsJ#<*yFl`#M#dHItLB`fK#SCQ-ZeBR+ zwSsiH1OqD*d+v(ockkMDXxXZ#!vg?Rll^ZwAAcVy z|13l1uBSBD`s$swCu2i{5A-n-LP(6r6M$G?5ekl3;v*VRFTpwy>+n}PL{kz0tz(_? z#^pAYin$q!vblL?{NkCe`AQzmC!MG43+L%u>aPBxN^Pr3+4YN6PR2BG8j{H%|*W~t> z_zEs@9(HUWKQp_ZkwEHbjDveGmtcVH>`+JY!yRwX8eNpT_OpLP7UPU(D$*7>Z5RE^~*-dBqty1be85m8#ZT_nwSFb zTJ2?#XivmBIxpdke=yG@i43v&W}ILJGvsbjube>r5iGJ;tS`J0GUKWR8Fg%RF#}AP zurf@IY^IZ@t$HCcEzC?R;Ro=K`84}-a)f4rD>(wEFy>@2xlKFM@7Bm;JiK+R2y3|Z zEUam@Ey$MAso;_c)?8PsJEz)ssrz-SBxDva{X1OCOV**zE1C^Od6fG7nR6{9E5MAW zNsNNEZuyPXSsrJMgjq;6^T*)>74#?Rr(&GB`d`rN>n7I7hekCP`vDm{iBr4mXOoMg zsM|F$(lK3aEf(vFt~yZo?Rs&3Ak;U}!b2ULD%PTGN*N8-f@B8lBUNhB zX|rQ?Y;9lIoxUDRwMphaGq34PhNHR`r+hvQU9vq9w6Ev3nhtwO$`d22)z~DbG)?S0 z$YGYBNH}TFbiOQ<4G80RyNKRx4pPsS`BKO zs1lRvicGAT!_EdOd!N-)6S#o1IP=3Z<6ur@;b*#hbu|t`jFSd~QUV8+B#ln`@=+LJ ziy}P1O6a_nnQ!$A$D~$T2Ek8mpC`kK430ae(Ie{U8~Suqw5GcPCHPdK)%~6gPV4=86m$ zJXiTG&#;}fQXZ#FeZxe7t19GzEA5-btYv0C%W6#@DSR7`hM~lvRc|wk%WbeSfFnC7 z;W+r3FF#=%{^Q%QU9=^w0zon*ykC-$W7n~VWdrSeH;#DT#h87(MIshp^EL_~>oy5s zj$%FSCX2w#A-rkE6%J0pu#F%M!i+DxE((%tVB3ySjRuoBYoQ>Ad#KtmKU`(T<;nK) zA^EwDvvyn)PRnh|&^e)r%Ip zxhe_4$6W(Knz*0ASg}5WG)beOn8uddd*b2B%*er7G^%9zvgi&)TEbQhcXLh4{Rj4t zp6R<9H&y8nCcYpRMIMap59 z4h8tE!^**4D9z{&^Nc#zq#{58lSWve3 zm3J!#n*B4Do7g%1uB1aV$sNXht=CfQ(?Cs0_j2r$@_@VKPElM%pJVP+Y_-_8*xv8T zI0_b`N0w5BZuYeXbh{svEg9GNWfMS=e*$>=MXH46&RYjBDc+v#u+pzx)b-m-(dwvS z=&E6bqh@TS8oS)0x_;NhtmUvwMA22X@fnH@QD**D%}5w$mcMvH#nZ6rUsKSAj z?$#4Y9DWyqz}*j7jyjLl@*!t>7c-cPAjaamv@-k?5GM(FEgJ^-9jdDkAvhyaUVbOH zN#K5;0Cqn%ccdDqBGtsRlIc?Kc@Q&hMvcrR^FuOz47soj#Y*e`_pHto=0NhhaO@-X zn{#dCy*eFV=-MiQd*OP)?I0ri6ZZ#-A$)e zY@fOtu3Hk?&yPf|@xz_CYjd$cyw{{1mtw7c#>iQ6XwL zbtTfZReU;dDBLn(WE!{YSF5{LmpH>v47WMw`ciuOZ_R^t8ucO&B-XXTebo1jSIr>@ zs}a~e$J`>E@$_Hyx#mvx?-+t9 zY!UF!vQ_^)LJBrH+t{dHicXmDi~=TfQftbAYHT z=rVdvPWEE-Y{mHDCP&Bw%&;Dst23WJBgExIeTo)b(7cLPBfF_rt!^8ob-WrT>fGJ1 zyjeLP#$B^wNpkg~z(-H{^t0Yy8gcVVGYc2Y7h7Zc*1ojQF-cncaK!iTvW1o|3hnuXf>kR&byvkJBX7A*N-mkI`cExSKj%81 z9%+PCs}%N*e2?p0Kc%a*i(Ac%XJr;le`E!vFPaSxF;FpmEG~r0jlt9aot^8t^pTow ze~yhws0BUvaJdCEx9*T!VABuhHJ`mP#;X&1_E2d-m0D?I@0BBfv7Y|Wk!5SSwH3L7 zEO@CB`E`K>=yi6;eRaPhVA4wgKys^}L2CSJQ>F_Y2KKyX4Xb*e_zABxsY4mdk!@c^ z3CE8m^sAvczfVT#Gz#4;z669^ymzn=KB9%2tLb@RImk3#@&^j~}EI#02NqJ!>D z=T_I4!7;+->jBj-Bb}Tke`rk^?dH$4gCmV)I3&CZ3t%f<$GwI%fB~l=?bgc3B{`&2 zV-9t9_6eyAMd}Y%fwbW7a;L3xmnE51H%hl<%(9yG@D^7HhkkrEZ*!N`>&8clYYY4q z63gnZ5WJIk5gR0Q<74bH#TyyKetjoJJetEbJrBbxJyNw`B|$Cj?tB9_LiV|%#sDrU zyu|@d*@CN-2M!7<{}B%&z%Q{?37EBXFC9f!+eW_1LH$ghszBLG(FJ+oWQpLWP>h?p zu6VCa3!UwD6<}Ttby$hDmG{$3*tLRC8+M&AH6Si^8BX6eMUpHIXHGUBa!v+)MoUKr>xPM3) zDZfwb2%_9`ZBcpobh207!q3wa#m-J8oAeYwR;c5%8uIkef%xMR8EgOagH+=JlZ6Re z`?vnQS+`{`FJ#>S8v6?TGyPqKau-k5ua1rh^$s0@pyDO+pdqY$qwBo@3(sb;rkBnW zUY`?lHu1T}EDW`aj$`Wu??EhWn>wOIFUiWC{GKs3`kD`a3@QZKDspElSHc`}bw4Cr zR|8T@w&qjllwa~d%l4DO9lBg?2oT$h^C41~2HMRj2Sy^=a|*9+5o}v0z2wR4OSR9prc-u3fJfAZB$M*92_&06w^PDiCkPn*W35SoSb1S z6=SCqwI=lmkyydGvMS`tFMYNH8eOIcsmZ)lmsQiA7D3Wt4^*a}Gxx$CuxK*?vOH{K zJno!pVm%Ji7x(SRHxUoH$EJst7}E%^jf+M8LPKoC9mG^u$1EygL&r*PT%Jyhuc3;k zaMFqUBj5vTB>F%a63Zv|f)(MN<``}?D5Pv>Ak!a6WmAQHU7xr#Z}H874(z%y$DogU z*%k^8QR(r&g%||Xshc0D4(nO6s*cAmn+x*<$u^2kX&o4PkR>NyQFWRpM~y)CE*jOZ zgyg6xw=6wTO<4Z;9Hqj4g4Z;ayQfUA6!ye%Zu4sTLId2puqL%-puJ2kac5ax4W!&>K^5{mgcqBtA$F=4A zhuJ+sFIgwAoY?zJq)m5rXxbrkH~IN3;-djrBVL_k&8u*(7~Ek=H)S1stD?;_nhMfB zgbF+452{60G?PAH8wFlbB^%KfJM6^=8D!l*3P<-!9WVXIYs1>0E+=C=*x>edmV%*u z(#IM{xAg@KS@oZVpFde{L!G&1a7)C^+CPym=P&FLRuiZP_1UOHaO1OfIVOiITqM{jx2_Z8rh7j5c;Q zzCM>k+oS6?5ge|6I%Fj!mu@9>?Cl7H-yH6@-dZW)ed{2O%hbz~%c22o3sz6I0vbR22!EzT=@MtV)IXg9$OlaHMp%8aHlXo;QexmD!#Ax-v_{ft^nf^cl|M zg97Gq=Wdq4L1W6ejX1_~OmW^%GlXSZeJ^F{T_m7O+xxrSh1%qT@W}5q2XXY?w=JB?;jsq-5=VPyX9(7XNM(NKRN0g#joq(Bi*q$J@DR!sWpB8=)p&$jv z=xrp_;54s%n!b2eD0as-eKpLv{&Lu;Z$pu16;r7OI2hp$A8sG%sMSs1T^qAQoLHN) z^TFZPvEy6$4nuT`qc_SRC7Vz9PSWb+fgAE*IlIozcy}XKHQ~=2EMDohsCz=4?*}iu zzmM){y4j>Af&u^x!vA4(=Wih$qkoC<{8Ny3$rn=<`9)-9Xhx`3c`$m7M0u7i*_b_; zXv(cLVuwT;YlYli+)e`m(UL$m)--W?C`?mAGH)uVs0cVc*_$!KuaF6xgg2S>{%Qi# zw&PM2u8WfqLvp@_o|f@oo&ApUz{|9ChWF)ZkQeZtZe2DPF&K)`4=xgAS3cRE-0yRH zOQ6H#3_J8aQ!N4CScMb5$DYfeBH% zqHD(D%;5nvUxyOSkwGJpHOR~Zeh_gqtj$ zs}xP&SC}a7bq{0#1i-`=QKw64bc0Hmt6E87H;2y&Z#xz=!k9`_gB^BaHnDU^M;!;- z#E2h`&68)=FiUdl&WJoth=od}0!zD1UYe9e95A1NUr7sijAW`xnkq@6TH4n4?`4t% z3M5E_<9k;J-f~=E88(?;Whl7ZI{_xeu2HN-_Fhk%F1eA2aT?GbCIPIGcpZ`Cgx&hc!e385>HP3J+{IsZ-wHTItFmBuXmJ&JRTO6qZ8f2PEJ+ zF{oU{8moQ=J1=)>wU9it;IJz!OMyritE^CsK2izNm|>4w%DwzZ>Etxp=J(P>^*Kp{ z`)r8C@_IUB9(b<2b=IaN4i5*DHQAE1z0Mh!Q$JZ^xudt4t+UezrKEwla`(W%x2hHYk-(Iodjd(44Du2{ge zNsn4I)Hj3j^#GnScu zg`N~Y6WKY=CR;wUx6QS&RtY1$_75KY27=UJchPD!}ST zIWdndMpIeDp({`y9+(wudLdK?+2JCp6B*f<($3xc&?SD=(&5V^-a-gV#cv<*i5_?&@ke?;F@2&lI`4>Y2PSvL%xl#I+u#c{ zNHjPF*>$FusSPsN28sV^DMuuAuuvo2qnue78-J;4mD3ufa@+td3SvXh8SF^}CEA{! zts^29ZSZ7Ab;m=XU0XGT;Sy-?vN!QX<-Ef+Ya)=o=imdGmkZ-A_H*S1e`x~mxGTf1 zCw4vBaHmwo1kC9y62Nc`ZE=?KwF`j^2};%&BQINSJpM7Km2 zoSwm~YJC0=(c5U7VlnW-J>Vk{8|M>+vfJVknOI)&0$%IRt`y;?_@*=v|d68 z{kcPy1O>jQMxA?rhIu`9Wh^?g-Hk#@s^3HU2$wZQIFimGB;X(m2tp$xy46mCJ!D1lnQOp^eEmVs|KNy~s5(qsU zyaF_W62 zyF=R|%6tM?bfY^Z(wAKaz7^-%+dG!-0tWO3T2WlUUu%Rh&l#>|#OVk(Gje|Ggx+!= z2oACQ&m}erwIM{OMb(|xYN-?O4H+=t0r|#^K5MrR=hs_`6?czq$ecO1=0Bl`bzF3w zk@EwCqRj+3+F6ATG)jK>_D-xErb%8zu+6Q~LfoQwyvFNRFE=3S`$-qeckz*;Vv#b^ z=w%7g@RB!Gn03PK{Ybq_#c)am%+D8RX%Z@}BF@Lj#T53FjS^WS!?~&Z3XMCfz z5&M-v@<~DaBk~46u<#&19l)9(QwPww&8D$PqV{^4nKu)C&AIIexQ0|FMafKj4mH8O ziwozhm_s4>-~o~bwnX1j#C?V76ZGV*oUgUZ<4rOk)J z2mpWw3;;m&7uNNE)$58<)co5kAx+KHX9PuBNc?1|dDJ7}s2y_UK=kN|4%3hVfxNh_| zb^Uu_scPT}HRWL`M6ZF)=oW-Yn@@^={Vli^`-?=i`OW$~0Q!{FYHa{IKN zinf9^6Suym5{_wsEb=FehFK^(vB^-M$XE2OArDrYw98NtuWaJ`bf_2kz7? zNeZSEtzBn-#7lA#HP_P)fOsA5D(}&@Wa{*mn#mZR3dr{xrkQd5Bm)mcAmeGlUYSC! zAd=|9SL_B>sBPev%hPS`N}k*mt!+W~HGV{AFP12ys&e4kJ2 zhD1$u$N2HQ5xQ`k01#LCJZLKCB3Z}c&er=K{dB_=-3)m7O zJcPH1n5v~~pp;@;X3Z)B{udk&?J67f1=a}-Nsh5Eh7v+Q7RL^=(cq#r6x^FiQcoOL zu7cK7zWZ@5i{(&K-{+DCdMK_Ia<9_hvqngNz`A%WpZ*z*k}}>J}Uo zyQ*?A-08c?i4KM2UCSIc#71K=w3_nzC5B2Tc4Wb-IUNqY?~o;QPq!~jlpR|@mMf@6 z?FO3?o#{C*2D;p}e(^*`nss92#2I%$_Lv|9uwp%;BQ|6)O38X-n`3mN&w(Y`>ju(-)1-VfA7=x#lQZn zOGhPo`H+Gq7t5_1o8~R%gjbmb5QX}JP-9V06=>y-eA01d+^&V~!&7Gcqig6N=4)JJWkM(zNs%!F5^CTm8 zw3E_fHKwTnl2oCBzE@In8_o~E`I2ZN1gw+`ApEgVG#9H+Zcr7;Z|8%Ab(NVJs$==WU;``WAe_6-05;P1tm{~?9`)}4PYgTD7({2c6G#Lxc; zw70{++1$#A+QHb+*xb(PUzsGSDJI4ye^8D~OHkJ_PuEW|0=-dzg1N^E>LR~2M0`8s zBtLva0{CNPb#Q?9kQP?rrxuqHp?%-%i=~msZ;e&n4y4~TA^+EA()=>wBEpJFG}0n} zW*F#q44MBJ2Y=QA@^>x1{(j{ASwqC%H9P_M{U+>fC#`R8qv)jnYn;BPHuE{{{9jN5`9o?UTUQ$^TYaN{%Fw+h z9I@qagnj#O`QFZ#>34)&pnp#IHqE%X^E*448QVCS8~%E!_PvGJEBFc6-!xOP008iR zXCVvxPc8g0-8UK9wY;}aBLIIFAO4r#LH$#@xAQf1Fm`m5GJl&SZT_bV>=Q}{uk&^( z_aOiN68$f%p#34eu$!Tkv!l6-v52{qF~6bVn+0io8-3IN$pBEP$C}pL1(yeU?;PQ{ ze`rATZ(o%ijUB{nt&OE@Z7rSc{+Rlt!Xlv<;;kRw&XnSJ)bs>@Lj8}jQ@{D%AM+lZ zsyUYZb_L!Jg5UAtQvV^Z_@8&7PFR4U;G1`y!M^7Mr}<+(8%HO7E35y>hf|Z0)xqCJ zYQ@`Rg7-VVNajD|`(w5uce*x}-^M@i-zVf>m=*g&HYr=v|FO5<*E#iK&s z?`vrPf}QkVt*HH;^L?$+U&vJYzgHf8&;GvB-!FKn{~zoAy{CR(Xzdr&wElY8wfC&= zE0+9%vGHH3U-DPEn}jL{#SCY?+v{drTm3FkN-%b^1Xrg0)W4e?ET-#0sh-w^_@)EFD(20{}K&*Z}YwK z(=T`i{+BvX?@hgz%J_xGi2qJFs1?c?0ni=q(^ZgU(UpT4%tIwm~bH0y$|3Yv3|1B2&p80)5@fU`> z{+syXzx@aAqFld__4U7qdA&FDKJxMlc0K=V9Oj>7|BSl)YtRJkcVyi?|8opRP7)a8 Sx4&U{`_X;#0j%ENzWqNgAOXGr diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ -#Sat Apr 24 16:03:07 CEST 2021 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip distributionPath=wrapper/dists -zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew --- a/gradlew +++ b/gradlew @@ -1,78 +1,127 @@ -#!/usr/bin/env sh +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -81,92 +130,120 @@ location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=$((i+1)) + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=$(save "$@") - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" fi +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat --- a/gradlew.bat +++ b/gradlew.bat @@ -1,4 +1,20 @@ -@if "%DEBUG%" == "" @echo off +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -9,19 +25,23 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -35,7 +55,7 @@ set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -45,38 +65,26 @@ goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal