From 5569326e0ae4b6be4010fb4259aa446847606fd9 Mon Sep 17 00:00:00 2001 From: Guillaume Fay Date: Tue, 24 Sep 2024 13:41:48 +0200 Subject: [PATCH] feat: page deploiement bal (#1824) * feat: page etat du deploiement * feat: add carto * fix: map ok * fix linter * fix type * fix: map hover and selected * fix: import in page * fix: build * fix ci * fix cache and vector tiles * fix: remove unused files * fix query stats to mes-adresses api * feat: add fullscreen control * fix: scroll in stats * fix: missing header content type --- .github/workflows/ci.yml | 1 + .gitignore | 1 + next.config.mjs | 3 + package-lock.json | 658 +++- package.json | 9 +- public/map-styles/osm-bright.json | 3464 +++++++++++++++++ .../[z]/[x]/[y].pbf/route.ts | 31 + src/app/api/deploiement-stats/route.ts | 12 + src/app/commune/[codeCommune]/page.tsx | 8 +- src/app/commune/page.tsx | 13 +- src/app/deploiement-bal/page.tsx | 55 + src/app/global.styles.ts | 1 + .../AutocompleteInput.tsx} | 34 +- .../index.tsx} | 14 +- src/components/ChartJS/DoughnutCounter.tsx | 50 + .../Commune}/CommuneDownloadSection.tsx | 0 .../Commune}/CommuneNavigation.tsx | 11 +- .../DeploiementBAL/BaseLocaleCard.tsx | 62 + .../DeploiementBAL/CommuneBALList.tsx | 63 + .../DeploiementBALDashboard.styles.ts | 51 + .../DeploiementBALDashboard.tsx | 125 + .../DeploiementBAL/DeploiementMap.tsx | 296 ++ src/components/DeploiementBAL/StatusBadge.tsx | 60 + .../DeploiementBAL/TabDeploiementBAL.tsx | 137 + .../DeploiementBAL/TabMesAdresses.tsx | 130 + .../index.tsx} | 0 .../{IconCard.tsx => IconCard/index.tsx} | 0 .../{ImageInput.tsx => ImageInput/index.tsx} | 0 src/components/Map/FullScreenControl.tsx | 16 + src/components/Map/map.config.ts | 2 + .../index.tsx} | 0 .../CandidacyForm/index.tsx | 23 +- .../SearchPartenaire/index.tsx | 17 +- .../index.tsx} | 0 .../index.tsx} | 0 .../{TagSelect.tsx => TagSelect/index.tsx} | 0 src/data/departement-center.json | 1310 +++++++ src/hooks/useStatsDeploiement.ts | 196 + src/instrumentation.ts | 6 + src/lib/api-ban.ts | 15 +- src/lib/api-geo.ts | 40 +- src/lib/api-mes-adresses.ts | 25 + src/lib/deploiement-stats.ts | 139 + src/theme/theme.ts | 7 + src/types/api-ban.types.ts | 44 + src/types/api-geo.types.ts | 12 +- src/types/api-mes-adresses.types.ts | 32 + src/utils/cache.ts | 78 + src/utils/cog.ts | 24 + src/utils/contours-communes.ts | 39 + src/utils/number.ts | 8 + 51 files changed, 7269 insertions(+), 53 deletions(-) create mode 100644 public/map-styles/osm-bright.json create mode 100644 src/app/api/deploiement-stats/[z]/[x]/[y].pbf/route.ts create mode 100644 src/app/api/deploiement-stats/route.ts create mode 100644 src/app/deploiement-bal/page.tsx rename src/components/{CommuneInput.tsx => Autocomplete/AutocompleteInput.tsx} (64%) rename src/components/{Autocomplete.tsx => Autocomplete/index.tsx} (95%) create mode 100644 src/components/ChartJS/DoughnutCounter.tsx rename src/{app/commune/[codeCommune] => components/Commune}/CommuneDownloadSection.tsx (100%) rename src/{app/commune/[codeCommune] => components/Commune}/CommuneNavigation.tsx (82%) create mode 100644 src/components/DeploiementBAL/BaseLocaleCard.tsx create mode 100644 src/components/DeploiementBAL/CommuneBALList.tsx create mode 100644 src/components/DeploiementBAL/DeploiementBALDashboard.styles.ts create mode 100644 src/components/DeploiementBAL/DeploiementBALDashboard.tsx create mode 100644 src/components/DeploiementBAL/DeploiementMap.tsx create mode 100644 src/components/DeploiementBAL/StatusBadge.tsx create mode 100644 src/components/DeploiementBAL/TabDeploiementBAL.tsx create mode 100644 src/components/DeploiementBAL/TabMesAdresses.tsx rename src/components/{DownloadCard.tsx => DownloadCard/index.tsx} (100%) rename src/components/{IconCard.tsx => IconCard/index.tsx} (100%) rename src/components/{ImageInput.tsx => ImageInput/index.tsx} (100%) create mode 100644 src/components/Map/FullScreenControl.tsx create mode 100644 src/components/Map/map.config.ts rename src/components/{MultiSelectInput.tsx => MultiSelectInput/index.tsx} (100%) rename src/components/{ResponsiveImage.tsx => ResponsiveImage/index.tsx} (100%) rename src/components/{SelectInput.tsx => SelectInput/index.tsx} (100%) rename src/components/{TagSelect.tsx => TagSelect/index.tsx} (100%) create mode 100644 src/data/departement-center.json create mode 100644 src/hooks/useStatsDeploiement.ts create mode 100644 src/instrumentation.ts create mode 100644 src/lib/api-mes-adresses.ts create mode 100644 src/lib/deploiement-stats.ts create mode 100644 src/types/api-mes-adresses.types.ts create mode 100644 src/utils/cache.ts create mode 100644 src/utils/cog.ts create mode 100644 src/utils/contours-communes.ts create mode 100644 src/utils/number.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0d5df64b2..e1df20cf4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,6 +14,7 @@ env: NEXT_PUBLIC_DATAGOUV_URL: ${{ vars.NEXT_PUBLIC_DATAGOUV_URL }} NEXT_PUBLIC_API_DEPOT_URL: ${{ vars.NEXT_PUBLIC_API_DEPOT_URL }} NEXT_PUBLIC_API_ETABLISSEMENTS_PUBLIC: ${{ vars.NEXT_PUBLIC_API_ETABLISSEMENTS_PUBLIC }} + NEXT_PUBLIC_BAL_API_URL: ${{ vars.NEXT_PUBLIC_BAL_API_URL }} jobs: build: diff --git a/.gitignore b/.gitignore index 506fbf6fb..5083f38ea 100644 --- a/.gitignore +++ b/.gitignore @@ -40,5 +40,6 @@ next-env.d.ts /.storybook/static/dsfr/ public/dsfr +public/communes-index.json .vscode/ diff --git a/next.config.mjs b/next.config.mjs index f4b49a5b4..5951001a2 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -20,6 +20,8 @@ const nextConfig = { test: /\.woff2$/, type: 'asset/resource', }) + config.resolve.fallback = { fs: false } + return config }, compiler: { @@ -29,6 +31,7 @@ const nextConfig = { serverActions: { bodySizeLimit: '3mb', }, + instrumentationHook: true, }, } diff --git a/package-lock.json b/package-lock.json index 34fb30a7f..d96a7875b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,16 +9,23 @@ "version": "2.0.0-alpha", "dependencies": { "@codegouvfr/react-dsfr": "^1.10.11", + "@etalab/decoupage-administratif": "^4.0.0", "@socialgouv/matomo-next": "^1.9.0", "@types/remark-heading-id": "^1.0.0", + "chart.js": "^4.4.4", + "geojson-vt": "^4.0.2", "gray-matter": "^4.0.3", + "maplibre-gl": "^4.7.0", "next": "14.2.5", "react": "^18", + "react-chartjs-2": "^5.2.0", "react-dom": "^18", + "react-map-gl": "^7.1.7", "remark": "^15.0.1", "remark-heading-id": "^1.0.1", "remark-html": "^16.0.1", - "styled-components": "^6.1.12" + "styled-components": "^6.1.12", + "vt-pbf": "^3.1.3" }, "devDependencies": { "@chromatic-com/storybook": "^1.6.1", @@ -2792,6 +2799,14 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@etalab/decoupage-administratif": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@etalab/decoupage-administratif/-/decoupage-administratif-4.0.0.tgz", + "integrity": "sha512-TMGZT4rFaQdkovoAZF5iMNJnxc2To8GXiC4x/N8Z5j6I0j29AeInE2Js86Wy9W2ZnjGFbKkSpmnsbLYwWYAFsw==", + "engines": { + "node": ">= 10" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -3391,6 +3406,94 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@kurkle/color": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", + "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" + }, + "node_modules/@mapbox/geojson-rewind": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.2.tgz", + "integrity": "sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA==", + "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://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz", + "integrity": "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@mapbox/mapbox-gl-supported": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-3.0.0.tgz", + "integrity": "sha512-2XghOwu16ZwPJLOFVuIOaLbN0iKMn867evzXFyf0P22dqugezfJwLmdanAgU25ITvz1TvOfVP4jsDImlDJzcWg==", + "optional": true, + "peer": true + }, + "node_modules/@mapbox/point-geometry": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz", + "integrity": "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==" + }, + "node_modules/@mapbox/tiny-sdf": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.6.tgz", + "integrity": "sha512-qMqa27TLw+ZQz5Jk+RcwZGH7BQf5G/TrutJhspsca/3SHwmgKQ1iq+d3Jxz5oysPVYTGP6aXxCo5Lk9Er6YBAA==" + }, + "node_modules/@mapbox/unitbezier": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz", + "integrity": "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==" + }, + "node_modules/@mapbox/vector-tile": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-1.3.1.tgz", + "integrity": "sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw==", + "dependencies": { + "@mapbox/point-geometry": "~0.1.0" + } + }, + "node_modules/@mapbox/whoots-js": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz", + "integrity": "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@maplibre/maplibre-gl-style-spec": { + "version": "20.3.1", + "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-20.3.1.tgz", + "integrity": "sha512-5ueL4UDitzVtceQ8J4kY+Px3WK+eZTsmGwha3MBKHKqiHvKrjWWwBCIl1K8BuJSc5OFh83uI8IFNoFvQxX2uUw==", + "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": "^2.0.0", + "rw": "^1.3.3", + "sort-object": "^3.0.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": "2.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", + "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==" + }, "node_modules/@mdx-js/react": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.0.1.tgz", @@ -5306,6 +5409,19 @@ "@types/send": "*" } }, + "node_modules/@types/geojson": { + "version": "7946.0.14", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz", + "integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==" + }, + "node_modules/@types/geojson-vt": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@types/geojson-vt/-/geojson-vt-3.2.5.tgz", + "integrity": "sha512-qDO7wqtprzlpe8FfQ//ClPV9xiuoh2nkIgiouIptON9w5jvD/fA4szvP9GBlDVdJ5dldAl0kX/sy3URbWwLx0g==", + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/hast": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", @@ -5344,6 +5460,29 @@ "integrity": "sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==", "dev": true }, + "node_modules/@types/mapbox__point-geometry": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.4.tgz", + "integrity": "sha512-mUWlSxAmYLfwnRBmgYV86tgYmMIICX4kza8YnE/eIlywGe2XoOxlpVnXWwir92xRLjwyarqwpu2EJKD2pk0IUA==" + }, + "node_modules/@types/mapbox__vector-tile": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@types/mapbox__vector-tile/-/mapbox__vector-tile-1.3.4.tgz", + "integrity": "sha512-bpd8dRn9pr6xKvuEBQup8pwQfD4VUyqO/2deGjfpe6AwC8YRlyEipvefyRJUSiCJTZuCb8Pl1ciVV5ekqJ96Bg==", + "dependencies": { + "@types/geojson": "*", + "@types/mapbox__point-geometry": "*", + "@types/pbf": "*" + } + }, + "node_modules/@types/mapbox-gl": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@types/mapbox-gl/-/mapbox-gl-3.4.0.tgz", + "integrity": "sha512-tbn++Mm94H1kE7W6FF0oVC9rMXHVzDDNUbS7KfBMRF8NV/8csFi+67ytKcZJ4LsrpsJ+8MC6Os6ZinEDCsrunw==", + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/mdx": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", @@ -5376,6 +5515,11 @@ "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", "dev": true }, + "node_modules/@types/pbf": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/pbf/-/pbf-3.0.5.tgz", + "integrity": "sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==" + }, "node_modules/@types/prop-types": { "version": "15.7.12", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", @@ -5477,6 +5621,14 @@ "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz", "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==" }, + "node_modules/@types/supercluster": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/supercluster/-/supercluster-7.1.3.tgz", + "integrity": "sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA==", + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/unist": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", @@ -6254,6 +6406,14 @@ "deep-equal": "^2.0.5" } }, + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/array-buffer-byte-length": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", @@ -6470,6 +6630,14 @@ "node": "*" } }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ast-types": { "version": "0.16.1", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", @@ -7111,6 +7279,23 @@ "node": ">= 0.8" } }, + "node_modules/bytewise": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/bytewise/-/bytewise-1.1.0.tgz", + "integrity": "sha512-rHuuseJ9iQ0na6UDhnrRVDh8YnWVlU6xM3VH6q/+yHDeUH2zIhUzP+2/h3LIrhLDBtTqzWpE3p3tP/boefskKQ==", + "dependencies": { + "bytewise-core": "^1.2.2", + "typewise": "^1.0.3" + } + }, + "node_modules/bytewise-core": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bytewise-core/-/bytewise-core-1.2.3.tgz", + "integrity": "sha512-nZD//kc78OOxeYtRlVk8/zXqTB4gf/nlguL1ggWA8FuchMyOxcyHR4QPQZMUmA7czC+YnaBrPUCubqAWe50DaA==", + "dependencies": { + "typewise-core": "^1.2" + } + }, "node_modules/call-bind": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", @@ -7255,6 +7440,24 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/chart.js": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.4.tgz", + "integrity": "sha512-emICKGBABnxhMjUjlYRR12PmOXhJ2eJjEHL2/dZlWjxRAZT1D8xplLFq5M0tMQK8ja+wBS/tuVEJB5C6r7VxJA==", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, + "node_modules/cheap-ruler": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cheap-ruler/-/cheap-ruler-4.0.0.tgz", + "integrity": "sha512-0BJa8f4t141BYKQyn9NSQt1PguFQXMXwZiA5shfoaBYHAb2fFk2RAX+tiWMoQU+Agtzt3mdt0JtuyshAXqZ+Vw==", + "optional": true, + "peer": true + }, "node_modules/check-error": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", @@ -7875,6 +8078,13 @@ "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", "dev": true }, + "node_modules/csscolorparser": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/csscolorparser/-/csscolorparser-1.0.3.tgz", + "integrity": "sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w==", + "optional": true, + "peer": true + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -8304,6 +8514,11 @@ "tslib": "^2.0.3" } }, + "node_modules/earcut": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.0.tgz", + "integrity": "sha512-41Fs7Q/PLq1SDbqjsgcY7GA42T0jvaCNGXgGtsNdvg+Yv8eIu06bxv4/PoREkZ9nMDNwnUSG9OFB9+yv8eKhDg==" + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -9407,6 +9622,13 @@ "walk-up-path": "^3.0.1" } }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "optional": true, + "peer": true + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -9756,6 +9978,11 @@ "node": ">=6.9.0" } }, + "node_modules/geojson-vt": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-4.0.2.tgz", + "integrity": "sha512-AV9ROqlNqoZEIJGfm1ncNjEXfkz2hdFlZf0qkVfmkwdKa8vj7H16YUOT81rJw1rdFhyEDlN2Tds91p/glzbl5A==" + }, "node_modules/get-func-name": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", @@ -9788,7 +10015,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" }, @@ -9825,6 +10051,14 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/giget": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/giget/-/giget-1.2.3.tgz", @@ -9850,6 +10084,11 @@ "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", "dev": true }, + "node_modules/gl-matrix": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz", + "integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==" + }, "node_modules/glob": { "version": "10.3.10", "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", @@ -9914,6 +10153,41 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/global-prefix": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-4.0.0.tgz", + "integrity": "sha512-w0Uf9Y9/nyHinEk5vMJKRie+wa4kR5hmDbEhGGds/kG1PwGLLHKRoNMeJOyCQjjBkANlnScqgzcFwGHgmgLkVA==", + "dependencies": { + "ini": "^4.1.3", + "kind-of": "^6.0.3", + "which": "^4.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/global-prefix/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "engines": { + "node": ">=16" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, "node_modules/globals": { "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", @@ -10022,6 +10296,13 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/grid-index": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/grid-index/-/grid-index-1.1.0.tgz", + "integrity": "sha512-HZRwumpOGUrHyxO5bqKZL0B0GlUpwtCAzZ42sgxUPniu33R1LSFH5yrIcBCHjkctCAh3mtWKcKd9J4vDDdeVHA==", + "optional": true, + "peer": true + }, "node_modules/has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", @@ -10491,7 +10772,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, "funding": [ { "type": "github", @@ -10588,6 +10868,14 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "node_modules/ini": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", + "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/internal-slot": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", @@ -11108,7 +11396,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -11274,6 +11561,11 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "node_modules/json-stringify-pretty-compact": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-4.0.0.tgz", + "integrity": "sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==" + }, "node_modules/json5": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", @@ -11313,6 +11605,11 @@ "node": ">=4.0" } }, + "node_modules/kdbush": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz", + "integrity": "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==" + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -11552,6 +11849,86 @@ "integrity": "sha512-0aF7ZmVon1igznGI4VS30yugpduQW3y3GkcgGJOp7d8x8QrizhigUxjI/m2UojsXXto+jLAH3KSz+xOJTiORjg==", "dev": true }, + "node_modules/mapbox-gl": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-3.6.0.tgz", + "integrity": "sha512-xjYHHIJDh6haYcKY+/9jh1eywwYfIOWCgT5Fowj4JriZexx/oOtg2S7BQDMZtpFyg9IN4VLCysmUWxY0pFNRWA==", + "optional": true, + "peer": true, + "workspaces": [ + "src/style-spec", + "test/build/typings" + ], + "dependencies": { + "@mapbox/jsonlint-lines-primitives": "^2.0.2", + "@mapbox/mapbox-gl-supported": "^3.0.0", + "@mapbox/point-geometry": "^0.1.0", + "@mapbox/tiny-sdf": "^2.0.6", + "@mapbox/unitbezier": "^0.0.1", + "@mapbox/vector-tile": "^1.3.1", + "@mapbox/whoots-js": "^3.1.0", + "@types/geojson": "^7946.0.14", + "@types/mapbox__vector-tile": "^1.3.4", + "cheap-ruler": "^4.0.0", + "csscolorparser": "~1.0.3", + "earcut": "^3.0.0", + "fflate": "^0.8.1", + "geojson-vt": "^4.0.2", + "gl-matrix": "^3.4.3", + "grid-index": "^1.1.0", + "kdbush": "^4.0.2", + "murmurhash-js": "^1.0.0", + "pbf": "^3.2.1", + "potpack": "^2.0.0", + "quickselect": "^3.0.0", + "rw": "^1.3.3", + "serialize-to-js": "^3.1.2", + "supercluster": "^8.0.1", + "tinyqueue": "^3.0.0", + "tweakpane": "^4.0.4", + "vt-pbf": "^3.1.3" + } + }, + "node_modules/maplibre-gl": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-4.7.0.tgz", + "integrity": "sha512-hkt7je7NxiMQE8EpCxLWP8t6tkK6SkrMe0hIBjYd4Ar/Q7BOCILxthGmGnU993Mwmkvs2mGiXnVUSOK12DeCzg==", + "dependencies": { + "@mapbox/geojson-rewind": "^0.5.2", + "@mapbox/jsonlint-lines-primitives": "^2.0.2", + "@mapbox/point-geometry": "^0.1.0", + "@mapbox/tiny-sdf": "^2.0.6", + "@mapbox/unitbezier": "^0.0.1", + "@mapbox/vector-tile": "^1.3.1", + "@mapbox/whoots-js": "^3.1.0", + "@maplibre/maplibre-gl-style-spec": "^20.3.1", + "@types/geojson": "^7946.0.14", + "@types/geojson-vt": "3.2.5", + "@types/mapbox__point-geometry": "^0.1.4", + "@types/mapbox__vector-tile": "^1.3.4", + "@types/pbf": "^3.0.5", + "@types/supercluster": "^7.1.3", + "earcut": "^3.0.0", + "geojson-vt": "^4.0.2", + "gl-matrix": "^3.4.3", + "global-prefix": "^4.0.0", + "kdbush": "^4.0.2", + "murmurhash-js": "^1.0.0", + "pbf": "^3.3.0", + "potpack": "^2.0.0", + "quickselect": "^3.0.0", + "supercluster": "^8.0.1", + "tinyqueue": "^3.0.0", + "vt-pbf": "^3.1.3" + }, + "engines": { + "node": ">=16.14.0", + "npm": ">=8.1.0" + }, + "funding": { + "url": "https://github.com/maplibre/maplibre-gl-js?sponsor=1" + } + }, "node_modules/markdown-to-jsx": { "version": "7.4.7", "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.4.7.tgz", @@ -12294,7 +12671,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -12362,6 +12738,11 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/murmurhash-js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz", + "integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==" + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -13164,6 +13545,18 @@ "node": "*" } }, + "node_modules/pbf": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.3.0.tgz", + "integrity": "sha512-XDF38WCH3z5OV/OVa8GKUNtLAyneuzbCisx7QUCF8Q6Nutx0WnJrQe5O+kOtBlLfRNUws98Y58Lblp+NJG5T4Q==", + "dependencies": { + "ieee754": "^1.1.12", + "resolve-protobuf-schema": "^2.1.0" + }, + "bin": { + "pbf": "bin/pbf" + } + }, "node_modules/pbkdf2": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", @@ -13484,6 +13877,11 @@ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, + "node_modules/potpack": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/potpack/-/potpack-2.0.0.tgz", + "integrity": "sha512-Q+/tYsFU9r7xoOJ+y/ZTtdVQwTWfzjbiXBDMM/JKUux3+QPP02iUuIoeBQ+Ot6oEDlC+/PGjB/5A3K7KKb7hcw==" + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -13598,6 +13996,11 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/protocol-buffers-schema": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", + "integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==" + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -13693,6 +14096,11 @@ } ] }, + "node_modules/quickselect": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz", + "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==" + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -13747,6 +14155,15 @@ "node": ">=0.10.0" } }, + "node_modules/react-chartjs-2": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.2.0.tgz", + "integrity": "sha512-98iN5aguJyVSxp5U3CblRLH67J8gkfyGNbiK3c+l1QI/G4irHMPQw44aEPmjVag+YKTyQ260NcF82GTQ3bdscA==", + "peerDependencies": { + "chart.js": "^4.1.1", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-colorful": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz", @@ -13841,6 +14258,52 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "dev": true }, + "node_modules/react-map-gl": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/react-map-gl/-/react-map-gl-7.1.7.tgz", + "integrity": "sha512-mwjc0obkBJOXCcoXQr3VoLqmqwo9vS4bXfbGsdxXzEgVCv/PM0v+1QggL7W0d/ccIy+VCjbXNlGij+PENz6VNg==", + "dependencies": { + "@maplibre/maplibre-gl-style-spec": "^19.2.1", + "@types/mapbox-gl": ">=1.0.0" + }, + "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-map-gl/node_modules/@maplibre/maplibre-gl-style-spec": { + "version": "19.3.3", + "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-19.3.3.tgz", + "integrity": "sha512-cOZZOVhDSulgK0meTsTkmNXb1ahVvmTmWmfx9gRBwc6hq98wS9JP35ESIoNq3xqEan+UN+gn8187Z6E4NKhLsw==", + "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/react-map-gl/node_modules/json-stringify-pretty-compact": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-3.0.0.tgz", + "integrity": "sha512-Rc2suX5meI0S3bfdZuA7JMFBGkJ875ApfVyq2WHELjBiiG22My/l7/8zPpH/CfFVQHuVLd8NLR0nv6vi0BYYKA==" + }, "node_modules/react-refresh": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", @@ -14351,6 +14814,14 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, + "node_modules/resolve-protobuf-schema": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz", + "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==", + "dependencies": { + "protocol-buffers-schema": "^3.3.1" + } + }, "node_modules/resolve-url-loader": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-5.0.0.tgz", @@ -14507,6 +14978,11 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" + }, "node_modules/safe-array-concat": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", @@ -14762,6 +15238,16 @@ "randombytes": "^2.1.0" } }, + "node_modules/serialize-to-js": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/serialize-to-js/-/serialize-to-js-3.1.2.tgz", + "integrity": "sha512-owllqNuDDEimQat7EPG0tH7JjO090xKNzUtYz6X+Sk2BXDnOCilDdNLwjWeFywG9xkJul1ULvtUQa9O4pUaY0w==", + "optional": true, + "peer": true, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/serve-static": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", @@ -14809,6 +15295,31 @@ "node": ">= 0.4" } }, + "node_modules/set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "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/set-value/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -14975,6 +15486,38 @@ "node": ">=8" } }, + "node_modules/sort-asc": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/sort-asc/-/sort-asc-0.2.0.tgz", + "integrity": "sha512-umMGhjPeHAI6YjABoSTrFp2zaBtXBej1a0yKkuMUyjjqu6FJsTF+JYwCswWDg+zJfk/5npWUUbd33HH/WLzpaA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-desc": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/sort-desc/-/sort-desc-0.2.0.tgz", + "integrity": "sha512-NqZqyvL4VPW+RAxxXnB8gvE1kyikh8+pR+T+CXLksVRN9eiQqkQlPwqWYU0mF9Jm7UnctShlxLyAt1CaBOTL1w==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-object": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sort-object/-/sort-object-3.0.3.tgz", + "integrity": "sha512-nK7WOY8jik6zaG9CRwZTaD5O7ETWDLZYMM12pqY8htll+7dYeqGfEUPcUBHOpSJg2vJOrvFIY2Dl5cX2ih1hAQ==", + "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.7.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", @@ -15020,6 +15563,51 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "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://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "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://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split-string/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -15586,6 +16174,14 @@ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz", "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==" }, + "node_modules/supercluster": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz", + "integrity": "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==", + "dependencies": { + "kdbush": "^4.0.2" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -15851,6 +16447,11 @@ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "dev": true }, + "node_modules/tinyqueue": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-3.0.0.tgz", + "integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==" + }, "node_modules/tinyspy": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", @@ -16032,6 +16633,16 @@ "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==", "dev": true }, + "node_modules/tweakpane": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tweakpane/-/tweakpane-4.0.4.tgz", + "integrity": "sha512-RkWD54zDlEbnN01wQPk0ANHGbdCvlJx/E8A1QxhTfCbX+ROWos1Ws2MnhOm39aUGMOh+36TjUwpDmLfmwTr1Fg==", + "optional": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/cocopon" + } + }, "node_modules/tween-functions": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/tween-functions/-/tween-functions-1.2.0.tgz", @@ -16170,6 +16781,19 @@ "node": ">=14.17" } }, + "node_modules/typewise": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typewise/-/typewise-1.0.3.tgz", + "integrity": "sha512-aXofE06xGhaQSPzt8hlTY+/YWQhm9P0jYUp1f2XtmW/3Bk0qzXcyFWAtPoo2uTGQj1ZwbDuSyuxicq+aDo8lCQ==", + "dependencies": { + "typewise-core": "^1.2.0" + } + }, + "node_modules/typewise-core": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/typewise-core/-/typewise-core-1.2.0.tgz", + "integrity": "sha512-2SCC/WLzj2SbUwzFOzqMCkz5amXLlxtJqDKTICqg30x+2DZxcfZN2MvQZmGfXWKNWaKK9pBPsvkcwv8bF/gxKg==" + }, "node_modules/ufo": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", @@ -16249,6 +16873,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "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/unique-string": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", @@ -16532,6 +17170,16 @@ "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", "dev": true }, + "node_modules/vt-pbf": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz", + "integrity": "sha512-2LzDFzt0mZKZ9IpVF2r69G9bXaP2Q2sArJCmcCgvfTdCCZzSyz4aCLoQyUilu37Ll56tCblIZrXFIjNUpGIlmA==", + "dependencies": { + "@mapbox/point-geometry": "0.1.0", + "@mapbox/vector-tile": "^1.3.1", + "pbf": "^3.2.1" + } + }, "node_modules/walk-up-path": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-3.0.1.tgz", diff --git a/package.json b/package.json index a7835c753..e53919567 100644 --- a/package.json +++ b/package.json @@ -20,16 +20,23 @@ }, "dependencies": { "@codegouvfr/react-dsfr": "^1.10.11", + "@etalab/decoupage-administratif": "^4.0.0", "@socialgouv/matomo-next": "^1.9.0", "@types/remark-heading-id": "^1.0.0", + "chart.js": "^4.4.4", + "geojson-vt": "^4.0.2", "gray-matter": "^4.0.3", + "maplibre-gl": "^4.7.0", "next": "14.2.5", "react": "^18", + "react-chartjs-2": "^5.2.0", "react-dom": "^18", + "react-map-gl": "^7.1.7", "remark": "^15.0.1", "remark-heading-id": "^1.0.1", "remark-html": "^16.0.1", - "styled-components": "^6.1.12" + "styled-components": "^6.1.12", + "vt-pbf": "^3.1.3" }, "devDependencies": { "@chromatic-com/storybook": "^1.6.1", diff --git a/public/map-styles/osm-bright.json b/public/map-styles/osm-bright.json new file mode 100644 index 000000000..0a72f705b --- /dev/null +++ b/public/map-styles/osm-bright.json @@ -0,0 +1,3464 @@ +{ + "version": 8, + "name": "Bright", + "metadata": { + "mapbox:autocomposite": false, + "mapbox:groups": { + "1444849242106.713": { + "collapsed": false, + "name": "Places" + }, + "1444849334699.1902": { + "collapsed": true, + "name": "Bridges" + }, + "1444849345966.4436": { + "collapsed": false, + "name": "Roads" + }, + "1444849354174.1904": { + "collapsed": true, + "name": "Tunnels" + }, + "1444849364238.8171": { + "collapsed": false, + "name": "Buildings" + }, + "1444849382550.77": { + "collapsed": false, + "name": "Water" + }, + "1444849388993.3071": { + "collapsed": false, + "name": "Land" + } + }, + "mapbox:type": "template", + "openmaptiles:mapbox:owner": "openmaptiles", + "openmaptiles:mapbox:source:url": "mapbox://openmaptiles.4qljc88t", + "openmaptiles:version": "3.x" + }, + "center": [0, 0], + "zoom": 1, + "bearing": 0, + "pitch": 0, + "sources": { + "openmaptiles": { + "type": "vector", + "url": "https://openmaptiles.data.gouv.fr/data/planet-vector.json" + } + }, + "sprite": "https://openmaptiles.data.gouv.fr/sprites/sprite", + "glyphs": "https://openmaptiles.data.gouv.fr/fonts/{fontstack}/{range}.pbf", + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "#f8f4f0" + } + }, + { + "id": "landcover-glacier", + "type": "fill", + "metadata": { + "mapbox:group": "1444849388993.3071" + }, + "source": "openmaptiles", + "source-layer": "landcover", + "filter": ["==", "subclass", "glacier"], + "layout": { + "visibility": "visible" + }, + "paint": { + "fill-color": "#fff", + "fill-opacity": { + "base": 1, + "stops": [ + [0, 0.9], + [10, 0.3] + ] + } + } + }, + { + "id": "landuse-residential", + "type": "fill", + "metadata": { + "mapbox:group": "1444849388993.3071" + }, + "source": "openmaptiles", + "source-layer": "landuse", + "filter": [ + "all", + ["in", "class", "residential", "suburb", "neighbourhood"] + ], + "layout": { + "visibility": "visible" + }, + "paint": { + "fill-color": { + "base": 1, + "stops": [ + [12, "hsla(30, 19%, 90%, 0.4)"], + [16, "hsla(30, 19%, 90%, 0.2)"] + ] + } + } + }, + { + "id": "landuse-commercial", + "type": "fill", + "metadata": { + "mapbox:group": "1444849388993.3071" + }, + "source": "openmaptiles", + "source-layer": "landuse", + "filter": [ + "all", + ["==", "$type", "Polygon"], + ["==", "class", "commercial"] + ], + "layout": { + "visibility": "visible" + }, + "paint": { + "fill-color": "hsla(0, 60%, 87%, 0.23)" + } + }, + { + "id": "landuse-industrial", + "type": "fill", + "source": "openmaptiles", + "source-layer": "landuse", + "filter": [ + "all", + ["==", "$type", "Polygon"], + ["in", "class", "industrial", "garages", "dam"] + ], + "layout": { + "visibility": "visible" + }, + "paint": { + "fill-color": "hsla(49, 100%, 88%, 0.34)" + } + }, + { + "id": "landuse-cemetery", + "type": "fill", + "metadata": { + "mapbox:group": "1444849388993.3071" + }, + "source": "openmaptiles", + "source-layer": "landuse", + "filter": ["==", "class", "cemetery"], + "paint": { + "fill-color": "#e0e4dd" + } + }, + { + "id": "landuse-hospital", + "type": "fill", + "metadata": { + "mapbox:group": "1444849388993.3071" + }, + "source": "openmaptiles", + "source-layer": "landuse", + "filter": ["==", "class", "hospital"], + "paint": { + "fill-color": "#fde" + } + }, + { + "id": "landuse-school", + "type": "fill", + "metadata": { + "mapbox:group": "1444849388993.3071" + }, + "source": "openmaptiles", + "source-layer": "landuse", + "filter": ["==", "class", "school"], + "paint": { + "fill-color": "#f0e8f8" + } + }, + { + "id": "landuse-railway", + "type": "fill", + "metadata": { + "mapbox:group": "1444849388993.3071" + }, + "source": "openmaptiles", + "source-layer": "landuse", + "filter": ["==", "class", "railway"], + "layout": { + "visibility": "visible" + }, + "paint": { + "fill-color": "hsla(30, 19%, 90%, 0.4)" + } + }, + { + "id": "landcover-wood", + "type": "fill", + "metadata": { + "mapbox:group": "1444849388993.3071" + }, + "source": "openmaptiles", + "source-layer": "landcover", + "filter": ["==", "class", "wood"], + "paint": { + "fill-antialias": { + "base": 1, + "stops": [ + [0, false], + [9, true] + ] + }, + "fill-color": "#6a4", + "fill-opacity": 0.1, + "fill-outline-color": "hsla(0, 0%, 0%, 0.03)" + } + }, + { + "id": "landcover-grass", + "type": "fill", + "metadata": { + "mapbox:group": "1444849388993.3071" + }, + "source": "openmaptiles", + "source-layer": "landcover", + "filter": ["==", "class", "grass"], + "paint": { + "fill-color": "#d8e8c8", + "fill-opacity": 1 + } + }, + { + "id": "landcover-grass-park", + "type": "fill", + "metadata": { + "mapbox:group": "1444849388993.3071" + }, + "source": "openmaptiles", + "source-layer": "park", + "filter": ["==", "class", "public_park"], + "paint": { + "fill-color": "#d8e8c8", + "fill-opacity": 0.8 + } + }, + { + "id": "waterway_tunnel", + "type": "line", + "source": "openmaptiles", + "source-layer": "waterway", + "minzoom": 14, + "filter": [ + "all", + ["in", "class", "river", "stream", "canal"], + ["==", "brunnel", "tunnel"] + ], + "layout": { + "line-cap": "round", + "visibility": "visible" + }, + "paint": { + "line-color": "#a0c8f0", + "line-dasharray": [2, 4], + "line-width": { + "base": 1.3, + "stops": [ + [13, 0.5], + [20, 6] + ] + } + } + }, + { + "id": "waterway-other", + "type": "line", + "metadata": { + "mapbox:group": "1444849382550.77" + }, + "source": "openmaptiles", + "source-layer": "waterway", + "filter": [ + "all", + ["!in", "class", "canal", "river", "stream"], + ["==", "intermittent", 0] + ], + "layout": { + "line-cap": "round", + "visibility": "visible" + }, + "paint": { + "line-color": "#a0c8f0", + "line-width": { + "base": 1.3, + "stops": [ + [13, 0.5], + [20, 2] + ] + } + } + }, + { + "id": "waterway-other-intermittent", + "type": "line", + "metadata": { + "mapbox:group": "1444849382550.77" + }, + "source": "openmaptiles", + "source-layer": "waterway", + "filter": [ + "all", + ["!in", "class", "canal", "river", "stream"], + ["==", "intermittent", 1] + ], + "layout": { + "line-cap": "round", + "visibility": "visible" + }, + "paint": { + "line-color": "#a0c8f0", + "line-dasharray": [4, 3], + "line-width": { + "base": 1.3, + "stops": [ + [13, 0.5], + [20, 2] + ] + } + } + }, + { + "id": "waterway-stream-canal", + "type": "line", + "metadata": { + "mapbox:group": "1444849382550.77" + }, + "source": "openmaptiles", + "source-layer": "waterway", + "filter": [ + "all", + ["in", "class", "canal", "stream"], + ["!=", "brunnel", "tunnel"], + ["==", "intermittent", 0] + ], + "layout": { + "line-cap": "round", + "visibility": "visible" + }, + "paint": { + "line-color": "#a0c8f0", + "line-width": { + "base": 1.3, + "stops": [ + [13, 0.5], + [20, 6] + ] + } + } + }, + { + "id": "waterway-stream-canal-intermittent", + "type": "line", + "metadata": { + "mapbox:group": "1444849382550.77" + }, + "source": "openmaptiles", + "source-layer": "waterway", + "filter": [ + "all", + ["in", "class", "canal", "stream"], + ["!=", "brunnel", "tunnel"], + ["==", "intermittent", 1] + ], + "layout": { + "line-cap": "round", + "visibility": "visible" + }, + "paint": { + "line-color": "#a0c8f0", + "line-dasharray": [4, 3], + "line-width": { + "base": 1.3, + "stops": [ + [13, 0.5], + [20, 6] + ] + } + } + }, + { + "id": "waterway-river", + "type": "line", + "metadata": { + "mapbox:group": "1444849382550.77" + }, + "source": "openmaptiles", + "source-layer": "waterway", + "filter": [ + "all", + ["==", "class", "river"], + ["!=", "brunnel", "tunnel"], + ["==", "intermittent", 0] + ], + "layout": { + "line-cap": "round", + "visibility": "visible" + }, + "paint": { + "line-color": "#a0c8f0", + "line-width": { + "base": 1.2, + "stops": [ + [10, 0.8], + [20, 6] + ] + } + } + }, + { + "id": "waterway-river-intermittent", + "type": "line", + "metadata": { + "mapbox:group": "1444849382550.77" + }, + "source": "openmaptiles", + "source-layer": "waterway", + "filter": [ + "all", + ["==", "class", "river"], + ["!=", "brunnel", "tunnel"], + ["==", "intermittent", 1] + ], + "layout": { + "line-cap": "round", + "visibility": "visible" + }, + "paint": { + "line-color": "#a0c8f0", + "line-dasharray": [3, 2.5], + "line-width": { + "base": 1.2, + "stops": [ + [10, 0.8], + [20, 6] + ] + } + } + }, + { + "id": "water-offset", + "type": "fill", + "metadata": { + "mapbox:group": "1444849382550.77" + }, + "source": "openmaptiles", + "source-layer": "water", + "maxzoom": 8, + "filter": ["==", "$type", "Polygon"], + "layout": { + "visibility": "visible" + }, + "paint": { + "fill-color": "#a0c8f0", + "fill-opacity": 1, + "fill-translate": { + "base": 1, + "stops": [ + [6, [2, 0]], + [8, [0, 0]] + ] + } + } + }, + { + "id": "water", + "type": "fill", + "metadata": { + "mapbox:group": "1444849382550.77" + }, + "source": "openmaptiles", + "source-layer": "water", + "filter": ["all", ["!=", "intermittent", 1], ["!=", "brunnel", "tunnel"]], + "layout": { + "visibility": "visible" + }, + "paint": { + "fill-color": "hsl(210, 67%, 85%)" + } + }, + { + "id": "water-intermittent", + "type": "fill", + "metadata": { + "mapbox:group": "1444849382550.77" + }, + "source": "openmaptiles", + "source-layer": "water", + "filter": ["all", ["==", "intermittent", 1]], + "layout": { + "visibility": "visible" + }, + "paint": { + "fill-color": "hsl(210, 67%, 85%)", + "fill-opacity": 0.7 + } + }, + { + "id": "water-pattern", + "type": "fill", + "metadata": { + "mapbox:group": "1444849382550.77" + }, + "source": "openmaptiles", + "source-layer": "water", + "filter": ["all"], + "layout": { + "visibility": "visible" + }, + "paint": { + "fill-pattern": "wave", + "fill-translate": [0, 2.5] + } + }, + { + "id": "landcover-ice-shelf", + "type": "fill", + "metadata": { + "mapbox:group": "1444849382550.77" + }, + "source": "openmaptiles", + "source-layer": "landcover", + "filter": ["==", "subclass", "ice_shelf"], + "layout": { + "visibility": "visible" + }, + "paint": { + "fill-color": "#fff", + "fill-opacity": { + "base": 1, + "stops": [ + [0, 0.9], + [10, 0.3] + ] + } + } + }, + { + "id": "landcover-sand", + "type": "fill", + "metadata": { + "mapbox:group": "1444849382550.77" + }, + "source": "openmaptiles", + "source-layer": "landcover", + "filter": ["all", ["==", "class", "sand"]], + "layout": { + "visibility": "visible" + }, + "paint": { + "fill-color": "rgba(245, 238, 188, 1)", + "fill-opacity": 1 + } + }, + { + "id": "building", + "type": "fill", + "metadata": { + "mapbox:group": "1444849364238.8171" + }, + "source": "openmaptiles", + "source-layer": "building", + "paint": { + "fill-antialias": true, + "fill-color": { + "base": 1, + "stops": [ + [15.5, "#f2eae2"], + [16, "#dfdbd7"] + ] + } + } + }, + { + "id": "building-top", + "type": "fill", + "metadata": { + "mapbox:group": "1444849364238.8171" + }, + "source": "openmaptiles", + "source-layer": "building", + "layout": { + "visibility": "visible" + }, + "paint": { + "fill-color": "#f2eae2", + "fill-opacity": { + "base": 1, + "stops": [ + [13, 0], + [16, 1] + ] + }, + "fill-outline-color": "#dfdbd7", + "fill-translate": { + "base": 1, + "stops": [ + [14, [0, 0]], + [16, [-2, -2]] + ] + } + } + }, + { + "id": "tunnel-service-track-casing", + "type": "line", + "metadata": { + "mapbox:group": "1444849354174.1904" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "brunnel", "tunnel"], + ["in", "class", "service", "track"] + ], + "layout": { + "line-join": "round" + }, + "paint": { + "line-color": "#cfcdca", + "line-dasharray": [0.5, 0.25], + "line-width": { + "base": 1.2, + "stops": [ + [15, 1], + [16, 4], + [20, 11] + ] + } + } + }, + { + "id": "tunnel-motorway-link-casing", + "type": "line", + "metadata": { + "mapbox:group": "1444849354174.1904" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "brunnel", "tunnel"], + ["==", "class", "motorway"], + ["==", "ramp", 1] + ], + "layout": { + "line-join": "round", + "visibility": "visible" + }, + "paint": { + "line-color": "rgba(200, 147, 102, 1)", + "line-dasharray": [0.5, 0.25], + "line-width": { + "base": 1.2, + "stops": [ + [12, 1], + [13, 3], + [14, 4], + [20, 15] + ] + } + } + }, + { + "id": "tunnel-minor-casing", + "type": "line", + "metadata": { + "mapbox:group": "1444849354174.1904" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "tunnel"], ["==", "class", "minor"]], + "layout": { + "line-join": "round" + }, + "paint": { + "line-color": "#cfcdca", + "line-opacity": { + "stops": [ + [12, 0], + [12.5, 1] + ] + }, + "line-width": { + "base": 1.2, + "stops": [ + [12, 0.5], + [13, 1], + [14, 4], + [20, 15] + ] + }, + "line-dasharray": [0.5, 0.25] + } + }, + { + "id": "tunnel-link-casing", + "type": "line", + "metadata": { + "mapbox:group": "1444849354174.1904" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "brunnel", "tunnel"], + ["in", "class", "trunk", "primary", "secondary", "tertiary"], + ["==", "ramp", 1] + ], + "layout": { + "line-join": "round" + }, + "paint": { + "line-color": "#e9ac77", + "line-opacity": 1, + "line-width": { + "base": 1.2, + "stops": [ + [12, 1], + [13, 3], + [14, 4], + [20, 15] + ] + }, + "line-dasharray": [0.5, 0.25] + } + }, + { + "id": "tunnel-secondary-tertiary-casing", + "type": "line", + "metadata": { + "mapbox:group": "1444849354174.1904" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "brunnel", "tunnel"], + ["in", "class", "secondary", "tertiary"], + ["!=", "ramp", 1] + ], + "layout": { + "line-join": "round" + }, + "paint": { + "line-color": "#e9ac77", + "line-opacity": 1, + "line-width": { + "base": 1.2, + "stops": [ + [8, 1.5], + [20, 17] + ] + }, + "line-dasharray": [0.5, 0.25] + } + }, + { + "id": "tunnel-trunk-primary-casing", + "type": "line", + "metadata": { + "mapbox:group": "1444849354174.1904" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "brunnel", "tunnel"], + ["in", "class", "primary", "trunk"], + ["!=", "ramp", 1] + ], + "layout": { + "line-join": "round" + }, + "paint": { + "line-color": "#e9ac77", + "line-width": { + "base": 1.2, + "stops": [ + [5, 0.4], + [6, 0.6], + [7, 1.5], + [20, 22] + ] + } + } + }, + { + "id": "tunnel-motorway-casing", + "type": "line", + "metadata": { + "mapbox:group": "1444849354174.1904" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "brunnel", "tunnel"], + ["==", "class", "motorway"], + ["!=", "ramp", 1] + ], + "layout": { + "line-join": "round", + "visibility": "visible" + }, + "paint": { + "line-color": "#e9ac77", + "line-dasharray": [0.5, 0.25], + "line-width": { + "base": 1.2, + "stops": [ + [5, 0.4], + [6, 0.6], + [7, 1.5], + [20, 22] + ] + } + } + }, + { + "id": "tunnel-path", + "type": "line", + "metadata": { + "mapbox:group": "1444849354174.1904" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "$type", "LineString"], + ["==", "brunnel", "tunnel"], + ["==", "class", "path"] + ], + "paint": { + "line-color": "#cba", + "line-dasharray": [1.5, 0.75], + "line-width": { + "base": 1.2, + "stops": [ + [15, 1.2], + [20, 4] + ] + } + } + }, + { + "id": "tunnel-motorway-link", + "type": "line", + "metadata": { + "mapbox:group": "1444849354174.1904" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "brunnel", "tunnel"], + ["==", "class", "motorway"], + ["==", "ramp", 1] + ], + "layout": { + "line-join": "round", + "visibility": "visible" + }, + "paint": { + "line-color": "rgba(244, 209, 158, 1)", + "line-width": { + "base": 1.2, + "stops": [ + [12.5, 0], + [13, 1.5], + [14, 2.5], + [20, 11.5] + ] + } + } + }, + { + "id": "tunnel-service-track", + "type": "line", + "metadata": { + "mapbox:group": "1444849354174.1904" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "brunnel", "tunnel"], + ["in", "class", "service", "track"] + ], + "layout": { + "line-join": "round" + }, + "paint": { + "line-color": "#fff", + "line-width": { + "base": 1.2, + "stops": [ + [15.5, 0], + [16, 2], + [20, 7.5] + ] + } + } + }, + { + "id": "tunnel-link", + "type": "line", + "metadata": { + "mapbox:group": "1444849354174.1904" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "brunnel", "tunnel"], + ["in", "class", "trunk", "primary", "secondary", "tertiary"], + ["==", "ramp", 1] + ], + "layout": { + "line-join": "round" + }, + "paint": { + "line-color": "#fff4c6", + "line-width": { + "base": 1.2, + "stops": [ + [12.5, 0], + [13, 1.5], + [14, 2.5], + [20, 11.5] + ] + } + } + }, + { + "id": "tunnel-minor", + "type": "line", + "metadata": { + "mapbox:group": "1444849354174.1904" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "brunnel", "tunnel"], + ["==", "class", "minor_road"] + ], + "layout": { + "line-join": "round" + }, + "paint": { + "line-color": "#fff", + "line-opacity": 1, + "line-width": { + "base": 1.2, + "stops": [ + [13.5, 0], + [14, 2.5], + [20, 11.5] + ] + } + } + }, + { + "id": "tunnel-secondary-tertiary", + "type": "line", + "metadata": { + "mapbox:group": "1444849354174.1904" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "brunnel", "tunnel"], + ["in", "class", "secondary", "tertiary"], + ["!=", "ramp", 1] + ], + "layout": { + "line-join": "round" + }, + "paint": { + "line-color": "#fff4c6", + "line-width": { + "base": 1.2, + "stops": [ + [6.5, 0], + [7, 0.5], + [20, 10] + ] + } + } + }, + { + "id": "tunnel-trunk-primary", + "type": "line", + "metadata": { + "mapbox:group": "1444849354174.1904" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "brunnel", "tunnel"], + ["in", "class", "primary", "trunk"], + ["!=", "ramp", 1] + ], + "layout": { + "line-join": "round" + }, + "paint": { + "line-color": "#fff4c6", + "line-width": { + "base": 1.2, + "stops": [ + [6.5, 0], + [7, 0.5], + [20, 18] + ] + } + } + }, + { + "id": "tunnel-motorway", + "type": "line", + "metadata": { + "mapbox:group": "1444849354174.1904" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "brunnel", "tunnel"], + ["==", "class", "motorway"], + ["!=", "ramp", 1] + ], + "layout": { + "line-join": "round", + "visibility": "visible" + }, + "paint": { + "line-color": "#ffdaa6", + "line-width": { + "base": 1.2, + "stops": [ + [6.5, 0], + [7, 0.5], + [20, 18] + ] + } + } + }, + { + "id": "tunnel-railway", + "type": "line", + "metadata": { + "mapbox:group": "1444849354174.1904" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "tunnel"], ["==", "class", "rail"]], + "paint": { + "line-color": "#bbb", + "line-dasharray": [2, 2], + "line-width": { + "base": 1.4, + "stops": [ + [14, 0.4], + [15, 0.75], + [20, 2] + ] + } + } + }, + { + "id": "ferry", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "filter": ["all", ["in", "class", "ferry"]], + "layout": { + "line-join": "round", + "visibility": "visible" + }, + "paint": { + "line-color": "rgba(108, 159, 182, 1)", + "line-dasharray": [2, 2], + "line-width": 1.1 + } + }, + { + "id": "aeroway-taxiway-casing", + "type": "line", + "metadata": { + "mapbox:group": "1444849345966.4436" + }, + "source": "openmaptiles", + "source-layer": "aeroway", + "minzoom": 12, + "filter": ["all", ["in", "class", "taxiway"]], + "layout": { + "line-cap": "round", + "line-join": "round", + "visibility": "visible" + }, + "paint": { + "line-color": "rgba(153, 153, 153, 1)", + "line-opacity": 1, + "line-width": { + "base": 1.5, + "stops": [ + [11, 2], + [17, 12] + ] + } + } + }, + { + "id": "aeroway-runway-casing", + "type": "line", + "metadata": { + "mapbox:group": "1444849345966.4436" + }, + "source": "openmaptiles", + "source-layer": "aeroway", + "minzoom": 12, + "filter": ["all", ["in", "class", "runway"]], + "layout": { + "line-cap": "round", + "line-join": "round", + "visibility": "visible" + }, + "paint": { + "line-color": "rgba(153, 153, 153, 1)", + "line-opacity": 1, + "line-width": { + "base": 1.5, + "stops": [ + [11, 5], + [17, 55] + ] + } + } + }, + { + "id": "aeroway-area", + "type": "fill", + "metadata": { + "mapbox:group": "1444849345966.4436" + }, + "source": "openmaptiles", + "source-layer": "aeroway", + "minzoom": 4, + "filter": [ + "all", + ["==", "$type", "Polygon"], + ["in", "class", "runway", "taxiway"] + ], + "layout": { + "visibility": "visible" + }, + "paint": { + "fill-color": "rgba(255, 255, 255, 1)", + "fill-opacity": { + "base": 1, + "stops": [ + [13, 0], + [14, 1] + ] + } + } + }, + { + "id": "aeroway-taxiway", + "type": "line", + "metadata": { + "mapbox:group": "1444849345966.4436" + }, + "source": "openmaptiles", + "source-layer": "aeroway", + "minzoom": 4, + "filter": [ + "all", + ["in", "class", "taxiway"], + ["==", "$type", "LineString"] + ], + "layout": { + "line-cap": "round", + "line-join": "round", + "visibility": "visible" + }, + "paint": { + "line-color": "rgba(255, 255, 255, 1)", + "line-opacity": { + "base": 1, + "stops": [ + [11, 0], + [12, 1] + ] + }, + "line-width": { + "base": 1.5, + "stops": [ + [11, 1], + [17, 10] + ] + } + } + }, + { + "id": "aeroway-runway", + "type": "line", + "metadata": { + "mapbox:group": "1444849345966.4436" + }, + "source": "openmaptiles", + "source-layer": "aeroway", + "minzoom": 4, + "filter": [ + "all", + ["in", "class", "runway"], + ["==", "$type", "LineString"] + ], + "layout": { + "line-cap": "round", + "line-join": "round", + "visibility": "visible" + }, + "paint": { + "line-color": "rgba(255, 255, 255, 1)", + "line-opacity": { + "base": 1, + "stops": [ + [11, 0], + [12, 1] + ] + }, + "line-width": { + "base": 1.5, + "stops": [ + [11, 4], + [17, 50] + ] + } + } + }, + { + "id": "road_area_pier", + "type": "fill", + "metadata": {}, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": ["all", ["==", "$type", "Polygon"], ["==", "class", "pier"]], + "layout": { + "visibility": "visible" + }, + "paint": { + "fill-antialias": true, + "fill-color": "#f8f4f0" + } + }, + { + "id": "road_pier", + "type": "line", + "metadata": {}, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": ["all", ["==", "$type", "LineString"], ["in", "class", "pier"]], + "layout": { + "line-cap": "round", + "line-join": "round" + }, + "paint": { + "line-color": "#f8f4f0", + "line-width": { + "base": 1.2, + "stops": [ + [15, 1], + [17, 4] + ] + } + } + }, + { + "id": "highway-area", + "type": "fill", + "metadata": { + "mapbox:group": "1444849345966.4436" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": ["all", ["==", "$type", "Polygon"], ["!in", "class", "pier"]], + "layout": { + "visibility": "visible" + }, + "paint": { + "fill-antialias": false, + "fill-color": "hsla(0, 0%, 89%, 0.56)", + "fill-opacity": 0.9, + "fill-outline-color": "#cfcdca" + } + }, + { + "id": "highway-motorway-link-casing", + "type": "line", + "metadata": { + "mapbox:group": "1444849345966.4436" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "minzoom": 12, + "filter": [ + "all", + ["!in", "brunnel", "bridge", "tunnel"], + ["==", "class", "motorway"], + ["==", "ramp", 1] + ], + "layout": { + "line-cap": "round", + "line-join": "round" + }, + "paint": { + "line-color": "#e9ac77", + "line-opacity": 1, + "line-width": { + "base": 1.2, + "stops": [ + [12, 1], + [13, 3], + [14, 4], + [20, 15] + ] + } + } + }, + { + "id": "highway-link-casing", + "type": "line", + "metadata": { + "mapbox:group": "1444849345966.4436" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "minzoom": 13, + "filter": [ + "all", + ["!in", "brunnel", "bridge", "tunnel"], + ["in", "class", "trunk", "primary", "secondary", "tertiary"], + ["==", "ramp", 1] + ], + "layout": { + "line-cap": "round", + "line-join": "round", + "visibility": "visible" + }, + "paint": { + "line-color": "#e9ac77", + "line-opacity": 1, + "line-width": { + "base": 1.2, + "stops": [ + [12, 1], + [13, 3], + [14, 4], + [20, 15] + ] + } + } + }, + { + "id": "highway-minor-casing", + "type": "line", + "metadata": { + "mapbox:group": "1444849345966.4436" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "$type", "LineString"], + ["!=", "brunnel", "tunnel"], + ["in", "class", "minor", "service", "track"] + ], + "layout": { + "line-cap": "round", + "line-join": "round" + }, + "paint": { + "line-color": "#cfcdca", + "line-opacity": { + "stops": [ + [12, 0], + [12.5, 1] + ] + }, + "line-width": { + "base": 1.2, + "stops": [ + [12, 0.5], + [13, 1], + [14, 4], + [20, 15] + ] + } + } + }, + { + "id": "highway-secondary-tertiary-casing", + "type": "line", + "metadata": { + "mapbox:group": "1444849345966.4436" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["!in", "brunnel", "bridge", "tunnel"], + ["in", "class", "secondary", "tertiary"], + ["!=", "ramp", 1] + ], + "layout": { + "line-cap": "butt", + "line-join": "round", + "visibility": "visible" + }, + "paint": { + "line-color": "#e9ac77", + "line-opacity": 1, + "line-width": { + "base": 1.2, + "stops": [ + [8, 1.5], + [20, 17] + ] + } + } + }, + { + "id": "highway-primary-casing", + "type": "line", + "metadata": { + "mapbox:group": "1444849345966.4436" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "minzoom": 5, + "filter": [ + "all", + ["!in", "brunnel", "bridge", "tunnel"], + ["in", "class", "primary"], + ["!=", "ramp", 1] + ], + "layout": { + "line-cap": "butt", + "line-join": "round", + "visibility": "visible" + }, + "paint": { + "line-color": "#e9ac77", + "line-opacity": { + "stops": [ + [7, 0], + [8, 1] + ] + }, + "line-width": { + "base": 1.2, + "stops": [ + [7, 0], + [8, 0.6], + [9, 1.5], + [20, 22] + ] + } + } + }, + { + "id": "highway-trunk-casing", + "type": "line", + "metadata": { + "mapbox:group": "1444849345966.4436" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "minzoom": 5, + "filter": [ + "all", + ["!in", "brunnel", "bridge", "tunnel"], + ["in", "class", "trunk"], + ["!=", "ramp", 1] + ], + "layout": { + "line-cap": "butt", + "line-join": "round", + "visibility": "visible" + }, + "paint": { + "line-color": "#e9ac77", + "line-opacity": { + "stops": [ + [5, 0], + [6, 1] + ] + }, + "line-width": { + "base": 1.2, + "stops": [ + [5, 0], + [6, 0.6], + [7, 1.5], + [20, 22] + ] + } + } + }, + { + "id": "highway-motorway-casing", + "type": "line", + "metadata": { + "mapbox:group": "1444849345966.4436" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "minzoom": 4, + "filter": [ + "all", + ["!in", "brunnel", "bridge", "tunnel"], + ["==", "class", "motorway"], + ["!=", "ramp", 1] + ], + "layout": { + "line-cap": "butt", + "line-join": "round", + "visibility": "visible" + }, + "paint": { + "line-color": "#e9ac77", + "line-opacity": { + "stops": [ + [4, 0], + [5, 1] + ] + }, + "line-width": { + "base": 1.2, + "stops": [ + [4, 0], + [5, 0.4], + [6, 0.6], + [7, 1.5], + [20, 22] + ] + } + } + }, + { + "id": "highway-path", + "type": "line", + "metadata": { + "mapbox:group": "1444849345966.4436" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "$type", "LineString"], + ["!in", "brunnel", "bridge", "tunnel"], + ["==", "class", "path"] + ], + "paint": { + "line-color": "#cba", + "line-dasharray": [1.5, 0.75], + "line-width": { + "base": 1.2, + "stops": [ + [15, 1.2], + [20, 4] + ] + } + } + }, + { + "id": "highway-motorway-link", + "type": "line", + "metadata": { + "mapbox:group": "1444849345966.4436" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "minzoom": 12, + "filter": [ + "all", + ["!in", "brunnel", "bridge", "tunnel"], + ["==", "class", "motorway"], + ["==", "ramp", 1] + ], + "layout": { + "line-cap": "round", + "line-join": "round" + }, + "paint": { + "line-color": "#fc8", + "line-width": { + "base": 1.2, + "stops": [ + [12.5, 0], + [13, 1.5], + [14, 2.5], + [20, 11.5] + ] + } + } + }, + { + "id": "highway-link", + "type": "line", + "metadata": { + "mapbox:group": "1444849345966.4436" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "minzoom": 13, + "filter": [ + "all", + ["!in", "brunnel", "bridge", "tunnel"], + ["in", "class", "trunk", "primary", "secondary", "tertiary"], + ["==", "ramp", 1] + ], + "layout": { + "line-cap": "round", + "line-join": "round", + "visibility": "visible" + }, + "paint": { + "line-color": "#fea", + "line-width": { + "base": 1.2, + "stops": [ + [12.5, 0], + [13, 1.5], + [14, 2.5], + [20, 11.5] + ] + } + } + }, + { + "id": "highway-minor", + "type": "line", + "metadata": { + "mapbox:group": "1444849345966.4436" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "$type", "LineString"], + ["!=", "brunnel", "tunnel"], + ["in", "class", "minor", "service", "track"] + ], + "layout": { + "line-cap": "round", + "line-join": "round" + }, + "paint": { + "line-color": "#fff", + "line-opacity": 1, + "line-width": { + "base": 1.2, + "stops": [ + [13.5, 0], + [14, 2.5], + [20, 11.5] + ] + } + } + }, + { + "id": "highway-secondary-tertiary", + "type": "line", + "metadata": { + "mapbox:group": "1444849345966.4436" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["!in", "brunnel", "bridge", "tunnel"], + ["in", "class", "secondary", "tertiary"], + ["!=", "ramp", 1] + ], + "layout": { + "line-cap": "round", + "line-join": "round", + "visibility": "visible" + }, + "paint": { + "line-color": "#fea", + "line-width": { + "base": 1.2, + "stops": [ + [6.5, 0], + [8, 0.5], + [20, 13] + ] + } + } + }, + { + "id": "highway-primary", + "type": "line", + "metadata": { + "mapbox:group": "1444849345966.4436" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "$type", "LineString"], + ["!in", "brunnel", "bridge", "tunnel"], + ["in", "class", "primary"], + ["!=", "ramp", 1] + ], + "layout": { + "line-cap": "round", + "line-join": "round", + "visibility": "visible" + }, + "paint": { + "line-color": "#fea", + "line-width": { + "base": 1.2, + "stops": [ + [8.5, 0], + [9, 0.5], + [20, 18] + ] + } + } + }, + { + "id": "highway-trunk", + "type": "line", + "metadata": { + "mapbox:group": "1444849345966.4436" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "$type", "LineString"], + ["!in", "brunnel", "bridge", "tunnel"], + ["in", "class", "trunk"], + ["!=", "ramp", 1] + ], + "layout": { + "line-cap": "round", + "line-join": "round", + "visibility": "visible" + }, + "paint": { + "line-color": "#fea", + "line-width": { + "base": 1.2, + "stops": [ + [6.5, 0], + [7, 0.5], + [20, 18] + ] + } + } + }, + { + "id": "highway-motorway", + "type": "line", + "metadata": { + "mapbox:group": "1444849345966.4436" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "minzoom": 5, + "filter": [ + "all", + ["==", "$type", "LineString"], + ["!in", "brunnel", "bridge", "tunnel"], + ["==", "class", "motorway"], + ["!=", "ramp", 1] + ], + "layout": { + "line-cap": "round", + "line-join": "round", + "visibility": "visible" + }, + "paint": { + "line-color": "#fc8", + "line-width": { + "base": 1.2, + "stops": [ + [6.5, 0], + [7, 0.5], + [20, 18] + ] + } + } + }, + { + "id": "railway-transit", + "type": "line", + "metadata": { + "mapbox:group": "1444849345966.4436" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "$type", "LineString"], + ["==", "class", "transit"], + ["!in", "brunnel", "tunnel"] + ], + "layout": { + "visibility": "visible" + }, + "paint": { + "line-color": "hsla(0, 0%, 73%, 0.77)", + "line-width": { + "base": 1.4, + "stops": [ + [14, 0.4], + [20, 1] + ] + } + } + }, + { + "id": "railway-transit-hatching", + "type": "line", + "metadata": { + "mapbox:group": "1444849345966.4436" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "$type", "LineString"], + ["==", "class", "transit"], + ["!in", "brunnel", "tunnel"] + ], + "layout": { + "visibility": "visible" + }, + "paint": { + "line-color": "hsla(0, 0%, 73%, 0.68)", + "line-dasharray": [0.2, 8], + "line-width": { + "base": 1.4, + "stops": [ + [14.5, 0], + [15, 2], + [20, 6] + ] + } + } + }, + { + "id": "railway-service", + "type": "line", + "metadata": { + "mapbox:group": "1444849345966.4436" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "$type", "LineString"], + ["==", "class", "rail"], + ["has", "service"] + ], + "paint": { + "line-color": "hsla(0, 0%, 73%, 0.77)", + "line-width": { + "base": 1.4, + "stops": [ + [14, 0.4], + [20, 1] + ] + } + } + }, + { + "id": "railway-service-hatching", + "type": "line", + "metadata": { + "mapbox:group": "1444849345966.4436" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "$type", "LineString"], + ["==", "class", "rail"], + ["has", "service"] + ], + "layout": { + "visibility": "visible" + }, + "paint": { + "line-color": "hsla(0, 0%, 73%, 0.68)", + "line-dasharray": [0.2, 8], + "line-width": { + "base": 1.4, + "stops": [ + [14.5, 0], + [15, 2], + [20, 6] + ] + } + } + }, + { + "id": "railway", + "type": "line", + "metadata": { + "mapbox:group": "1444849345966.4436" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "$type", "LineString"], + ["!has", "service"], + ["!in", "brunnel", "bridge", "tunnel"], + ["==", "class", "rail"] + ], + "paint": { + "line-color": "#bbb", + "line-width": { + "base": 1.4, + "stops": [ + [14, 0.4], + [15, 0.75], + [20, 2] + ] + } + } + }, + { + "id": "railway-hatching", + "type": "line", + "metadata": { + "mapbox:group": "1444849345966.4436" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "$type", "LineString"], + ["!has", "service"], + ["!in", "brunnel", "bridge", "tunnel"], + ["==", "class", "rail"] + ], + "paint": { + "line-color": "#bbb", + "line-dasharray": [0.2, 8], + "line-width": { + "base": 1.4, + "stops": [ + [14.5, 0], + [15, 3], + [20, 8] + ] + } + } + }, + { + "id": "bridge-motorway-link-casing", + "type": "line", + "metadata": { + "mapbox:group": "1444849334699.1902" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "brunnel", "bridge"], + ["==", "class", "motorway"], + ["==", "ramp", 1] + ], + "layout": { + "line-join": "round" + }, + "paint": { + "line-color": "#e9ac77", + "line-opacity": 1, + "line-width": { + "base": 1.2, + "stops": [ + [12, 1], + [13, 3], + [14, 4], + [20, 19] + ] + } + } + }, + { + "id": "bridge-link-casing", + "type": "line", + "metadata": { + "mapbox:group": "1444849334699.1902" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "brunnel", "bridge"], + ["in", "class", "trunk", "primary", "secondary", "tertiary"], + ["==", "ramp", 1] + ], + "layout": { + "line-join": "round" + }, + "paint": { + "line-color": "#e9ac77", + "line-opacity": 1, + "line-width": { + "base": 1.2, + "stops": [ + [12, 1], + [13, 3], + [14, 4], + [20, 19] + ] + } + } + }, + { + "id": "bridge-secondary-tertiary-casing", + "type": "line", + "metadata": { + "mapbox:group": "1444849334699.1902" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "brunnel", "bridge"], + ["in", "class", "secondary", "tertiary"], + ["!=", "ramp", 1] + ], + "layout": { + "line-join": "round" + }, + "paint": { + "line-color": "#e9ac77", + "line-opacity": 1, + "line-width": { + "base": 1.2, + "stops": [ + [5, 0.4], + [7, 0.6], + [8, 1.5], + [20, 21] + ] + } + } + }, + { + "id": "bridge-trunk-primary-casing", + "type": "line", + "metadata": { + "mapbox:group": "1444849334699.1902" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "brunnel", "bridge"], + ["in", "class", "primary", "trunk"], + ["!=", "ramp", 1] + ], + "layout": { + "line-join": "round" + }, + "paint": { + "line-color": "hsl(28, 76%, 67%)", + "line-width": { + "base": 1.2, + "stops": [ + [5, 0.4], + [6, 0.6], + [7, 1.5], + [20, 26] + ] + } + } + }, + { + "id": "bridge-motorway-casing", + "type": "line", + "metadata": { + "mapbox:group": "1444849334699.1902" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "brunnel", "bridge"], + ["==", "class", "motorway"], + ["!=", "ramp", 1] + ], + "layout": { + "line-join": "round" + }, + "paint": { + "line-color": "#e9ac77", + "line-width": { + "base": 1.2, + "stops": [ + [5, 0.4], + [6, 0.6], + [7, 1.5], + [20, 26] + ] + } + } + }, + { + "id": "bridge-minor-casing", + "type": "line", + "metadata": { + "mapbox:group": "1444849345966.4436" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "$type", "LineString"], + ["==", "brunnel", "bridge"], + ["in", "class", "minor", "service", "track"] + ], + "layout": { + "line-cap": "butt", + "line-join": "round" + }, + "paint": { + "line-color": "#cfcdca", + "line-opacity": { + "stops": [ + [12, 0], + [12.5, 1] + ] + }, + "line-width": { + "base": 1.2, + "stops": [ + [12, 0.5], + [13, 1], + [14, 6], + [20, 24] + ] + } + } + }, + { + "id": "bridge-path-casing", + "type": "line", + "metadata": { + "mapbox:group": "1444849334699.1902" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "$type", "LineString"], + ["==", "brunnel", "bridge"], + ["==", "class", "path"] + ], + "paint": { + "line-color": "#f8f4f0", + "line-width": { + "base": 1.2, + "stops": [ + [15, 1.2], + [20, 18] + ] + } + } + }, + { + "id": "bridge-path", + "type": "line", + "metadata": { + "mapbox:group": "1444849334699.1902" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "$type", "LineString"], + ["==", "brunnel", "bridge"], + ["==", "class", "path"] + ], + "paint": { + "line-color": "#cba", + "line-dasharray": [1.5, 0.75], + "line-width": { + "base": 1.2, + "stops": [ + [15, 1.2], + [20, 4] + ] + } + } + }, + { + "id": "bridge-motorway-link", + "type": "line", + "metadata": { + "mapbox:group": "1444849334699.1902" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "brunnel", "bridge"], + ["==", "class", "motorway"], + ["==", "ramp", 1] + ], + "layout": { + "line-join": "round" + }, + "paint": { + "line-color": "#fc8", + "line-width": { + "base": 1.2, + "stops": [ + [12.5, 0], + [13, 1.5], + [14, 2.5], + [20, 11.5] + ] + } + } + }, + { + "id": "bridge-link", + "type": "line", + "metadata": { + "mapbox:group": "1444849334699.1902" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "brunnel", "bridge"], + ["in", "class", "trunk", "primary", "secondary", "tertiary"], + ["==", "ramp", 1] + ], + "layout": { + "line-join": "round" + }, + "paint": { + "line-color": "#fea", + "line-width": { + "base": 1.2, + "stops": [ + [12.5, 0], + [13, 1.5], + [14, 2.5], + [20, 11.5] + ] + } + } + }, + { + "id": "bridge-minor", + "type": "line", + "metadata": { + "mapbox:group": "1444849345966.4436" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "$type", "LineString"], + ["==", "brunnel", "bridge"], + ["in", "class", "minor", "service", "track"] + ], + "layout": { + "line-cap": "round", + "line-join": "round" + }, + "paint": { + "line-color": "#fff", + "line-opacity": 1, + "line-width": { + "base": 1.2, + "stops": [ + [13.5, 0], + [14, 2.5], + [20, 11.5] + ] + } + } + }, + { + "id": "bridge-secondary-tertiary", + "type": "line", + "metadata": { + "mapbox:group": "1444849334699.1902" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "brunnel", "bridge"], + ["in", "class", "secondary", "tertiary"], + ["!=", "ramp", 1] + ], + "layout": { + "line-join": "round" + }, + "paint": { + "line-color": "#fea", + "line-width": { + "base": 1.2, + "stops": [ + [6.5, 0], + [8, 0.5], + [20, 13] + ] + } + } + }, + { + "id": "bridge-trunk-primary", + "type": "line", + "metadata": { + "mapbox:group": "1444849334699.1902" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "brunnel", "bridge"], + ["in", "class", "primary", "trunk"], + ["!=", "ramp", 1] + ], + "layout": { + "line-join": "round" + }, + "paint": { + "line-color": "#fea", + "line-width": { + "base": 1.2, + "stops": [ + [6.5, 0], + [7, 0.5], + [20, 18] + ] + } + } + }, + { + "id": "bridge-motorway", + "type": "line", + "metadata": { + "mapbox:group": "1444849334699.1902" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": [ + "all", + ["==", "brunnel", "bridge"], + ["==", "class", "motorway"], + ["!=", "ramp", 1] + ], + "layout": { + "line-join": "round" + }, + "paint": { + "line-color": "#fc8", + "line-width": { + "base": 1.2, + "stops": [ + [6.5, 0], + [7, 0.5], + [20, 18] + ] + } + } + }, + { + "id": "bridge-railway", + "type": "line", + "metadata": { + "mapbox:group": "1444849334699.1902" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "bridge"], ["==", "class", "rail"]], + "paint": { + "line-color": "#bbb", + "line-width": { + "base": 1.4, + "stops": [ + [14, 0.4], + [15, 0.75], + [20, 2] + ] + } + } + }, + { + "id": "bridge-railway-hatching", + "type": "line", + "metadata": { + "mapbox:group": "1444849334699.1902" + }, + "source": "openmaptiles", + "source-layer": "transportation", + "filter": ["all", ["==", "brunnel", "bridge"], ["==", "class", "rail"]], + "paint": { + "line-color": "#bbb", + "line-dasharray": [0.2, 8], + "line-width": { + "base": 1.4, + "stops": [ + [14.5, 0], + [15, 3], + [20, 8] + ] + } + } + }, + { + "id": "cablecar", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "minzoom": 13, + "filter": ["==", "subclass", "cable_car"], + "layout": { + "line-cap": "round", + "visibility": "visible" + }, + "paint": { + "line-color": "hsl(0, 0%, 70%)", + "line-width": { + "base": 1, + "stops": [ + [11, 1], + [19, 2.5] + ] + } + } + }, + { + "id": "cablecar-dash", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "minzoom": 13, + "filter": ["==", "subclass", "cable_car"], + "layout": { + "line-cap": "round", + "visibility": "visible" + }, + "paint": { + "line-color": "hsl(0, 0%, 70%)", + "line-dasharray": [2, 3], + "line-width": { + "base": 1, + "stops": [ + [11, 3], + [19, 5.5] + ] + } + } + }, + { + "id": "boundary-land-level-4", + "type": "line", + "source": "openmaptiles", + "source-layer": "boundary", + "minzoom": 2, + "filter": [ + "all", + [">=", "admin_level", 3], + ["<=", "admin_level", 8], + ["!=", "maritime", 1] + ], + "layout": { + "line-join": "round", + "visibility": "visible" + }, + "paint": { + "line-color": "#9e9cab", + "line-dasharray": [3, 1, 1, 1], + "line-width": { + "base": 1.4, + "stops": [ + [4, 0.4], + [5, 1], + [12, 3] + ] + } + } + }, + { + "id": "boundary-land-level-2", + "type": "line", + "source": "openmaptiles", + "source-layer": "boundary", + "filter": [ + "all", + ["==", "admin_level", 2], + ["!=", "maritime", 1], + ["!=", "disputed", 1] + ], + "layout": { + "line-cap": "round", + "line-join": "round", + "visibility": "visible" + }, + "paint": { + "line-color": "hsl(248, 7%, 66%)", + "line-width": { + "base": 1, + "stops": [ + [0, 0.6], + [4, 1.4], + [5, 2], + [12, 8] + ] + } + } + }, + { + "id": "boundary-land-disputed", + "type": "line", + "source": "openmaptiles", + "source-layer": "boundary", + "filter": ["all", ["!=", "maritime", 1], ["==", "disputed", 1]], + "layout": { + "line-cap": "round", + "line-join": "round", + "visibility": "visible" + }, + "paint": { + "line-color": "hsl(248, 7%, 70%)", + "line-dasharray": [1, 3], + "line-width": { + "base": 1, + "stops": [ + [0, 0.6], + [4, 1.4], + [5, 2], + [12, 8] + ] + } + } + }, + { + "id": "boundary-water", + "type": "line", + "source": "openmaptiles", + "source-layer": "boundary", + "minzoom": 4, + "filter": ["all", ["in", "admin_level", 2, 4], ["==", "maritime", 1]], + "layout": { + "line-cap": "round", + "line-join": "round", + "visibility": "visible" + }, + "paint": { + "line-color": "rgba(154, 189, 214, 1)", + "line-opacity": { + "stops": [ + [6, 0.6], + [10, 1] + ] + }, + "line-width": { + "base": 1, + "stops": [ + [0, 0.6], + [4, 1.4], + [5, 2], + [12, 8] + ] + } + } + }, + { + "id": "waterway-name", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "waterway", + "minzoom": 13, + "filter": ["all", ["==", "$type", "LineString"], ["has", "name"]], + "layout": { + "symbol-placement": "line", + "symbol-spacing": 350, + "text-field": "{name:latin} {name:nonlatin}", + "text-font": ["Noto Sans Italic"], + "text-letter-spacing": 0.2, + "text-max-width": 5, + "text-rotation-alignment": "map", + "text-size": 14 + }, + "paint": { + "text-color": "#74aee9", + "text-halo-color": "rgba(255,255,255,0.7)", + "text-halo-width": 1.5 + } + }, + { + "id": "water-name-lakeline", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "water_name", + "filter": ["==", "$type", "LineString"], + "layout": { + "symbol-placement": "line", + "symbol-spacing": 350, + "text-field": "{name:latin}\n{name:nonlatin}", + "text-font": ["Noto Sans Italic"], + "text-letter-spacing": 0.2, + "text-max-width": 5, + "text-rotation-alignment": "map", + "text-size": 14 + }, + "paint": { + "text-color": "#74aee9", + "text-halo-color": "rgba(255,255,255,0.7)", + "text-halo-width": 1.5 + } + }, + { + "id": "water-name-ocean", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "water_name", + "filter": ["all", ["==", "$type", "Point"], ["==", "class", "ocean"]], + "layout": { + "symbol-placement": "point", + "symbol-spacing": 350, + "text-field": "{name:latin}", + "text-font": ["Noto Sans Italic"], + "text-letter-spacing": 0.2, + "text-max-width": 5, + "text-rotation-alignment": "map", + "text-size": 14 + }, + "paint": { + "text-color": "#74aee9", + "text-halo-color": "rgba(255,255,255,0.7)", + "text-halo-width": 1.5 + } + }, + { + "id": "water-name-other", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "water_name", + "filter": ["all", ["==", "$type", "Point"], ["!in", "class", "ocean"]], + "layout": { + "symbol-placement": "point", + "symbol-spacing": 350, + "text-field": "{name:latin}\n{name:nonlatin}", + "text-font": ["Noto Sans Italic"], + "text-letter-spacing": 0.2, + "text-max-width": 5, + "text-rotation-alignment": "map", + "text-size": { + "stops": [ + [0, 10], + [6, 14] + ] + }, + "visibility": "visible" + }, + "paint": { + "text-color": "#74aee9", + "text-halo-color": "rgba(255,255,255,0.7)", + "text-halo-width": 1.5 + } + }, + { + "id": "poi-level-3", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "poi", + "minzoom": 16, + "filter": [ + "all", + ["==", "$type", "Point"], + [">=", "rank", 25], + ["any", ["!has", "level"], ["==", "level", 0]] + ], + "layout": { + "icon-image": "{class}_11", + "text-anchor": "top", + "text-field": "{name:latin}\n{name:nonlatin}", + "text-font": ["Noto Sans Regular"], + "text-max-width": 9, + "text-offset": [0, 0.6], + "text-padding": 2, + "text-size": 12, + "visibility": "visible" + }, + "paint": { + "text-color": "#666", + "text-halo-blur": 0.5, + "text-halo-color": "#ffffff", + "text-halo-width": 1 + } + }, + { + "id": "poi-level-2", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "poi", + "minzoom": 15, + "filter": [ + "all", + ["==", "$type", "Point"], + ["<=", "rank", 24], + [">=", "rank", 15], + ["any", ["!has", "level"], ["==", "level", 0]] + ], + "layout": { + "icon-image": "{class}_11", + "text-anchor": "top", + "text-field": "{name:latin}\n{name:nonlatin}", + "text-font": ["Noto Sans Regular"], + "text-max-width": 9, + "text-offset": [0, 0.6], + "text-padding": 2, + "text-size": 12, + "visibility": "visible" + }, + "paint": { + "text-color": "#666", + "text-halo-blur": 0.5, + "text-halo-color": "#ffffff", + "text-halo-width": 1 + } + }, + { + "id": "poi-level-1", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "poi", + "minzoom": 14, + "filter": [ + "all", + ["==", "$type", "Point"], + ["<=", "rank", 14], + ["has", "name"], + ["any", ["!has", "level"], ["==", "level", 0]] + ], + "layout": { + "icon-image": "{class}_11", + "text-anchor": "top", + "text-field": "{name:latin}\n{name:nonlatin}", + "text-font": ["Noto Sans Regular"], + "text-max-width": 9, + "text-offset": [0, 0.6], + "text-padding": 2, + "text-size": 12, + "visibility": "visible" + }, + "paint": { + "text-color": "#666", + "text-halo-blur": 0.5, + "text-halo-color": "#ffffff", + "text-halo-width": 1 + } + }, + { + "id": "poi-railway", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "poi", + "minzoom": 13, + "filter": [ + "all", + ["==", "$type", "Point"], + ["has", "name"], + ["==", "class", "railway"], + ["==", "subclass", "station"] + ], + "layout": { + "icon-allow-overlap": false, + "icon-ignore-placement": false, + "icon-image": "{class}_11", + "icon-optional": false, + "text-allow-overlap": false, + "text-anchor": "top", + "text-field": "{name:latin}\n{name:nonlatin}", + "text-font": ["Noto Sans Regular"], + "text-ignore-placement": false, + "text-max-width": 9, + "text-offset": [0, 0.6], + "text-optional": true, + "text-padding": 2, + "text-size": 12 + }, + "paint": { + "text-color": "#666", + "text-halo-blur": 0.5, + "text-halo-color": "#ffffff", + "text-halo-width": 1 + } + }, + { + "id": "road_oneway", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "transportation", + "minzoom": 15, + "filter": [ + "all", + ["==", "oneway", 1], + [ + "in", + "class", + "motorway", + "trunk", + "primary", + "secondary", + "tertiary", + "minor", + "service" + ] + ], + "layout": { + "icon-image": "oneway", + "icon-padding": 2, + "icon-rotate": 90, + "icon-rotation-alignment": "map", + "icon-size": { + "stops": [ + [15, 0.5], + [19, 1] + ] + }, + "symbol-placement": "line", + "symbol-spacing": 75 + }, + "paint": { + "icon-opacity": 0.5 + } + }, + { + "id": "road_oneway_opposite", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "transportation", + "minzoom": 15, + "filter": [ + "all", + ["==", "oneway", -1], + [ + "in", + "class", + "motorway", + "trunk", + "primary", + "secondary", + "tertiary", + "minor", + "service" + ] + ], + "layout": { + "icon-image": "oneway", + "icon-padding": 2, + "icon-rotate": -90, + "icon-rotation-alignment": "map", + "icon-size": { + "stops": [ + [15, 0.5], + [19, 1] + ] + }, + "symbol-placement": "line", + "symbol-spacing": 75 + }, + "paint": { + "icon-opacity": 0.5 + } + }, + { + "id": "highway-name-path", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "transportation_name", + "minzoom": 15.5, + "filter": ["==", "class", "path"], + "layout": { + "symbol-placement": "line", + "text-field": "{name:latin} {name:nonlatin}", + "text-font": ["Noto Sans Regular"], + "text-rotation-alignment": "map", + "text-size": { + "base": 1, + "stops": [ + [13, 12], + [14, 13] + ] + } + }, + "paint": { + "text-color": "hsl(30, 23%, 62%)", + "text-halo-color": "#f8f4f0", + "text-halo-width": 0.5 + } + }, + { + "id": "highway-name-minor", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "transportation_name", + "minzoom": 15, + "filter": [ + "all", + ["==", "$type", "LineString"], + ["in", "class", "minor", "service", "track"] + ], + "layout": { + "symbol-placement": "line", + "text-field": "{name:latin} {name:nonlatin}", + "text-font": ["Noto Sans Regular"], + "text-rotation-alignment": "map", + "text-size": { + "base": 1, + "stops": [ + [13, 12], + [14, 13] + ] + } + }, + "paint": { + "text-color": "#765", + "text-halo-blur": 0.5, + "text-halo-width": 1 + } + }, + { + "id": "highway-name-major", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "transportation_name", + "minzoom": 12.2, + "filter": ["in", "class", "primary", "secondary", "tertiary", "trunk"], + "layout": { + "symbol-placement": "line", + "text-field": "{name:latin} {name:nonlatin}", + "text-font": ["Noto Sans Regular"], + "text-rotation-alignment": "map", + "text-size": { + "base": 1, + "stops": [ + [13, 12], + [14, 13] + ] + } + }, + "paint": { + "text-color": "#765", + "text-halo-blur": 0.5, + "text-halo-width": 1 + } + }, + { + "id": "highway-shield", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "transportation_name", + "minzoom": 8, + "filter": [ + "all", + ["<=", "ref_length", 6], + ["==", "$type", "LineString"], + ["!in", "network", "us-interstate", "us-highway", "us-state"] + ], + "layout": { + "icon-image": "road_{ref_length}", + "icon-rotation-alignment": "viewport", + "icon-size": 1, + "symbol-placement": { + "base": 1, + "stops": [ + [10, "point"], + [11, "line"] + ] + }, + "symbol-spacing": 200, + "text-field": "{ref}", + "text-font": ["Noto Sans Regular"], + "text-rotation-alignment": "viewport", + "text-size": 10 + }, + "paint": {} + }, + { + "id": "highway-shield-us-interstate", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "transportation_name", + "minzoom": 7, + "filter": [ + "all", + ["<=", "ref_length", 6], + ["==", "$type", "LineString"], + ["in", "network", "us-interstate"] + ], + "layout": { + "icon-image": "{network}_{ref_length}", + "icon-rotation-alignment": "viewport", + "icon-size": 1, + "symbol-placement": { + "base": 1, + "stops": [ + [7, "point"], + [7, "line"], + [8, "line"] + ] + }, + "symbol-spacing": 200, + "text-field": "{ref}", + "text-font": ["Noto Sans Regular"], + "text-rotation-alignment": "viewport", + "text-size": 10 + }, + "paint": { + "text-color": "rgba(0, 0, 0, 1)" + } + }, + { + "id": "highway-shield-us-other", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "transportation_name", + "minzoom": 9, + "filter": [ + "all", + ["<=", "ref_length", 6], + ["==", "$type", "LineString"], + ["in", "network", "us-highway", "us-state"] + ], + "layout": { + "icon-image": "{network}_{ref_length}", + "icon-rotation-alignment": "viewport", + "icon-size": 1, + "symbol-placement": { + "base": 1, + "stops": [ + [10, "point"], + [11, "line"] + ] + }, + "symbol-spacing": 200, + "text-field": "{ref}", + "text-font": ["Noto Sans Regular"], + "text-rotation-alignment": "viewport", + "text-size": 10 + }, + "paint": { + "text-color": "rgba(0, 0, 0, 1)" + } + }, + { + "id": "airport-label-major", + "type": "symbol", + "source": "openmaptiles", + "source-layer": "aerodrome_label", + "minzoom": 10, + "filter": ["all", ["has", "iata"]], + "layout": { + "icon-image": "airport_11", + "icon-size": 1, + "text-anchor": "top", + "text-field": "{name:latin}\n{name:nonlatin}", + "text-font": ["Noto Sans Regular"], + "text-max-width": 9, + "text-offset": [0, 0.6], + "text-optional": true, + "text-padding": 2, + "text-size": 12, + "visibility": "visible" + }, + "paint": { + "text-color": "#666", + "text-halo-blur": 0.5, + "text-halo-color": "#ffffff", + "text-halo-width": 1 + } + }, + { + "id": "place-other", + "type": "symbol", + "metadata": { + "mapbox:group": "1444849242106.713" + }, + "source": "openmaptiles", + "source-layer": "place", + "filter": [ + "!in", + "class", + "city", + "town", + "village", + "state", + "country", + "continent" + ], + "layout": { + "text-field": "{name:latin}\n{name:nonlatin}", + "text-font": ["Noto Sans Bold"], + "text-letter-spacing": 0.1, + "text-max-width": 9, + "text-size": { + "base": 1.2, + "stops": [ + [12, 10], + [15, 14] + ] + }, + "text-transform": "uppercase", + "visibility": "visible" + }, + "paint": { + "text-color": "#633", + "text-halo-color": "rgba(255,255,255,0.8)", + "text-halo-width": 1.2 + } + }, + { + "id": "place-village", + "type": "symbol", + "metadata": { + "mapbox:group": "1444849242106.713" + }, + "source": "openmaptiles", + "source-layer": "place", + "filter": ["==", "class", "village"], + "layout": { + "text-field": "{name:latin}\n{name:nonlatin}", + "text-font": ["Noto Sans Regular"], + "text-max-width": 8, + "text-size": { + "base": 1.2, + "stops": [ + [10, 12], + [15, 22] + ] + }, + "visibility": "visible" + }, + "paint": { + "text-color": "#333", + "text-halo-color": "rgba(255,255,255,0.8)", + "text-halo-width": 1.2 + } + }, + { + "id": "place-town", + "type": "symbol", + "metadata": { + "mapbox:group": "1444849242106.713" + }, + "source": "openmaptiles", + "source-layer": "place", + "filter": ["==", "class", "town"], + "layout": { + "text-field": "{name:latin}\n{name:nonlatin}", + "text-font": ["Noto Sans Regular"], + "text-max-width": 8, + "text-size": { + "base": 1.2, + "stops": [ + [10, 14], + [15, 24] + ] + }, + "visibility": "visible" + }, + "paint": { + "text-color": "#333", + "text-halo-color": "rgba(255,255,255,0.8)", + "text-halo-width": 1.2 + } + }, + { + "id": "place-city", + "type": "symbol", + "metadata": { + "mapbox:group": "1444849242106.713" + }, + "source": "openmaptiles", + "source-layer": "place", + "filter": ["all", ["!=", "capital", 2], ["==", "class", "city"]], + "layout": { + "text-field": "{name:latin}\n{name:nonlatin}", + "text-font": ["Noto Sans Regular"], + "text-max-width": 8, + "text-size": { + "base": 1.2, + "stops": [ + [7, 14], + [11, 24] + ] + }, + "visibility": "visible" + }, + "paint": { + "text-color": "#333", + "text-halo-color": "rgba(255,255,255,0.8)", + "text-halo-width": 1.2 + } + }, + { + "id": "place-city-capital", + "type": "symbol", + "metadata": { + "mapbox:group": "1444849242106.713" + }, + "source": "openmaptiles", + "source-layer": "place", + "filter": ["all", ["==", "capital", 2], ["==", "class", "city"]], + "layout": { + "icon-image": "star_11", + "icon-size": 0.8, + "text-anchor": "left", + "text-field": "{name:latin}\n{name:nonlatin}", + "text-font": ["Noto Sans Regular"], + "text-max-width": 8, + "text-offset": [0.4, 0], + "text-size": { + "base": 1.2, + "stops": [ + [7, 14], + [11, 24] + ] + }, + "visibility": "visible" + }, + "paint": { + "text-color": "#333", + "text-halo-color": "rgba(255,255,255,0.8)", + "text-halo-width": 1.2 + } + }, + { + "id": "place-state", + "type": "symbol", + "metadata": { + "mapbox:group": "1444849242106.713" + }, + "source": "openmaptiles", + "source-layer": "place", + "filter": ["in", "class", "state"], + "layout": { + "text-field": "{name:latin}", + "text-font": ["Noto Sans Bold"], + "text-letter-spacing": 0.1, + "text-max-width": 9, + "text-size": { + "base": 1.2, + "stops": [ + [12, 10], + [15, 14] + ] + }, + "text-transform": "uppercase", + "visibility": "visible" + }, + "paint": { + "text-color": "#633", + "text-halo-color": "rgba(255,255,255,0.8)", + "text-halo-width": 1.2 + } + }, + { + "id": "place-country-other", + "type": "symbol", + "metadata": { + "mapbox:group": "1444849242106.713" + }, + "source": "openmaptiles", + "source-layer": "place", + "filter": [ + "all", + ["==", "class", "country"], + [">=", "rank", 3], + ["!has", "iso_a2"] + ], + "layout": { + "text-field": "{name:latin}", + "text-font": ["Noto Sans Italic"], + "text-max-width": 6.25, + "text-size": { + "stops": [ + [3, 11], + [7, 17] + ] + }, + "text-transform": "uppercase", + "visibility": "visible" + }, + "paint": { + "text-color": "#334", + "text-halo-blur": 1, + "text-halo-color": "rgba(255,255,255,0.8)", + "text-halo-width": 2 + } + }, + { + "id": "place-country-3", + "type": "symbol", + "metadata": { + "mapbox:group": "1444849242106.713" + }, + "source": "openmaptiles", + "source-layer": "place", + "filter": [ + "all", + ["==", "class", "country"], + [">=", "rank", 3], + ["has", "iso_a2"] + ], + "layout": { + "text-field": "{name:latin}", + "text-font": ["Noto Sans Bold"], + "text-max-width": 6.25, + "text-size": { + "stops": [ + [3, 11], + [7, 17] + ] + }, + "text-transform": "uppercase", + "visibility": "visible" + }, + "paint": { + "text-color": "#334", + "text-halo-blur": 1, + "text-halo-color": "rgba(255,255,255,0.8)", + "text-halo-width": 2 + } + }, + { + "id": "place-country-2", + "type": "symbol", + "metadata": { + "mapbox:group": "1444849242106.713" + }, + "source": "openmaptiles", + "source-layer": "place", + "filter": [ + "all", + ["==", "class", "country"], + ["==", "rank", 2], + ["has", "iso_a2"] + ], + "layout": { + "text-field": "{name:latin}", + "text-font": ["Noto Sans Bold"], + "text-max-width": 6.25, + "text-size": { + "stops": [ + [2, 11], + [5, 17] + ] + }, + "text-transform": "uppercase", + "visibility": "visible" + }, + "paint": { + "text-color": "#334", + "text-halo-blur": 1, + "text-halo-color": "rgba(255,255,255,0.8)", + "text-halo-width": 2 + } + }, + { + "id": "place-country-1", + "type": "symbol", + "metadata": { + "mapbox:group": "1444849242106.713" + }, + "source": "openmaptiles", + "source-layer": "place", + "filter": [ + "all", + ["==", "class", "country"], + ["==", "rank", 1], + ["has", "iso_a2"] + ], + "layout": { + "text-field": "{name:latin}", + "text-font": ["Noto Sans Bold"], + "text-max-width": 6.25, + "text-size": { + "stops": [ + [1, 11], + [4, 17] + ] + }, + "text-transform": "uppercase", + "visibility": "visible" + }, + "paint": { + "text-color": "#334", + "text-halo-blur": 1, + "text-halo-color": "rgba(255,255,255,0.8)", + "text-halo-width": 2 + } + }, + { + "id": "place-continent", + "type": "symbol", + "metadata": { + "mapbox:group": "1444849242106.713" + }, + "source": "openmaptiles", + "source-layer": "place", + "maxzoom": 1, + "filter": ["==", "class", "continent"], + "layout": { + "text-field": "{name:latin}", + "text-font": ["Noto Sans Bold"], + "text-max-width": 6.25, + "text-size": 14, + "text-transform": "uppercase", + "visibility": "visible" + }, + "paint": { + "text-color": "#334", + "text-halo-blur": 1, + "text-halo-color": "rgba(255,255,255,0.8)", + "text-halo-width": 2 + } + } + ], + "id": "bright" +} diff --git a/src/app/api/deploiement-stats/[z]/[x]/[y].pbf/route.ts b/src/app/api/deploiement-stats/[z]/[x]/[y].pbf/route.ts new file mode 100644 index 000000000..474f9e2c0 --- /dev/null +++ b/src/app/api/deploiement-stats/[z]/[x]/[y].pbf/route.ts @@ -0,0 +1,31 @@ +import { computeStats, fetchStatsData } from '@/lib/deploiement-stats' +import { getCachedData } from '@/utils/cache' +import { NextRequest, NextResponse } from 'next/server' +import geojsonVt from 'geojson-vt' +// @ts-ignore +import vtpbf from 'vt-pbf' + +const computeTiles = async () => { + const statsData = await getCachedData('stats-data', fetchStatsData) + const featureCollection = await computeStats(statsData, []) + + return geojsonVt(featureCollection as geojsonVt.Data, { indexMaxZoom: 9 }) +} + +export async function GET(request: NextRequest, { params }: { params: { x: string, y: string, z: string } }) { + const tiles = await getCachedData('deploiement-tiles', computeTiles) + + const z = Number.parseInt(params.z, 10) + const x = Number.parseInt(params.x, 10) + const y = Number.parseInt(params.y, 10) + + const tile = tiles.getTile(z, x, y) + + if (z > 14 || !tile) { + return new Response('', { status: 200 }) + } + + const pbf = vtpbf.fromGeojsonVt({ communes: tile }) + + return new NextResponse(pbf, { headers: { 'Content-Type': 'application/x-protobuf' } }) +} diff --git a/src/app/api/deploiement-stats/route.ts b/src/app/api/deploiement-stats/route.ts new file mode 100644 index 000000000..117924fc4 --- /dev/null +++ b/src/app/api/deploiement-stats/route.ts @@ -0,0 +1,12 @@ +import { computeStats, fetchStatsData } from '@/lib/deploiement-stats' +import { getCachedData } from '@/utils/cache' +import { NextRequest, NextResponse } from 'next/server' + +export async function GET(request: NextRequest) { + const codesCommune = request.nextUrl.searchParams.get('codesCommune') || '' + const codesCommuneArr = codesCommune.split(',') || [] + const statsData = await getCachedData('stats-data', fetchStatsData) + const featureCollection = await computeStats(statsData, codesCommuneArr) + + return NextResponse.json(featureCollection) +} diff --git a/src/app/commune/[codeCommune]/page.tsx b/src/app/commune/[codeCommune]/page.tsx index b0f6e5a15..757dc30fa 100644 --- a/src/app/commune/[codeCommune]/page.tsx +++ b/src/app/commune/[codeCommune]/page.tsx @@ -5,11 +5,11 @@ import { StyledCommunePage } from './page.styles' import { getRevisionDetails, getRevisions } from '@/lib/api-depot' import CardWrapper from '@/components/CardWrapper' import { Table } from '@codegouvfr/react-dsfr/Table' -import { CommuneNavigation } from './CommuneNavigation' +import { CommuneNavigation } from '../../../components/Commune/CommuneNavigation' import Image from 'next/image' import { getCommune as getAPIGeoCommune, getEPCI } from '@/lib/api-geo' import { getCommuneFlag } from '@/lib/api-wikidata' -import { CommuneDownloadSection } from './CommuneDownloadSection' +import { CommuneDownloadSection } from '../../../components/Commune/CommuneDownloadSection' import { formatFr } from '@/lib/array' interface CommunePageProps { @@ -59,7 +59,7 @@ export default async function CommunePage({ params }: CommunePageProps) { Département
- {commune.departement.nom} + {commune.departement.nom}
@@ -67,7 +67,7 @@ export default async function CommunePage({ params }: CommunePageProps) { Intercommunalité
- {EPCI?.nom || '-'} + {EPCI ? {EPCI.nom} : '-'}
diff --git a/src/app/commune/page.tsx b/src/app/commune/page.tsx index 7959fda2b..211013f27 100644 --- a/src/app/commune/page.tsx +++ b/src/app/commune/page.tsx @@ -1,17 +1,26 @@ 'use client' -import CommuneInput from '@/components/CommuneInput' +import AutocompleteInput from '@/components/Autocomplete/AutocompleteInput' import Section from '@/components/Section' +import { getCommunes } from '@/lib/api-geo' import { useRouter } from 'next/navigation' +import { useCallback } from 'react' export default function VotreCommuneEtSaBALPage() { const router = useRouter() + const fetchCommunes = useCallback((query: string) => getCommunes({ q: query }), []) return ( <>
- commune && router.push(`/commune/${commune.code}`)} /> + commune && router.push(`/commune/${commune.code}`)} + placeholder="Nom ou code INSEE, exemple : 64256 ou Hasparren" + />
diff --git a/src/app/deploiement-bal/page.tsx b/src/app/deploiement-bal/page.tsx new file mode 100644 index 000000000..7ccd53232 --- /dev/null +++ b/src/app/deploiement-bal/page.tsx @@ -0,0 +1,55 @@ +import { getDepartements, getEPCI } from '@/lib/api-geo' +import departementCenterMap from '@/data/departement-center.json' +import { getStats } from '@/lib/api-ban' +import DeploiementBALDashboard from '../../components/DeploiementBAL/DeploiementBALDashboard' +import Section from '@/components/Section' +import { mapToSearchResult } from '@/lib/deploiement-stats' + +export type DeploiementBALSearchResultEPCI = { + type: 'EPCI' + code: string + nom: string + center: { type: string, coordinates: [number, number] } + contour: { type: string, coordinates: number[][][] } +} + +export type DeploiementBALSearchResultDepartement = { + type: 'Département' + code: string + nom: string + center: { type: string, coordinates: [number, number] } +} + +export type DeploiementBALSearchResult = DeploiementBALSearchResultEPCI | DeploiementBALSearchResultDepartement + +export default async function DeploiementBALPage({ searchParams }: { searchParams: Record }) { + const stats = await getStats() + const departements = await getDepartements() + const departementsWithCenter = departements.map(({ code, ...rest }) => { + const { geometry } = (departementCenterMap as any)[code] + + return { + ...rest, + code, + centre: geometry, + } + }) + + let initialFilter: DeploiementBALSearchResult | null = null + if (searchParams.departement) { + const codeDepartement = searchParams.departement + const dpt = departementsWithCenter.find(({ code }) => code === codeDepartement) + initialFilter = mapToSearchResult([dpt], 'Département')[0] + } + else if (searchParams.epci) { + const codeEPCI = searchParams.epci + const epci = await getEPCI(codeEPCI as string, ['centre', 'contour']) + initialFilter = mapToSearchResult([epci], 'EPCI')[0] + } + + return ( +
+ +
+ ) +} diff --git a/src/app/global.styles.ts b/src/app/global.styles.ts index d150101ab..843efea7d 100644 --- a/src/app/global.styles.ts +++ b/src/app/global.styles.ts @@ -1,4 +1,5 @@ import { createGlobalStyle } from 'styled-components' +import 'maplibre-gl/dist/maplibre-gl.css' const GlobalStyle = createGlobalStyle` * { diff --git a/src/components/CommuneInput.tsx b/src/components/Autocomplete/AutocompleteInput.tsx similarity index 64% rename from src/components/CommuneInput.tsx rename to src/components/Autocomplete/AutocompleteInput.tsx index c736f5e20..334a4269a 100644 --- a/src/components/CommuneInput.tsx +++ b/src/components/Autocomplete/AutocompleteInput.tsx @@ -1,46 +1,46 @@ 'use client' -import { useCallback } from 'react' -import Autocomplete, { StyledResultList } from './Autocomplete' -import { Commune } from '@/types/api-geo.types' -import { getCommunes } from '@/lib/api-geo' +import Autocomplete, { StyledResultList } from '.' import Button from '@codegouvfr/react-dsfr/Button' import styled from 'styled-components' const StyledWrapper = styled.div` - .commune-name-wrapper { + label { + margin-bottom: 0.5rem; + text-align: left; + } + .name-wrapper { display: flex; align-items: center; - margin-top: 0.5rem; + height: 40px; > button { margin-left: 1rem; } } ` -export interface CommuneInputProps { +export interface AutocompleteInputProps { label?: string - value?: Partial + value: { nom: string, code: string } | null placeholder?: string - onChange: (value?: Commune) => void + onChange: (value: { nom: string, code: string } | null) => void + fetchResults: (query: string) => Promise<{ nom: string, code: string }[]> } -export default function CommuneInput({ value, onChange, label, placeholder }: CommuneInputProps) { - const fetchCommunes = useCallback((query: string) => getCommunes({ q: query }), []) - +export default function AutocompleteInput({ value, onChange, label, fetchResults, placeholder }: AutocompleteInputProps) { return value ? ( - + {label && ( )} -
+
{value.nom} ({value.code}) + + ) +} diff --git a/src/components/DeploiementBAL/TabMesAdresses.tsx b/src/components/DeploiementBAL/TabMesAdresses.tsx new file mode 100644 index 000000000..a6d376fda --- /dev/null +++ b/src/components/DeploiementBAL/TabMesAdresses.tsx @@ -0,0 +1,130 @@ +'use client' + +import { useEffect, useState, useMemo, useCallback } from 'react' +import { groupBy } from 'lodash' +import { Doughnut } from 'react-chartjs-2' +import { Chart as ChartJS, ArcElement, Tooltip, Legend } from 'chart.js' + +import { getStatsBals, getBalsStatus } from '@/lib/api-mes-adresses' +import CommuneBALList from './CommuneBALList' +import styled from 'styled-components' +import { BaseAdresseLocale, BaseAdresseLocaleStatus } from '@/types/api-mes-adresses.types' +import { toolsColors } from '@/theme/theme' + +ChartJS.register(ArcElement, Tooltip, Legend) + +const options = { + height: 50, + width: 50, + responsive: true, + plugins: { + legend: { + display: true, + }, + tooltip: { + enabled: true, + }, + }, +} + +const initialStats = { + labels: [ + 'Publiée', + 'Brouillon', + ], + datasets: [{ + data: [0, 0, 0], + backgroundColor: [ + toolsColors.mesAdresses, + `${toolsColors.mesAdresses}80`, + ], + hoverOffset: 4, + }], +} + +const StyledWrapper = styled.div` + margin-bottom: 2rem; + display: flex; + justify-content: space-around; + +` + +interface TabMesAdressesProps { + filteredCodesCommmune: string[] +} + +export default function TabMesAdresses({ filteredCodesCommmune }: TabMesAdressesProps) { + const [bals, setBals] = useState[]>([]) + const [dataStats, setDataStats] = useState(initialStats) + + const setDataStatsWithBal = useCallback((data: any) => { + setDataStats((dataStats) => { + return { + ...dataStats, + datasets: [ + { + ...dataStats.datasets[0], + data, + }, + ], + } + }) + }, []) + + useEffect(() => { + async function loadBalsStatus() { + const balsStatus = await getBalsStatus() + const statusData = [ + balsStatus.find(({ status }: Partial) => status === BaseAdresseLocaleStatus.PUBLISHED)?.count || 0, + balsStatus.find(({ status }: Partial) => status === BaseAdresseLocaleStatus.DRAFT)?.count || 0, + ] + setDataStatsWithBal(statusData) + } + + async function loadBals() { + const fields = ['id', 'commune', 'status', 'nom', 'updatedAt', 'sync'] + const balsFiltered = await getStatsBals(fields, filteredCodesCommmune) + setBals(balsFiltered) + const statusData = [ + balsFiltered.filter(({ status }) => status === BaseAdresseLocaleStatus.PUBLISHED).length, + balsFiltered.filter(({ status }) => status === BaseAdresseLocaleStatus.DRAFT).length, + ] + setDataStatsWithBal(statusData) + } + + if (filteredCodesCommmune.length <= 0) { + loadBalsStatus() + } + else { + loadBals() + } + }, [filteredCodesCommmune, setDataStatsWithBal]) + + const balsByCommune = useMemo(() => { + return groupBy(bals, 'commune') + }, [bals]) + + return ( + +
+

Statut des BAL(s)

+ +
+
+

Liste des Communes

+ {filteredCodesCommmune.length > 0 + ? ( +
+ {Object.keys(balsByCommune).map((codeCommune) => { + const balsCommune = balsByCommune[codeCommune] || [] + return + })} +
+ ) + : ( +

Aucun Département ou EPCI sélectionné

+ )} +
+
+ ) +} diff --git a/src/components/DownloadCard.tsx b/src/components/DownloadCard/index.tsx similarity index 100% rename from src/components/DownloadCard.tsx rename to src/components/DownloadCard/index.tsx diff --git a/src/components/IconCard.tsx b/src/components/IconCard/index.tsx similarity index 100% rename from src/components/IconCard.tsx rename to src/components/IconCard/index.tsx diff --git a/src/components/ImageInput.tsx b/src/components/ImageInput/index.tsx similarity index 100% rename from src/components/ImageInput.tsx rename to src/components/ImageInput/index.tsx diff --git a/src/components/Map/FullScreenControl.tsx b/src/components/Map/FullScreenControl.tsx new file mode 100644 index 000000000..7a7d0070d --- /dev/null +++ b/src/components/Map/FullScreenControl.tsx @@ -0,0 +1,16 @@ +import maplibregl from 'maplibre-gl' +import { ControlPosition, useControl } from 'react-map-gl/maplibre' + +type FullScreenControlProps = { + position?: ControlPosition +} + +export function FullScreenControl(props: FullScreenControlProps) { + const { position = 'top-right' } = props + + useControl(() => new maplibregl.FullscreenControl(), { + position, + }) + + return null +} diff --git a/src/components/Map/map.config.ts b/src/components/Map/map.config.ts new file mode 100644 index 000000000..c35ceaf25 --- /dev/null +++ b/src/components/Map/map.config.ts @@ -0,0 +1,2 @@ +export const DEFAULT_CENTER: [number, number] = [1.7, 46.9] +export const DEFAULT_ZOOM = 4.4 diff --git a/src/components/MultiSelectInput.tsx b/src/components/MultiSelectInput/index.tsx similarity index 100% rename from src/components/MultiSelectInput.tsx rename to src/components/MultiSelectInput/index.tsx diff --git a/src/components/PartenairesDeLaCharte/CandidacyForm/index.tsx b/src/components/PartenairesDeLaCharte/CandidacyForm/index.tsx index df7b9238c..d08057bf6 100644 --- a/src/components/PartenairesDeLaCharte/CandidacyForm/index.tsx +++ b/src/components/PartenairesDeLaCharte/CandidacyForm/index.tsx @@ -1,4 +1,4 @@ -import { FormEvent, useState } from 'react' +import { FormEvent, useCallback, useState } from 'react' import { capitalize } from 'lodash' import SelectInput from '../../SelectInput' @@ -9,8 +9,9 @@ import Button from '@codegouvfr/react-dsfr/Button' import { candidateToPartenairesDeLaCharte } from '@/lib/api-bal-admin' import { Commune, Departement } from '@/types/api-geo.types' import { StyledForm } from './CandidacyForm.styles' -import CommuneInput from '@/components/CommuneInput' import { CandidatePartenaireDeLaCharteType, PartenaireDeLaCharteOrganismeTypeEnum, PartenaireDeLaCharteServiceEnum, PartenaireDeLaCharteTypeEnum } from '@/types/partenaire.types' +import { getCommunes } from '@/lib/api-geo' +import AutocompleteInput from '@/components/Autocomplete/AutocompleteInput' const typeOptions = [ { value: PartenaireDeLaCharteTypeEnum.COMMUNE, label: 'Commune' }, @@ -32,7 +33,7 @@ interface CandidacyFormProps { } function CandidacyForm({ onClose, services, departements }: CandidacyFormProps) { - const [selectedCommune, setSelectedCommune] = useState() + const [selectedCommune, setSelectedCommune] = useState(null) const [formData, setFormData] = useState({ type: PartenaireDeLaCharteTypeEnum.COMMUNE, organismeType: PartenaireDeLaCharteOrganismeTypeEnum.EPCI, @@ -61,12 +62,14 @@ function CandidacyForm({ onClose, services, departements }: CandidacyFormProps) const servicesOptions = Object.values(services).map(value => ({ value, label: capitalize(value) })) const departementsOptions = departements.map(departement => ({ value: departement.code, label: `${departement.code} - ${departement.nom}` })) + const fetchCommunes = useCallback((query: string) => getCommunes({ q: query }), []) + const handleEdit = (property: keyof CandidatePartenaireDeLaCharteType) => (event: any) => { const { value } = event.target setFormData(state => ({ ...state, [property]: value })) } - const handleSelectCommune = (commune?: Commune) => { + const handleSelectCommune = (commune: Commune | null) => { if (commune) { setSelectedCommune(commune) setFormData(state => ({ @@ -78,7 +81,7 @@ function CandidacyForm({ onClose, services, departements }: CandidacyFormProps) })) } else { - setSelectedCommune(undefined) + setSelectedCommune(null) setFormData(state => ({ ...state, name: '', @@ -142,7 +145,15 @@ function CandidacyForm({ onClose, services, departements }: CandidacyFormProps) })) }} /> - {formData.type === PartenaireDeLaCharteTypeEnum.COMMUNE && } + {formData.type === PartenaireDeLaCharteTypeEnum.COMMUNE && ( + handleSelectCommune(value as Commune | null)} + placeholder="Rechercher ma commune" + /> + ) } {formData.type === PartenaireDeLaCharteTypeEnum.ORGANISME && ( <> (null) const [currentPage, setCurrentPage] = useState(getPageFromHash(hash)) - const [selectedCommune, setSelectedCommune] = useState() + const [selectedCommune, setSelectedCommune] = useState(null) const [selectedServices, setSelectedServices] = useState([]) const [partenaires, setPartenaires] = useState(initialPartenaires) + const fetchCommunes = useCallback((query: string) => getCommunes({ q: query }), []) + const updatePartenaires = useCallback(async (page: number) => { const updatedPartenaires = await getPartenairesDeLaCharte({ ...filter, @@ -86,7 +89,15 @@ export default function SearchPartenaire({
- {searchBy === 'perimeter' && ( setSelectedCommune(commune)} />)} + {searchBy === 'perimeter' && ( + setSelectedCommune(commune as Commune | null)} + placeholder="Rechercher ma commune" + /> + ) } {searchBy === 'name' && ( distanceCD ? distanceAB : distanceCD + + if (biggestDistance > 0 && biggestDistance < 0.4) { + return maxZoomLevel + } + + if (biggestDistance > 0.4 && biggestDistance < 0.7) { + return maxZoomLevel - 1 + } + + return maxZoomLevel - 2 +} + +interface StatsDeploiement { + initialStats: BANStats + initialFilter: DeploiementBALSearchResult | null +} + +export function useStatsDeploiement({ initialStats, initialFilter }: StatsDeploiement) { + const [isLoading, setIsLoading] = useState(false) + const [stats, setStats] = useState(initialStats) + const [filter, setFilter] = useState(initialFilter) + const [filteredCodesCommmune, setFilteredCodesCommune] = useState([]) + const [geometry, setGeometry] = useState({ + center: DEFAULT_CENTER, + zoom: DEFAULT_ZOOM, + }) + + useEffect(() => { + async function updateStats() { + setIsLoading(true) + try { + let filteredCommunes: Commune[] = [] + if (filter?.type === 'EPCI') { + filteredCommunes = await getEpciCommunes(filter.code) + setGeometry({ + center: filter.center.coordinates, + zoom: estimateZoomFromContour(filter.contour, 10), + }) + } + else if (filter?.type === 'Département') { + filteredCommunes = await getDepartementCommunes(filter.code) + setGeometry({ + center: filter.center.coordinates, + zoom: 8, + }) + } + + let filteredCodesCommmune = filteredCommunes.map(({ code }) => code) + const communeWithArrondissement = filteredCodesCommmune.find(code => communesWithArrondissements[parseInt(code) as keyof typeof communesWithArrondissements]) + + if (communeWithArrondissement) { + const districts = await getCommunes({ q: communesWithArrondissements[parseInt(communeWithArrondissement) as keyof typeof communesWithArrondissements], type: 'arrondissement-municipal' }) + filteredCodesCommmune.splice(filteredCodesCommmune.indexOf(communeWithArrondissement), 1) + filteredCodesCommmune = [...filteredCodesCommmune, ...districts.map(({ code }) => code)] + } + + setFilteredCodesCommune(filteredCodesCommmune) + const filteredStats = await getFilteredStats(filteredCodesCommmune) + + setStats(filteredStats) + } + catch (err) { + console.error(err) + } + finally { + setIsLoading(false) + } + } + + if (filter === null) { + setStats(initialStats) + setGeometry({ + center: DEFAULT_CENTER, + zoom: DEFAULT_ZOOM, + }) + setFilteredCodesCommune([]) + } + else { + updateStats() + } + }, [filter, initialStats]) + + const formatedStats = useMemo(() => { + const total = stats.total || stats.france + + // Calcul population couverte + const populationCouvertePercent = Math.round((stats.bal.populationCouverte * 100) / total.population) + const allPopulationCouverte = 100 - Math.round((stats.bal.populationCouverte * 100) / total.population) + const dataPopulationCouverte = toCounterData(populationCouvertePercent, allPopulationCouverte) + + // Calcul communes couvertes + const communesCouvertesPercent = Math.round((stats.bal.nbCommunesCouvertes * 100) / total.nbCommunes) + const allCommunesCouvertesPercent = 100 - Math.round((stats.bal.nbCommunesCouvertes * 100) / total.nbCommunes) + const dataCommunesCouvertes = toCounterData(communesCouvertesPercent, allCommunesCouvertesPercent) + + // Calcul communes avec l'identifiant BAN + const communesAvecBanIdPercent = Math.round((stats.ban.nbCommunesAvecBanId * 100) / total.nbCommunes) + const allCommunesAvecBanIdPercent = 100 - Math.round((stats.ban.nbCommunesAvecBanId * 100) / total.nbCommunes) + const dataCommunesAvecBanId = toCounterData(communesAvecBanIdPercent, allCommunesAvecBanIdPercent) + + // Calcul adresses gerees dans la BAL + const adressesGereesBALPercent = Math.round((stats.bal.nbAdresses * 100) / stats.ban.nbAdresses) + const allAdressesGereesBALPercent = 100 - Math.round((stats.bal.nbAdresses * 100) / stats.ban.nbAdresses) + const dataAdressesGereesBAL = toCounterData(adressesGereesBALPercent, allAdressesGereesBALPercent) + + // Calcul adresses certifiees + const adressesCertifieesPercent = Math.round((stats.bal.nbAdressesCertifiees * 100) / stats.ban.nbAdresses) + const allAdressesCertifieesPercent = 100 - Math.round((stats.bal.nbAdressesCertifiees * 100) / stats.ban.nbAdresses) + const dataAdressesCertifiees = toCounterData(adressesCertifieesPercent, allAdressesCertifieesPercent) + + // Calcul adresses avec identifiant BAN + const adressesAvecBanIdPercent = Math.round((stats.ban.nbAdressesAvecBanId * 100) / stats.ban.nbAdresses) + const allAdressesAvecBanIdPercent = 100 - Math.round((stats.ban.nbAdressesAvecBanId * 100) / stats.ban.nbAdresses) + const dataAdressesAvecBanId = toCounterData(adressesAvecBanIdPercent, allAdressesAvecBanIdPercent) + + return { + populationCouvertePercent, + allPopulationCouverte, + dataPopulationCouverte, + communesCouvertesPercent, + allCommunesCouvertesPercent, + dataCommunesCouvertes, + adressesGereesBALPercent, + allAdressesGereesBALPercent, + dataAdressesGereesBAL, + adressesCertifieesPercent, + allAdressesCertifieesPercent, + dataAdressesCertifiees, + communesAvecBanIdPercent, + allCommunesAvecBanIdPercent, + dataCommunesAvecBanId, + adressesAvecBanIdPercent, + allAdressesAvecBanIdPercent, + dataAdressesAvecBanId, + total, + } + }, [stats]) + + return { + stats, + geometry, + formatedStats, + filter, + setFilter, + filteredCodesCommmune, + isLoading, + } +} diff --git a/src/instrumentation.ts b/src/instrumentation.ts new file mode 100644 index 000000000..fed5c4f32 --- /dev/null +++ b/src/instrumentation.ts @@ -0,0 +1,6 @@ +import { downloadContoursCommunes } from './utils/contours-communes' + +// This function is called once when the application starts +export async function register() { + await downloadContoursCommunes() +} diff --git a/src/lib/api-ban.ts b/src/lib/api-ban.ts index 09d634150..b03f87694 100644 --- a/src/lib/api-ban.ts +++ b/src/lib/api-ban.ts @@ -1,11 +1,12 @@ import { BANCommune, BANVoie } from '@/types/api-ban.types' +import { BANStats } from '@/types/api-ban.types' import { customFetch } from './fetch' if (!process.env.NEXT_PUBLIC_API_BAN_URL) { throw new Error('NEXT_PUBLIC_API_BAN_URL is not defined') } -export function getStats() { +export function getStats(): Promise { return customFetch(`${process.env.NEXT_PUBLIC_API_BAN_URL}/ban/stats`) } @@ -43,3 +44,15 @@ export function assemblageSources(voies: BANVoie[]) { .filter(source => Boolean(sources[source])) .map(source => sources[source] || source) } + +export function getFilteredStats(codesCommune: string[]): Promise { + return customFetch(`${process.env.NEXT_PUBLIC_API_BAN_URL}/ban/stats`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json; charset=UTF-8', + }, + body: JSON.stringify({ + codesCommune: codesCommune || [], + }), + }) +} diff --git a/src/lib/api-geo.ts b/src/lib/api-geo.ts index 98c9119d6..2d167020d 100644 --- a/src/lib/api-geo.ts +++ b/src/lib/api-geo.ts @@ -1,11 +1,11 @@ -import { Commune } from '@/types/api-geo.types' +import { Commune, Departement, EPCI } from '@/types/api-geo.types' import { customFetch } from './fetch' if (!process.env.NEXT_PUBLIC_API_GEO_URL) { throw new Error('NEXT_PUBLIC_API_GEO_URL is not defined') } -export function getDepartements() { +export function getDepartements(): Promise { return customFetch(`${process.env.NEXT_PUBLIC_API_GEO_URL}/departements`) } @@ -21,8 +21,14 @@ export function getCommune(code: string): Promise { return customFetch(`${process.env.NEXT_PUBLIC_API_GEO_URL}/communes/${code}`) } -export function getEPCI(code: string) { - return customFetch(`${process.env.NEXT_PUBLIC_API_GEO_URL}/epcis/${code}`) +export function getEPCI(code: string, fields?: string[]): Promise { + const url = new URL(`${process.env.NEXT_PUBLIC_API_GEO_URL}/epcis/${code}`) + + if (fields) { + url.searchParams.append('fields', fields.toString()) + } + + return customFetch(url) } export function getCommunes(args: any): Promise { @@ -48,3 +54,29 @@ export function getCommunes(args: any): Promise { return customFetch(url) } + +export function getEpcis({ q, limit, fields }: { q: string, limit: number, fields: string[] }): Promise { + const url = new URL(`${process.env.NEXT_PUBLIC_API_GEO_URL}/epcis`) + + if (q) { + url.searchParams.append('nom', q) + } + + if (fields) { + url.searchParams.append('fields', fields.toString()) + } + + if (limit) { + url.searchParams.append('limit', limit.toString()) + } + + return customFetch(url.toString()) +} + +export function getEpciCommunes(code: string): Promise { + return customFetch(`${process.env.NEXT_PUBLIC_API_GEO_URL}/epcis/${code}/communes`) +} + +export function getDepartementCommunes(code: string): Promise { + return customFetch(`${process.env.NEXT_PUBLIC_API_GEO_URL}/departements/${code}/communes`) +} diff --git a/src/lib/api-mes-adresses.ts b/src/lib/api-mes-adresses.ts new file mode 100644 index 000000000..2d697f58b --- /dev/null +++ b/src/lib/api-mes-adresses.ts @@ -0,0 +1,25 @@ +import { BaseAdresseLocale } from '@/types/api-mes-adresses.types' +import { customFetch } from './fetch' + +if (!process.env.NEXT_PUBLIC_BAL_API_URL) { + throw new Error('NEXT_PUBLIC_BAL_API_URL is not defined') +} + +export async function getStatsBals(fields: string[], codeCommunes: string[]): Promise[]> { + const url = new URL(`${process.env.NEXT_PUBLIC_BAL_API_URL}/stats/bals`) + for (const field of fields) { + url.searchParams.append('fields', field) + } + const body = JSON.stringify({ codeCommunes }) + return customFetch(url, { + method: 'POST', + body, + headers: { + 'Content-Type': 'application/json', + }, + }) +} + +export async function getBalsStatus() { + return customFetch(`${process.env.NEXT_PUBLIC_BAL_API_URL}/stats/bals/status`) +} diff --git a/src/lib/deploiement-stats.ts b/src/lib/deploiement-stats.ts new file mode 100644 index 000000000..19a688c10 --- /dev/null +++ b/src/lib/deploiement-stats.ts @@ -0,0 +1,139 @@ +import { customFetch } from './fetch' +import { keyBy, groupBy } from 'lodash' +import { getContourCommune } from '../utils/contours-communes' +import { DeploiementBALSearchResult } from '@/app/deploiement-bal/page' + +if (!process.env.NEXT_PUBLIC_API_BAN_URL) { + throw new Error('NEXT_PUBLIC_API_BAN_URL is not defined in the environment') +} + +if (!process.env.NEXT_PUBLIC_API_DEPOT_URL) { + throw new Error('NEXT_PUBLIC_API_DEPOT_URL is not defined in the environment') +} + +if (!process.env.NEXT_PUBLIC_BAL_API_URL) { + throw new Error('NEXT_PUBLIC_BAL_API_URL is not defined in the environment') +} + +function spaceThousands(number: number) { + return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' ') +} + +function getPercentage(value: number, totalValue: number) { + const percentage = (value * 100) / totalValue + const roundedPercentage = Math.floor(percentage * 10) / 10 + return (roundedPercentage ? String(roundedPercentage).replace('.', ',') : roundedPercentage) || 0 +} + +type RevisionSummary = { + _id: string + codeCommune: string + client: { + _id: string + id: string + nom: string + mandataire: string + } + publishedAt: string +} + +type CommuneSummary = { + codeCommune: string + departement: string + nbLieuxDits: number + nbNumeros: number + nbVoies: number + nomCommune: string + population: number + region: string + typeComposition: string + analyseAdressage: { + nbAdressesAttendues: number + ratio: number + deficitAdresses: boolean + } + nbNumerosCertifies: number + composedAt: string + dateRevision: string + idRevision: string +} + +export async function fetchStatsData() { + const currentRevisions: RevisionSummary[] = await customFetch(`${process.env.NEXT_PUBLIC_API_DEPOT_URL}/current-revisions`) + const communesSummary: CommuneSummary[] = await customFetch(`${process.env.NEXT_PUBLIC_API_BAN_URL}/api/communes-summary`) + const bals = await customFetch(`${process.env.NEXT_PUBLIC_BAL_API_URL}/stats/bals?fields=id&fields=commune&fields=status`, { method: 'POST' }) + + return { currentRevisions, communesSummary, bals } +} + +function computeStatusBals(bals: { status: 'published' | 'replaced' | 'draft' }[] = []) { + if (bals.map(b => b.status).includes('published')) { + return 'published' + } + + if (bals.map(b => b.status).includes('replaced')) { + return 'replaced' + } + + if (bals.map(b => b.status).includes('draft')) { + return 'draft' + } + + return 'unknown' +} + +export function createFeature(communeWithContour: any, currentRevisionsIndex: Record, communesSummaryIndex: Record, communesBalsIndex: Record) { + const { properties: { code, nom }, geometry } = communeWithContour + const revisions = currentRevisionsIndex[code] + const summary = communesSummaryIndex[code] + const nbNumeros = spaceThousands(summary.nbNumeros) + const certificationPercentage = getPercentage(summary.nbNumerosCertifies, summary.nbNumeros) + const hasBAL = summary.typeComposition === 'bal' + const statusBals = computeStatusBals(communesBalsIndex[code]) + + const feature = { + type: 'Feature', + properties: { + nom, + code, + nbNumeros, + hasBAL, + certificationPercentage, + ...((hasBAL && revisions) + ? { + idClient: revisions.client._id || '', + nomClient: revisions.client.nom || '', + } + : {}), + statusBals, + }, + geometry, + } + + return feature +} + +export async function computeStats({ currentRevisions, communesSummary, bals }: { currentRevisions: RevisionSummary[], communesSummary: CommuneSummary[], bals: any }, codesCommune: string[]) { + const currentRevisionsIndex = keyBy(currentRevisions, 'codeCommune') + const communesSummaryIndex = keyBy(communesSummary, 'codeCommune') + const communesBalsIndex = groupBy(bals, 'commune') + + const communes = codesCommune?.length > 0 ? new Set(codesCommune) : new Set([...currentRevisions.map(c => c.codeCommune), ...communesSummary.map(c => c.codeCommune)]) + + let communesWithContours = [] + for (const codeCommune of communes) { + const communeWithContours = await getContourCommune(codeCommune) + if (communeWithContours) { + communesWithContours.push(communeWithContours) + } + } + + return { + type: 'FeatureCollection', + features: communesWithContours + .filter(communeWithContour => communesSummaryIndex[communeWithContour.properties.code]) + .map(communeWithContour => createFeature(communeWithContour, currentRevisionsIndex, communesSummaryIndex, communesBalsIndex)), + } +} + +export const mapToSearchResult = (values: any[], type: 'EPCI' | 'Département'): DeploiementBALSearchResult[] => values.map(({ code, nom, centre, contour }) => ({ code, type, nom, center: centre, contour })) diff --git a/src/theme/theme.ts b/src/theme/theme.ts index 2733e2f13..51354e320 100644 --- a/src/theme/theme.ts +++ b/src/theme/theme.ts @@ -24,4 +24,11 @@ const theme = { export type ColorTheme = keyof typeof theme.colors +export const toolsColors = { + mesAdresses: '#469c76', + moissonneur: '#E69F00', + api: '#F0E442', + formulaire: '#0072B2', +} + export default theme diff --git a/src/types/api-ban.types.ts b/src/types/api-ban.types.ts index 05ae3339f..47dd6a8b4 100644 --- a/src/types/api-ban.types.ts +++ b/src/types/api-ban.types.ts @@ -32,3 +32,47 @@ export type BANCommune = { dateRevision: string voies: BANVoie[] } + +export type BANStats = { + total: { + population: number + nbCommunes: number + } + france: { + population: number + nbCommunes: number + } + ban: { + nbAdresses: number + nbAdressesCertifiees: number + nbCommunesCouvertes: number + populationCouverte: number + nbCommunesIdSocle: number + nbAdressesIdSocle: number + _id: string + nbAdressesAvecBanId: number + nbCommunesAvecBanId: number + } + bal: { + nbAdresses: number + nbAdressesCertifiees: number + nbCommunesCouvertes: number + populationCouverte: number + nbCommunesIdSocle: number + nbAdressesIdSocle: number + _id: string + nbAdressesAvecBanId: number + nbCommunesAvecBanId: number + } + assemblage: { + nbAdresses: number + nbAdressesCertifiees: number + nbCommunesCouvertes: number + populationCouverte: number + nbCommunesIdSocle: number + nbAdressesIdSocle: number + _id: string + nbAdressesAvecBanId: number + nbCommunesAvecBanId: number + } +} diff --git a/src/types/api-geo.types.ts b/src/types/api-geo.types.ts index 624a051c8..11e3cce3e 100644 --- a/src/types/api-geo.types.ts +++ b/src/types/api-geo.types.ts @@ -18,7 +18,15 @@ export type Commune = { export type EPCI = { nom: string code: string - codesDepartements: string[] - codesRegions: string[] + codeDepartement: string + codeRegion: string population: number + centre?: { + type: string + coordinates: number[] + } + contour?: { + type: string + coordinates: number[][][] + } } diff --git a/src/types/api-mes-adresses.types.ts b/src/types/api-mes-adresses.types.ts new file mode 100644 index 000000000..a0ab02ccc --- /dev/null +++ b/src/types/api-mes-adresses.types.ts @@ -0,0 +1,32 @@ +export enum BaseAdresseLocaleStatus { + PUBLISHED = 'published', + REPLACED = 'replaced', + DRAFT = 'draft', + DEMO = 'demo', +} + +export enum BaseAdresseLocaleSyncStatus { + SYNCED = 'synced', + OUTDATED = 'outdated', + CONFLICT = 'conflict', +} + +export type BaseLocaleSync = { + status: BaseAdresseLocaleSyncStatus + isPaused: boolean + lastUploadedRevisionId: string + currentUpdated: string +} + +export type BaseAdresseLocale = { + id: string + banId: string + createdAt: string + updatedAt: string + deletedAt: string + nom: string + commune: string + emails: string[] + status: BaseAdresseLocaleStatus + sync: BaseLocaleSync +} diff --git a/src/utils/cache.ts b/src/utils/cache.ts new file mode 100644 index 000000000..4b75cdad6 --- /dev/null +++ b/src/utils/cache.ts @@ -0,0 +1,78 @@ +class Cache { + constructor(public internalCache: Map Promise }> = new Map()) { + // Refresh cache every 3 hours + setInterval(() => this.updateCache(), 3 * 3600000) + } + + async updateCache() { + console.log('Updating cache...') + const now = Date.now() + for (const [key, cacheEntry] of this.internalCache) { + // If the cache entry has an expiration date and it's passed, we delete it + if (cacheEntry.expiresAt && now > cacheEntry.expiresAt) { + console.log(`Deleting cache ${key}`) + this.internalCache.delete(key) + } + // If the cache entry has no expiration date, we update it + else if (!cacheEntry.expiresAt) { + try { + console.log(`Updating cache ${key}`) + const updatedValue = await cacheEntry.resolver() + this.internalCache.set(key, { value: updatedValue, resolver: cacheEntry.resolver }) + } + catch (e) { + console.error(`Error while updating cache ${key}`, e) + } + } + } + console.log('Cache updated') + } + + has(key: string) { + if (this.internalCache.has(key)) { + const now = Date.now() + const cacheEntry = this.internalCache.get(key) as { value: any, expiresAt?: number } + if (cacheEntry.expiresAt) { + return now < cacheEntry.expiresAt + } + else { + return true + } + } + + return false + } + + get(key: string) { + if (this.has(key)) { + const cacheEntry = this.internalCache.get(key) + + if (!cacheEntry) { + throw new Error('Unexpected behavior: cache entry not available anymore') + } + + return cacheEntry.value + } + } + + set(key: string, value: any, resolver: () => Promise, ttl?: number) { + const expiresAt = ttl && (Date.now() + (ttl * 1000)) + return this.internalCache.set(key, { value, expiresAt, resolver }) + } +} + +const globalCache = new Cache() + +export async function getCachedData(key: string, resolver: () => Promise, ttl?: number) { + if (!globalCache.has(key)) { + const promizedValue = resolver() + globalCache.set(key, promizedValue.then((value) => { + globalCache.set(key, value, resolver, ttl) + return value + }), resolver, ttl) + + return promizedValue + } + + return globalCache.get(key) +} diff --git a/src/utils/cog.ts b/src/utils/cog.ts new file mode 100644 index 000000000..86e99c827 --- /dev/null +++ b/src/utils/cog.ts @@ -0,0 +1,24 @@ +import { keyBy } from 'lodash' +import communes from '@etalab/decoupage-administratif/data/communes.json' + +type CogCommune = { + code: string + nom: string + type: string + typeLiaison: number + zone: string + arrondissement: string + departement: string + region: string + rangChefLieu: number + siren: string + codesPostaux: string[] + population: number +} + +const communesActuelles = (communes as CogCommune[]).filter(({ type }: CogCommune) => type === 'commune-actuelle') +const communesIndex = keyBy(communesActuelles, 'code') + +export function findCommuneName(codeCommune: string) { + return communesIndex[codeCommune]?.nom || '' +} diff --git a/src/utils/contours-communes.ts b/src/utils/contours-communes.ts new file mode 100644 index 000000000..bf0c9b2ee --- /dev/null +++ b/src/utils/contours-communes.ts @@ -0,0 +1,39 @@ +import { writeFileSync, readFile, existsSync } from 'fs' +import { keyBy } from 'lodash' +import { getCachedData } from './cache' + +const FILE_PATH = 'public/communes-index.json' + +export async function downloadContoursCommunes() { + if (existsSync(FILE_PATH)) { + console.log('Contours communes already downloaded') + return + } + + console.log('Downloading contours communes…') + + const response = await fetch('http://etalab-datasets.geo.data.gouv.fr/contours-administratifs/2024/geojson/communes-100m.geojson') + const responseJson = await response.json() + const communesIndex = JSON.stringify(keyBy([...responseJson.features], f => f.properties.code)) + writeFileSync(FILE_PATH, communesIndex) + + console.log('Contours communes ready') +} + +async function readCommunesIndex() { + const fileData: Buffer = await new Promise((resove, reject) => { + readFile(FILE_PATH, (err, data) => { + if (err) { + reject(err) + } + resove(data) + }) + }) + + return JSON.parse(fileData.toString()) +} + +export async function getContourCommune(codeCommune: string) { + const communeIndex = await getCachedData('communes-index', readCommunesIndex) + return communeIndex[codeCommune] +} diff --git a/src/utils/number.ts b/src/utils/number.ts new file mode 100644 index 000000000..03fa03c2f --- /dev/null +++ b/src/utils/number.ts @@ -0,0 +1,8 @@ +export function numFormater(num: number, lang = 'fr-FR') { + if (num.toString().length > 5) { + const formatedNumber = (Math.round(num / 10000) / 100) + return `${new Intl.NumberFormat(lang).format(formatedNumber)} million${formatedNumber > 1.99 ? 's' : ''}` + } + + return new Intl.NumberFormat(lang).format(num) +}