From 16d49fd09bd6c9783f57885eb3314001fe9398dc Mon Sep 17 00:00:00 2001 From: Mustafa Turhan Date: Mon, 6 Oct 2025 16:06:13 +0300 Subject: [PATCH 1/8] feat: use maplibre-gl instead of leaflet --- .env | 2 +- eslint.config.js | 17 +- package-lock.json | 1508 +++- package.json | 11 +- src/app.tsx | 14 + src/components/heightgraph.tsx | 368 + src/controls/directions/output-control.tsx | 18 +- src/index.css | 4 +- src/map/draw-control.tsx | 53 + src/map/extra-markers.ts | 174 - src/map/index.tsx | 1496 ++-- src/map/map.css | 6 +- src/map/style.json | 8809 ++++++++++++++++++++ src/reducers/common.ts | 2 +- src/utils/heightgraph.ts | 16 +- 15 files changed, 11451 insertions(+), 1047 deletions(-) create mode 100644 src/components/heightgraph.tsx create mode 100644 src/map/draw-control.tsx delete mode 100644 src/map/extra-markers.ts create mode 100644 src/map/style.json diff --git a/.env b/.env index 29f24b1..6c3f8d8 100644 --- a/.env +++ b/.env @@ -6,4 +6,4 @@ REACT_APP_CENTER_COORDS="52.51831,13.393707" # uncomment this variable to set boundaries on the leaflet map # southwest corner, northeast corner -REACT_APP_MAX_BOUNDS="-90,-1e7,90,1e7" +REACT_APP_MAX_BOUNDS="-180,-90,180,90" diff --git a/eslint.config.js b/eslint.config.js index 4aa23f2..03ebb8b 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -16,21 +16,6 @@ export default defineConfig( 'coverage', 'playwright-report', 'test-results', - '**/*.css', - '**/*.scss', - '**/*.less', - '**/*.svg', - '**/*.png', - '**/*.jpg', - '**/*.jpeg', - '**/*.gif', - '**/*.ico', - '**/*.woff', - '**/*.woff2', - '**/*.ttf', - '**/*.eot', - '**/*.DS_Store', - 'src/vite-env.d.ts', ], }, { @@ -103,7 +88,7 @@ export default defineConfig( }, }, { - files: ['src/**/*.*'], + files: ['**/*.ts', '**/*.tsx'], plugins: { 'check-file': checkFile }, rules: { 'check-file/filename-naming-convention': [ diff --git a/package-lock.json b/package-lock.json index bf3f7df..a2256a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,21 +10,22 @@ "dependencies": { "@emotion/react": "^11.10.5", "@emotion/styled": "^11.10.5", - "@geoman-io/leaflet-geoman-free": "^2.13.1", "@mui/material": "^5.10.14", "@turf/turf": "^6.5.0", + "@versatiles/style": "^5.7.0", + "@watergis/maplibre-gl-terradraw": "^1.8.0", "axios": "^1.1.3", + "d3": "^7.9.0", "date-fns": "^2.29.3", "json-format": "^1.0.1", - "leaflet": "^1.9.3", - "leaflet-extra-markers": "^1.2.1", - "leaflet.heightgraph": "^1.4.0", + "maplibre-gl": "^5.8.0", "prop-types": "^15.8.1", "ramda": "^0.28.0", "react": "^17.0.0", "react-beautiful-dnd": "^13.1.1", "react-copy-to-clipboard": "^5.1.0", "react-dom": "^17.0.0", + "react-map-gl": "^8.0.4", "react-modern-drawer": "^1.1.2", "react-redux": "^8.0.4", "react-toastify": "^9.1.2", @@ -40,8 +41,8 @@ "@playwright/test": "^1.55.0", "@svgr/core": "^8.1.0", "@svgr/plugin-jsx": "^8.1.0", + "@types/d3": "^7.4.3", "@types/eslint-plugin-jsx-a11y": "^6.10.0", - "@types/leaflet": "^1.9.20", "@types/node": "^24.4.0", "@types/ramda": "^0.31.1", "@types/react-beautiful-dnd": "^13.1.8", @@ -1171,22 +1172,6 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, - "node_modules/@geoman-io/leaflet-geoman-free": { - "version": "2.14.2", - "resolved": "https://registry.npmjs.org/@geoman-io/leaflet-geoman-free/-/leaflet-geoman-free-2.14.2.tgz", - "integrity": "sha512-6lIyG8RvSVdFjVjiQgBPyNASjymSyqzsiUeBW0pA+q41lB5fAg4SDC6SfJvWdEyDHa81Jb5FWjUkCc9O+u0gbg==", - "dependencies": { - "@turf/boolean-contains": "^6.5.0", - "@turf/kinks": "^6.5.0", - "@turf/line-intersect": "^6.5.0", - "@turf/line-split": "^6.5.0", - "lodash": "4.17.21", - "polygon-clipping": "0.15.3" - }, - "peerDependencies": { - "leaflet": "^1.2.0" - } - }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1371,6 +1356,118 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mapbox/geojson-rewind": { + "version": "0.5.2", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@mapbox/geojson-rewind/-/geojson-rewind-0.5.2.tgz", + "integrity": "sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA==", + "license": "ISC", + "dependencies": { + "get-stream": "^6.0.1", + "minimist": "^1.2.6" + }, + "bin": { + "geojson-rewind": "geojson-rewind" + } + }, + "node_modules/@mapbox/jsonlint-lines-primitives": { + "version": "2.0.2", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz", + "integrity": "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@mapbox/point-geometry": { + "version": "1.1.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@mapbox/point-geometry/-/point-geometry-1.1.0.tgz", + "integrity": "sha512-YGcBz1cg4ATXDCM/71L9xveh4dynfGmcLDqufR+nQQy3fKwsAZsWd/x4621/6uJaeB9mwOHE6hPeDgXz9uViUQ==", + "license": "ISC" + }, + "node_modules/@mapbox/tiny-sdf": { + "version": "2.0.7", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@mapbox/tiny-sdf/-/tiny-sdf-2.0.7.tgz", + "integrity": "sha512-25gQLQMcpivjOSA40g3gO6qgiFPDpWRoMfd+G/GoppPIeP6JDaMMkMrEJnMZhKyyS6iKwVt5YKu02vCUyJM3Ug==", + "license": "BSD-2-Clause" + }, + "node_modules/@mapbox/unitbezier": { + "version": "0.0.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz", + "integrity": "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==", + "license": "BSD-2-Clause" + }, + "node_modules/@mapbox/vector-tile": { + "version": "2.0.4", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@mapbox/vector-tile/-/vector-tile-2.0.4.tgz", + "integrity": "sha512-AkOLcbgGTdXScosBWwmmD7cDlvOjkg/DetGva26pIRiZPdeJYjYKarIlb4uxVzi6bwHO6EWH82eZ5Nuv4T5DUg==", + "license": "BSD-3-Clause", + "dependencies": { + "@mapbox/point-geometry": "~1.1.0", + "@types/geojson": "^7946.0.16", + "pbf": "^4.0.1" + } + }, + "node_modules/@mapbox/vector-tile/node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, + "node_modules/@mapbox/whoots-js": { + "version": "3.1.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz", + "integrity": "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==", + "license": "ISC", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@maplibre/maplibre-gl-style-spec": { + "version": "24.2.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-24.2.0.tgz", + "integrity": "sha512-cE80g83fRcBbZbQC70siOUxUK6YJ/5ZkClDZbmm+hzrUbv+J6yntkMmcpdz9DbOrWOM7FHKR5rruc6Q/hWx5cA==", + "license": "ISC", + "dependencies": { + "@mapbox/jsonlint-lines-primitives": "~2.0.2", + "@mapbox/unitbezier": "^0.0.1", + "json-stringify-pretty-compact": "^4.0.0", + "minimist": "^1.2.8", + "quickselect": "^3.0.0", + "rw": "^1.3.3", + "tinyqueue": "^3.0.0" + }, + "bin": { + "gl-style-format": "dist/gl-style-format.mjs", + "gl-style-migrate": "dist/gl-style-migrate.mjs", + "gl-style-validate": "dist/gl-style-validate.mjs" + } + }, + "node_modules/@maplibre/maplibre-gl-style-spec/node_modules/quickselect": { + "version": "3.0.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/quickselect/-/quickselect-3.0.0.tgz", + "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==", + "license": "ISC" + }, + "node_modules/@maplibre/maplibre-gl-style-spec/node_modules/tinyqueue": { + "version": "3.0.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/tinyqueue/-/tinyqueue-3.0.0.tgz", + "integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==", + "license": "ISC" + }, + "node_modules/@maplibre/vt-pbf": { + "version": "4.0.3", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@maplibre/vt-pbf/-/vt-pbf-4.0.3.tgz", + "integrity": "sha512-YsW99BwnT+ukJRkseBcLuZHfITB4puJoxnqPVjo72rhW/TaawVYsgQHcqWLzTxqknttYoDpgyERzWSa/XrETdA==", + "license": "MIT", + "dependencies": { + "@mapbox/point-geometry": "^1.1.0", + "@mapbox/vector-tile": "^2.0.4", + "@types/geojson-vt": "3.2.5", + "@types/supercluster": "^7.1.3", + "geojson-vt": "^4.0.2", + "pbf": "^4.0.1", + "supercluster": "^8.0.1" + } + }, "node_modules/@mui/base": { "version": "5.0.0-alpha.124", "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.124.tgz", @@ -3900,6 +3997,290 @@ "@types/deep-eql": "*" } }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, "node_modules/@types/deep-eql": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", @@ -3940,6 +4321,15 @@ "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.8.tgz", "integrity": "sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA==" }, + "node_modules/@types/geojson-vt": { + "version": "3.2.5", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@types/geojson-vt/-/geojson-vt-3.2.5.tgz", + "integrity": "sha512-qDO7wqtprzlpe8FfQ//ClPV9xiuoh2nkIgiouIptON9w5jvD/fA4szvP9GBlDVdJ5dldAl0kX/sy3URbWwLx0g==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/hoist-non-react-statics": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", @@ -3963,16 +4353,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/leaflet": { - "version": "1.9.20", - "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.20.tgz", - "integrity": "sha512-rooalPMlk61LCaLOvBF2VIf9M47HgMQqi5xQ9QRi7c8PkdIe0WrIi5IxXUXQjAdL0c+vcQ01mYWbthzmp9GHWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/geojson": "*" - } - }, "node_modules/@types/node": { "version": "24.4.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.4.0.tgz", @@ -4076,6 +4456,15 @@ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" }, + "node_modules/@types/supercluster": { + "version": "7.1.3", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@types/supercluster/-/supercluster-7.1.3.tgz", + "integrity": "sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/throttle-debounce": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@types/throttle-debounce/-/throttle-debounce-5.0.2.tgz", @@ -4652,6 +5041,97 @@ "win32" ] }, + "node_modules/@versatiles/style": { + "version": "5.7.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@versatiles/style/-/style-5.7.0.tgz", + "integrity": "sha512-0ErIltcPs92T1jfleEm5SKJEO7wYMO9Ji4Sxkr3zpJNZjah94gNIoEiqTcr2Eq7SH4D1MxbIUwYbMhfdhpuLRg==", + "license": "MIT", + "dependencies": { + "@versatiles/style": "^5.6.0", + "brace-expansion": "^4.0.1" + } + }, + "node_modules/@versatiles/style/node_modules/balanced-match": { + "version": "3.0.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/balanced-match/-/balanced-match-3.0.1.tgz", + "integrity": "sha512-vjtV3hiLqYDNRoiAv0zC4QaGAMPomEoq83PRmYIofPswwZurCeWR5LByXm7SyoL0Zh5+2z0+HC7jG8gSZJUh0w==", + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/@versatiles/style/node_modules/brace-expansion": { + "version": "4.0.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/brace-expansion/-/brace-expansion-4.0.1.tgz", + "integrity": "sha512-YClrbvTCXGe70pU2JiEiPLYXO9gQkyxYeKpJIQHVS/gOs6EWMQP2RYBwjFLNT322Ji8TOC3IMPfsYCedNpzKfA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^3.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@vis.gl/react-mapbox": { + "version": "8.0.4", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@vis.gl/react-mapbox/-/react-mapbox-8.0.4.tgz", + "integrity": "sha512-NFk0vsWcNzSs0YCsVdt2100Zli9QWR+pje8DacpLkkGEAXFaJsFtI1oKD0Hatiate4/iAIW39SQHhgfhbeEPfQ==", + "license": "MIT", + "peerDependencies": { + "mapbox-gl": ">=3.5.0", + "react": ">=16.3.0", + "react-dom": ">=16.3.0" + }, + "peerDependenciesMeta": { + "mapbox-gl": { + "optional": true + } + } + }, + "node_modules/@vis.gl/react-maplibre": { + "version": "8.0.4", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@vis.gl/react-maplibre/-/react-maplibre-8.0.4.tgz", + "integrity": "sha512-HwZyfLjEu+y1mUFvwDAkVxinGm8fEegaWN+O8np/WZ2Sqe5Lv6OXFpV6GWz9LOEvBYMbGuGk1FQdejo+4HCJ5w==", + "license": "MIT", + "dependencies": { + "@maplibre/maplibre-gl-style-spec": "^19.2.1" + }, + "peerDependencies": { + "maplibre-gl": ">=4.0.0", + "react": ">=16.3.0", + "react-dom": ">=16.3.0" + }, + "peerDependenciesMeta": { + "maplibre-gl": { + "optional": true + } + } + }, + "node_modules/@vis.gl/react-maplibre/node_modules/@maplibre/maplibre-gl-style-spec": { + "version": "19.3.3", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-19.3.3.tgz", + "integrity": "sha512-cOZZOVhDSulgK0meTsTkmNXb1ahVvmTmWmfx9gRBwc6hq98wS9JP35ESIoNq3xqEan+UN+gn8187Z6E4NKhLsw==", + "license": "ISC", + "dependencies": { + "@mapbox/jsonlint-lines-primitives": "~2.0.2", + "@mapbox/unitbezier": "^0.0.1", + "json-stringify-pretty-compact": "^3.0.0", + "minimist": "^1.2.8", + "rw": "^1.3.3", + "sort-object": "^3.0.3" + }, + "bin": { + "gl-style-format": "dist/gl-style-format.mjs", + "gl-style-migrate": "dist/gl-style-migrate.mjs", + "gl-style-validate": "dist/gl-style-validate.mjs" + } + }, + "node_modules/@vis.gl/react-maplibre/node_modules/json-stringify-pretty-compact": { + "version": "3.0.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/json-stringify-pretty-compact/-/json-stringify-pretty-compact-3.0.0.tgz", + "integrity": "sha512-Rc2suX5meI0S3bfdZuA7JMFBGkJ875ApfVyq2WHELjBiiG22My/l7/8zPpH/CfFVQHuVLd8NLR0nv6vi0BYYKA==", + "license": "MIT" + }, "node_modules/@vitejs/plugin-react": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.0.3.tgz", @@ -4832,10 +5312,32 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "node_modules/@watergis/maplibre-gl-terradraw": { + "version": "1.8.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@watergis/maplibre-gl-terradraw/-/maplibre-gl-terradraw-1.8.0.tgz", + "integrity": "sha512-TtcRB7twFo3R9lSrtgo2M/vlm5E4fg2BFhhdHIJqkirCy2c2s2uFS9Mct3TuSt0Jf2vqCpcJggalHCWGi9di7w==", + "license": "MIT", + "engines": { + "pnpm": "^10.0.0" + }, + "peerDependencies": { + "maplibre-gl": "^4.0.0 || ^5.0.0", + "terra-draw": "^1.0.0", + "terra-draw-maplibre-gl-adapter": "^1.0.0" + }, + "peerDependenciesMeta": { + "terra-draw-maplibre-gl-adapter": { + "optional": true + }, + "terradraw": { + "optional": true + } + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", "bin": { @@ -4941,6 +5443,15 @@ "node": ">= 0.4" } }, + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/array-buffer-byte-length": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", @@ -5132,6 +5643,15 @@ "node": ">=12" } }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ast-types-flow": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", @@ -5345,6 +5865,25 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bytewise": { + "version": "1.1.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/bytewise/-/bytewise-1.1.0.tgz", + "integrity": "sha512-rHuuseJ9iQ0na6UDhnrRVDh8YnWVlU6xM3VH6q/+yHDeUH2zIhUzP+2/h3LIrhLDBtTqzWpE3p3tP/boefskKQ==", + "license": "MIT", + "dependencies": { + "bytewise-core": "^1.2.2", + "typewise": "^1.0.3" + } + }, + "node_modules/bytewise-core": { + "version": "1.2.3", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/bytewise-core/-/bytewise-core-1.2.3.tgz", + "integrity": "sha512-nZD//kc78OOxeYtRlVk8/zXqTB4gf/nlguL1ggWA8FuchMyOxcyHR4QPQZMUmA7czC+YnaBrPUCubqAWe50DaA==", + "license": "MIT", + "dependencies": { + "typewise-core": "^1.2" + } + }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -5654,38 +6193,444 @@ "dev": true, "license": "MIT", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-box-model": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", + "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", + "dependencies": { + "tiny-invariant": "^1.0.6" + } + }, + "node_modules/csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", + "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==" + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour/node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.7.1.tgz", + "integrity": "sha512-O4AempWAr+P5qbk2bC2FuN/sDW4z+dN2wDf9QV3bxQt4M5HfOEeXLgJ/UKQW0+o1Dj8BE+L5kiDbdWUMjsmQpw==", + "dependencies": { + "d3-array": "1" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale/node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time/node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" }, "engines": { - "node": ">= 8" + "node": ">=12" } }, - "node_modules/css-box-model": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", - "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", - "dependencies": { - "tiny-invariant": "^1.0.6" + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" } }, - "node_modules/csstype": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" - }, - "node_modules/d3-array": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", - "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==" - }, - "node_modules/d3-geo": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.7.1.tgz", - "integrity": "sha512-O4AempWAr+P5qbk2bC2FuN/sDW4z+dN2wDf9QV3bxQt4M5HfOEeXLgJ/UKQW0+o1Dj8BE+L5kiDbdWUMjsmQpw==", + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", "dependencies": { - "d3-array": "1" + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" } }, "node_modules/d3-voronoi": { @@ -5693,6 +6638,46 @@ "resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.2.tgz", "integrity": "sha512-RhGS1u2vavcO7ay7ZNAPo4xeDh/VYeGof3x5ZLJBQgYhLegxr3s5IykvWmJ94FTU6mcbtp4sloqZ54mP6R4Utw==" }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -5855,6 +6840,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/delaunator/node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "license": "Unlicense" + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -6841,6 +7841,18 @@ "node": ">=12.0.0" } }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -7211,6 +8223,12 @@ "quickselect": "^2.0.0" } }, + "node_modules/geojson-vt": { + "version": "4.0.2", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/geojson-vt/-/geojson-vt-4.0.2.tgz", + "integrity": "sha512-AV9ROqlNqoZEIJGfm1ncNjEXfkz2hdFlZf0qkVfmkwdKa8vj7H16YUOT81rJw1rdFhyEDlN2Tds91p/glzbl5A==", + "license": "ISC" + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://packages.atlassian.com/api/npm/npm-remote/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -7252,7 +8270,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, "engines": { "node": ">=10" }, @@ -7291,6 +8308,15 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/get-value": { + "version": "2.0.6", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/gh-pages": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-4.0.0.tgz", @@ -7313,6 +8339,12 @@ "node": ">=10" } }, + "node_modules/gl-matrix": { + "version": "3.4.4", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/gl-matrix/-/gl-matrix-3.4.4.tgz", + "integrity": "sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==", + "license": "MIT" + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -7557,6 +8589,18 @@ "url": "https://github.com/sponsors/typicode" } }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", @@ -7630,6 +8674,15 @@ "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -7806,6 +8859,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -7927,6 +8989,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -8096,6 +9170,15 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", @@ -8277,6 +9360,12 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "node_modules/json-stringify-pretty-compact": { + "version": "4.0.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/json-stringify-pretty-compact/-/json-stringify-pretty-compact-4.0.0.tgz", + "integrity": "sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==", + "license": "MIT" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -8314,6 +9403,12 @@ "node": ">=4.0" } }, + "node_modules/kdbush": { + "version": "4.0.2", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/kdbush/-/kdbush-4.0.2.tgz", + "integrity": "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==", + "license": "ISC" + }, "node_modules/keyboard-key": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/keyboard-key/-/keyboard-key-1.1.0.tgz", @@ -8349,27 +9444,6 @@ "node": ">=0.10" } }, - "node_modules/leaflet": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.3.tgz", - "integrity": "sha512-iB2cR9vAkDOu5l3HAay2obcUHZ7xwUBBjph8+PGtmW/2lYhbLizWtG7nTeYht36WfOslixQF9D/uSIzhZgGMfQ==" - }, - "node_modules/leaflet-extra-markers": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/leaflet-extra-markers/-/leaflet-extra-markers-1.2.1.tgz", - "integrity": "sha512-k3mGGBJg5wr4LY89MyLHcWCol3yNC9qcK1+5os+HjBk09RlxmqsywNSBwmpP1WKnkKCIgofnOFOhdM5GeK0kHQ==", - "peerDependencies": { - "leaflet": ">= 0.5 < 2" - } - }, - "node_modules/leaflet.heightgraph": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/leaflet.heightgraph/-/leaflet.heightgraph-1.4.0.tgz", - "integrity": "sha512-X/HEtDIr9SCKbyJYdhv+jxqN1MwWEusxIaDB4fjQ5Cz4Tx8u7blcRZtVgig7tP237nVuHNnTpS6FZKdfCGMVng==", - "dependencies": { - "leaflet": "^1.6.0" - } - }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -8717,6 +9791,67 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/maplibre-gl": { + "version": "5.8.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/maplibre-gl/-/maplibre-gl-5.8.0.tgz", + "integrity": "sha512-zLblPFK+z5sxeitDF8RL2cnqfRaivNwxbGoQMfwAm9st6d1lRGTxgI7NNNr/U1AEPkp5+X+wjROagiHvJD8aqg==", + "license": "BSD-3-Clause", + "dependencies": { + "@mapbox/geojson-rewind": "^0.5.2", + "@mapbox/jsonlint-lines-primitives": "^2.0.2", + "@mapbox/point-geometry": "^1.1.0", + "@mapbox/tiny-sdf": "^2.0.7", + "@mapbox/unitbezier": "^0.0.1", + "@mapbox/vector-tile": "^2.0.4", + "@mapbox/whoots-js": "^3.1.0", + "@maplibre/maplibre-gl-style-spec": "^24.2.0", + "@maplibre/vt-pbf": "^4.0.3", + "@types/geojson": "^7946.0.16", + "@types/geojson-vt": "3.2.5", + "@types/supercluster": "^7.1.3", + "earcut": "^3.0.2", + "geojson-vt": "^4.0.2", + "gl-matrix": "^3.4.4", + "kdbush": "^4.0.2", + "murmurhash-js": "^1.0.0", + "pbf": "^4.0.1", + "potpack": "^2.1.0", + "quickselect": "^3.0.0", + "supercluster": "^8.0.1", + "tinyqueue": "^3.0.0" + }, + "engines": { + "node": ">=16.14.0", + "npm": ">=8.1.0" + }, + "funding": { + "url": "https://github.com/maplibre/maplibre-gl-js?sponsor=1" + } + }, + "node_modules/maplibre-gl/node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, + "node_modules/maplibre-gl/node_modules/earcut": { + "version": "3.0.2", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/earcut/-/earcut-3.0.2.tgz", + "integrity": "sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==", + "license": "ISC" + }, + "node_modules/maplibre-gl/node_modules/quickselect": { + "version": "3.0.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/quickselect/-/quickselect-3.0.0.tgz", + "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==", + "license": "ISC" + }, + "node_modules/maplibre-gl/node_modules/tinyqueue": { + "version": "3.0.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/tinyqueue/-/tinyqueue-3.0.0.tgz", + "integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==", + "license": "ISC" + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://packages.atlassian.com/api/npm/npm-remote/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -8808,7 +9943,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -8830,6 +9964,12 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/murmurhash-js": { + "version": "1.0.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/murmurhash-js/-/murmurhash-js-1.0.0.tgz", + "integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==", + "license": "MIT" + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://packages.atlassian.com/api/npm/npm-remote/nanoid/-/nanoid-3.3.11.tgz", @@ -9288,6 +10428,18 @@ "node": ">= 14.16" } }, + "node_modules/pbf": { + "version": "4.0.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/pbf/-/pbf-4.0.1.tgz", + "integrity": "sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA==", + "license": "BSD-3-Clause", + "dependencies": { + "resolve-protobuf-schema": "^2.1.0" + }, + "bin": { + "pbf": "bin/pbf" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://packages.atlassian.com/api/npm/npm-remote/picocolors/-/picocolors-1.1.1.tgz", @@ -9496,6 +10648,12 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/potpack": { + "version": "2.1.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/potpack/-/potpack-2.1.0.tgz", + "integrity": "sha512-pcaShQc1Shq0y+E7GqJqvZj8DTthWV1KeHGdi0Z6IAin2Oi3JnLCOfwnCo84qc+HAp52wT9nK9H7FAJp5a44GQ==", + "license": "ISC" + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -9549,6 +10707,12 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/protocol-buffers-schema": { + "version": "3.6.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", + "integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==", + "license": "MIT" + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -9706,6 +10870,30 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, + "node_modules/react-map-gl": { + "version": "8.0.4", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/react-map-gl/-/react-map-gl-8.0.4.tgz", + "integrity": "sha512-SHdpvFIvswsZBg6BCPcwY/nbKuCo3sJM1Cj7Sd+gA3gFRFOixD+KtZ2XSuUWq2WySL2emYEXEgrLZoXsV4Ut4Q==", + "license": "MIT", + "dependencies": { + "@vis.gl/react-mapbox": "8.0.4", + "@vis.gl/react-maplibre": "8.0.4" + }, + "peerDependencies": { + "mapbox-gl": ">=1.13.0", + "maplibre-gl": ">=1.13.0", + "react": ">=16.3.0", + "react-dom": ">=16.3.0" + }, + "peerDependenciesMeta": { + "mapbox-gl": { + "optional": true + }, + "maplibre-gl": { + "optional": true + } + } + }, "node_modules/react-modern-drawer": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/react-modern-drawer/-/react-modern-drawer-1.2.0.tgz", @@ -9924,6 +11112,15 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, + "node_modules/resolve-protobuf-schema": { + "version": "2.1.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz", + "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==", + "license": "MIT", + "dependencies": { + "protocol-buffers-schema": "^3.3.1" + } + }, "node_modules/restore-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", @@ -10007,6 +11204,12 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, "node_modules/rxjs": { "version": "7.8.0", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", @@ -10071,6 +11274,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, "node_modules/scheduler": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", @@ -10169,6 +11378,21 @@ "node": ">= 0.4" } }, + "node_modules/set-value": { + "version": "2.0.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "license": "MIT", + "dependencies": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/shallowequal": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", @@ -10328,6 +11552,41 @@ "tslib": "^2.0.3" } }, + "node_modules/sort-asc": { + "version": "0.2.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/sort-asc/-/sort-asc-0.2.0.tgz", + "integrity": "sha512-umMGhjPeHAI6YjABoSTrFp2zaBtXBej1a0yKkuMUyjjqu6FJsTF+JYwCswWDg+zJfk/5npWUUbd33HH/WLzpaA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-desc": { + "version": "0.2.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/sort-desc/-/sort-desc-0.2.0.tgz", + "integrity": "sha512-NqZqyvL4VPW+RAxxXnB8gvE1kyikh8+pR+T+CXLksVRN9eiQqkQlPwqWYU0mF9Jm7UnctShlxLyAt1CaBOTL1w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-object": { + "version": "3.0.3", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/sort-object/-/sort-object-3.0.3.tgz", + "integrity": "sha512-nK7WOY8jik6zaG9CRwZTaD5O7ETWDLZYMM12pqY8htll+7dYeqGfEUPcUBHOpSJg2vJOrvFIY2Dl5cX2ih1hAQ==", + "license": "MIT", + "dependencies": { + "bytewise": "^1.1.0", + "get-value": "^2.0.2", + "is-extendable": "^0.1.1", + "sort-asc": "^0.2.0", + "sort-desc": "^0.2.0", + "union-value": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -10351,6 +11610,43 @@ "resolved": "https://registry.npmjs.org/splaytree/-/splaytree-3.1.1.tgz", "integrity": "sha512-9FaQ18FF0+sZc/ieEeXHt+Jw2eSpUgUtTLDYB/HXKWvhYVyOc7h1hzkn5MMO3GPib9MmXG1go8+OsBBzs/NMww==" }, + "node_modules/split-string": { + "version": "3.1.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "license": "MIT", + "dependencies": { + "extend-shallow": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split-string/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "license": "MIT", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split-string/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/stable-hash-x": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/stable-hash-x/-/stable-hash-x-0.2.0.tgz", @@ -10695,6 +11991,15 @@ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.1.3.tgz", "integrity": "sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==" }, + "node_modules/supercluster": { + "version": "8.0.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/supercluster/-/supercluster-8.0.1.tgz", + "integrity": "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==", + "license": "ISC", + "dependencies": { + "kdbush": "^4.0.2" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://packages.atlassian.com/api/npm/npm-remote/supports-color/-/supports-color-7.2.0.tgz", @@ -10746,6 +12051,13 @@ "resolved": "https://registry.npmjs.org/tachyons/-/tachyons-4.12.0.tgz", "integrity": "sha512-2nA2IrYFy3raCM9fxJ2KODRGHVSZNTW3BR0YnlGsLUf1DA3pk3YfWZ/DdfbnZK6zLZS+jUenlUGJsKcA5fUiZg==" }, + "node_modules/terra-draw": { + "version": "1.15.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/terra-draw/-/terra-draw-1.15.0.tgz", + "integrity": "sha512-a+Tb+Fo1Qq5Ps3HmCem+06Qt0cEdyZVOa6/7Teo5DkvRikxVzdTKxzASNmsG9V9ckJlJI+Nce5sdP/7L9Lb0ig==", + "license": "MIT", + "peer": true + }, "node_modules/test-exclude": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", @@ -11196,6 +12508,21 @@ "typescript": ">=4.8.4 <6.0.0" } }, + "node_modules/typewise": { + "version": "1.0.3", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/typewise/-/typewise-1.0.3.tgz", + "integrity": "sha512-aXofE06xGhaQSPzt8hlTY+/YWQhm9P0jYUp1f2XtmW/3Bk0qzXcyFWAtPoo2uTGQj1ZwbDuSyuxicq+aDo8lCQ==", + "license": "MIT", + "dependencies": { + "typewise-core": "^1.2.0" + } + }, + "node_modules/typewise-core": { + "version": "1.2.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/typewise-core/-/typewise-core-1.2.0.tgz", + "integrity": "sha512-2SCC/WLzj2SbUwzFOzqMCkz5amXLlxtJqDKTICqg30x+2DZxcfZN2MvQZmGfXWKNWaKK9pBPsvkcwv8bF/gxKg==", + "license": "MIT" + }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", @@ -11222,6 +12549,21 @@ "dev": true, "license": "MIT" }, + "node_modules/union-value": { + "version": "1.0.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "license": "MIT", + "dependencies": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", diff --git a/package.json b/package.json index 5b5b600..83118f2 100644 --- a/package.json +++ b/package.json @@ -25,21 +25,22 @@ "dependencies": { "@emotion/react": "^11.10.5", "@emotion/styled": "^11.10.5", - "@geoman-io/leaflet-geoman-free": "^2.13.1", "@mui/material": "^5.10.14", "@turf/turf": "^6.5.0", + "@versatiles/style": "^5.7.0", + "@watergis/maplibre-gl-terradraw": "^1.8.0", "axios": "^1.1.3", + "d3": "^7.9.0", "date-fns": "^2.29.3", "json-format": "^1.0.1", - "leaflet": "^1.9.3", - "leaflet-extra-markers": "^1.2.1", - "leaflet.heightgraph": "^1.4.0", + "maplibre-gl": "^5.8.0", "prop-types": "^15.8.1", "ramda": "^0.28.0", "react": "^17.0.0", "react-beautiful-dnd": "^13.1.1", "react-copy-to-clipboard": "^5.1.0", "react-dom": "^17.0.0", + "react-map-gl": "^8.0.4", "react-modern-drawer": "^1.1.2", "react-redux": "^8.0.4", "react-toastify": "^9.1.2", @@ -55,8 +56,8 @@ "@playwright/test": "^1.55.0", "@svgr/core": "^8.1.0", "@svgr/plugin-jsx": "^8.1.0", + "@types/d3": "^7.4.3", "@types/eslint-plugin-jsx-a11y": "^6.10.0", - "@types/leaflet": "^1.9.20", "@types/node": "^24.4.0", "@types/ramda": "^0.31.1", "@types/react-beautiful-dnd": "^13.1.8", diff --git a/src/app.tsx b/src/app.tsx index 60692b1..9e0eb83 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -1,6 +1,7 @@ import Map from './map'; import MainControl from './controls'; import SettingsPanel from './controls/settings-panel'; +import { ToastContainer } from 'react-toastify'; export const App = () => { return ( @@ -8,6 +9,19 @@ export const App = () => { + ); }; diff --git a/src/components/heightgraph.tsx b/src/components/heightgraph.tsx new file mode 100644 index 0000000..d64c7f2 --- /dev/null +++ b/src/components/heightgraph.tsx @@ -0,0 +1,368 @@ +import React, { useEffect, useRef, useState } from 'react'; +import * as d3 from 'd3'; +import { colorMappings } from '@/utils/heightgraph'; +import makeResizable from '@/utils/resizable'; +import type { FeatureCollection, GeoJsonProperties, Geometry } from 'geojson'; + +interface HeightGraphProps { + data: FeatureCollection[]; + width: number; + height?: number; + onExpand?: (expanded: boolean) => void; + onHighlight?: (index: number | null) => void; +} + +const HeightGraph: React.FC = ({ + data, + width, + height = 200, + onExpand, + onHighlight, +}) => { + const svgRef = useRef(null); + const containerRef = useRef(null); + const [isExpanded, setIsExpanded] = useState(false); + const [dimensions, setDimensions] = useState({ width, height }); + const resizerRef = useRef<{ destroy: () => void } | null>(null); + + useEffect(() => { + if (!data || data.length === 0 || !svgRef.current) return; + + const svg = d3.select(svgRef.current); + svg.selectAll('*').remove(); + + const margin = { top: 20, right: 30, bottom: 50, left: 50 }; + const chartWidth = dimensions.width - margin.left - margin.right; + const chartHeight = dimensions.height - margin.top - margin.bottom; + + const g = svg + .append('g') + .attr('transform', `translate(${margin.left},${margin.top})`); + + // Process data + // Coordinates are [lng, lat, elevation, distance] + interface CoordinateData { + lng: number; + lat: number; + elevation: number; + distance: number; + attributeType: number; + } + + const allCoordinates: CoordinateData[] = + data[0]?.features.flatMap((f) => + (f.geometry as { coordinates: number[][] }).coordinates.map((c) => ({ + lng: c[0] ?? 0, + lat: c[1] ?? 0, + elevation: c[2] ?? 0, + distance: c[3] ?? 0, // Distance from start in meters + attributeType: (f.properties?.attributeType as number) ?? 0, + })) + ) || []; + + if (!allCoordinates || allCoordinates.length === 0) return; + + // Create scales + const xExtent = d3.extent(allCoordinates, (d) => d.distance) as [ + number, + number, + ]; + const yExtent = d3.extent(allCoordinates, (d) => d.elevation) as [ + number, + number, + ]; + + const xScale = d3.scaleLinear().domain(xExtent).range([0, chartWidth]); + + const yScale = d3 + .scaleLinear() + .domain(yExtent) + .range([chartHeight, 0]) + .nice(); + + // Draw features with colors + // Coordinates are [lng, lat, elevation, distance] + data[0]?.features.forEach((feature) => { + const coords = (feature.geometry as { coordinates: number[][] }) + .coordinates; + const attributeType = feature.properties?.attributeType as number; + const color = + colorMappings.steepness[ + attributeType.toString() as keyof typeof colorMappings.steepness + ]?.color || '#cccccc'; + + const area = d3 + .area() + .x((d) => xScale(d[3] ?? 0)) // d[3] is distance + .y0(chartHeight) + .y1((d) => yScale(d[2] ?? 0)) // d[2] is elevation + .curve(d3.curveMonotoneX); + + g.append('path') + .datum(coords) + .attr('fill', color) + .attr('fill-opacity', 0.9) + .attr('stroke', 'none') + .attr('d', area); + }); + + // Add axes + const xAxis = d3 + .axisBottom(xScale) + .tickFormat((d) => `${(+d / 1000).toFixed(1)} km`); + const yAxis = d3.axisLeft(yScale).tickFormat((d) => `${d} m`); + + g.append('g') + .attr('transform', `translate(0,${chartHeight})`) + .call(xAxis) + .attr('class', 'x-axis') + .style('color', '#666'); + + g.append('g').call(yAxis).attr('class', 'y-axis').style('color', '#666'); + + // Add grid lines + g.append('g') + .attr('class', 'grid') + .attr('transform', `translate(0,${chartHeight})`) + .call( + d3 + .axisBottom(xScale) + .tickSize(-chartHeight) + .tickFormat(() => '') + ) + .style('stroke-opacity', 0.1); + + g.append('g') + .attr('class', 'grid') + .call( + d3 + .axisLeft(yScale) + .tickSize(-chartWidth) + .tickFormat(() => '') + ) + .style('stroke-opacity', 0.1); + + // Add labels + g.append('text') + .attr('x', chartWidth / 2) + .attr('y', chartHeight + 40) + .attr('text-anchor', 'middle') + .style('font-size', '12px') + .style('fill', '#666') + .text('Distance from start'); + + g.append('text') + .attr('transform', 'rotate(-90)') + .attr('x', -chartHeight / 2) + .attr('y', -35) + .attr('text-anchor', 'middle') + .style('font-size', '12px') + .style('fill', '#666') + .text('Elevation (m)'); + + // Add hover interaction + const focus = g.append('g').attr('class', 'focus').style('display', 'none'); + + focus + .append('line') + .attr('class', 'x-hover-line hover-line') + .attr('y1', 0) + .attr('y2', chartHeight); + + focus.append('circle').attr('r', 5).style('fill', 'blue'); + + const tooltip = d3 + .select(containerRef.current) + .append('div') + .attr('class', 'heightgraph-tooltip') + .style('position', 'absolute') + .style('background', 'white') + .style('padding', '8px') + .style('border', '1px solid #ccc') + .style('border-radius', '4px') + .style('pointer-events', 'none') + .style('opacity', 0) + .style('font-size', '12px') + .style('z-index', 1000); + + svg + .append('rect') + .attr('class', 'overlay') + .attr('width', dimensions.width) + .attr('height', dimensions.height) + .style('fill', 'none') + .style('pointer-events', 'all') + .on('mouseover', () => { + focus.style('display', null); + tooltip.style('opacity', 1); + }) + .on('mouseout', () => { + focus.style('display', 'none'); + tooltip.style('opacity', 0); + if (onHighlight) onHighlight(null); + }) + .on('mousemove', function (event) { + const [mouseX] = d3.pointer(event); + const adjustedX = mouseX - margin.left; + const x0 = xScale.invert(adjustedX); + + // Find closest point + const bisect = d3.bisector((d: CoordinateData) => d.distance).left; + const i = bisect(allCoordinates, x0); + const d0 = allCoordinates[i - 1]; + const d1 = allCoordinates[i]; + const d = + d0 && d1 ? (x0 - d0.distance > d1.distance - x0 ? d1 : d0) : d0 || d1; + + if (d) { + focus.attr( + 'transform', + `translate(${xScale(d.distance)},${yScale(d.elevation)})` + ); + focus.select('.x-hover-line').attr('x1', 0).attr('x2', 0); + + tooltip + .style('left', `${mouseX + 10}px`) + .style('top', `${event.offsetY - 10}px`) + .html( + `Distance: ${(d.distance / 1000).toFixed(2)} km
Elevation: ${d.elevation.toFixed(0)} m` + ); + + if (onHighlight) { + // Pass the distance value instead of index + // The map component will find the closest point on the route + onHighlight(d.distance); + } + } + }); + + return () => { + tooltip.remove(); + }; + }, [data, dimensions, onHighlight]); + + useEffect(() => { + setDimensions({ width, height }); + }, [width, height]); + + useEffect(() => { + if (containerRef.current && isExpanded) { + resizerRef.current = makeResizable(containerRef.current, { + handles: 'w, n, nw', + minWidth: 380, + minHeight: 140, + applyInlineSize: false, + onResize: ({ width, height }) => { + setDimensions({ width, height }); + }, + onStop: () => { + // Clear inline styles + if (containerRef.current) { + containerRef.current.style.width = ''; + containerRef.current.style.height = ''; + containerRef.current.style.left = ''; + containerRef.current.style.top = ''; + } + }, + }); + } + + return () => { + if ( + resizerRef.current && + typeof resizerRef.current.destroy === 'function' + ) { + resizerRef.current.destroy(); + } + }; + }, [isExpanded]); + + const handleToggleExpand = () => { + const newState = !isExpanded; + setIsExpanded(newState); + if (onExpand) onExpand(newState); + }; + + return ( + <> +
+ {isExpanded ? '−' : '▲'} +
+
+ {isExpanded && ( +
+ + {/* Legend */} +
+ {Object.entries(colorMappings.steepness).map(([key, value]) => ( +
+
+ {value.text} +
+ ))} +
+
+ )} +
+ + ); +}; + +export default HeightGraph; diff --git a/src/controls/directions/output-control.tsx b/src/controls/directions/output-control.tsx index c85438b..cec73c5 100644 --- a/src/controls/directions/output-control.tsx +++ b/src/controls/directions/output-control.tsx @@ -1,7 +1,6 @@ import { useState, useEffect, useCallback, useRef } from 'react'; import { connect } from 'react-redux'; import { Segment, Button, Icon } from 'semantic-ui-react'; -import L from 'leaflet'; import { makeRequest } from '@/actions/directions-actions'; import { downloadFile } from '@/actions/common-actions'; @@ -109,10 +108,19 @@ const OutputControl = ({ const data = routeResult?.data; const coordinates = data?.decodedGeometry; if (!coordinates) return; - const formattedData = jsonFormat( - L.polyline(coordinates as L.LatLngExpression[]).toGeoJSON(), - jsonConfig - ); + + const geoJsonCoordinates = coordinates.map(([lat, lng]) => [lng, lat]); + + const geoJson = { + type: 'Feature', + geometry: { + type: 'LineString', + coordinates: geoJsonCoordinates, + }, + properties: {}, + }; + + const formattedData = jsonFormat(geoJson, jsonConfig); e.preventDefault(); downloadFile({ data: formattedData, diff --git a/src/index.css b/src/index.css index d024dac..a1c58a4 100644 --- a/src/index.css +++ b/src/index.css @@ -1,8 +1,6 @@ @import '~semantic-ui-css/semantic.css'; -@import '~leaflet/dist/leaflet.css'; @import '~tachyons/css/tachyons.css'; @import '~react-toastify/dist/ReactToastify.css'; -@import '~leaflet-extra-markers/dist/css/leaflet.extra-markers.min.css'; body { margin: 0; @@ -68,4 +66,4 @@ i.help.grey.icon { .ui.menu:not(.vertical) .item{ justify-content: center; -} \ No newline at end of file +} diff --git a/src/map/draw-control.tsx b/src/map/draw-control.tsx new file mode 100644 index 0000000..2b9a096 --- /dev/null +++ b/src/map/draw-control.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import { useControl } from 'react-map-gl/maplibre'; +import { MaplibreTerradrawControl } from '@watergis/maplibre-gl-terradraw'; + +interface DrawControlProps { + position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'; + onUpdate?: () => void; + controlRef?: React.MutableRefObject; +} + +export function DrawControl(props: DrawControlProps) { + useControl( + // onCreate + () => { + const control = new MaplibreTerradrawControl({ + modes: ['polygon', 'select', 'delete'], + open: true, + }); + + // Store reference + if (props.controlRef) { + props.controlRef.current = control; + } + + return control; + }, + // onAdd + () => { + if (props.controlRef?.current && props.onUpdate) { + const terraDrawInstance = + props.controlRef.current.getTerraDrawInstance(); + if (terraDrawInstance) { + terraDrawInstance.on('finish', props.onUpdate); + } + } + }, + // onRemove + () => { + if (props.controlRef?.current && props.onUpdate) { + const terraDrawInstance = + props.controlRef.current.getTerraDrawInstance(); + if (terraDrawInstance) { + terraDrawInstance.off('finish', props.onUpdate); + } + } + }, + { + position: props.position || 'top-right', + } + ); + + return null; +} diff --git a/src/map/extra-markers.ts b/src/map/extra-markers.ts deleted file mode 100644 index f312a36..0000000 --- a/src/map/extra-markers.ts +++ /dev/null @@ -1,174 +0,0 @@ -import * as L from 'leaflet'; - -interface ExtraMarkersInterface { - Icon: new (options?: L.IconOptions) => L.Icon; - icon: (options: L.IconOptions) => L.Icon; -} - -const ExtraMarkers: ExtraMarkersInterface = {} as ExtraMarkersInterface; - -ExtraMarkers.Icon = L.Icon.extend({ - options: { - iconSize: [35, 45], - iconAnchor: [17, 42], - popupAnchor: [1, -32], - shadowAnchor: [10, 12], - shadowSize: [36, 16], - className: '', - prefix: '', - extraClasses: '', - shape: 'circle', - icon: '', - innerHTML: '', - markerColor: 'red', - svgBorderColor: '#fff', - svgOpacity: 1, - iconColor: '#fff', - number: '', - svg: false, - }, - initialize: function (options: L.IconOptions) { - L.Util.setOptions(this, options); - }, - createIcon: function () { - const div = document.createElement('div'); - const options = this.options; - if (options.icon) { - div.innerHTML = this._createInner(); - } - if (options.innerHTML) { - div.innerHTML = options.innerHTML; - } - if (options.bgPos) { - div.style.backgroundPosition = - -options.bgPos.x + 'px ' + -options.bgPos.y + 'px'; - } - if (!options.svg) { - this._setIconStyles(div, options.shape + '-' + options.markerColor); - } else { - this._setIconStyles(div, 'svg'); - } - return div; - }, - _createInner: function () { - let iconColorStyle = ''; - let iconNumber = ''; - const options = this.options; - if (options.iconColor) { - iconColorStyle = "style='color: " + options.iconColor + "' "; - } - if (options.number) { - iconNumber = "number='" + options.number + "' "; - } - if (options.svg) { - let svg = - ''; - if (options.shape === 'square') { - svg = - ''; - } - if (options.shape === 'star') { - svg = - ''; - } - if (options.shape === 'penta') { - svg = - ''; - } - return ( - svg + - '" - ); - } - return ( - '" - ); - }, - _setIconStyles: function (img: HTMLElement, name: string) { - const options = this.options; - const size = L.point( - options[name === 'shadow' ? 'shadowSize' : 'iconSize'] - ); - let anchor; - let leafletName; - if (name === 'shadow') { - anchor = L.point(options.shadowAnchor || options.iconAnchor); - leafletName = 'shadow'; - } else { - anchor = L.point(options.iconAnchor); - leafletName = 'icon'; - } - if (!anchor && size) { - anchor = size.divideBy(2); - } - - img.className = - 'leaflet-marker-' + - leafletName + - ' extra-marker extra-marker-' + - name + - ' ' + - options.className; - if (anchor) { - img.style.marginLeft = -anchor.x + 'px'; - img.style.marginTop = -anchor.y + 'px'; - } - if (size) { - img.style.width = size.x + 'px'; - img.style.height = size.y + 'px'; - } - }, - createShadow: function () { - const div = document.createElement('div'); - this._setIconStyles(div, 'shadow'); - return div; - }, -}); - -ExtraMarkers.icon = function (options) { - return new ExtraMarkers.Icon(options); -}; - -export default ExtraMarkers; diff --git a/src/map/index.tsx b/src/map/index.tsx index 3a9e477..638bd7a 100644 --- a/src/map/index.tsx +++ b/src/map/index.tsx @@ -1,19 +1,26 @@ -import React, { useState, useEffect, useCallback, useRef } from 'react'; -import ReactDOM from 'react-dom'; +import React, { + useState, + useEffect, + useCallback, + useRef, + useMemo, +} from 'react'; import { connect } from 'react-redux'; -import L, { LatLng, type LatLngExpression } from 'leaflet'; - -import '@geoman-io/leaflet-geoman-free'; -import '@geoman-io/leaflet-geoman-free/dist/leaflet-geoman.css'; -import 'leaflet.heightgraph'; -import 'leaflet.heightgraph/dist/L.Control.Heightgraph.min.css'; +import Map, { + Marker, + Source, + Layer, + Popup, + type MapRef, + NavigationControl, +} from 'react-map-gl/maplibre'; +import type { MaplibreTerradrawControl } from '@watergis/maplibre-gl-terradraw'; +import 'maplibre-gl/dist/maplibre-gl.css'; import axios from 'axios'; - import * as R from 'ramda'; -import ExtraMarkers from './extra-markers'; -import { Button, Label, Icon, Popup } from 'semantic-ui-react'; -import { ToastContainer } from 'react-toastify'; +import { throttle } from 'throttle-debounce'; +import { Button, Label, Icon, Popup as SemanticPopup } from 'semantic-ui-react'; import { CopyToClipboard } from 'react-copy-to-clipboard'; import { fetchReverseGeocode, @@ -26,9 +33,9 @@ import { buildHeightRequest, buildLocateRequest, } from '@/utils/valhalla'; -import { colorMappings, buildHeightgraphData } from '@/utils/heightgraph'; -import { formatDuration } from '@/utils/date-time'; -import makeResizable from '@/utils/resizable'; +import { buildHeightgraphData } from '@/utils/heightgraph'; +import HeightGraph from '@/components/heightgraph'; +import { DrawControl } from './draw-control'; import './map.css'; import { convertDDToDMS } from './utils'; import type { LastCenterStorageValue } from './types'; @@ -39,79 +46,55 @@ import type { DirectionsState } from '@/reducers/directions'; import type { IsochroneState } from '@/reducers/isochrones'; import type { Profile } from '@/reducers/common'; import type { ParsedDirectionsGeometry } from '@/common/types'; +import type { Feature, FeatureCollection, LineString } from 'geojson'; -const OSMTiles = L.tileLayer(process.env.REACT_APP_TILE_SERVER_URL!, { - attribution: - 'About this service and privacy policy | © OpenStreetMap contributors', -}); - -// for this app we create two leaflet layer groups to control, one for the isochrone centers and one for the isochrone contours -const isoCenterLayer = L.featureGroup(); -const isoPolygonLayer = L.featureGroup(); -const isoLocationsLayer = L.featureGroup(); -const routeMarkersLayer = L.featureGroup(); -const routeLineStringLayer = L.featureGroup(); -const highlightRouteSegmentlayer = L.featureGroup(); -const highlightRouteIndexLayer = L.featureGroup(); -const excludePolygonsLayer = L.featureGroup(); +// Import the style JSON +import mapStyle from './style.json'; const centerCoords = process.env.REACT_APP_CENTER_COORDS!.split(','); -let center = [ - parseFloat(centerCoords[0] || '52.51831'), +let center: [number, number] = [ parseFloat(centerCoords[1] || '13.393707'), + parseFloat(centerCoords[0] || '52.51831'), ]; let zoom_initial = 10; if (localStorage.getItem('last_center')) { - const last_center = JSON.parse( - localStorage.getItem('last_center')! - ) as LastCenterStorageValue; - center = last_center.center; - zoom_initial = last_center.zoom_level; + try { + const last_center = JSON.parse( + localStorage.getItem('last_center')! + ) as LastCenterStorageValue; + + // Validate coordinates: lng must be -180 to 180, lat must be -90 to 90 + const lng = last_center.center[1]; + const lat = last_center.center[0]; + + if ( + typeof lng === 'number' && + typeof lat === 'number' && + lng >= -180 && + lng <= 180 && + lat >= -90 && + lat <= 90 + ) { + center = [lng, lat]; + zoom_initial = last_center.zoom_level; + } else { + // Invalid coordinates, clear localStorage and use defaults + console.warn('Invalid coordinates in localStorage, using defaults'); + localStorage.removeItem('last_center'); + } + } catch { + // Invalid JSON, clear localStorage + console.warn('Invalid localStorage data, using defaults'); + localStorage.removeItem('last_center'); + } } -const maxBoundsString = ( - process.env.REACT_APP_MAX_BOUNDS || '-90,-1e7,90,1e7' -).split(','); - -const maxBounds = maxBoundsString - ? [ - //south west corner - [ - parseFloat(maxBoundsString[0] || '-90'), - parseFloat(maxBoundsString[1] || '-1e7'), - ], - //north east corner - [ - parseFloat(maxBoundsString[2] || '90'), - parseFloat(maxBoundsString[3] || '1e7'), - ], - ] - : undefined; - -const mapParams = { - center, - maxBounds, - maxBoundsViscosity: 1.0, - zoomControl: false, - zoom: zoom_initial, - maxZoom: 18, - minZoom: 2, - worldCopyJump: true, - layers: [ - isoCenterLayer, - routeMarkersLayer, - isoPolygonLayer, - isoLocationsLayer, - routeLineStringLayer, - highlightRouteSegmentlayer, - highlightRouteIndexLayer, - excludePolygonsLayer, - OSMTiles, - ], -}; +// Remove maxBounds to allow navigation anywhere in the world +// The old env var had invalid values for MapLibre (-1e7, 1e7 are not valid longitudes) +const maxBounds: [[number, number], [number, number]] | undefined = undefined; const routeObjects = { [VALHALLA_OSM_URL!]: { @@ -127,12 +110,24 @@ interface MapProps { isochrones: IsochroneState; profile: Profile; activeTab: number; - coordinates: number[]; + coordinates: number[][]; showDirectionsPanel: boolean; showSettings: boolean; } -const Map = ({ +interface MarkerData { + id: string; + lng: number; + lat: number; + type: 'waypoint' | 'isocenter'; + index?: number; + title?: string; + color?: string; + shape?: string; + number?: string; +} + +const MapComponent = ({ dispatch, directions, isochrones, @@ -147,7 +142,10 @@ const Map = ({ const [isHeightLoading, setIsHeightLoading] = useState(false); const [locate, setLocate] = useState([]); const [showInfoPopup, setShowInfoPopup] = useState(false); - const [latLng, setLatLng] = useState(null); + const [popupLngLat, setPopupLngLat] = useState<{ + lng: number; + lat: number; + } | null>(null); const [hasCopied, setHasCopied] = useState(false); const [elevation, setElevation] = useState(''); const [heightPayload, setHeightPayload] = useState<{ @@ -155,45 +153,83 @@ const Map = ({ shape: { lat: number; lon: number }[]; id: string; } | null>(null); - - const mapRef = useRef(null); - const layerControlRef = useRef(null); - // heightgraph is not typed unfortunately. - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const hgRef = useRef(null); - const heightgraphResizerRef = useRef(null); + const [heightgraphData, setHeightgraphData] = useState( + [] + ); + const [markers, setMarkers] = useState([]); + const [routeGeoJSON, setRouteGeoJSON] = useState( + null + ); + const [isochroneGeoJSON, setIsochroneGeoJSON] = + useState(null); + const [isoLocationsGeoJSON, setIsoLocationsGeoJSON] = + useState(null); + const [highlightSegmentGeoJSON, setHighlightSegmentGeoJSON] = + useState | null>(null); + const [heightgraphHoverDistance, setHeightgraphHoverDistance] = useState< + number | null + >(null); + const [viewState, setViewState] = useState({ + longitude: center[0], + latitude: center[1], + zoom: zoom_initial, + }); + + const mapRef = useRef(null); + const drawRef = useRef(null); + + // Throttle heightgraph hover updates for better performance + const throttledSetHeightgraphHoverDistance = useMemo( + () => throttle(50, setHeightgraphHoverDistance), + [] + ); const updateExcludePolygons = useCallback(() => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const excludePolygons: any[] = []; - excludePolygonsLayer.eachLayer((layer) => { - const lngLatArray: number[][] = []; - // @ts-expect-error _latlngs is not typed - for (const coords of layer._latlngs[0]) { - lngLatArray.push([coords.lng, coords.lat]); + if (!drawRef.current) return; + const terraDrawInstance = drawRef.current.getTerraDrawInstance(); + if (!terraDrawInstance) return; + + const snapshot = terraDrawInstance.getSnapshot(); + const excludePolygons: number[][][] = []; + + snapshot.forEach((feature) => { + if (feature.geometry.type === 'Polygon') { + const coords = feature.geometry.coordinates[0]; + if (coords) { + const lngLatArray = coords.map((coord) => [ + coord[0] ?? 0, + coord[1] ?? 0, + ]); + excludePolygons.push(lngLatArray); + } } - excludePolygons.push(lngLatArray); }); - const name = 'exclude_polygons'; - const value = excludePolygons; + dispatch( updateSettings({ - name, - value, + name: 'exclude_polygons', + value: excludePolygons as unknown as string, }) ); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const updateWaypointPosition = useCallback((object) => { - dispatch(fetchReverseGeocode(object)); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [dispatch]); + + const updateWaypointPosition = useCallback( + (object: { + latLng: { lat: number; lng: number }; + index: number; + fromDrag?: boolean; + }) => { + dispatch(fetchReverseGeocode(object)); + }, + [dispatch] + ); - const updateIsoPosition = useCallback((coord) => { - dispatch(fetchReverseGeocodeIso(coord.lng, coord.lat)); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + const updateIsoPosition = useCallback( + (lng: number, lat: number) => { + dispatch(fetchReverseGeocodeIso(lng, lat)); + }, + [dispatch] + ); const handleCopy = useCallback(() => { setHasCopied(true); @@ -203,24 +239,21 @@ const Map = ({ }, []); const handleOpenOSM = useCallback(() => { - const { lat, lng } = mapRef.current!.getCenter(); - const zoom = mapRef.current!.getZoom(); + if (!mapRef.current) return; + const { lng, lat } = mapRef.current.getCenter(); + const zoom = mapRef.current.getZoom(); const osmURL = `https://www.openstreetmap.org/#map=${zoom}/${lat}/${lng}`; window.open(osmURL, '_blank'); }, []); - const getHeight = useCallback((position) => { + const getHeight = useCallback((lng: number, lat: number) => { setIsHeightLoading(true); axios - .post( - VALHALLA_OSM_URL + '/height', - buildHeightRequest([[position.lat, position.lng]]), - { - headers: { - 'Content-Type': 'application/json', - }, - } - ) + .post(VALHALLA_OSM_URL + '/height', buildHeightRequest([[lat, lng]]), { + headers: { + 'Content-Type': 'application/json', + }, + }) .then(({ data }) => { if ('height' in data) { setElevation(data.height[0] + ' m'); @@ -235,12 +268,12 @@ const Map = ({ }, []); const getLocate = useCallback( - (position) => { + (lng: number, lat: number) => { setIsLocateLoading(true); axios .post( VALHALLA_OSM_URL + '/locate', - buildLocateRequest(position, profile), + buildLocateRequest({ lng, lat }, profile), { headers: { 'Content-Type': 'application/json', @@ -261,32 +294,33 @@ const Map = ({ ); const handleAddWaypoint = useCallback( - (data, e) => { - mapRef.current!.closePopup(); + (index: number) => { + if (!popupLngLat) return; + setShowPopup(false); updateWaypointPosition({ - latLng: latLng, - index: e.index, + latLng: { lat: popupLngLat.lat, lng: popupLngLat.lng }, + index: index, }); }, - [latLng, updateWaypointPosition] + [popupLngLat, updateWaypointPosition] ); const handleAddIsoWaypoint = useCallback(() => { - mapRef.current!.closePopup(); - updateIsoPosition(latLng); - }, [latLng, updateIsoPosition]); + if (!popupLngLat) return; + setShowPopup(false); + updateIsoPosition(popupLngLat.lng, popupLngLat.lat); + }, [popupLngLat, updateIsoPosition]); const getHeightData = useCallback(() => { const { results } = directions; + if (!results[VALHALLA_OSM_URL!]?.data?.decodedGeometry) return; + const heightPayloadNew = buildHeightRequest( results[VALHALLA_OSM_URL!]!.data.decodedGeometry as [number, number][] ); if (!R.equals(heightPayload, heightPayloadNew)) { - if (hgRef.current?._removeChart) { - hgRef.current._removeChart(); - } setIsHeightLoading(true); setHeightPayload(heightPayloadNew); axios @@ -296,7 +330,6 @@ const Map = ({ }, }) .then(({ data }) => { - // lets build geojson object with steepness for the height graph const reversedGeometry = JSON.parse( JSON.stringify(results[VALHALLA_OSM_URL!]!.data.decodedGeometry) ).map((pair: number[]) => { @@ -313,10 +346,7 @@ const Map = ({ declineTotal, }) ); - - if (hgRef.current) { - hgRef.current.addData(heightData); - } + setHeightgraphData(heightData); }) .catch(({ response }) => { console.log(response); @@ -327,349 +357,174 @@ const Map = ({ } }, [directions, heightPayload, dispatch]); - // Map initialization effect + // Update markers when waypoints or isochrone centers change useEffect(() => { - // our map! - // @ts-expect-error this is not typed - mapRef.current = L.map('map', mapParams); - - // we create a leaflet pane which will hold all isochrone polygons with a given opacity - const isochronesPane = mapRef.current.createPane('isochronesPane'); - isochronesPane.style.opacity = '0.9'; - - // our basemap and add it to the map - const baseMaps = { - OpenStreetMap: OSMTiles, - }; - - const overlayMaps = { - Waypoints: routeMarkersLayer, - 'Isochrone Center': isoCenterLayer, - Routes: routeLineStringLayer, - Isochrones: isoPolygonLayer, - 'Isochrones (locations)': isoLocationsLayer, - }; - - // @ts-expect-error this is not typed - layerControlRef.current = L.control - .layers(baseMaps, overlayMaps) - .addTo(mapRef.current); - - // we do want a zoom control - L.control - .zoom({ - position: 'topright', - }) - .addTo(mapRef.current); - - //and for the sake of advertising your company, you may add a logo to the map - // @ts-expect-error this is not typed - const brand = L.control({ - position: 'bottomleft', - }); - - brand.onAdd = () => { - const div = L.DomUtil.create('div', 'brand'); - div.innerHTML = - ''; - return div; - }; + const newMarkers: MarkerData[] = []; - mapRef.current.addControl(brand); - - // @ts-expect-error this is not typed - const valhallaBrand = L.control({ - position: 'bottomleft', + // Add waypoint markers + const { waypoints } = directions; + waypoints.forEach((waypoint, index) => { + waypoint.geocodeResults.forEach((address) => { + if (address.selected) { + newMarkers.push({ + id: `waypoint-${index}`, + lng: address.displaylnglat[0], + lat: address.displaylnglat[1], + type: 'waypoint', + index: index, + title: address.title, + color: 'green', + number: (index + 1).toString(), + }); + } + }); }); - valhallaBrand.onAdd = () => { - const div = L.DomUtil.create('div', 'brand'); - div.innerHTML = - ''; - return div; - }; - mapRef.current.addControl(valhallaBrand); - - const popup = L.popup({ className: 'valhalla-popup' }); - - mapRef.current.on('popupclose', () => { - setHasCopied(false); - setLocate([]); - }); - mapRef.current.on('contextmenu', (event) => { - popup.setLatLng(event.latlng).openOn(mapRef.current!); - - setTimeout(() => { - // as setContent needs the react dom we are setting the state here - // to showPopup which then again renders a react portal in the render - // return function.. - setShowPopup(true); - setShowInfoPopup(false); - setLatLng(event.latlng); - - popup.update(); - }, 20); + // Add isochrone center marker + const { geocodeResults } = isochrones; + geocodeResults.forEach((address) => { + if (address.selected) { + newMarkers.push({ + id: 'iso-center', + lng: address.displaylnglat[0], + lat: address.displaylnglat[1], + type: 'isocenter', + title: address.title, + color: 'purple', + shape: 'star', + number: '1', + }); + } }); - mapRef.current.on('click', (event) => { - if ( - !mapRef.current!.pm.globalRemovalModeEnabled() && - !mapRef.current!.pm.globalDrawModeEnabled() - ) { - popup.setLatLng(event.latlng).openOn(mapRef.current!); - - getHeight(event.latlng); + setMarkers(newMarkers); + }, [ + directions.selectedAddresses, + isochrones.selectedAddress, + directions.waypoints, + isochrones.geocodeResults, + ]); - setTimeout(() => { - setShowPopup(true); - setShowInfoPopup(true); - setLatLng(event.latlng); - popup.update(); - }, 20); - } - }); + // Update route lines + useEffect(() => { + const { results } = directions; - mapRef.current.on('moveend', () => { - const last_coords = mapRef.current!.getCenter(); - const zoom_level = mapRef.current!.getZoom(); + if ( + !results[VALHALLA_OSM_URL!]?.data || + Object.keys(results[VALHALLA_OSM_URL!]!.data).length === 0 || + !directions.successful + ) { + setRouteGeoJSON(null); + return; + } - const last_center = JSON.stringify({ - center: [last_coords.lat, last_coords.lng], - zoom_level: zoom_level, + const response = results[VALHALLA_OSM_URL!]!.data; + const showRoutes = results[VALHALLA_OSM_URL!]!.show || {}; + const features: Feature[] = []; + + // Add alternates + if (response.alternates) { + response.alternates.forEach((alternate, i) => { + if (!showRoutes[i]) return; + const coords = (alternate! as ParsedDirectionsGeometry)! + .decodedGeometry; + const summary = alternate!.trip.summary; + + features.push({ + type: 'Feature', + geometry: { + type: 'LineString', + coordinates: coords.map((c) => [c[1] ?? 0, c[0] ?? 0]), + }, + properties: { + color: routeObjects[VALHALLA_OSM_URL!]!.alternativeColor, + type: 'alternate', + summary, + }, + }); }); - localStorage.setItem('last_center', last_center); - }); - - // add Leaflet-Geoman controls with some options to the map - mapRef.current.pm.addControls({ - position: 'topright', - drawCircle: false, - drawMarker: false, - drawPolyline: false, - cutPolygon: false, - drawCircleMarker: false, - drawRectangle: false, - dragMode: true, - allowSelfIntersection: false, - editPolygon: true, - deleteLayer: true, - }); + } - mapRef.current.pm.setGlobalOptions({ - layerGroup: excludePolygonsLayer, - }); + // Add main route + if (showRoutes[-1] !== false) { + const coords = response.decodedGeometry; + const summary = response.trip.summary; - mapRef.current.on('pm:create', ({ layer }) => { - layer.on('pm:edit', () => { - updateExcludePolygons(); - }); - layer.on('pm:dragend', () => { - updateExcludePolygons(); + features.push({ + type: 'Feature', + geometry: { + type: 'LineString', + coordinates: coords.map((c) => [c[1] ?? 0, c[0] ?? 0]), + }, + properties: { + color: routeObjects[VALHALLA_OSM_URL!]!.color, + type: 'main', + summary, + }, }); - updateExcludePolygons(); - }); + } - mapRef.current.on('pm:remove', () => { - updateExcludePolygons(); + setRouteGeoJSON({ + type: 'FeatureCollection', + features, }); + }, [directions.results, directions.successful]); - // @ts-expect-error this is not typed - hgRef.current = L.control.heightgraph({ - mappings: colorMappings, - graphStyle: { - opacity: 0.9, - 'fill-opacity': 1, - 'stroke-width': '0px', - }, - translation: { - distance: 'Distance from start', - }, - expandCallback(expand: boolean) { - if (expand) { - getHeightData(); - } - }, - expandControls: true, - expand: false, - highlightStyle: { - color: 'blue', - }, - width: showDirectionsPanel - ? window.innerWidth * 0.75 - : window.innerWidth * 0.9, - }); - hgRef.current.addTo(mapRef.current); - const hg = hgRef.current; - // Added title property to heightgraph-toggle element to show "Height Graph" tooltip - document - .querySelector('.heightgraph-toggle')! - .setAttribute('title', 'Height Graph'); - - const heightgraphEl = document.querySelector('.heightgraph')!; - if (heightgraphEl) { - // @ts-expect-error this is not typed - heightgraphResizerRef.current = makeResizable( - heightgraphEl as HTMLElement, - { - handles: 'w, n, nw', - minWidth: 380, - minHeight: 140, - applyInlineSize: false, - onResize: ({ width, height }) => { - hg.resize({ width, height }); - }, - onStop: () => { - // Clear inline size/position if any - (heightgraphEl as HTMLElement).style.width = ''; - (heightgraphEl as HTMLElement).style.height = ''; - (heightgraphEl as HTMLElement).style.left = ''; - (heightgraphEl as HTMLElement).style.top = ''; - }, - } - ); + // Update isochrones + useEffect(() => { + const { results } = isochrones; + + if (!results || !isochrones.successful) { + setIsochroneGeoJSON(null); + setIsoLocationsGeoJSON(null); + return; } - // Cleanup function - return () => { + const isoFeatures: Feature[] = []; + const locationFeatures: Feature[] = []; + + for (const provider of [VALHALLA_OSM_URL]) { if ( - heightgraphResizerRef.current && - // @ts-expect-error this is not typed - typeof heightgraphResizerRef.current.destroy === 'function' + results[provider!]?.data && + Object.keys(results[provider!]!.data).length > 0 && + results[provider!]!.show ) { - // @ts-expect-error this is not typed - heightgraphResizerRef.current.destroy(); - } - if (mapRef.current) { - mapRef.current.remove(); + for (const feature of results[provider!]!.data.features) { + if (['Polygon', 'MultiPolygon'].includes(feature.geometry.type)) { + isoFeatures.push({ + ...feature, + properties: { + ...feature.properties, + fillColor: feature.properties?.fill || '#6200ea', + }, + }); + } else { + // locations + if (feature.properties!.type !== 'input') { + locationFeatures.push(feature); + } + } + } } - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + } - // Map update functions - const zoomToCoordinates = useCallback(() => { - const maxZoom = coordinates.length === 1 ? 11 : 18; - const paddingTopLeft = [ - screen.width < 550 ? 50 : showDirectionsPanel ? 420 : 50, - 50, - ]; - - const paddingBottomRight = [ - screen.width < 550 ? 50 : showSettings ? 420 : 50, - 50, - ]; - - // @ts-expect-error this is not typed - mapRef.current!.fitBounds(coordinates, { - paddingBottomRight, - paddingTopLeft, - maxZoom, + setIsochroneGeoJSON({ + type: 'FeatureCollection', + features: isoFeatures, }); - }, [coordinates, showDirectionsPanel, showSettings]); - - const zoomTo = useCallback( - (idx) => { - const { results } = directions; - - if (!results[VALHALLA_OSM_URL!]?.data?.decodedGeometry) { - return; - } - - const coords = results[VALHALLA_OSM_URL!]!.data.decodedGeometry; - - if (!mapRef.current || !coords[idx]) { - return; - } - - // @ts-expect-error this is not typed - mapRef.current.setView(coords[idx], 17); - - const highlightMarker = ExtraMarkers.icon({ - // @ts-expect-error this is not typed - icon: 'fa-coffee', - markerColor: 'blue', - shape: 'circle', - prefix: 'fa', - iconColor: 'white', - }); - - // @ts-expect-error this is not typed - L.marker(coords[idx], { - icon: highlightMarker, - pmIgnore: true, - }).addTo(highlightRouteIndexLayer); - - setTimeout(() => { - highlightRouteIndexLayer.clearLayers(); - }, 1000); - }, - [directions] - ); - - const getIsoTooltip = useCallback((contour, area) => { - return ` -
-
-
- Isochrone Summary -
-
-
- -
- ${contour} mins -
-
-
- -
- ${area} km2 -
-
-
- `; - }, []); - - const getIsoLocationTooltip = useCallback(() => { - return ` -
-
- Snapped location -
-
- `; - }, []); - const getRouteToolTip = useCallback((summary) => { - return ` -
-
-
- Route Summary -
-
-
- -
- ${summary.length.toFixed(summary.length > 1000 ? 0 : 1)} km -
-
-
- -
- ${formatDuration(summary.time)} -
-
-
- `; - }, []); + setIsoLocationsGeoJSON({ + type: 'FeatureCollection', + features: locationFeatures, + }); + }, [isochrones.results, isochrones.successful]); - // Handle map layers updates - const handleHighlightSegment = useCallback(() => { + // Update highlight segment + useEffect(() => { const { highlightSegment, results } = directions; if (!highlightSegment || !results[VALHALLA_OSM_URL!]?.data) { - highlightRouteSegmentlayer.clearLayers(); + setHighlightSegmentGeoJSON(null); return; } @@ -680,7 +535,7 @@ const Map = ({ coords = results[VALHALLA_OSM_URL!]!.data.decodedGeometry; } else { if (!results[VALHALLA_OSM_URL!]!.data.alternates?.[alternate]) { - highlightRouteSegmentlayer.clearLayers(); + setHighlightSegmentGeoJSON(null); return; } coords = (results[VALHALLA_OSM_URL!]!.data.alternates?.[ @@ -689,273 +544,173 @@ const Map = ({ } if (startIndex > -1 && endIndex > -1 && coords) { - L.polyline(coords.slice(startIndex, endIndex + 1) as LatLngExpression[], { - color: 'yellow', - weight: 4, - opacity: 1, - pmIgnore: true, - }).addTo(highlightRouteSegmentlayer); + setHighlightSegmentGeoJSON({ + type: 'Feature', + geometry: { + type: 'LineString', + coordinates: coords + .slice(startIndex, endIndex + 1) + .map((c) => [c[1] ?? 0, c[0] ?? 0]), + }, + properties: {}, + }); } else { - highlightRouteSegmentlayer.clearLayers(); - } - }, [directions]); - - const addWaypoints = useCallback(() => { - routeMarkersLayer.clearLayers(); - const { waypoints } = directions; - let index = 0; - for (const waypoint of waypoints) { - for (const address of waypoint.geocodeResults) { - if (address.selected) { - const wpMarker = ExtraMarkers.icon({ - // @ts-expect-error this is not typed - icon: 'fa-number', - markerColor: 'green', - //shape: 'star', - prefix: 'fa', - number: (index + 1).toString(), - }); - - L.marker([address.displaylnglat[1], address.displaylnglat[0]], { - icon: wpMarker, - draggable: true, - // @ts-expect-error this is not typed - index: index, - pmIgnore: true, - }) - .addTo(routeMarkersLayer) - .bindTooltip(address.title, { - permanent: false, - }) - //.openTooltip() - .on('dragend', (e) => { - updateWaypointPosition({ - latLng: e.target.getLatLng(), - index: e.target.options.index, - fromDrag: true, - }); - }); - } - } - index += 1; - } - }, [directions, updateWaypointPosition]); - - const addIsoCenter = useCallback(() => { - isoCenterLayer.clearLayers(); - const { geocodeResults } = isochrones; - for (const address of geocodeResults) { - if (address.selected) { - const isoMarker = ExtraMarkers.icon({ - // @ts-expect-error this is not typed - icon: 'fa-number', - markerColor: 'purple', - shape: 'star', - prefix: 'fa', - number: '1', - }); - - L.marker([address.displaylnglat[1], address.displaylnglat[0]], { - icon: isoMarker, - draggable: true, - pmIgnore: true, - }) - .addTo(isoCenterLayer) - .bindTooltip(address.title, { permanent: false }) - //.openTooltip() - .on('dragend', (e) => { - updateIsoPosition(e.target.getLatLng()); - }); - } + setHighlightSegmentGeoJSON(null); } - }, [isochrones, updateIsoPosition]); + }, [directions.highlightSegment, directions.results]); - const addIsochrones = useCallback(() => { - const { results } = isochrones; - isoPolygonLayer.clearLayers(); - isoLocationsLayer.clearLayers(); + // Zoom to coordinates + useEffect(() => { + if (coordinates && coordinates.length > 0 && mapRef.current) { + const firstCoord = coordinates[0]; + if (!firstCoord || !firstCoord[0] || !firstCoord[1]) return; + + const bounds: [[number, number], [number, number]] = coordinates.reduce< + [[number, number], [number, number]] + >( + (acc, coord) => { + if (!coord || !coord[0] || !coord[1]) return acc; + return [ + [Math.min(acc[0][0], coord[1]), Math.min(acc[0][1], coord[0])], + [Math.max(acc[1][0], coord[1]), Math.max(acc[1][1], coord[0])], + ]; + }, + [ + [firstCoord[1], firstCoord[0]], + [firstCoord[1], firstCoord[0]], + ] + ); - if (!results) { - return; + const paddingTopLeft = [ + screen.width < 550 ? 50 : showDirectionsPanel ? 420 : 50, + 50, + ]; + + const paddingBottomRight = [ + screen.width < 550 ? 50 : showSettings ? 420 : 50, + 50, + ]; + + mapRef.current.fitBounds(bounds, { + padding: { + top: paddingTopLeft[1] as number, + bottom: paddingBottomRight[1] as number, + left: paddingTopLeft[0] as number, + right: paddingBottomRight[0] as number, + }, + maxZoom: coordinates.length === 1 ? 11 : 18, + }); } + }, [coordinates, showDirectionsPanel, showSettings]); - for (const provider of [VALHALLA_OSM_URL]) { - if ( - results[provider!]?.data && - Object.keys(results[provider!]!.data).length > 0 && - results[provider!]!.show - ) { - for (const feature of results[provider!]!.data.features) { - const coords_reversed = []; - // @ts-expect-error this is not typed - for (const coord of feature.geometry.coordinates) { - coords_reversed.push([coord[1], coord[0]]); - } - if (['Polygon', 'MultiPolygon'].includes(feature.geometry.type)) { - L.geoJSON(feature, { - style: (feat) => ({ - ...feat!.properties, - color: '#fff', - opacity: 1, - }), - }) - .bindTooltip( - getIsoTooltip( - feature.properties!.contour, - feature.properties!.area.toFixed(2) - ), - { permanent: false, sticky: true } - ) - .addTo(isoPolygonLayer); - } else { - // locations - - if (feature.properties!.type === 'input') { - return; - } - L.geoJSON(feature, { - pointToLayer: (feat, ll) => { - return L.circleMarker(ll, { - radius: 6, - color: '#000', - fillColor: '#fff', - fill: true, - fillOpacity: 1, - }).bindTooltip(getIsoLocationTooltip(), { - permanent: false, - sticky: true, - }); - }, - }).addTo(isoLocationsLayer); + // Handle map click + const handleMapClick = useCallback( + (event: { lngLat: { lng: number; lat: number } }) => { + // Check if TerraDraw is in an active drawing mode + if (drawRef.current) { + const terraDrawInstance = drawRef.current.getTerraDrawInstance(); + if (terraDrawInstance) { + const mode = terraDrawInstance.getMode(); + // Don't show popup if actively drawing (polygon mode, not select/delete) + if (mode === 'polygon') { + return; } } } - } - }, [isochrones, getIsoTooltip, getIsoLocationTooltip]); - const addRoutes = useCallback(() => { - const { results } = directions; - routeLineStringLayer.clearLayers(); + const { lngLat } = event; + setPopupLngLat(lngLat); + setShowPopup(true); + setShowInfoPopup(true); + getHeight(lngLat.lng, lngLat.lat); + }, + [getHeight] + ); - if ( - results[VALHALLA_OSM_URL!]?.data && - Object.keys(results[VALHALLA_OSM_URL!]!.data).length > 0 - ) { - const response = results[VALHALLA_OSM_URL!]!.data; - const showRoutes = results[VALHALLA_OSM_URL!]!.show || {}; - // show alternates if they exist on the respsonse - if (response.alternates) { - for (let i = 0; i < response.alternates.length; i++) { - if (!showRoutes[i]) { - continue; - } - const alternate = response.alternates[i]; - const coords = (alternate! as ParsedDirectionsGeometry)! - .decodedGeometry; - const summary = alternate!.trip.summary; - // @ts-expect-error this is not typed - L.polyline(coords, { - color: '#FFF', - weight: 9, - opacity: 1, - pmIgnore: true, - }).addTo(routeLineStringLayer); - // @ts-expect-error this is not typed - L.polyline(coords, { - color: routeObjects[VALHALLA_OSM_URL!]!.alternativeColor, - weight: 5, - opacity: 1, - pmIgnore: true, - }) - .addTo(routeLineStringLayer) - .bindTooltip(getRouteToolTip(summary), { - permanent: false, - sticky: true, - }); - } - } - if (!showRoutes[-1]) { - return; - } - const coords = response.decodedGeometry; - const summary = response.trip.summary; - // @ts-expect-error this is not typed - L.polyline(coords, { - color: '#FFF', - weight: 9, - opacity: 1, - pmIgnore: true, - }).addTo(routeLineStringLayer); - // @ts-expect-error this is not typed - L.polyline(coords, { - color: routeObjects[VALHALLA_OSM_URL!]!.color, - weight: 5, - opacity: 1, - pmIgnore: true, - }) - .addTo(routeLineStringLayer) - .bindTooltip(getRouteToolTip(summary), { - permanent: false, - sticky: true, - }); + // Handle map context menu + const handleMapContextMenu = useCallback( + (event: { lngLat: { lng: number; lat: number } }) => { + const { lngLat } = event; + setPopupLngLat(lngLat); + setShowPopup(true); + setShowInfoPopup(false); + }, + [] + ); - if (hgRef.current && hgRef.current._showState === true) { - hgRef.current._expand(); - } - } - }, [directions, getRouteToolTip]); + // Handle move end to save position + const handleMoveEnd = useCallback(() => { + if (!mapRef.current) return; + const { lng, lat } = mapRef.current.getCenter(); + const zoom = mapRef.current.getZoom(); - // Effect for handling map updates based on prop changes - useEffect(() => { - addWaypoints(); - addIsoCenter(); - addIsochrones(); - addRoutes(); - handleHighlightSegment(); - - if (!directions.successful) { - routeLineStringLayer.clearLayers(); - } - if (!isochrones.successful) { - isoPolygonLayer.clearLayers(); - isoLocationsLayer.clearLayers(); - } - }, [ - directions.selectedAddresses, - directions.successful, - directions.results, - directions.highlightSegment, - isochrones.selectedAddress, - isochrones.successful, - isochrones.results, - addWaypoints, - addIsoCenter, - addIsochrones, - addRoutes, - handleHighlightSegment, - ]); + const last_center = JSON.stringify({ + center: [lat, lng], + zoom_level: zoom, + }); + localStorage.setItem('last_center', last_center); + }, []); - // Effect for coordinates changes - useEffect(() => { - if (coordinates && coordinates.length > 0) { - zoomToCoordinates(); - } - }, [coordinates, zoomToCoordinates]); + const MarkerIcon = ({ + color, + number, + }: { + color: string; + number?: string; + }) => { + const markerColors: Record = { + green: '#28a745', + purple: '#6f42c1', + blue: '#007bff', + }; - // Effect for zoom object changes - useEffect(() => { - if ( - directions.zoomObj && - directions.zoomObj.index !== undefined && - directions.zoomObj.timeNow - ) { - zoomTo(directions.zoomObj.index); - } - }, [directions.zoomObj, zoomTo]); + const bgColor = markerColors[color] || markerColors.green; + + return ( +
+ + + + {number && ( +
+ {number} +
+ )} +
+ ); + }; - // Render function const MapPopup = (isInfo: boolean) => { - if (!latLng) { + if (!popupLngLat) { return null; } @@ -965,21 +720,27 @@ const Map = ({
- } /> - - @@ -1150,9 +919,7 @@ const Map = ({ ) : ( - + )} @@ -1160,24 +927,241 @@ const Map = ({ ); }; - const leafletPopupDiv = document.querySelector('.leaflet-popup-content'); return (
- -
+ setViewState(evt.viewState)} + onMoveEnd={handleMoveEnd} + onClick={handleMapClick} + onContextMenu={handleMapContextMenu} + mapStyle={mapStyle as unknown as maplibregl.StyleSpecification} + style={{ width: '100%', height: '100vh' }} + maxBounds={maxBounds} + minZoom={2} + maxZoom={18} + data-testid="map" + > + + + + {/* Route lines */} + {routeGeoJSON && ( + + {/* White outline */} + + {/* Colored line */} + + + )} + + {/* Highlight segment */} + {highlightSegmentGeoJSON && ( + + + + )} + + {/* Isochrone polygons */} + {isochroneGeoJSON && ( + + + + + )} + + {/* Isochrone locations */} + {isoLocationsGeoJSON && ( + + + + )} + + {/* Markers */} + {markers.map((marker) => ( + { + if (marker.type === 'waypoint') { + updateWaypointPosition({ + latLng: { lat: e.lngLat.lat, lng: e.lngLat.lng }, + index: marker.index ?? 0, + fromDrag: true, + }); + } else if (marker.type === 'isocenter') { + updateIsoPosition(e.lngLat.lng, e.lngLat.lat); + } + }} + > + + + ))} + + {/* Heightgraph hover marker */} + {useMemo(() => { + if ( + heightgraphHoverDistance !== null && + heightgraphData.length > 0 + ) { + // The heightgraph data has coordinates as [lng, lat, elevation, distance] + // Find the coordinate closest to the hovered distance + let closestCoord = null; + let minDistanceDiff = Infinity; + + for (const feature of heightgraphData[0]?.features || []) { + if (feature.geometry.type === 'LineString') { + const coords = feature.geometry.coordinates as number[][]; + for (const coord of coords) { + const [lng, lat, , distance] = coord; + if (distance !== undefined) { + const diff = Math.abs( + distance - heightgraphHoverDistance + ); + if (diff < minDistanceDiff) { + minDistanceDiff = diff; + closestCoord = [lng ?? 0, lat ?? 0]; + } + } + } + } + } + + if (closestCoord) { + return ( + +
+ + ); + } + } + return null; + }, [heightgraphHoverDistance, heightgraphData])} + + {/* Popup */} + {showPopup && popupLngLat && ( + { + setShowPopup(false); + setHasCopied(false); + setLocate([]); + }} + closeOnClick={false} + maxWidth="none" + > + {MapPopup(showInfoPopup)} + + )} + + {/* Brand logos */} +
+ +
+ + +
+ +
+ + -
-
- {showPopup && leafletPopupDiv && latLng - ? ReactDOM.createPortal(MapPopup(showInfoPopup), leafletPopupDiv) - : null} + + {/* Height graph */} + {directions.successful && ( + { + if (expanded) { + getHeightData(); + } + }} + onHighlight={throttledSetHeightgraphHoverDistance} + /> + )}
); @@ -1210,4 +1208,4 @@ const mapStateToProps = (state: RootState) => { }; }; -export default connect(mapStateToProps)(Map); +export default connect(mapStateToProps)(MapComponent); diff --git a/src/map/map.css b/src/map/map.css index eba1b29..78fa4db 100644 --- a/src/map/map.css +++ b/src/map/map.css @@ -1,7 +1,7 @@ #osm-button { position: absolute; - bottom: 20px; - right: 60px; + bottom: 60px; + right: 50px; z-index: 1000; } @@ -17,4 +17,4 @@ bottom: 40px; right: 40px; } -} \ No newline at end of file +} diff --git a/src/map/style.json b/src/map/style.json new file mode 100644 index 0000000..924aed2 --- /dev/null +++ b/src/map/style.json @@ -0,0 +1,8809 @@ +{ + "version": 8, + "name": "versatiles-colorful", + "metadata": { + "license": "https://creativecommons.org/publicdomain/zero/1.0/" + }, + "glyphs": "https://vector.openstreetmap.org/demo/shortbread/fonts/{fontstack}/{range}.pbf", + "sprite": [ + { + "id": "basics", + "url": "https://vector.openstreetmap.org/demo/shortbread/sprites/basics/sprites" + } + ], + "sources": { + "versatiles-shortbread": { + "attribution": "About this service and privacy policy | © OpenStreetMap contributors", + "tiles": ["https://vector.openstreetmap.org/shortbread_v1/{z}/{x}/{y}.mvt"], + "type": "vector", + "scheme": "xyz", + "bounds": [-180, -85.0511287798066, 180, 85.0511287798066], + "minzoom": 0, + "maxzoom": 14 + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "rgb(249,244,238)" + } + }, + { + "source": "versatiles-shortbread", + "id": "water-ocean", + "type": "fill", + "source-layer": "ocean", + "paint": { + "fill-color": "rgb(190,221,243)" + } + }, + { + "source": "versatiles-shortbread", + "id": "land-glacier", + "type": "fill", + "source-layer": "water_polygons", + "filter": ["all", ["==", "kind", "glacier"]], + "paint": { + "fill-color": "rgb(255,255,255)" + } + }, + { + "source": "versatiles-shortbread", + "id": "land-commercial", + "type": "fill", + "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] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "land-industrial", + "type": "fill", + "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] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "land-residential", + "type": "fill", + "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] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "land-agriculture", + "type": "fill", + "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] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "land-waste", + "type": "fill", + "source-layer": "land", + "filter": ["all", ["in", "kind", "landfill"]], + "paint": { + "fill-color": "rgb(219,214,189)", + "fill-opacity": { + "stops": [ + [10, 0], + [11, 1] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "land-park", + "type": "fill", + "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] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "land-garden", + "type": "fill", + "source-layer": "land", + "filter": ["all", ["in", "kind", "allotments", "garden"]], + "paint": { + "fill-color": "rgb(217,217,165)", + "fill-opacity": { + "stops": [ + [11, 0], + [12, 1] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "land-burial", + "type": "fill", + "source-layer": "land", + "filter": ["all", ["in", "kind", "cemetery", "grave_yard"]], + "paint": { + "fill-color": "rgb(221,219,202)", + "fill-opacity": { + "stops": [ + [13, 0], + [14, 1] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "land-leisure", + "type": "fill", + "source-layer": "land", + "filter": [ + "all", + ["in", "kind", "miniature_golf", "playground", "golf_course"] + ], + "paint": { + "fill-color": "rgb(231,237,222)" + } + }, + { + "source": "versatiles-shortbread", + "id": "land-rock", + "type": "fill", + "source-layer": "land", + "filter": ["all", ["in", "kind", "bare_rock", "scree", "shingle"]], + "paint": { + "fill-color": "rgb(224,228,229)" + } + }, + { + "source": "versatiles-shortbread", + "id": "land-forest", + "type": "fill", + "source-layer": "land", + "filter": ["all", ["in", "kind", "forest"]], + "paint": { + "fill-color": "rgb(102,170,68)", + "fill-opacity": { + "stops": [ + [7, 0], + [8, 0.1] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "land-grass", + "type": "fill", + "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] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "land-vegetation", + "type": "fill", + "source-layer": "land", + "filter": ["all", ["in", "kind", "heath", "scrub"]], + "paint": { + "fill-color": "rgb(217,217,165)", + "fill-opacity": { + "stops": [ + [11, 0], + [12, 1] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "land-sand", + "type": "fill", + "source-layer": "land", + "filter": ["all", ["in", "kind", "beach", "sand"]], + "paint": { + "fill-color": "rgb(250,250,237)" + } + }, + { + "source": "versatiles-shortbread", + "id": "land-wetland", + "type": "fill", + "source-layer": "land", + "filter": ["all", ["in", "kind", "bog", "marsh", "string_bog", "swamp"]], + "paint": { + "fill-color": "rgb(211,230,219)" + } + }, + { + "source": "versatiles-shortbread", + "id": "water-river", + "type": "line", + "source-layer": "water_lines", + "filter": [ + "all", + ["in", "kind", "river"], + ["!=", "tunnel", true], + ["!=", "bridge", true] + ], + "paint": { + "line-color": "rgb(190,221,243)", + "line-width": { + "stops": [ + [9, 0], + [10, 3], + [15, 5], + [17, 9], + [18, 20], + [20, 60] + ] + } + }, + "layout": { + "line-cap": "round", + "line-join": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "water-canal", + "type": "line", + "source-layer": "water_lines", + "filter": [ + "all", + ["in", "kind", "canal"], + ["!=", "tunnel", true], + ["!=", "bridge", true] + ], + "paint": { + "line-color": "rgb(190,221,243)", + "line-width": { + "stops": [ + [9, 0], + [10, 2], + [15, 4], + [17, 8], + [18, 17], + [20, 50] + ] + } + }, + "layout": { + "line-cap": "round", + "line-join": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "water-stream", + "type": "line", + "source-layer": "water_lines", + "filter": [ + "all", + ["in", "kind", "stream"], + ["!=", "tunnel", true], + ["!=", "bridge", true] + ], + "paint": { + "line-color": "rgb(190,221,243)", + "line-width": { + "stops": [ + [13, 0], + [14, 1], + [15, 2], + [17, 6], + [18, 12], + [20, 30] + ] + } + }, + "layout": { + "line-cap": "round", + "line-join": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "water-ditch", + "type": "line", + "source-layer": "water_lines", + "filter": [ + "all", + ["in", "kind", "ditch"], + ["!=", "tunnel", true], + ["!=", "bridge", true] + ], + "paint": { + "line-color": "rgb(190,221,243)", + "line-width": { + "stops": [ + [14, 0], + [15, 1], + [17, 4], + [18, 8], + [20, 20] + ] + } + }, + "layout": { + "line-cap": "round", + "line-join": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "water-area", + "type": "fill", + "source-layer": "water_polygons", + "filter": ["==", "kind", "water"], + "paint": { + "fill-color": "rgb(190,221,243)", + "fill-opacity": { + "stops": [ + [4, 0], + [6, 1] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "water-area-river", + "type": "fill", + "source-layer": "water_polygons", + "filter": ["==", "kind", "river"], + "paint": { + "fill-color": "rgb(190,221,243)", + "fill-opacity": { + "stops": [ + [4, 0], + [6, 1] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "water-area-small", + "type": "fill", + "source-layer": "water_polygons", + "filter": ["in", "kind", "reservoir", "basin", "dock"], + "paint": { + "fill-color": "rgb(190,221,243)", + "fill-opacity": { + "stops": [ + [4, 0], + [6, 1] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "water-dam-area", + "type": "fill", + "source-layer": "dam_polygons", + "filter": ["==", "kind", "dam"], + "paint": { + "fill-color": "rgb(249,244,238)", + "fill-opacity": { + "stops": [ + [12, 0], + [13, 1] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "water-dam", + "type": "line", + "source-layer": "dam_lines", + "filter": ["==", "kind", "dam"], + "paint": { + "line-color": "rgb(190,221,243)" + }, + "layout": { + "line-cap": "round", + "line-join": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "water-pier-area", + "type": "fill", + "source-layer": "pier_polygons", + "filter": ["in", "kind", "pier", "breakwater", "groyne"], + "paint": { + "fill-color": "rgb(249,244,238)", + "fill-opacity": { + "stops": [ + [12, 0], + [13, 1] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "water-pier", + "type": "line", + "source-layer": "pier_lines", + "filter": ["in", "kind", "pier", "breakwater", "groyne"], + "paint": { + "line-color": "rgb(249,244,238)" + }, + "layout": { + "line-cap": "round", + "line-join": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "site-dangerarea", + "type": "fill", + "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" + } + }, + { + "source": "versatiles-shortbread", + "id": "site-university", + "type": "fill", + "source-layer": "sites", + "filter": ["in", "kind", "university"], + "paint": { + "fill-color": "rgb(255,255,128)", + "fill-opacity": 0.1 + } + }, + { + "source": "versatiles-shortbread", + "id": "site-college", + "type": "fill", + "source-layer": "sites", + "filter": ["in", "kind", "college"], + "paint": { + "fill-color": "rgb(255,255,128)", + "fill-opacity": 0.1 + } + }, + { + "source": "versatiles-shortbread", + "id": "site-school", + "type": "fill", + "source-layer": "sites", + "filter": ["in", "kind", "school"], + "paint": { + "fill-color": "rgb(255,255,128)", + "fill-opacity": 0.1 + } + }, + { + "source": "versatiles-shortbread", + "id": "site-hospital", + "type": "fill", + "source-layer": "sites", + "filter": ["in", "kind", "hospital"], + "paint": { + "fill-color": "rgb(255,102,102)", + "fill-opacity": 0.1 + } + }, + { + "source": "versatiles-shortbread", + "id": "site-prison", + "type": "fill", + "source-layer": "sites", + "filter": ["in", "kind", "prison"], + "paint": { + "fill-color": "rgb(253,242,252)", + "fill-pattern": "basics:pattern-striped", + "fill-opacity": 0.1 + } + }, + { + "source": "versatiles-shortbread", + "id": "site-parking", + "type": "fill", + "source-layer": "sites", + "filter": ["in", "kind", "parking"], + "paint": { + "fill-color": "rgb(235,232,230)" + } + }, + { + "source": "versatiles-shortbread", + "id": "site-bicycleparking", + "type": "fill", + "source-layer": "sites", + "filter": ["in", "kind", "bicycle_parking"], + "paint": { + "fill-color": "rgb(235,232,230)" + } + }, + { + "source": "versatiles-shortbread", + "id": "site-construction", + "type": "fill", + "source-layer": "sites", + "filter": ["in", "kind", "construction"], + "paint": { + "fill-color": "rgb(169,169,169)", + "fill-pattern": "basics:pattern-hatched_thin", + "fill-opacity": 0.1 + } + }, + { + "source": "versatiles-shortbread", + "id": "airport-area", + "type": "fill", + "source-layer": "street_polygons", + "filter": ["in", "kind", "runway", "taxiway"], + "paint": { + "fill-color": "rgb(255,255,255)", + "fill-opacity": 0.5 + } + }, + { + "source": "versatiles-shortbread", + "id": "airport-taxiway:outline", + "type": "line", + "source-layer": "streets", + "filter": ["==", "kind", "taxiway"], + "paint": { + "line-color": "rgb(207,205,202)", + "line-width": { + "stops": [ + [13, 0], + [14, 2], + [15, 10], + [16, 14], + [18, 20], + [20, 40] + ] + } + }, + "layout": { + "line-join": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "airport-runway:outline", + "type": "line", + "source-layer": "streets", + "filter": ["==", "kind", "runway"], + "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] + ] + } + }, + "layout": { + "line-join": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "airport-taxiway", + "type": "line", + "source-layer": "streets", + "filter": ["==", "kind", "taxiway"], + "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] + ] + } + }, + "layout": { + "line-join": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "airport-runway", + "type": "line", + "source-layer": "streets", + "filter": ["==", "kind", "runway"], + "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] + ] + } + }, + "layout": { + "line-join": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "building:outline", + "type": "fill", + "source-layer": "buildings", + "paint": { + "fill-color": "rgb(223,219,215)", + "fill-opacity": { + "stops": [ + [14, 0], + [15, 1] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "building", + "type": "fill", + "source-layer": "buildings", + "paint": { + "fill-color": "rgb(242,234,226)", + "fill-opacity": { + "stops": [ + [14, 0], + [15, 1] + ] + }, + "fill-translate": [-2, -2] + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-street-pedestrian-zone", + "type": "fill", + "source-layer": "street_polygons", + "filter": ["all", ["==", "tunnel", true], ["==", "kind", "pedestrian"]], + "paint": { + "fill-color": "rgb(247,247,247)", + "fill-opacity": { + "stops": [ + [12, 0], + [13, 1] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-way-footway:outline", + "type": "line", + "source-layer": "streets", + "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%)" + }, + "minzoom": 15 + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-way-steps:outline", + "type": "line", + "source-layer": "streets", + "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%)" + }, + "minzoom": 15 + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-way-path:outline", + "type": "line", + "source-layer": "streets", + "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%)" + }, + "minzoom": 15 + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-way-cycleway:outline", + "type": "line", + "source-layer": "streets", + "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%)" + }, + "minzoom": 15 + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-street-track:outline", + "type": "line", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "track"], ["==", "tunnel", true]], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-street-pedestrian:outline", + "type": "line", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "pedestrian"], ["==", "tunnel", true]], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-street-service:outline", + "type": "line", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "service"], ["==", "tunnel", true]], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-street-livingstreet:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "living_street"], + ["==", "tunnel", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-street-residential:outline", + "type": "line", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "residential"], ["==", "tunnel", true]], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-street-unclassified:outline", + "type": "line", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "unclassified"], ["==", "tunnel", true]], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-street-tertiary-link:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "tertiary"], + ["==", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-street-secondary-link:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "secondary"], + ["==", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + }, + "minzoom": 13 + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-street-primary-link:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "primary"], + ["==", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + }, + "minzoom": 13 + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-street-trunk-link:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "trunk"], + ["==", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + }, + "minzoom": 13 + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-street-motorway-link:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "motorway"], + ["==", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + }, + "minzoom": 12 + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-street-tertiary:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "tertiary"], + ["!=", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-street-secondary:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "secondary"], + ["!=", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-street-primary:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "primary"], + ["!=", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-street-trunk:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "trunk"], + ["!=", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-street-motorway:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "motorway"], + ["!=", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-way-footway", + "type": "line", + "source-layer": "streets", + "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] + }, + "minzoom": 15 + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-way-steps", + "type": "line", + "source-layer": "streets", + "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] + }, + "minzoom": 15 + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-way-path", + "type": "line", + "source-layer": "streets", + "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] + }, + "minzoom": 15 + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-way-cycleway", + "type": "line", + "source-layer": "streets", + "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] + }, + "minzoom": 15 + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-street-track", + "type": "line", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "track"], ["==", "tunnel", true]], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-street-pedestrian", + "type": "line", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "pedestrian"], ["==", "tunnel", true]], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-street-service", + "type": "line", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "service"], ["==", "tunnel", true]], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-street-livingstreet", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "living_street"], + ["==", "tunnel", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-street-residential", + "type": "line", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "residential"], ["==", "tunnel", true]], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-street-unclassified", + "type": "line", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "unclassified"], ["==", "tunnel", true]], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-street-track-bicycle", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "track"], + ["==", "bicycle", "designated"], + ["==", "tunnel", true] + ], + "paint": { + "line-color": "rgb(247,247,247)" + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-street-pedestrian-bicycle", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "pedestrian"], + ["==", "bicycle", "designated"], + ["==", "tunnel", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-street-service-bicycle", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "service"], + ["==", "bicycle", "designated"], + ["==", "tunnel", true] + ], + "paint": { + "line-color": "rgb(247,247,247)" + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-street-livingstreet-bicycle", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "living_street"], + ["==", "bicycle", "designated"], + ["==", "tunnel", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-street-residential-bicycle", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "residential"], + ["==", "bicycle", "designated"], + ["==", "tunnel", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-street-unclassified-bicycle", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "unclassified"], + ["==", "bicycle", "designated"], + ["==", "tunnel", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-street-tertiary-link", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "tertiary"], + ["==", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-street-secondary-link", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "secondary"], + ["==", "link", true] + ], + "paint": { + "line-color": "rgb(255,240,179)", + "line-width": { + "stops": [ + [12, 1], + [14, 2], + [16, 5], + [18, 12], + [20, 38] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + }, + "minzoom": 13 + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-street-primary-link", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "primary"], + ["==", "link", true] + ], + "paint": { + "line-color": "rgb(255,240,179)", + "line-width": { + "stops": [ + [12, 1], + [14, 2], + [16, 5], + [18, 12], + [20, 38] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + }, + "minzoom": 13 + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-street-trunk-link", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "trunk"], + ["==", "link", true] + ], + "paint": { + "line-color": "rgb(255,240,179)", + "line-width": { + "stops": [ + [12, 1], + [14, 2], + [16, 5], + [18, 12], + [20, 38] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + }, + "minzoom": 13 + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-street-motorway-link", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "motorway"], + ["==", "link", true] + ], + "paint": { + "line-color": "rgb(255,209,148)", + "line-width": { + "stops": [ + [12, 1], + [14, 2], + [16, 5], + [18, 12], + [20, 38] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + }, + "minzoom": 12 + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-street-tertiary", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "tertiary"], + ["!=", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-street-secondary", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "secondary"], + ["!=", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-street-primary", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "primary"], + ["!=", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-street-trunk", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "trunk"], + ["!=", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-street-motorway", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "tunnel", true], + ["in", "kind", "motorway"], + ["!=", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-transport-tram:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["in", "kind", "tram"], + ["!has", "service"], + ["==", "tunnel", true] + ], + "minzoom": 15, + "paint": { + "line-color": "rgb(177,187,196)", + "line-width": { + "stops": [ + [15, 0], + [16, 5], + [18, 7], + [20, 20] + ] + }, + "line-dasharray": [0.1, 0.5] + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-transport-narrowgauge:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["in", "kind", "narrow_gauge"], + ["!has", "service"], + ["==", "tunnel", true] + ], + "minzoom": 15, + "paint": { + "line-color": "rgb(177,187,196)", + "line-width": { + "stops": [ + [15, 0], + [16, 5], + [18, 7], + [20, 20] + ] + }, + "line-dasharray": [0.1, 0.5] + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-transport-subway:outline", + "type": "line", + "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] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-transport-lightrail:outline", + "type": "line", + "source-layer": "streets", + "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] + ] + } + }, + "minzoom": 8 + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-transport-lightrail-service:outline", + "type": "line", + "source-layer": "streets", + "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] + ] + } + }, + "minzoom": 14 + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-transport-rail:outline", + "type": "line", + "source-layer": "streets", + "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] + ] + } + }, + "minzoom": 8 + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-transport-rail-service:outline", + "type": "line", + "source-layer": "streets", + "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] + ] + } + }, + "minzoom": 14 + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-transport-monorail:outline", + "type": "line", + "source-layer": "streets", + "filter": ["all", ["in", "kind", "monorail"], ["==", "tunnel", true]], + "minzoom": 15, + "paint": { + "line-color": "rgb(177,187,196)", + "line-width": { + "stops": [ + [15, 0], + [16, 5], + [18, 7], + [20, 20] + ] + }, + "line-dasharray": [0.1, 0.5] + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-transport-funicular:outline", + "type": "line", + "source-layer": "streets", + "filter": ["all", ["in", "kind", "funicular"], ["==", "tunnel", true]], + "minzoom": 15, + "paint": { + "line-color": "rgb(177,187,196)", + "line-width": { + "stops": [ + [15, 0], + [16, 5], + [18, 7], + [20, 20] + ] + }, + "line-dasharray": [0.1, 0.5] + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-transport-tram", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["in", "kind", "tram"], + ["!has", "service"], + ["==", "tunnel", true] + ], + "minzoom": 13, + "paint": { + "line-width": { + "stops": [ + [13, 0], + [16, 1], + [17, 2], + [18, 3], + [20, 5] + ] + }, + "line-color": "rgb(177,187,196)" + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-transport-narrowgauge", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["in", "kind", "narrow_gauge"], + ["!has", "service"], + ["==", "tunnel", true] + ], + "minzoom": 13, + "paint": { + "line-width": { + "stops": [ + [13, 0], + [16, 1], + [17, 2], + [18, 3], + [20, 5] + ] + }, + "line-color": "rgb(177,187,196)" + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-transport-subway", + "type": "line", + "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] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-transport-lightrail", + "type": "line", + "source-layer": "streets", + "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] + ] + } + }, + "minzoom": 14 + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-transport-lightrail-service", + "type": "line", + "source-layer": "streets", + "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] + }, + "minzoom": 15 + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-transport-rail", + "type": "line", + "source-layer": "streets", + "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] + ] + } + }, + "minzoom": 14 + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-transport-rail-service", + "type": "line", + "source-layer": "streets", + "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] + }, + "minzoom": 15 + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-transport-monorail", + "type": "line", + "source-layer": "streets", + "filter": ["all", ["in", "kind", "monorail"], ["==", "tunnel", true]], + "minzoom": 13, + "paint": { + "line-width": { + "stops": [ + [13, 0], + [16, 1], + [17, 2], + [18, 3], + [20, 5] + ] + }, + "line-color": "rgb(177,187,196)" + } + }, + { + "source": "versatiles-shortbread", + "id": "tunnel-transport-funicular", + "type": "line", + "source-layer": "streets", + "filter": ["all", ["in", "kind", "funicular"], ["==", "tunnel", true]], + "minzoom": 13, + "paint": { + "line-width": { + "stops": [ + [13, 0], + [16, 1], + [17, 2], + [18, 3], + [20, 5] + ] + }, + "line-color": "rgb(177,187,196)" + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge", + "type": "fill", + "source-layer": "bridges", + "paint": { + "fill-color": "rgb(244,239,233)", + "fill-antialias": true, + "fill-opacity": 0.8 + } + }, + { + "source": "versatiles-shortbread", + "id": "street-pedestrian-zone", + "type": "fill", + "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] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "way-footway:outline", + "type": "line", + "source-layer": "streets", + "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)" + }, + "minzoom": 15 + }, + { + "source": "versatiles-shortbread", + "id": "way-steps:outline", + "type": "line", + "source-layer": "streets", + "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)" + }, + "minzoom": 15 + }, + { + "source": "versatiles-shortbread", + "id": "way-path:outline", + "type": "line", + "source-layer": "streets", + "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)" + }, + "minzoom": 15 + }, + { + "source": "versatiles-shortbread", + "id": "way-cycleway:outline", + "type": "line", + "source-layer": "streets", + "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)" + }, + "minzoom": 15 + }, + { + "source": "versatiles-shortbread", + "id": "street-track:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "track"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "street-pedestrian:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "pedestrian"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "street-service:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "service"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "street-livingstreet:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "living_street"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "street-residential:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "residential"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "street-unclassified:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "unclassified"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "street-tertiary-link:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "tertiary"], + ["==", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "street-secondary-link:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "secondary"], + ["==", "link", true] + ], + "paint": { + "line-color": "rgb(233,172,119)", + "line-width": { + "stops": [ + [12, 2], + [14, 3], + [16, 7], + [18, 14], + [20, 40] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + }, + "minzoom": 13 + }, + { + "source": "versatiles-shortbread", + "id": "street-primary-link:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "primary"], + ["==", "link", true] + ], + "paint": { + "line-color": "rgb(233,172,119)", + "line-width": { + "stops": [ + [12, 2], + [14, 3], + [16, 7], + [18, 14], + [20, 40] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + }, + "minzoom": 13 + }, + { + "source": "versatiles-shortbread", + "id": "street-trunk-link:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "trunk"], + ["==", "link", true] + ], + "paint": { + "line-color": "rgb(233,172,119)", + "line-width": { + "stops": [ + [12, 2], + [14, 3], + [16, 7], + [18, 14], + [20, 40] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + }, + "minzoom": 13 + }, + { + "source": "versatiles-shortbread", + "id": "street-motorway-link:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "motorway"], + ["==", "link", true] + ], + "paint": { + "line-color": "rgb(233,172,119)", + "line-width": { + "stops": [ + [12, 2], + [14, 3], + [16, 7], + [18, 14], + [20, 40] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + }, + "minzoom": 12 + }, + { + "source": "versatiles-shortbread", + "id": "street-tertiary:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "tertiary"], + ["!=", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "street-secondary:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "secondary"], + ["!=", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "street-primary:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "primary"], + ["!=", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "street-trunk:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "trunk"], + ["!=", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "street-motorway:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "motorway"], + ["!=", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "way-footway", + "type": "line", + "source-layer": "streets", + "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)" + }, + "minzoom": 15 + }, + { + "source": "versatiles-shortbread", + "id": "way-steps", + "type": "line", + "source-layer": "streets", + "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)" + }, + "minzoom": 15 + }, + { + "source": "versatiles-shortbread", + "id": "way-path", + "type": "line", + "source-layer": "streets", + "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)" + }, + "minzoom": 15 + }, + { + "source": "versatiles-shortbread", + "id": "way-cycleway", + "type": "line", + "source-layer": "streets", + "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)" + }, + "minzoom": 15 + }, + { + "source": "versatiles-shortbread", + "id": "street-track", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "track"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "street-pedestrian", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "pedestrian"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "street-service", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "service"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "street-livingstreet", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "living_street"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "street-residential", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "residential"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "street-unclassified", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "unclassified"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "street-track-bicycle", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "track"], + ["==", "bicycle", "designated"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "paint": { + "line-color": "rgb(255,255,255)" + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "street-pedestrian-bicycle", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "pedestrian"], + ["==", "bicycle", "designated"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "street-service-bicycle", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "service"], + ["==", "bicycle", "designated"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "paint": { + "line-color": "rgb(255,255,255)" + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "street-livingstreet-bicycle", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "living_street"], + ["==", "bicycle", "designated"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "street-residential-bicycle", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "residential"], + ["==", "bicycle", "designated"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "street-unclassified-bicycle", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "unclassified"], + ["==", "bicycle", "designated"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "street-tertiary-link", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "tertiary"], + ["==", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "street-secondary-link", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "secondary"], + ["==", "link", true] + ], + "paint": { + "line-color": "rgb(255,238,170)", + "line-width": { + "stops": [ + [12, 1], + [14, 2], + [16, 5], + [18, 12], + [20, 38] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + }, + "minzoom": 13 + }, + { + "source": "versatiles-shortbread", + "id": "street-primary-link", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "primary"], + ["==", "link", true] + ], + "paint": { + "line-color": "rgb(255,238,170)", + "line-width": { + "stops": [ + [12, 1], + [14, 2], + [16, 5], + [18, 12], + [20, 38] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + }, + "minzoom": 13 + }, + { + "source": "versatiles-shortbread", + "id": "street-trunk-link", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "trunk"], + ["==", "link", true] + ], + "paint": { + "line-color": "rgb(255,238,170)", + "line-width": { + "stops": [ + [12, 1], + [14, 2], + [16, 5], + [18, 12], + [20, 38] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + }, + "minzoom": 13 + }, + { + "source": "versatiles-shortbread", + "id": "street-motorway-link", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "motorway"], + ["==", "link", true] + ], + "paint": { + "line-color": "rgb(255,204,136)", + "line-width": { + "stops": [ + [12, 1], + [14, 2], + [16, 5], + [18, 12], + [20, 38] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + }, + "minzoom": 12 + }, + { + "source": "versatiles-shortbread", + "id": "street-tertiary", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "tertiary"], + ["!=", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "street-secondary", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "secondary"], + ["!=", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "street-primary", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "primary"], + ["!=", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "street-trunk", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "trunk"], + ["!=", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "street-motorway", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["!=", "bridge", true], + ["!=", "tunnel", true], + ["in", "kind", "motorway"], + ["!=", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "transport-tram:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["in", "kind", "tram"], + ["!has", "service"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "minzoom": 15, + "paint": { + "line-color": "rgb(177,187,196)", + "line-width": { + "stops": [ + [15, 0], + [16, 5], + [18, 7], + [20, 20] + ] + }, + "line-dasharray": [0.1, 0.5] + } + }, + { + "source": "versatiles-shortbread", + "id": "transport-narrowgauge:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["in", "kind", "narrow_gauge"], + ["!has", "service"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "minzoom": 15, + "paint": { + "line-color": "rgb(177,187,196)", + "line-width": { + "stops": [ + [15, 0], + [16, 5], + [18, 7], + [20, 20] + ] + }, + "line-dasharray": [0.1, 0.5] + } + }, + { + "source": "versatiles-shortbread", + "id": "transport-subway:outline", + "type": "line", + "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] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "transport-lightrail:outline", + "type": "line", + "source-layer": "streets", + "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] + ] + } + }, + "minzoom": 8 + }, + { + "source": "versatiles-shortbread", + "id": "transport-lightrail-service:outline", + "type": "line", + "source-layer": "streets", + "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] + ] + } + }, + "minzoom": 14 + }, + { + "source": "versatiles-shortbread", + "id": "transport-rail:outline", + "type": "line", + "source-layer": "streets", + "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] + ] + } + }, + "minzoom": 8 + }, + { + "source": "versatiles-shortbread", + "id": "transport-rail-service:outline", + "type": "line", + "source-layer": "streets", + "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] + ] + } + }, + "minzoom": 14 + }, + { + "source": "versatiles-shortbread", + "id": "transport-monorail:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["in", "kind", "monorail"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "minzoom": 15, + "paint": { + "line-color": "rgb(177,187,196)", + "line-width": { + "stops": [ + [15, 0], + [16, 5], + [18, 7], + [20, 20] + ] + }, + "line-dasharray": [0.1, 0.5] + } + }, + { + "source": "versatiles-shortbread", + "id": "transport-funicular:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["in", "kind", "funicular"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "minzoom": 15, + "paint": { + "line-color": "rgb(177,187,196)", + "line-width": { + "stops": [ + [15, 0], + [16, 5], + [18, 7], + [20, 20] + ] + }, + "line-dasharray": [0.1, 0.5] + } + }, + { + "source": "versatiles-shortbread", + "id": "transport-tram", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["in", "kind", "tram"], + ["!has", "service"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "minzoom": 13, + "paint": { + "line-width": { + "stops": [ + [13, 0], + [16, 1], + [17, 2], + [18, 3], + [20, 5] + ] + }, + "line-color": "rgb(177,187,196)" + } + }, + { + "source": "versatiles-shortbread", + "id": "transport-narrowgauge", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["in", "kind", "narrow_gauge"], + ["!has", "service"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "minzoom": 13, + "paint": { + "line-width": { + "stops": [ + [13, 0], + [16, 1], + [17, 2], + [18, 3], + [20, 5] + ] + }, + "line-color": "rgb(177,187,196)" + } + }, + { + "source": "versatiles-shortbread", + "id": "transport-subway", + "type": "line", + "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] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "transport-lightrail", + "type": "line", + "source-layer": "streets", + "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] + ] + } + }, + "minzoom": 14 + }, + { + "source": "versatiles-shortbread", + "id": "transport-lightrail-service", + "type": "line", + "source-layer": "streets", + "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] + }, + "minzoom": 15 + }, + { + "source": "versatiles-shortbread", + "id": "transport-rail", + "type": "line", + "source-layer": "streets", + "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] + ] + } + }, + "minzoom": 14 + }, + { + "source": "versatiles-shortbread", + "id": "transport-rail-service", + "type": "line", + "source-layer": "streets", + "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] + }, + "minzoom": 15 + }, + { + "source": "versatiles-shortbread", + "id": "transport-monorail", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["in", "kind", "monorail"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "minzoom": 13, + "paint": { + "line-width": { + "stops": [ + [13, 0], + [16, 1], + [17, 2], + [18, 3], + [20, 5] + ] + }, + "line-color": "rgb(177,187,196)" + } + }, + { + "source": "versatiles-shortbread", + "id": "transport-funicular", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["in", "kind", "funicular"], + ["!=", "bridge", true], + ["!=", "tunnel", true] + ], + "minzoom": 13, + "paint": { + "line-width": { + "stops": [ + [13, 0], + [16, 1], + [17, 2], + [18, 3], + [20, 5] + ] + }, + "line-color": "rgb(177,187,196)" + } + }, + { + "source": "versatiles-shortbread", + "id": "transport-ferry", + "type": "line", + "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] + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-way-footway:bridge", + "type": "line", + "source-layer": "streets", + "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] + ] + } + }, + "minzoom": 15 + }, + { + "source": "versatiles-shortbread", + "id": "bridge-way-steps:bridge", + "type": "line", + "source-layer": "streets", + "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] + ] + } + }, + "minzoom": 15 + }, + { + "source": "versatiles-shortbread", + "id": "bridge-way-path:bridge", + "type": "line", + "source-layer": "streets", + "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] + ] + } + }, + "minzoom": 15 + }, + { + "source": "versatiles-shortbread", + "id": "bridge-way-cycleway:bridge", + "type": "line", + "source-layer": "streets", + "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] + ] + } + }, + "minzoom": 15 + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-track:bridge", + "type": "line", + "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] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-pedestrian:bridge", + "type": "line", + "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] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-service:bridge", + "type": "line", + "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] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-livingstreet:bridge", + "type": "line", + "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] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-residential:bridge", + "type": "line", + "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] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-unclassified:bridge", + "type": "line", + "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] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-tertiary-link:bridge", + "type": "line", + "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] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-secondary-link:bridge", + "type": "line", + "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": 0.5, + "line-width": { + "stops": [ + [12, 3], + [14, 4], + [16, 10], + [18, 20], + [20, 56] + ] + } + }, + "minzoom": 13 + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-primary-link:bridge", + "type": "line", + "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": [ + [12, 3], + [14, 4], + [16, 10], + [18, 20], + [20, 56] + ] + } + }, + "minzoom": 13 + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-trunk-link:bridge", + "type": "line", + "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": [ + [12, 3], + [14, 4], + [16, 10], + [18, 20], + [20, 56] + ] + } + }, + "minzoom": 13 + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-motorway-link:bridge", + "type": "line", + "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": [ + [12, 3], + [14, 4], + [16, 10], + [18, 20], + [20, 56] + ] + } + }, + "minzoom": 12 + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-tertiary:bridge", + "type": "line", + "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] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-secondary:bridge", + "type": "line", + "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] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-primary:bridge", + "type": "line", + "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] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-trunk:bridge", + "type": "line", + "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] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-motorway:bridge", + "type": "line", + "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] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-pedestrian-zone", + "type": "fill", + "source-layer": "street_polygons", + "filter": ["all", ["==", "bridge", true], ["==", "kind", "pedestrian"]], + "paint": { + "fill-color": "rgb(255,255,255)", + "fill-opacity": { + "stops": [ + [12, 0], + [13, 1] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-way-footway:outline", + "type": "line", + "source-layer": "streets", + "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)" + }, + "minzoom": 15 + }, + { + "source": "versatiles-shortbread", + "id": "bridge-way-steps:outline", + "type": "line", + "source-layer": "streets", + "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)" + }, + "minzoom": 15 + }, + { + "source": "versatiles-shortbread", + "id": "bridge-way-path:outline", + "type": "line", + "source-layer": "streets", + "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)" + }, + "minzoom": 15 + }, + { + "source": "versatiles-shortbread", + "id": "bridge-way-cycleway:outline", + "type": "line", + "source-layer": "streets", + "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)" + }, + "minzoom": 15 + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-track:outline", + "type": "line", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "track"], ["==", "bridge", true]], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-pedestrian:outline", + "type": "line", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "pedestrian"], ["==", "bridge", true]], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-service:outline", + "type": "line", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "service"], ["==", "bridge", true]], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-livingstreet:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "living_street"], + ["==", "bridge", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-residential:outline", + "type": "line", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "residential"], ["==", "bridge", true]], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-unclassified:outline", + "type": "line", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "unclassified"], ["==", "bridge", true]], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-tertiary-link:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "tertiary"], + ["==", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-secondary-link:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "secondary"], + ["==", "link", true] + ], + "paint": { + "line-color": "rgb(233,172,119)", + "line-width": { + "stops": [ + [12, 2], + [14, 3], + [16, 7], + [18, 14], + [20, 40] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + }, + "minzoom": 13 + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-primary-link:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "primary"], + ["==", "link", true] + ], + "paint": { + "line-color": "rgb(233,172,119)", + "line-width": { + "stops": [ + [12, 2], + [14, 3], + [16, 7], + [18, 14], + [20, 40] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + }, + "minzoom": 13 + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-trunk-link:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "trunk"], + ["==", "link", true] + ], + "paint": { + "line-color": "rgb(233,172,119)", + "line-width": { + "stops": [ + [12, 2], + [14, 3], + [16, 7], + [18, 14], + [20, 40] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + }, + "minzoom": 13 + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-motorway-link:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "motorway"], + ["==", "link", true] + ], + "paint": { + "line-color": "rgb(233,172,119)", + "line-width": { + "stops": [ + [12, 2], + [14, 3], + [16, 7], + [18, 14], + [20, 40] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + }, + "minzoom": 12 + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-tertiary:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "tertiary"], + ["!=", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-secondary:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "secondary"], + ["!=", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-primary:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "primary"], + ["!=", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-trunk:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "trunk"], + ["!=", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-motorway:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "motorway"], + ["!=", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-way-footway", + "type": "line", + "source-layer": "streets", + "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)" + }, + "minzoom": 15 + }, + { + "source": "versatiles-shortbread", + "id": "bridge-way-steps", + "type": "line", + "source-layer": "streets", + "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)" + }, + "minzoom": 15 + }, + { + "source": "versatiles-shortbread", + "id": "bridge-way-path", + "type": "line", + "source-layer": "streets", + "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)" + }, + "minzoom": 15 + }, + { + "source": "versatiles-shortbread", + "id": "bridge-way-cycleway", + "type": "line", + "source-layer": "streets", + "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)" + }, + "minzoom": 15 + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-track", + "type": "line", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "track"], ["==", "bridge", true]], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-pedestrian", + "type": "line", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "pedestrian"], ["==", "bridge", true]], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-service", + "type": "line", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "service"], ["==", "bridge", true]], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-livingstreet", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "living_street"], + ["==", "bridge", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-residential", + "type": "line", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "residential"], ["==", "bridge", true]], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-unclassified", + "type": "line", + "source-layer": "streets", + "filter": ["all", ["==", "kind", "unclassified"], ["==", "bridge", true]], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-track-bicycle", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "track"], + ["==", "bicycle", "designated"], + ["==", "bridge", true] + ], + "paint": { + "line-color": "rgb(255,255,255)" + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-pedestrian-bicycle", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "pedestrian"], + ["==", "bicycle", "designated"], + ["==", "bridge", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-service-bicycle", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "service"], + ["==", "bicycle", "designated"], + ["==", "bridge", true] + ], + "paint": { + "line-color": "rgb(255,255,255)" + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-livingstreet-bicycle", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "living_street"], + ["==", "bicycle", "designated"], + ["==", "bridge", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-residential-bicycle", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "residential"], + ["==", "bicycle", "designated"], + ["==", "bridge", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-unclassified-bicycle", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "kind", "unclassified"], + ["==", "bicycle", "designated"], + ["==", "bridge", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-tertiary-link", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "tertiary"], + ["==", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-secondary-link", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "secondary"], + ["==", "link", true] + ], + "paint": { + "line-color": "rgb(255,238,170)", + "line-width": { + "stops": [ + [12, 1], + [14, 2], + [16, 5], + [18, 12], + [20, 38] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + }, + "minzoom": 13 + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-primary-link", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "primary"], + ["==", "link", true] + ], + "paint": { + "line-color": "rgb(255,238,170)", + "line-width": { + "stops": [ + [12, 1], + [14, 2], + [16, 5], + [18, 12], + [20, 38] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + }, + "minzoom": 13 + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-trunk-link", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "trunk"], + ["==", "link", true] + ], + "paint": { + "line-color": "rgb(255,238,170)", + "line-width": { + "stops": [ + [12, 1], + [14, 2], + [16, 5], + [18, 12], + [20, 38] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + }, + "minzoom": 13 + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-motorway-link", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "motorway"], + ["==", "link", true] + ], + "paint": { + "line-color": "rgb(255,204,136)", + "line-width": { + "stops": [ + [12, 1], + [14, 2], + [16, 5], + [18, 12], + [20, 38] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + }, + "minzoom": 12 + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-tertiary", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "tertiary"], + ["!=", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-secondary", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "secondary"], + ["!=", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-primary", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "primary"], + ["!=", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-trunk", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "trunk"], + ["!=", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-street-motorway", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["==", "bridge", true], + ["in", "kind", "motorway"], + ["!=", "link", true] + ], + "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] + ] + } + }, + "layout": { + "line-join": "round", + "line-cap": "butt" + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-transport-tram:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["in", "kind", "tram"], + ["!has", "service"], + ["==", "bridge", true] + ], + "minzoom": 15, + "paint": { + "line-color": "rgb(177,187,196)", + "line-width": { + "stops": [ + [15, 0], + [16, 5], + [18, 7], + [20, 20] + ] + }, + "line-dasharray": [0.1, 0.5] + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-transport-narrowgauge:outline", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["in", "kind", "narrow_gauge"], + ["!has", "service"], + ["==", "bridge", true] + ], + "minzoom": 15, + "paint": { + "line-color": "rgb(177,187,196)", + "line-width": { + "stops": [ + [15, 0], + [16, 5], + [18, 7], + [20, 20] + ] + }, + "line-dasharray": [0.1, 0.5] + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-transport-subway:outline", + "type": "line", + "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] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-transport-lightrail:outline", + "type": "line", + "source-layer": "streets", + "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] + ] + } + }, + "minzoom": 8 + }, + { + "source": "versatiles-shortbread", + "id": "bridge-transport-lightrail-service:outline", + "type": "line", + "source-layer": "streets", + "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] + ] + } + }, + "minzoom": 14 + }, + { + "source": "versatiles-shortbread", + "id": "bridge-transport-rail:outline", + "type": "line", + "source-layer": "streets", + "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] + ] + } + }, + "minzoom": 8 + }, + { + "source": "versatiles-shortbread", + "id": "bridge-transport-rail-service:outline", + "type": "line", + "source-layer": "streets", + "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] + ] + } + }, + "minzoom": 14 + }, + { + "source": "versatiles-shortbread", + "id": "bridge-transport-monorail:outline", + "type": "line", + "source-layer": "streets", + "filter": ["all", ["in", "kind", "monorail"], ["==", "bridge", true]], + "minzoom": 15, + "paint": { + "line-color": "rgb(177,187,196)", + "line-width": { + "stops": [ + [15, 0], + [16, 5], + [18, 7], + [20, 20] + ] + }, + "line-dasharray": [0.1, 0.5] + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-transport-funicular:outline", + "type": "line", + "source-layer": "streets", + "filter": ["all", ["in", "kind", "funicular"], ["==", "bridge", true]], + "minzoom": 15, + "paint": { + "line-color": "rgb(177,187,196)", + "line-width": { + "stops": [ + [15, 0], + [16, 5], + [18, 7], + [20, 20] + ] + }, + "line-dasharray": [0.1, 0.5] + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-transport-tram", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["in", "kind", "tram"], + ["!has", "service"], + ["==", "bridge", true] + ], + "minzoom": 13, + "paint": { + "line-width": { + "stops": [ + [13, 0], + [16, 1], + [17, 2], + [18, 3], + [20, 5] + ] + }, + "line-color": "rgb(177,187,196)" + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-transport-narrowgauge", + "type": "line", + "source-layer": "streets", + "filter": [ + "all", + ["in", "kind", "narrow_gauge"], + ["!has", "service"], + ["==", "bridge", true] + ], + "minzoom": 13, + "paint": { + "line-width": { + "stops": [ + [13, 0], + [16, 1], + [17, 2], + [18, 3], + [20, 5] + ] + }, + "line-color": "rgb(177,187,196)" + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-transport-subway", + "type": "line", + "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] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-transport-lightrail", + "type": "line", + "source-layer": "streets", + "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] + ] + } + }, + "minzoom": 14 + }, + { + "source": "versatiles-shortbread", + "id": "bridge-transport-lightrail-service", + "type": "line", + "source-layer": "streets", + "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] + }, + "minzoom": 15 + }, + { + "source": "versatiles-shortbread", + "id": "bridge-transport-rail", + "type": "line", + "source-layer": "streets", + "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] + ] + } + }, + "minzoom": 14 + }, + { + "source": "versatiles-shortbread", + "id": "bridge-transport-rail-service", + "type": "line", + "source-layer": "streets", + "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] + }, + "minzoom": 15 + }, + { + "source": "versatiles-shortbread", + "id": "bridge-transport-monorail", + "type": "line", + "source-layer": "streets", + "filter": ["all", ["in", "kind", "monorail"], ["==", "bridge", true]], + "minzoom": 13, + "paint": { + "line-width": { + "stops": [ + [13, 0], + [16, 1], + [17, 2], + [18, 3], + [20, 5] + ] + }, + "line-color": "rgb(177,187,196)" + } + }, + { + "source": "versatiles-shortbread", + "id": "bridge-transport-funicular", + "type": "line", + "source-layer": "streets", + "filter": ["all", ["in", "kind", "funicular"], ["==", "bridge", true]], + "minzoom": 13, + "paint": { + "line-width": { + "stops": [ + [13, 0], + [16, 1], + [17, 2], + [18, 3], + [20, 5] + ] + }, + "line-color": "rgb(177,187,196)" + } + }, + { + "source": "versatiles-shortbread", + "id": "poi-amenity", + "type": "symbol", + "source-layer": "pois", + "filter": ["to-boolean", ["get", "amenity"]], + "minzoom": 16, + "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)" + } + }, + { + "source": "versatiles-shortbread", + "id": "poi-leisure", + "type": "symbol", + "source-layer": "pois", + "filter": ["to-boolean", ["get", "leisure"]], + "minzoom": 16, + "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)" + } + }, + { + "source": "versatiles-shortbread", + "id": "poi-tourism", + "type": "symbol", + "source-layer": "pois", + "filter": ["to-boolean", ["get", "tourism"]], + "minzoom": 16, + "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)" + } + }, + { + "source": "versatiles-shortbread", + "id": "poi-shop", + "type": "symbol", + "source-layer": "pois", + "filter": ["to-boolean", ["get", "shop"]], + "minzoom": 16, + "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)" + } + }, + { + "source": "versatiles-shortbread", + "id": "poi-man_made", + "type": "symbol", + "source-layer": "pois", + "filter": ["to-boolean", ["get", "man_made"]], + "minzoom": 16, + "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)" + } + }, + { + "source": "versatiles-shortbread", + "id": "poi-historic", + "type": "symbol", + "source-layer": "pois", + "filter": ["to-boolean", ["get", "historic"]], + "minzoom": 16, + "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)" + } + }, + { + "source": "versatiles-shortbread", + "id": "poi-emergency", + "type": "symbol", + "source-layer": "pois", + "filter": ["to-boolean", ["get", "emergency"]], + "minzoom": 16, + "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)" + } + }, + { + "source": "versatiles-shortbread", + "id": "poi-highway", + "type": "symbol", + "source-layer": "pois", + "filter": ["to-boolean", ["get", "highway"]], + "minzoom": 16, + "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)" + } + }, + { + "source": "versatiles-shortbread", + "id": "poi-office", + "type": "symbol", + "source-layer": "pois", + "filter": ["to-boolean", ["get", "office"]], + "minzoom": 16, + "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)" + } + }, + { + "source": "versatiles-shortbread", + "id": "boundary-country:outline", + "type": "line", + "source-layer": "boundaries", + "filter": [ + "all", + ["==", "admin_level", 2], + ["!=", "maritime", true], + ["!=", "disputed", true], + ["!=", "coastline", true] + ], + "paint": { + "line-color": "rgb(249,245,239)", + "line-blur": 1, + "line-width": { + "stops": [ + [2, 0], + [3, 2], + [10, 8] + ] + }, + "line-opacity": 0.75 + }, + "layout": { + "line-cap": "round", + "line-join": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "boundary-country-disputed:outline", + "type": "line", + "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)" + } + }, + { + "source": "versatiles-shortbread", + "id": "boundary-state:outline", + "type": "line", + "source-layer": "boundaries", + "filter": [ + "all", + ["==", "admin_level", 4], + ["!=", "maritime", true], + ["!=", "disputed", true], + ["!=", "coastline", true] + ], + "paint": { + "line-color": "rgb(250,245,240)", + "line-blur": 1, + "line-width": { + "stops": [ + [7, 0], + [8, 2], + [10, 4] + ] + }, + "line-opacity": 0.75 + }, + "layout": { + "line-cap": "round", + "line-join": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "boundary-country", + "type": "line", + "source-layer": "boundaries", + "filter": [ + "all", + ["==", "admin_level", 2], + ["!=", "maritime", true], + ["!=", "disputed", true], + ["!=", "coastline", true] + ], + "paint": { + "line-color": "rgb(166,166,200)", + "line-width": { + "stops": [ + [2, 0], + [3, 1], + [10, 4] + ] + } + }, + "layout": { + "line-cap": "round", + "line-join": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "boundary-country-disputed", + "type": "line", + "source-layer": "boundaries", + "filter": [ + "all", + ["==", "admin_level", 2], + ["==", "disputed", true], + ["!=", "maritime", true], + ["!=", "coastline", true] + ], + "paint": { + "line-width": { + "stops": [ + [2, 0], + [3, 1], + [10, 4] + ] + }, + "line-color": "rgb(190,188,207)", + "line-dasharray": [2, 1] + }, + "layout": { + "line-cap": "square" + } + }, + { + "source": "versatiles-shortbread", + "id": "boundary-state", + "type": "line", + "source-layer": "boundaries", + "filter": [ + "all", + ["==", "admin_level", 4], + ["!=", "maritime", true], + ["!=", "disputed", true], + ["!=", "coastline", true] + ], + "paint": { + "line-color": "rgb(166,166,200)", + "line-width": { + "stops": [ + [7, 0], + [8, 1], + [10, 2] + ] + } + }, + "layout": { + "line-cap": "round", + "line-join": "round" + } + }, + { + "source": "versatiles-shortbread", + "id": "label-address-housenumber", + "type": "symbol", + "source-layer": "addresses", + "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)" + }, + "minzoom": 17 + }, + { + "source": "versatiles-shortbread", + "id": "label-motorway-shield", + "type": "symbol", + "source-layer": "street_labels", + "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 + }, + "minzoom": 14 + }, + { + "source": "versatiles-shortbread", + "id": "label-street-pedestrian", + "type": "symbol", + "source-layer": "street_labels", + "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 + }, + "minzoom": 12 + }, + { + "source": "versatiles-shortbread", + "id": "label-street-livingstreet", + "type": "symbol", + "source-layer": "street_labels", + "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 + }, + "minzoom": 12 + }, + { + "source": "versatiles-shortbread", + "id": "label-street-residential", + "type": "symbol", + "source-layer": "street_labels", + "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 + }, + "minzoom": 12 + }, + { + "source": "versatiles-shortbread", + "id": "label-street-unclassified", + "type": "symbol", + "source-layer": "street_labels", + "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 + }, + "minzoom": 12 + }, + { + "source": "versatiles-shortbread", + "id": "label-street-tertiary", + "type": "symbol", + "source-layer": "street_labels", + "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 + }, + "minzoom": 12 + }, + { + "source": "versatiles-shortbread", + "id": "label-street-secondary", + "type": "symbol", + "source-layer": "street_labels", + "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 + }, + "minzoom": 12 + }, + { + "source": "versatiles-shortbread", + "id": "label-street-primary", + "type": "symbol", + "source-layer": "street_labels", + "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 + }, + "minzoom": 12 + }, + { + "source": "versatiles-shortbread", + "id": "label-street-trunk", + "type": "symbol", + "source-layer": "street_labels", + "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 + }, + "minzoom": 12 + }, + { + "source": "versatiles-shortbread", + "id": "label-place-neighbourhood", + "type": "symbol", + "source-layer": "place_labels", + "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 + }, + "minzoom": 14 + }, + { + "source": "versatiles-shortbread", + "id": "label-place-quarter", + "type": "symbol", + "source-layer": "place_labels", + "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 + }, + "minzoom": 13 + }, + { + "source": "versatiles-shortbread", + "id": "label-place-suburb", + "type": "symbol", + "source-layer": "place_labels", + "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 + }, + "minzoom": 11 + }, + { + "source": "versatiles-shortbread", + "id": "label-place-hamlet", + "type": "symbol", + "source-layer": "place_labels", + "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 + }, + "minzoom": 13 + }, + { + "source": "versatiles-shortbread", + "id": "label-place-village", + "type": "symbol", + "source-layer": "place_labels", + "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 + }, + "minzoom": 11 + }, + { + "source": "versatiles-shortbread", + "id": "label-place-town", + "type": "symbol", + "source-layer": "place_labels", + "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 + }, + "minzoom": 9 + }, + { + "source": "versatiles-shortbread", + "id": "label-boundary-state", + "type": "symbol", + "source-layer": "boundary_labels", + "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 + }, + "minzoom": 5 + }, + { + "source": "versatiles-shortbread", + "id": "label-place-city", + "type": "symbol", + "source-layer": "place_labels", + "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 + }, + "minzoom": 7 + }, + { + "source": "versatiles-shortbread", + "id": "label-place-statecapital", + "type": "symbol", + "source-layer": "place_labels", + "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 + }, + "minzoom": 6 + }, + { + "source": "versatiles-shortbread", + "id": "label-place-capital", + "type": "symbol", + "source-layer": "place_labels", + "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 + }, + "minzoom": 5 + }, + { + "source": "versatiles-shortbread", + "id": "label-boundary-country-small", + "type": "symbol", + "source-layer": "boundary_labels", + "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 + }, + "minzoom": 4 + }, + { + "source": "versatiles-shortbread", + "id": "label-boundary-country-medium", + "type": "symbol", + "source-layer": "boundary_labels", + "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 + }, + "minzoom": 3 + }, + { + "source": "versatiles-shortbread", + "id": "label-boundary-country-large", + "type": "symbol", + "source-layer": "boundary_labels", + "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 + }, + "minzoom": 2 + }, + { + "source": "versatiles-shortbread", + "id": "marking-oneway", + "type": "symbol", + "source-layer": "streets", + "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"] + }, + "minzoom": 16, + "paint": { + "icon-opacity": { + "stops": [ + [16, 0], + [17, 0.4], + [20, 0.4] + ] + }, + "text-opacity": { + "stops": [ + [16, 0], + [17, 0.4], + [20, 0.4] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "marking-oneway-reverse", + "type": "symbol", + "source-layer": "streets", + "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"] + }, + "minzoom": 16, + "paint": { + "icon-opacity": { + "stops": [ + [16, 0], + [17, 0.4], + [20, 0.4] + ] + }, + "text-opacity": { + "stops": [ + [16, 0], + [17, 0.4], + [20, 0.4] + ] + } + } + }, + { + "source": "versatiles-shortbread", + "id": "symbol-transit-bus", + "type": "symbol", + "source-layer": "public_transport", + "filter": ["==", "kind", "bus_stop"], + "layout": { + "text-field": "{name}", + "icon-size": { + "stops": [ + [16, 0.5], + [18, 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-bus" + }, + "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 + }, + "minzoom": 16 + }, + { + "source": "versatiles-shortbread", + "id": "symbol-transit-tram", + "type": "symbol", + "source-layer": "public_transport", + "filter": ["==", "kind", "tram_stop"], + "layout": { + "text-field": "{name}", + "icon-size": { + "stops": [ + [15, 0.5], + [17, 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:transport-tram" + }, + "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 + }, + "minzoom": 15 + }, + { + "source": "versatiles-shortbread", + "id": "symbol-transit-subway", + "type": "symbol", + "source-layer": "public_transport", + "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" + }, + "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 + }, + "minzoom": 14 + }, + { + "source": "versatiles-shortbread", + "id": "symbol-transit-lightrail", + "type": "symbol", + "source-layer": "public_transport", + "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" + }, + "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 + }, + "minzoom": 14 + }, + { + "source": "versatiles-shortbread", + "id": "symbol-transit-station", + "type": "symbol", + "source-layer": "public_transport", + "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 + }, + "minzoom": 13 + }, + { + "source": "versatiles-shortbread", + "id": "symbol-transit-airfield", + "type": "symbol", + "source-layer": "public_transport", + "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 + }, + "minzoom": 13 + }, + { + "source": "versatiles-shortbread", + "id": "symbol-transit-airport", + "type": "symbol", + "source-layer": "public_transport", + "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 + }, + "minzoom": 12 + } + ] +} diff --git a/src/reducers/common.ts b/src/reducers/common.ts index 56c8707..5aafc5a 100644 --- a/src/reducers/common.ts +++ b/src/reducers/common.ts @@ -41,7 +41,7 @@ const initialState = { activeTab: 0, showSettings: false, showDirectionsPanel: true, - coordinates: [], + coordinates: [] as number[][], loading: false, message: { receivedAt: 0, diff --git a/src/utils/heightgraph.ts b/src/utils/heightgraph.ts index c3314ec..3fbfcc0 100644 --- a/src/utils/heightgraph.ts +++ b/src/utils/heightgraph.ts @@ -1,6 +1,6 @@ type Coordinate = [number, number]; // [latitude, longitude] type RangeHeightPoint = [number, number]; // [distance, elevation] -type LineStringCoordinate = [number, number, number]; // [longitude, latitude, elevation] +type LineStringCoordinate = [number, number, number, number]; // [longitude, latitude, elevation, distance] type HeightClass = -5 | -4 | -3 | -2 | -1 | 0 | 1 | 2 | 3 | 4 | 5; interface HeightGraphFeature { @@ -107,9 +107,10 @@ export const buildHeightgraphData = ( if (previousHeightClass !== heightClass) { // since at this point we have a change in height class this will be the last point in current LineString LineStringCoordinates.push([ - coordinates[index]![0], - coordinates[index]![1], - rangeHeightData[index]![1], + coordinates[index]![0], // lng + coordinates[index]![1], // lat + rangeHeightData[index]![1], // elevation + rangeHeightData[index]![0], // distance from start ]); // add current LineString as one feature in GeoJSON @@ -130,9 +131,10 @@ export const buildHeightgraphData = ( } // current point is also the stratting point for new LineString LineStringCoordinates.push([ - coordinates[index]![0], - coordinates[index]![1], - rangeHeightData[index]![1], + coordinates[index]![0], // lng + coordinates[index]![1], // lat + rangeHeightData[index]![1], // elevation + rangeHeightData[index]![0], // distance from start ]); // replace previousHeightClass with current heightClass previousHeightClass = heightClass; From 41e7fbb8d901e8e3407b4d3616fc42eb66da83b3 Mon Sep 17 00:00:00 2001 From: Mustafa Turhan Date: Mon, 6 Oct 2025 16:16:51 +0300 Subject: [PATCH 2/8] add route summary on user hover a route --- src/map/index.tsx | 125 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 124 insertions(+), 1 deletion(-) diff --git a/src/map/index.tsx b/src/map/index.tsx index 638bd7a..96fabe1 100644 --- a/src/map/index.tsx +++ b/src/map/index.tsx @@ -15,6 +15,7 @@ import Map, { NavigationControl, } from 'react-map-gl/maplibre'; import type { MaplibreTerradrawControl } from '@watergis/maplibre-gl-terradraw'; +import type maplibregl from 'maplibre-gl'; import 'maplibre-gl/dist/maplibre-gl.css'; import axios from 'axios'; @@ -34,6 +35,7 @@ import { buildLocateRequest, } from '@/utils/valhalla'; import { buildHeightgraphData } from '@/utils/heightgraph'; +import { formatDuration } from '@/utils/date-time'; import HeightGraph from '@/components/heightgraph'; import { DrawControl } from './draw-control'; import './map.css'; @@ -45,7 +47,7 @@ import type { ThunkDispatch } from 'redux-thunk'; import type { DirectionsState } from '@/reducers/directions'; import type { IsochroneState } from '@/reducers/isochrones'; import type { Profile } from '@/reducers/common'; -import type { ParsedDirectionsGeometry } from '@/common/types'; +import type { ParsedDirectionsGeometry, Summary } from '@/common/types'; import type { Feature, FeatureCollection, LineString } from 'geojson'; // Import the style JSON @@ -169,6 +171,11 @@ const MapComponent = ({ const [heightgraphHoverDistance, setHeightgraphHoverDistance] = useState< number | null >(null); + const [routeHoverPopup, setRouteHoverPopup] = useState<{ + lng: number; + lat: number; + summary: Summary; + } | null>(null); const [viewState, setViewState] = useState({ longitude: center[0], latitude: center[1], @@ -651,6 +658,66 @@ const MapComponent = ({ localStorage.setItem('last_center', last_center); }, []); + // Handle route line hover + const onRouteLineHover = useCallback( + (event: maplibregl.MapLayerMouseEvent) => { + if (!mapRef.current) return; + + const map = mapRef.current.getMap(); + map.getCanvas().style.cursor = 'pointer'; + + const feature = event.features?.[0]; + if (feature && feature.properties?.summary) { + // Parse the summary if it's a string + const summary = + typeof feature.properties.summary === 'string' + ? JSON.parse(feature.properties.summary) + : feature.properties.summary; + + setRouteHoverPopup({ + lng: event.lngLat.lng, + lat: event.lngLat.lat, + summary: summary as Summary, + }); + } + }, + [] + ); + + const handleMouseMove = useCallback( + (event: maplibregl.MapLayerMouseEvent) => { + if (!mapRef.current || showPopup) return; // Don't show if click popup is visible + + const features = event.features; + // Check if we're hovering over the routes-line layer + const isOverRoute = + features && + features.length > 0 && + features[0]?.layer?.id === 'routes-line'; + + if (isOverRoute) { + onRouteLineHover(event); + } else { + // Clear popup and cursor when not over route + if (routeHoverPopup) { + setRouteHoverPopup(null); + } + const map = mapRef.current.getMap(); + if (map.getCanvas().style.cursor === 'pointer') { + map.getCanvas().style.cursor = ''; + } + } + }, + [showPopup, routeHoverPopup, onRouteLineHover] + ); + + const handleMouseLeave = useCallback(() => { + if (!mapRef.current) return; + const map = mapRef.current.getMap(); + map.getCanvas().style.cursor = ''; + setRouteHoverPopup(null); + }, []); + const MarkerIcon = ({ color, number, @@ -937,6 +1004,9 @@ const MapComponent = ({ onMoveEnd={handleMoveEnd} onClick={handleMapClick} onContextMenu={handleMapContextMenu} + onMouseMove={handleMouseMove} + onMouseLeave={handleMouseLeave} + interactiveLayerIds={['routes-line']} mapStyle={mapStyle as unknown as maplibregl.StyleSpecification} style={{ width: '100%', height: '100vh' }} maxBounds={maxBounds} @@ -1134,6 +1204,59 @@ const MapComponent = ({ )} + {/* Route hover popup */} + {routeHoverPopup && ( + + {/* todo: update styling with tailwind when we migrate to it */} +
+
+ Route Summary +
+
+ + + {`${routeHoverPopup.summary.length.toFixed( + routeHoverPopup.summary.length > 1000 ? 0 : 1 + )} km`} + +
+
+ + + {formatDuration(routeHoverPopup.summary.time)} + +
+
+
+ )} + {/* Brand logos */}
Date: Mon, 6 Oct 2025 16:25:44 +0300 Subject: [PATCH 3/8] add maplibre-gl-terradraw css --- src/map/draw-control.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/map/draw-control.tsx b/src/map/draw-control.tsx index 2b9a096..67964ce 100644 --- a/src/map/draw-control.tsx +++ b/src/map/draw-control.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { useControl } from 'react-map-gl/maplibre'; import { MaplibreTerradrawControl } from '@watergis/maplibre-gl-terradraw'; +import '@watergis/maplibre-gl-terradraw/dist/maplibre-gl-terradraw.css'; interface DrawControlProps { position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'; From 4d79baca0decfa41f766761555ecea0b6e0d021a Mon Sep 17 00:00:00 2001 From: Mustafa Turhan Date: Tue, 7 Oct 2025 22:03:59 +0300 Subject: [PATCH 4/8] disable popup on select mode --- src/map/index.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/map/index.tsx b/src/map/index.tsx index 96fabe1..c433ce2 100644 --- a/src/map/index.tsx +++ b/src/map/index.tsx @@ -618,8 +618,7 @@ const MapComponent = ({ const terraDrawInstance = drawRef.current.getTerraDrawInstance(); if (terraDrawInstance) { const mode = terraDrawInstance.getMode(); - // Don't show popup if actively drawing (polygon mode, not select/delete) - if (mode === 'polygon') { + if (mode === 'polygon' || mode === 'select' || mode === 'delete') { return; } } From 8e59b1bf0cb5e49a9b628e73893ab9c0cc48dbc4 Mon Sep 17 00:00:00 2001 From: Mustafa Turhan Date: Tue, 7 Oct 2025 22:08:35 +0300 Subject: [PATCH 5/8] use delete-selection instead of delete --- src/map/draw-control.tsx | 4 +++- src/map/index.tsx | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/map/draw-control.tsx b/src/map/draw-control.tsx index 67964ce..835a04b 100644 --- a/src/map/draw-control.tsx +++ b/src/map/draw-control.tsx @@ -14,7 +14,7 @@ export function DrawControl(props: DrawControlProps) { // onCreate () => { const control = new MaplibreTerradrawControl({ - modes: ['polygon', 'select', 'delete'], + modes: ['polygon', 'select', 'delete-selection'], open: true, }); @@ -32,6 +32,7 @@ export function DrawControl(props: DrawControlProps) { props.controlRef.current.getTerraDrawInstance(); if (terraDrawInstance) { terraDrawInstance.on('finish', props.onUpdate); + terraDrawInstance.on('change', props.onUpdate); } } }, @@ -42,6 +43,7 @@ export function DrawControl(props: DrawControlProps) { props.controlRef.current.getTerraDrawInstance(); if (terraDrawInstance) { terraDrawInstance.off('finish', props.onUpdate); + terraDrawInstance.off('change', props.onUpdate); } } }, diff --git a/src/map/index.tsx b/src/map/index.tsx index c433ce2..5da2854 100644 --- a/src/map/index.tsx +++ b/src/map/index.tsx @@ -618,7 +618,7 @@ const MapComponent = ({ const terraDrawInstance = drawRef.current.getTerraDrawInstance(); if (terraDrawInstance) { const mode = terraDrawInstance.getMode(); - if (mode === 'polygon' || mode === 'select' || mode === 'delete') { + if (mode === 'polygon' || mode === 'select' || mode === 'delete-selection') { return; } } From 87a4782818a9f1b650973392b5e2a99f057714eb Mon Sep 17 00:00:00 2001 From: Mustafa Turhan Date: Tue, 7 Oct 2025 22:15:44 +0300 Subject: [PATCH 6/8] update file names, tests and revert the change event in draw control --- eslint.config.js | 1 + playwright.config.ts | 1 + src/map/draw-control.tsx | 2 -- src/map/index.tsx | 6 +++++- src/utils/heightgraph.spec.ts | 2 +- 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 03ebb8b..f1f8cb1 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -113,6 +113,7 @@ export default defineConfig( '**/*.test.{js,jsx,ts,tsx}', '**/*.d.ts', 'src/vite-env.d.ts', + 'vite.config.ts', ], rules: { 'check-file/filename-naming-convention': 'off', diff --git a/playwright.config.ts b/playwright.config.ts index 1bc9dee..a0a9e1d 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,3 +1,4 @@ +/* eslint-disable check-file/filename-naming-convention */ import { defineConfig, devices } from '@playwright/test'; /** diff --git a/src/map/draw-control.tsx b/src/map/draw-control.tsx index 835a04b..b0d7721 100644 --- a/src/map/draw-control.tsx +++ b/src/map/draw-control.tsx @@ -32,7 +32,6 @@ export function DrawControl(props: DrawControlProps) { props.controlRef.current.getTerraDrawInstance(); if (terraDrawInstance) { terraDrawInstance.on('finish', props.onUpdate); - terraDrawInstance.on('change', props.onUpdate); } } }, @@ -43,7 +42,6 @@ export function DrawControl(props: DrawControlProps) { props.controlRef.current.getTerraDrawInstance(); if (terraDrawInstance) { terraDrawInstance.off('finish', props.onUpdate); - terraDrawInstance.off('change', props.onUpdate); } } }, diff --git a/src/map/index.tsx b/src/map/index.tsx index 5da2854..c0e690a 100644 --- a/src/map/index.tsx +++ b/src/map/index.tsx @@ -618,7 +618,11 @@ const MapComponent = ({ const terraDrawInstance = drawRef.current.getTerraDrawInstance(); if (terraDrawInstance) { const mode = terraDrawInstance.getMode(); - if (mode === 'polygon' || mode === 'select' || mode === 'delete-selection') { + if ( + mode === 'polygon' || + mode === 'select' || + mode === 'delete-selection' + ) { return; } } diff --git a/src/utils/heightgraph.spec.ts b/src/utils/heightgraph.spec.ts index 09d2560..dc4c01c 100644 --- a/src/utils/heightgraph.spec.ts +++ b/src/utils/heightgraph.spec.ts @@ -187,7 +187,7 @@ describe('buildHeightgraphData', () => { // Check coordinate format: [longitude, latitude, elevation] const firstCoord = feature.geometry.coordinates[0]!; - expect(firstCoord).toHaveLength(3); + expect(firstCoord).toHaveLength(4); expect(firstCoord[0]).toBe(40.7128); // longitude (first element from coordinates) expect(firstCoord[1]).toBe(-74.006); // latitude (second element from coordinates) expect(firstCoord[2]).toBe(100); // elevation from rangeHeightData From 1b6c59eb5b9ad46c1c18b5472e8d4f8fff9d5d06 Mon Sep 17 00:00:00 2001 From: Mustafa Turhan Date: Tue, 7 Oct 2025 23:27:23 +0300 Subject: [PATCH 7/8] update e2e tests --- e2e/homepage.spec.ts | 28 +-- e2e/map.spec.ts | 223 ++++++++--------- e2e/mocks.ts | 541 +++++++++++++++++++++++++++++++++++++++++ src/controls/index.tsx | 28 +-- src/map/index.tsx | 1 + 5 files changed, 662 insertions(+), 159 deletions(-) create mode 100644 e2e/mocks.ts diff --git a/e2e/homepage.spec.ts b/e2e/homepage.spec.ts index 47e2b38..0385c2f 100644 --- a/e2e/homepage.spec.ts +++ b/e2e/homepage.spec.ts @@ -14,7 +14,7 @@ test('has default elements in the page', async ({ page }) => { await setupStatusMock(page); - await expect(page.getByTestId('map')).toBeVisible(); + await expect(page.getByRole('region', { name: 'Map' })).toBeVisible(); await expect(page.getByTestId('directions-tab-button')).toBeVisible(); await expect(page.getByTestId('isochrones-tab-button')).toBeVisible(); @@ -57,33 +57,13 @@ test('has default elements in the page', async ({ page }) => { await expect(page.getByText('Denoise0.1')).toBeVisible(); await expect(page.getByText('Generalizemeters0')).toBeVisible(); - await expect( - page.getByText('Last Data Update: 2025-09-17, 04:19') - ).toBeVisible(); - - await expect(page.getByRole('button', { name: 'Layers' })).toBeVisible(); + // await expect(page.getByRole('button', { name: 'Layers' })).toBeVisible(); await expect(page.getByRole('button', { name: 'Zoom in' })).toBeVisible(); await expect(page.getByRole('button', { name: 'Zoom out' })).toBeVisible(); - await expect( - page.getByTitle('Draw Polygons').getByRole('button') - ).toBeVisible(); - await expect(page.getByTitle('Draw Text').getByRole('button')).toBeVisible(); - - await expect( - page.getByTitle('Edit Layers').getByRole('button') - ).toBeVisible(); - await expect( - page.getByTitle('Drag Layers').getByRole('button') - ).toBeVisible(); - await expect( - page.getByTitle('Remove Layers').getByRole('button') - ).toBeVisible(); - await expect( - page.getByTitle('Rotate Layers').getByRole('button') - ).toBeVisible(); + await expect(page.getByRole('button', { name: 'Polygon' })).toBeVisible(); + await expect(page.getByRole('button', { name: 'Select' })).toBeVisible(); await expect(page.getByRole('button', { name: 'Open OSM' })).toBeVisible(); - await expect(page.getByTitle('Height Graph')).toBeVisible(); }); diff --git a/e2e/map.spec.ts b/e2e/map.spec.ts index a2dd5e1..f0e91f0 100644 --- a/e2e/map.spec.ts +++ b/e2e/map.spec.ts @@ -8,6 +8,7 @@ import { setupSearchMock, simpleMockNominatimResponse, } from './helpers'; +import { customRouteResponse } from './mocks'; interface NominatimApiRequest { url: string; @@ -62,7 +63,7 @@ test.describe('Map interactions with right context menu', () => { }); test('should show right-click context menu', async ({ page }) => { - await page.getByTestId('map').click({ + await page.getByRole('region', { name: 'Map' }).click({ button: 'right', }); await expect( @@ -81,13 +82,11 @@ test.describe('Map interactions with right context menu', () => { }) => { const apiRequests = await setupNominatimMock(page); - await page.getByTestId('map').click({ button: 'right' }); + await page.getByRole('region', { name: 'Map' }).click({ button: 'right' }); await page.getByRole('button', { name: 'Directions from here' }).click(); await page.waitForTimeout(2000); - await expect( - page.getByTestId('map').getByRole('button', { name: '1' }) - ).toBeVisible(); + await expect(page.getByLabel('Map marker').getByRole('img')).toBeVisible(); expect(apiRequests.length).toBeGreaterThan(0); @@ -109,10 +108,7 @@ test.describe('Map interactions with right context menu', () => { simpleMockNominatimResponse as any ); - await page.waitForSelector('[data-testid="map"]', { state: 'visible' }); - await page.waitForTimeout(1000); - - await page.getByTestId('map').click({ + await page.getByRole('region', { name: 'Map' }).click({ button: 'right', force: true, }); @@ -120,9 +116,7 @@ test.describe('Map interactions with right context menu', () => { await page.getByRole('button', { name: 'Directions from here' }).click(); await page.waitForTimeout(2000); - await expect( - page.getByTestId('map').getByRole('button', { name: '1' }) - ).toBeVisible(); + await expect(page.getByLabel('Map marker').getByRole('img')).toBeVisible(); expect(apiRequests.length).toBe(1); @@ -136,13 +130,11 @@ test.describe('Map interactions with right context menu', () => { }) => { await setupNominatimMock(page); - await page.getByTestId('map').click({ button: 'right' }); + await page.getByRole('region', { name: 'Map' }).click({ button: 'right' }); await page.getByRole('button', { name: 'Directions from here' }).click(); await page.waitForTimeout(2000); - await expect( - page.getByTestId('map').getByRole('button', { name: '1' }) - ).toBeVisible(); + await expect(page.getByLabel('Map marker').getByRole('img')).toBeVisible(); await expect( page @@ -156,13 +148,11 @@ test.describe('Map interactions with right context menu', () => { }) => { const apiRequests = await setupNominatimMock(page); - await page.getByTestId('map').click({ button: 'right' }); + await page.getByRole('region', { name: 'Map' }).click({ button: 'right' }); await page.getByRole('button', { name: 'Directions to here' }).click(); await page.waitForTimeout(2000); - await expect( - page.getByTestId('map').getByRole('button', { name: '2' }) - ).toBeVisible(); + await expect(page.getByLabel('Map marker').getByRole('img')).toBeVisible(); expect(apiRequests.length).toBeGreaterThan(0); @@ -184,10 +174,7 @@ test.describe('Map interactions with right context menu', () => { simpleMockNominatimResponse as any ); - await page.waitForSelector('[data-testid="map"]', { state: 'visible' }); - await page.waitForTimeout(1000); - - await page.getByTestId('map').click({ + await page.getByRole('region', { name: 'Map' }).click({ button: 'right', force: true, }); @@ -195,9 +182,7 @@ test.describe('Map interactions with right context menu', () => { await page.getByRole('button', { name: 'Directions to here' }).click(); await page.waitForTimeout(2000); - await expect( - page.getByTestId('map').getByRole('button', { name: '2' }) - ).toBeVisible(); + await expect(page.getByLabel('Map marker').getByRole('img')).toBeVisible(); expect(apiRequests.length).toBe(1); @@ -209,13 +194,11 @@ test.describe('Map interactions with right context menu', () => { test('should populate "to" input with Nominatim result', async ({ page }) => { await setupNominatimMock(page); - await page.getByTestId('map').click({ button: 'right' }); + await page.getByRole('region', { name: 'Map' }).click({ button: 'right' }); await page.getByRole('button', { name: 'Directions to here' }).click(); await page.waitForTimeout(2000); - await expect( - page.getByTestId('map').getByRole('button', { name: '2' }) - ).toBeVisible(); + await expect(page.getByLabel('Map marker').getByRole('img')).toBeVisible(); await expect( page @@ -229,13 +212,11 @@ test.describe('Map interactions with right context menu', () => { }) => { const apiRequests = await setupNominatimMock(page); - await page.getByTestId('map').click({ button: 'right' }); + await page.getByRole('region', { name: 'Map' }).click({ button: 'right' }); await page.getByRole('button', { name: 'Add as via point' }).click(); await page.waitForTimeout(2000); - await expect( - page.getByTestId('map').getByRole('button', { name: '2' }) - ).toBeVisible(); + await expect(page.getByLabel('Map marker').getByRole('img')).toBeVisible(); expect(apiRequests.length).toBeGreaterThan(0); @@ -253,13 +234,11 @@ test.describe('Map interactions with right context menu', () => { }) => { await setupNominatimMock(page); - await page.getByTestId('map').click({ button: 'right' }); + await page.getByRole('region', { name: 'Map' }).click({ button: 'right' }); await page.getByRole('button', { name: 'Add as via point' }).click(); await page.waitForTimeout(2000); - await expect( - page.getByTestId('map').getByRole('button', { name: '2' }) - ).toBeVisible(); + await expect(page.getByLabel('Map marker').getByRole('img')).toBeVisible(); await expect( page @@ -272,25 +251,25 @@ test.describe('Map interactions with right context menu', () => { await setupNominatimMock(page); // Add first via point - await page.getByTestId('map').click({ button: 'right' }); + await page.getByRole('region', { name: 'Map' }).click({ button: 'right' }); await page.getByRole('button', { name: 'Add as via point' }).click(); - await page.waitForTimeout(2000); - await expect( - page.getByTestId('map').getByRole('button', { name: '2' }) - ).toBeVisible(); + await expect(page.getByLabel('Map marker').getByRole('img')).toBeVisible(); // Add second via point - await page.getByTestId('map').click({ button: 'right' }); + await page + .getByRole('region', { name: 'Map' }) + .click({ button: 'right', position: { x: 800, y: 300 }, force: true }); await page.getByRole('button', { name: 'Add as via point' }).click(); - await page.waitForTimeout(2000); - - await expect( - page.getByTestId('map').getByRole('button', { name: '3' }) - ).toBeVisible(); await expect(page.getByTestId('waypoint-input-1')).toBeVisible(); await expect(page.getByTestId('waypoint-input-2')).toBeVisible(); + await expect( + page.getByLabel('Map marker 2').getByRole('img') + ).toBeVisible(); + await expect( + page.getByLabel('Map marker 3').getByRole('img') + ).toBeVisible(); await expect( page @@ -309,40 +288,42 @@ test.describe('Map interactions with right context menu', () => { await setupNominatimMock(page); // Add "from" waypoint - await page.getByTestId('map').click({ button: 'right' }); + await page.getByRole('region', { name: 'Map' }).click({ button: 'right' }); await page.getByRole('button', { name: 'Directions from here' }).click(); await page.waitForTimeout(1000); await expect( - page.getByTestId('map').getByRole('button', { name: '1' }) + page.getByLabel('Map marker 1').getByRole('img') ).toBeVisible(); // Add "to" waypoint - await page.getByTestId('map').click({ button: 'right' }); + await page + .getByRole('region', { name: 'Map' }) + .click({ button: 'right', position: { x: 800, y: 300 }, force: true }); await page.getByRole('button', { name: 'Directions to here' }).click(); await page.waitForTimeout(1000); await expect( - page.getByTestId('map').getByRole('button', { name: '2' }) + page.getByLabel('Map marker 2').getByRole('img') ).toBeVisible(); // Add 7 via points (total waypoints = 9, but only 8 should be allowed) for (let i = 0; i < 7; i++) { - await page.getByTestId('map').click({ button: 'right' }); + await page.getByRole('region', { name: 'Map' }).click({ + button: 'right', + position: { x: 800, y: 100 + i * 100 }, + force: true, + }); await page.getByRole('button', { name: 'Add as via point' }).click(); await page.waitForTimeout(1000); } - // Check that waypoint inputs 0 to 7 and waypoint markers 1 to 8 are visible for (let i = 0; i < 8; i++) { await expect(page.getByTestId(`waypoint-input-${i}`)).toBeVisible(); await expect( - page.getByTestId('map').getByRole('button', { name: `${i + 1}` }) + page.getByLabel(`Map marker ${i + 1}`).getByRole('img') ).toBeVisible(); } - - // Check that waypoint input 8 does not exist - await expect(page.getByTestId('waypoint-input-8')).toHaveCount(1); }); test('selecting two point should display route on the map', async ({ @@ -352,32 +333,31 @@ test.describe('Map interactions with right context menu', () => { const apiRequests = await setupRouteMock(page); // Select "from" point - await page.getByTestId('map').click({ button: 'right', force: true }); + await page + .getByRole('region', { name: 'Map' }) + .click({ button: 'right', position: { x: 800, y: 100 }, force: true }); await page.getByRole('button', { name: 'Directions from here' }).click(); await page.waitForTimeout(1000); await expect( - page.getByTestId('map').getByRole('button', { name: '1' }) + page.getByLabel('Map marker 1').getByRole('img') ).toBeVisible(); // Select "to" point - await page.getByTestId('map').click({ button: 'right', force: true }); + await page + .getByRole('region', { name: 'Map' }) + .click({ button: 'right', position: { x: 800, y: 300 }, force: true }); await page.getByRole('button', { name: 'Directions to here' }).click(); await page.waitForTimeout(2000); await expect( - page.getByTestId('map').getByRole('button', { name: '2' }) + page.getByLabel('Map marker 2').getByRole('img') ).toBeVisible(); expect(apiRequests.length).toBeGreaterThan(0); const request = apiRequests[0] as RouteApiRequest; validateRouteApiRequest(request); - - await expect(page.locator('svg.leaflet-zoom-animated')).toHaveCount(1); - await expect( - page.locator('svg.leaflet-zoom-animated .leaflet-interactive') - ).toHaveCount(2); }); test('should display maneuvers when route is created', async ({ page }) => { @@ -385,30 +365,36 @@ test.describe('Map interactions with right context menu', () => { await setupRouteMock(page); // Select "from" point - await page.getByTestId('map').click({ button: 'right', force: true }); + await page + .getByRole('region', { name: 'Map' }) + .click({ button: 'right', force: true }); await page.getByRole('button', { name: 'Directions from here' }).click(); await page.waitForTimeout(1000); await expect( - page.getByTestId('map').getByRole('button', { name: '1' }) + page.getByLabel('Map marker 1').getByRole('img') ).toBeVisible(); // Select "to" point - await page.getByTestId('map').click({ button: 'right', force: true }); + await page + .getByRole('region', { name: 'Map' }) + .click({ button: 'right', position: { x: 800, y: 300 }, force: true }); await page.getByRole('button', { name: 'Directions to here' }).click(); await page.waitForTimeout(1000); await expect( - page.getByTestId('map').getByRole('button', { name: '2' }) + page.getByLabel('Map marker 2').getByRole('img') ).toBeVisible(); // Add a via point - await page.getByTestId('map').click({ button: 'right', force: true }); + await page + .getByRole('region', { name: 'Map' }) + .click({ button: 'right', position: { x: 800, y: 100 }, force: true }); await page.getByRole('button', { name: 'Add as via point' }).click(); await page.waitForTimeout(2000); await expect( - page.getByTestId('map').getByRole('button', { name: '3' }) + page.getByLabel('Map marker 3').getByRole('img') ).toBeVisible(); await expect( @@ -436,27 +422,31 @@ test.describe('Map interactions with right context menu', () => { page, }) => { const nominatimRequests = await setupNominatimMock(page); - const routeRequests = await setupRouteMock(page); + const routeRequests = await setupRouteMock( + page, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + customRouteResponse as any + ); // Add "from" waypoint - await page.getByTestId('map').click({ button: 'right' }); + await page + .getByRole('region', { name: 'Map' }) + .click({ button: 'right', position: { x: 800, y: 100 }, force: true }); await page.getByRole('button', { name: 'Directions from here' }).click(); await page.waitForTimeout(1000); - const fromWaypoint = page - .getByTestId('map') - .getByRole('button', { name: '1' }); + const fromWaypoint = page.getByLabel('Map marker 1').getByRole('img'); await expect(fromWaypoint).toBeVisible(); // Add "to" waypoint - await page.getByTestId('map').click({ button: 'right' }); + await page + .getByRole('region', { name: 'Map' }) + .click({ button: 'right', position: { x: 800, y: 200 }, force: true }); await page.getByRole('button', { name: 'Directions to here' }).click(); await page.waitForTimeout(1000); - const toWaypoint = page - .getByTestId('map') - .getByRole('button', { name: '2' }); + const toWaypoint = page.getByLabel('Map marker 2').getByRole('img'); await expect(toWaypoint).toBeVisible(); @@ -497,22 +487,22 @@ test.describe('Map interactions with left context menu', () => { test('should show left-click context menu', async ({ page }) => { await setupHeightMock(page); - await page.getByTestId('map').click({ + await page.getByRole('region', { name: 'Map' }).click({ button: 'left', }); await expect(page.getByTestId('dd-button')).toContainText( - '13.393707, 52.517892' + '13.393707, 52.518310' ); await expect(page.getByTestId('dd-copy-button')).toBeVisible(); await expect(page.getByTestId('latlng-button')).toContainText( - '52.517892, 13.393707' + '52.518310, 13.393707' ); await expect(page.getByTestId('latlng-copy-button')).toBeVisible(); await expect(page.getByTestId('dms-button')).toContainText( - '52° 31\' 4" N 13° 23\' 37" E' + '52° 31\' 6" N 13° 23\' 37" E' ); await expect(page.getByTestId('dms-copy-button')).toBeVisible(); @@ -532,7 +522,7 @@ test.describe('Map interactions with left context menu', () => { test('should show height from api response', async ({ page }) => { await setupHeightMock(page); - await page.getByTestId('map').click({ + await page.getByRole('region', { name: 'Map' }).click({ button: 'left', }); @@ -543,7 +533,7 @@ test.describe('Map interactions with left context menu', () => { await setupHeightMock(page); const locateRequests = await setupLocateMock(page); - await page.getByTestId('map').click({ + await page.getByRole('region', { name: 'Map' }).click({ button: 'left', }); @@ -563,14 +553,14 @@ test.describe('Map interactions with left context menu', () => { expect(locateRequest.body).toBeDefined(); expect(locateRequest.body?.costing).toBe('bicycle'); expect(locateRequest.body?.locations).toStrictEqual([ - { lat: 52.51789222838286, lon: 13.393707275390627 }, + { lat: 52.51830999999976, lon: 13.393706999999239 }, ]); }); test('should copy text to clipboard', async ({ page }) => { await setupHeightMock(page); - await page.getByTestId('map').click({ + await page.getByRole('region', { name: 'Map' }).click({ button: 'left', }); @@ -581,7 +571,7 @@ test.describe('Map interactions with left context menu', () => { const clipboardContent = await page.evaluate(() => navigator.clipboard.readText() ); - expect(clipboardContent).toBe('13.393707,52.517892'); + expect(clipboardContent).toBe('13.393707,52.518310'); }); }); @@ -598,16 +588,11 @@ test.describe('Map interactions with URL parameters', () => { await page.waitForTimeout(2000); - await expect(page.locator('svg.leaflet-zoom-animated')).toHaveCount(1); - await expect( - page.locator('svg.leaflet-zoom-animated .leaflet-interactive') - ).toHaveCount(2); - // Check that waypoint inputs 0 to 1 and waypoint markers 1 to 2 are visible for (let i = 0; i < 2; i++) { await expect(page.getByTestId(`waypoint-input-${i}`)).toBeVisible(); await expect( - page.getByTestId('map').getByRole('button', { name: `${i + 1}` }) + page.getByLabel(`Map marker ${i + 1}`).getByRole('img') ).toBeVisible(); } @@ -627,16 +612,11 @@ test.describe('Map interactions with URL parameters', () => { await page.waitForTimeout(2000); - await expect(page.locator('svg.leaflet-zoom-animated')).toHaveCount(1); - await expect( - page.locator('svg.leaflet-zoom-animated .leaflet-interactive') - ).toHaveCount(2); - // Check that waypoint inputs 0 to 7 and waypoint markers 1 to 8 are visible for (let i = 0; i < 8; i++) { await expect(page.getByTestId(`waypoint-input-${i}`)).toBeVisible(); await expect( - page.getByTestId('map').getByRole('button', { name: `${i + 1}` }) + page.getByLabel(`Map marker ${i + 1}`).getByRole('img') ).toBeVisible(); } @@ -686,7 +666,7 @@ https: test.describe('Left drawer', () => { await searchResult.click(); await expect( - page.getByTestId('map').getByRole('button', { name: '1' }) + page.getByLabel('Map marker 1').getByRole('img') ).toBeVisible(); expect(searchRequests.length).toBe(1); @@ -714,7 +694,7 @@ https: test.describe('Left drawer', () => { await firstSearchResult.click(); await expect( - page.getByTestId('map').getByRole('button', { name: '1' }) + page.getByLabel('Map marker 1').getByRole('img') ).toBeVisible(); // Add "to" waypoint @@ -732,14 +712,9 @@ https: test.describe('Left drawer', () => { await secondSearchResult.click(); await expect( - page.getByTestId('map').getByRole('button', { name: '2' }) + page.getByLabel('Map marker 2').getByRole('img') ).toBeVisible(); - await expect(page.locator('svg.leaflet-zoom-animated')).toHaveCount(1); - await expect( - page.locator('svg.leaflet-zoom-animated .leaflet-interactive') - ).toHaveCount(2); - expect(routeRequests.length).toBe(1); expect(searchRequests.length).toBe(2); }); @@ -750,21 +725,23 @@ https: test.describe('Left drawer', () => { await setupNominatimMock(page); // Add first via waypoint - await page.getByTestId('map').click({ button: 'right' }); + await page.getByRole('region', { name: 'Map' }).click({ button: 'right' }); await page.getByRole('button', { name: 'Add as via point' }).click(); await page.waitForTimeout(2000); await expect( - page.getByTestId('map').getByRole('button', { name: '2' }) + page.getByLabel('Map marker 2').getByRole('img') ).toBeVisible(); // Add "to" waypoint - await page.getByTestId('map').click({ button: 'right' }); + await page + .getByRole('region', { name: 'Map' }) + .click({ button: 'right', position: { x: 800, y: 200 }, force: true }); await page.getByRole('button', { name: 'Directions to here' }).click(); await page.waitForTimeout(2000); await expect( - page.getByTestId('map').getByRole('button', { name: '3' }) + page.getByLabel('Map marker 3').getByRole('img') ).toBeVisible(); await expect( @@ -820,21 +797,25 @@ https: test.describe('Left drawer', () => { const routeRequests = await setupRouteMock(page); // Add first via point - await page.getByTestId('map').click({ button: 'right' }); + await page.getByRole('region', { name: 'Map' }).click({ button: 'right' }); await page.getByRole('button', { name: 'Add as via point' }).click(); await page.waitForTimeout(2000); await expect( - page.getByTestId('map').getByRole('button', { name: '2' }) + page.getByLabel('Map marker 2').getByRole('img') ).toBeVisible(); // Add second via point - await page.getByTestId('map').click({ button: 'right' }); + await page.getByRole('region', { name: 'Map' }).click({ + button: 'right', + position: { x: 800, y: 100 }, + force: true, + }); await page.getByRole('button', { name: 'Add as via point' }).click(); await page.waitForTimeout(2000); await expect( - page.getByTestId('map').getByRole('button', { name: '3' }) + page.getByLabel('Map marker 3').getByRole('img') ).toBeVisible(); await expect(page.getByTestId('waypoint-input-1')).toBeVisible(); diff --git a/e2e/mocks.ts b/e2e/mocks.ts new file mode 100644 index 0000000..561d3ee --- /dev/null +++ b/e2e/mocks.ts @@ -0,0 +1,541 @@ +export const customRouteResponse = { + trip: { + locations: [ + { + type: 'break', + lat: 52.626811, + lon: 13.50357, + side_of_street: 'right', + original_index: 0, + }, + { type: 'break', lat: 52.543372, lon: 13.50357, original_index: 1 }, + ], + legs: [ + { + maneuvers: [ + { + type: 2, + instruction: 'Bike northeast.', + verbal_succinct_transition_instruction: + 'Bike northeast. Then Turn right.', + verbal_pre_transition_instruction: + 'Bike northeast. Then Turn right.', + verbal_post_transition_instruction: 'Continue for 100 meters.', + bearing_after: 33, + time: 24.512, + length: 0.128, + cost: 48.902, + begin_shape_index: 0, + end_shape_index: 8, + verbal_multi_cue: true, + travel_mode: 'bicycle', + travel_type: 'hybrid', + }, + { + type: 10, + instruction: 'Turn right.', + verbal_transition_alert_instruction: 'Turn right.', + verbal_succinct_transition_instruction: 'Turn right.', + verbal_pre_transition_instruction: 'Turn right.', + verbal_post_transition_instruction: 'Continue for 100 meters.', + bearing_before: 21, + bearing_after: 91, + time: 22.155, + length: 0.105, + cost: 41.949, + begin_shape_index: 8, + end_shape_index: 10, + travel_mode: 'bicycle', + travel_type: 'hybrid', + }, + { + type: 15, + instruction: 'Turn left.', + verbal_transition_alert_instruction: 'Turn left.', + verbal_succinct_transition_instruction: 'Turn left.', + verbal_pre_transition_instruction: 'Turn left.', + verbal_post_transition_instruction: 'Continue for 100 meters.', + bearing_before: 91, + bearing_after: 36, + time: 53.74, + length: 0.106, + cost: 539.053, + begin_shape_index: 10, + end_shape_index: 18, + travel_mode: 'bicycle', + travel_type: 'hybrid', + }, + { + type: 10, + instruction: 'Turn right onto Lindenberger Weg.', + verbal_transition_alert_instruction: + 'Turn right onto Lindenberger Weg.', + verbal_succinct_transition_instruction: 'Turn right.', + verbal_pre_transition_instruction: + 'Turn right onto Lindenberger Weg.', + verbal_post_transition_instruction: 'Continue for 300 meters.', + street_names: ['Lindenberger Weg'], + bearing_before: 55, + bearing_after: 163, + time: 57.082, + length: 0.293, + cost: 118.469, + begin_shape_index: 18, + end_shape_index: 43, + travel_mode: 'bicycle', + travel_type: 'hybrid', + }, + { + type: 15, + instruction: 'Turn left to stay on Lindenberger Weg.', + verbal_transition_alert_instruction: + 'Turn left to stay on Lindenberger Weg.', + verbal_succinct_transition_instruction: + 'Turn left. Then Turn right to stay on Lindenberger Weg.', + verbal_pre_transition_instruction: + 'Turn left to stay on Lindenberger Weg. Then Turn right to stay on Lindenberger Weg.', + verbal_post_transition_instruction: 'Continue for 10 meters.', + street_names: ['Lindenberger Weg'], + bearing_before: 149, + bearing_after: 83, + time: 5.16, + length: 0.012, + cost: 18.905, + begin_shape_index: 43, + end_shape_index: 47, + verbal_multi_cue: true, + travel_mode: 'bicycle', + travel_type: 'hybrid', + }, + { + type: 10, + instruction: 'Turn right to stay on Lindenberger Weg.', + verbal_transition_alert_instruction: + 'Turn right to stay on Lindenberger Weg.', + verbal_succinct_transition_instruction: 'Turn right.', + verbal_pre_transition_instruction: + 'Turn right to stay on Lindenberger Weg.', + verbal_post_transition_instruction: 'Continue for 3 kilometers.', + street_names: ['Lindenberger Weg'], + bearing_before: 83, + bearing_after: 140, + time: 565.71, + length: 2.939, + cost: 1049.577, + begin_shape_index: 47, + end_shape_index: 138, + travel_mode: 'bicycle', + travel_type: 'hybrid', + }, + { + type: 10, + instruction: 'Turn right onto Karl-Marx-Straße.', + verbal_transition_alert_instruction: + 'Turn right onto Karl-Marx-Straße.', + verbal_succinct_transition_instruction: 'Turn right.', + verbal_pre_transition_instruction: + 'Turn right onto Karl-Marx-Straße.', + verbal_post_transition_instruction: 'Continue for 200 meters.', + street_names: ['Karl-Marx-Straße'], + bearing_before: 170, + bearing_after: 244, + time: 39.211, + length: 0.184, + cost: 112.711, + begin_shape_index: 138, + end_shape_index: 152, + travel_mode: 'bicycle', + travel_type: 'hybrid', + }, + { + type: 15, + instruction: 'Turn left onto Alte Schulstraße.', + verbal_transition_alert_instruction: + 'Turn left onto Alte Schulstraße.', + verbal_succinct_transition_instruction: 'Turn left.', + verbal_pre_transition_instruction: + 'Turn left onto Alte Schulstraße.', + verbal_post_transition_instruction: 'Continue for 400 meters.', + street_names: ['Alte Schulstraße'], + bearing_before: 248, + bearing_after: 171, + time: 73.852, + length: 0.37, + cost: 144.458, + begin_shape_index: 152, + end_shape_index: 168, + travel_mode: 'bicycle', + travel_type: 'hybrid', + }, + { + type: 10, + instruction: 'Turn right to stay on Alte Schulstraße.', + verbal_transition_alert_instruction: + 'Turn right to stay on Alte Schulstraße.', + verbal_succinct_transition_instruction: + 'Turn right. Then Turn left onto Wartenberger Straße.', + verbal_pre_transition_instruction: + 'Turn right to stay on Alte Schulstraße. Then Turn left onto Wartenberger Straße.', + verbal_post_transition_instruction: 'Continue for 40 meters.', + street_names: ['Alte Schulstraße'], + bearing_before: 172, + bearing_after: 253, + time: 8.91, + length: 0.041, + cost: 16.743, + begin_shape_index: 168, + end_shape_index: 171, + verbal_multi_cue: true, + travel_mode: 'bicycle', + travel_type: 'hybrid', + }, + { + type: 15, + instruction: 'Turn left onto Wartenberger Straße.', + verbal_transition_alert_instruction: + 'Turn left onto Wartenberger Straße.', + verbal_succinct_transition_instruction: 'Turn left.', + verbal_pre_transition_instruction: + 'Turn left onto Wartenberger Straße.', + verbal_post_transition_instruction: 'Continue for 700 meters.', + street_names: ['Wartenberger Straße'], + bearing_before: 254, + bearing_after: 165, + time: 141.798, + length: 0.723, + cost: 253.854, + begin_shape_index: 171, + end_shape_index: 198, + travel_mode: 'bicycle', + travel_type: 'hybrid', + }, + { + type: 9, + instruction: 'Bear right onto Lindenberger Straße.', + verbal_transition_alert_instruction: + 'Bear right onto Lindenberger Straße.', + verbal_succinct_transition_instruction: 'Bear right.', + verbal_pre_transition_instruction: + 'Bear right onto Lindenberger Straße.', + verbal_post_transition_instruction: 'Continue for 2 kilometers.', + street_names: ['Lindenberger Straße'], + bearing_before: 157, + bearing_after: 180, + time: 391.396, + length: 2.111, + cost: 695.407, + begin_shape_index: 198, + end_shape_index: 242, + travel_mode: 'bicycle', + travel_type: 'hybrid', + }, + { + type: 15, + instruction: 'Turn left onto Dorfstraße.', + verbal_transition_alert_instruction: 'Turn left onto Dorfstraße.', + verbal_succinct_transition_instruction: 'Turn left.', + verbal_pre_transition_instruction: 'Turn left onto Dorfstraße.', + verbal_post_transition_instruction: 'Continue for 800 meters.', + street_names: ['Dorfstraße'], + bearing_before: 190, + bearing_after: 106, + time: 158.929, + length: 0.763, + cost: 392.027, + begin_shape_index: 242, + end_shape_index: 290, + travel_mode: 'bicycle', + travel_type: 'hybrid', + }, + { + type: 10, + instruction: 'Turn right onto the cycleway.', + verbal_transition_alert_instruction: + 'Turn right onto the cycleway.', + verbal_succinct_transition_instruction: 'Turn right.', + verbal_pre_transition_instruction: 'Turn right onto the cycleway.', + verbal_post_transition_instruction: 'Continue for 500 meters.', + bearing_before: 169, + bearing_after: 253, + time: 117.172, + length: 0.466, + cost: 222.945, + begin_shape_index: 290, + end_shape_index: 321, + travel_mode: 'bicycle', + travel_type: 'hybrid', + }, + { + type: 9, + instruction: 'Bear right onto Falkenberger Chaussee.', + verbal_transition_alert_instruction: + 'Bear right onto Falkenberger Chaussee.', + verbal_succinct_transition_instruction: 'Bear right.', + verbal_pre_transition_instruction: + 'Bear right onto Falkenberger Chaussee.', + verbal_post_transition_instruction: 'Continue for 800 meters.', + street_names: ['Falkenberger Chaussee'], + bearing_before: 198, + bearing_after: 238, + time: 140.678, + length: 0.772, + cost: 290.044, + begin_shape_index: 321, + end_shape_index: 356, + travel_mode: 'bicycle', + travel_type: 'hybrid', + }, + { + type: 15, + instruction: 'Turn left onto Rüdickenstraße.', + verbal_transition_alert_instruction: + 'Turn left onto Rüdickenstraße.', + verbal_succinct_transition_instruction: 'Turn left.', + verbal_pre_transition_instruction: 'Turn left onto Rüdickenstraße.', + verbal_post_transition_instruction: 'Continue for 200 meters.', + street_names: ['Rüdickenstraße'], + bearing_before: 239, + bearing_after: 144, + time: 48.024, + length: 0.191, + cost: 116.324, + begin_shape_index: 356, + end_shape_index: 375, + travel_mode: 'bicycle', + travel_type: 'hybrid', + }, + { + type: 10, + instruction: 'Turn right to stay on Rüdickenstraße.', + verbal_transition_alert_instruction: + 'Turn right to stay on Rüdickenstraße.', + verbal_succinct_transition_instruction: 'Turn right.', + verbal_pre_transition_instruction: + 'Turn right to stay on Rüdickenstraße.', + verbal_post_transition_instruction: 'Continue for 100 meters.', + street_names: ['Rüdickenstraße'], + bearing_before: 140, + bearing_after: 235, + time: 21.57, + length: 0.098, + cost: 40.015, + begin_shape_index: 375, + end_shape_index: 379, + travel_mode: 'bicycle', + travel_type: 'hybrid', + }, + { + type: 15, + instruction: 'Turn left to stay on Rüdickenstraße.', + verbal_transition_alert_instruction: + 'Turn left to stay on Rüdickenstraße.', + verbal_succinct_transition_instruction: + 'Turn left. Then Turn left onto Privatstraße 12.', + verbal_pre_transition_instruction: + 'Turn left to stay on Rüdickenstraße. Then Turn left onto Privatstraße 12.', + verbal_post_transition_instruction: 'Continue for 60 meters.', + street_names: ['Rüdickenstraße'], + bearing_before: 250, + bearing_after: 150, + time: 18.64, + length: 0.058, + cost: 72.753, + begin_shape_index: 379, + end_shape_index: 389, + verbal_multi_cue: true, + travel_mode: 'bicycle', + travel_type: 'hybrid', + }, + { + type: 15, + instruction: 'Turn left onto Privatstraße 12.', + verbal_transition_alert_instruction: + 'Turn left onto Privatstraße 12.', + verbal_succinct_transition_instruction: 'Turn left.', + verbal_pre_transition_instruction: + 'Turn left onto Privatstraße 12.', + verbal_post_transition_instruction: 'Continue for 300 meters.', + street_names: ['Privatstraße 12'], + bearing_before: 200, + bearing_after: 114, + time: 49.169, + length: 0.254, + cost: 109.937, + begin_shape_index: 389, + end_shape_index: 391, + travel_mode: 'bicycle', + travel_type: 'hybrid', + }, + { + type: 10, + instruction: 'Turn right onto Straße 142.', + verbal_transition_alert_instruction: 'Turn right onto Straße 142.', + verbal_succinct_transition_instruction: 'Turn right.', + verbal_pre_transition_instruction: 'Turn right onto Straße 142.', + verbal_post_transition_instruction: 'Continue for 1 kilometer.', + street_names: ['Straße 142'], + bearing_before: 114, + bearing_after: 202, + time: 238.501, + length: 1.203, + cost: 419.536, + begin_shape_index: 391, + end_shape_index: 430, + travel_mode: 'bicycle', + travel_type: 'hybrid', + }, + { + type: 15, + instruction: 'Turn left onto Niehofer Straße.', + verbal_transition_alert_instruction: + 'Turn left onto Niehofer Straße.', + verbal_succinct_transition_instruction: + 'Turn left. Then, in 40 meters, Turn right onto Seefelder Straße.', + verbal_pre_transition_instruction: + 'Turn left onto Niehofer Straße. Then, in 40 meters, Turn right onto Seefelder Straße.', + verbal_post_transition_instruction: 'Continue for 40 meters.', + street_names: ['Niehofer Straße'], + bearing_before: 191, + bearing_after: 106, + time: 9.389, + length: 0.038, + cost: 31.002, + begin_shape_index: 430, + end_shape_index: 433, + verbal_multi_cue: true, + travel_mode: 'bicycle', + travel_type: 'hybrid', + }, + { + type: 10, + instruction: 'Turn right onto Seefelder Straße.', + verbal_transition_alert_instruction: + 'Turn right onto Seefelder Straße.', + verbal_succinct_transition_instruction: 'Turn right.', + verbal_pre_transition_instruction: + 'Turn right onto Seefelder Straße.', + verbal_post_transition_instruction: 'Continue for 100 meters.', + street_names: ['Seefelder Straße'], + bearing_before: 108, + bearing_after: 198, + time: 36.064, + length: 0.128, + cost: 81.905, + begin_shape_index: 433, + end_shape_index: 446, + travel_mode: 'bicycle', + travel_type: 'hybrid', + }, + { + type: 16, + instruction: 'Bear left onto the cycleway.', + verbal_transition_alert_instruction: 'Bear left onto the cycleway.', + verbal_succinct_transition_instruction: + 'Bear left. Then Turn right onto Konrad-Wolf-Straße.', + verbal_pre_transition_instruction: + 'Bear left onto the cycleway. Then Turn right onto Konrad-Wolf-Straße.', + verbal_post_transition_instruction: 'Continue for 30 meters.', + bearing_before: 197, + bearing_after: 184, + time: 8.25, + length: 0.025, + cost: 20.244, + begin_shape_index: 446, + end_shape_index: 452, + verbal_multi_cue: true, + travel_mode: 'bicycle', + travel_type: 'hybrid', + }, + { + type: 10, + instruction: 'Turn right onto Konrad-Wolf-Straße.', + verbal_transition_alert_instruction: + 'Turn right onto Konrad-Wolf-Straße.', + verbal_succinct_transition_instruction: 'Turn right.', + verbal_pre_transition_instruction: + 'Turn right onto Konrad-Wolf-Straße.', + verbal_post_transition_instruction: 'Continue for 400 meters.', + street_names: ['Konrad-Wolf-Straße'], + bearing_before: 141, + bearing_after: 229, + time: 75.026, + length: 0.41, + cost: 170.491, + begin_shape_index: 452, + end_shape_index: 476, + travel_mode: 'bicycle', + travel_type: 'hybrid', + }, + { + type: 15, + instruction: 'Turn left onto Bahnhofstraße.', + verbal_transition_alert_instruction: + 'Turn left onto Bahnhofstraße.', + verbal_succinct_transition_instruction: 'Turn left.', + verbal_pre_transition_instruction: 'Turn left onto Bahnhofstraße.', + verbal_post_transition_instruction: 'Continue for 600 meters.', + street_names: ['Bahnhofstraße'], + bearing_before: 229, + bearing_after: 140, + time: 163.234, + length: 0.604, + cost: 1345.086, + begin_shape_index: 476, + end_shape_index: 516, + travel_mode: 'bicycle', + travel_type: 'hybrid', + }, + { + type: 4, + instruction: 'You have arrived at your destination.', + verbal_transition_alert_instruction: + 'You will arrive at your destination.', + verbal_pre_transition_instruction: + 'You have arrived at your destination.', + bearing_before: 99, + time: 0.0, + length: 0.0, + cost: 0.0, + begin_shape_index: 516, + end_shape_index: 516, + travel_mode: 'bicycle', + travel_type: 'hybrid', + }, + ], + summary: { + has_time_restrictions: false, + has_toll: false, + has_highway: false, + has_ferry: false, + min_lat: 52.543372, + min_lon: 13.496889, + max_lat: 52.628583, + max_lon: 13.527286, + time: 2468.182, + length: 12.027, + cost: 6352.35, + }, + shape: + 'g|akcBcvdwXY[aUeXuA{G}CcGwBiBwBiBgOuAwDsBLwv@Roh@uGgIwCsDaJaLkCeDqDqEa@c@sGeKcAmF|LcFxCoAnVoKvJgEjJaEf[yMpB{@|LgFhCqAjEuBdBu@rCuAfCeBrA{Bp@yAtAwDd@`@p@Tn@Pp@Ap@Wd@a@^o@Vq@Nw@JaA@eACcAc@aCXs@\\g@pR_X|OuThd@yn@vi@}u@pX_`@|HkKRYdE_GvAmBrAiBxCcEzQ}VrUq\\nQeWd^sf@zAsBvFwGj_@sd@ll@_v@`v@e`AnZa^xNsR`Wo`@lKeS~AwChHcPdEkJvCgGnGgK~FsHlIuG~@a@fH{ClG}B`h@yPdXyInWgGzUoD`TaDvFq@rZoD~eA{L~_BcVrjBiYpBYbc@yFrZcGhWkDzd@kFjPiBzCg@jDkA|GcDdL_FfKyGrVqP`JoG|FaErAaAvOaK|@q@bFkDlCgBjQuLdHqEfLyFjD{ApCgApD}AfVmJhBs@nDcArAUdCa@fDlA`BRV@nABdKZbQ\\dIMtAE|@GhPqEXGrPgEhPkDZGvFgA`AvE`AzE`AtEn@zCvCdNrCrNzB~KrAlH`@|BdKnl@Jh@t@zE`BfKl@vDjDm@VE`]uFnEu@hIuA`E}@xWeIlZ}DfJu@dBM`SqAzLwAzCWzSiAhOw@~Ai@lD~[N|AZzCbQ{FnJyDbAc@zH}CrFyB`CaA~CeAbIuCdGyBjTcIvAi@nJaEbNgFjDoB`GgH|PaWdLyPxOaVbXa`@bdAc|AbGaIdIyJfGkHbKoLpD}DzDyDhKyG`OCbA?vGAbJMhPq@zKStDf@|FzAhr@n]tdAhj@hAl@z@b@dmHvsDdy@db@rDpB~~@pe@hu@d]nB|@lZdNxbChfApPlHfDhAdKzDzHvC~B`A|I|D`n@hW|HdDvI`ApUjCjFl@xOrBrB^ZFb@NhDlAjXzLvFnCnHfDvXhMnAj@bGnCRH|FjApDy_@Hy@vB}P`AqEz@uDjCkIz@aC~@kCjAuC|Vqh@n@qAr@iA~@cAvA_AlCe@fIB^ArITvHG|Is@x_@cGzB]`LgBhL_B|Dw@tZyFtHsAnCOdGEnLPrLzApPnDnAVpLdC`B\\vJfBtHtA|Gn@|EPfEUjHm@~Cq@zB[~QcE|D}@VGf@IlAYb@|Df@fETpEShL?vARjM|@xJnCxUpHte@lCrHXjB~D|T`BnIZ~AB~AJnCv@rDh@hCt@`D|A~Br@lB`Orj@nDhQ~Wl~@X|@|B`IlAfEbCdGvAlDhDvLzCrApGdUbDbLbApD~FtSnFhRbHpVp@`Cn@|B~BnIvCbKzCnKzJp]bCdIbB|FrDfMtSft@zEbTvXheA`@zAfCfI|DbMtR`q@jJ`\\tIh[jIb[pJh^n@`CvGjWH\\xBjI^zAhAjE@Dl@xBT|@\\c@r@{@f@m@t@_ApCmDtDyEn@u@\\c@`@e@rCeDvAcBf@k@lC{C`AkAPSpXu[|DyEdMgOdF_ItAfExZr~@TdAN`BpAmA~@_AvCuEbAaA`A]~ABlCpAt@f@rCfB|@d@l@iDxw@upEzD~BhN`Ird@jXbe@`YvT`NvPzJbh@tZrc@pW~c@`Xfe@pXp@^frAtv@leA`o@|@l@^\\lBfBvAjCPTpGjJlBvCtFnIx@lA`HhKjG`Id@j@pFzGnEpFxIdJjHvHb@`@h@d@xQrPbTdOjEjCzBrA~@j@rDzBlO`HxEz@VaDNiAdDaYfBt@bNxGpCvAfJfFbHhDvJvETLRJ|BjA|@lBVh@TDvAZnBTfAWrAy@d@g@fA_BT]f@jAXn@Xn@`MzYvD`JhNn\\|BrFbB~DvBbFnErKr@`B`CzFdC|Fh@pAn[bu@`@fA|H`R|ArDrGpOlBpEvIpSjCjG~EvLnFfMR]fBmCRWpHaK`GqGbG_G`B{AxOuNfBaB~HiHxHmHd]s\\x@cAzAiBBC^c@tCaExB{CVe@@CfD_GfCqEl@cApCmGf@sAx@wB|BmGvCkInFkQNm@bF_TZ{ArB_KjAmH~BgQdBwQj@oJVeFJkBpJkvB', + }, + ], + summary: { + has_time_restrictions: false, + has_toll: false, + has_highway: false, + has_ferry: false, + min_lat: 52.543372, + min_lon: 13.496889, + max_lat: 52.628583, + max_lon: 13.527286, + time: 2468.182, + length: 12.027, + cost: 6352.35, + }, + status_message: 'Found route between points', + status: 0, + units: 'kilometers', + language: 'en-US', + }, + id: 'valhalla_directions', +} as const; diff --git a/src/controls/index.tsx b/src/controls/index.tsx index 0f45562..cc21065 100644 --- a/src/controls/index.tsx +++ b/src/controls/index.tsx @@ -286,20 +286,20 @@ const MainControl = (props: MainControlProps) => { )}
-
- Last Data Update:{' '} - {lastUpdate - ? `${lastUpdate.toISOString().slice(0, 10)}, ${lastUpdate - .toISOString() - .slice(11, 16)}` - : '0000-00-00, 00:00'} -
+ {lastUpdate && ( +
+ Last Data Update:{' '} + {`${lastUpdate.toISOString().slice(0, 10)}, ${lastUpdate + .toISOString() + .slice(11, 16)}`} +
+ )} ); diff --git a/src/map/index.tsx b/src/map/index.tsx index c0e690a..977d877 100644 --- a/src/map/index.tsx +++ b/src/map/index.tsx @@ -744,6 +744,7 @@ const MapComponent = ({ position: 'relative', cursor: 'pointer', }} + aria-label={`Map marker ${number}`} > Date: Wed, 8 Oct 2025 16:11:01 +0300 Subject: [PATCH 8/8] ignore vitest from filename-naming-convention --- eslint.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/eslint.config.js b/eslint.config.js index f1f8cb1..d628210 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -114,6 +114,7 @@ export default defineConfig( '**/*.d.ts', 'src/vite-env.d.ts', 'vite.config.ts', + 'vitest.config.ts', ], rules: { 'check-file/filename-naming-convention': 'off',