diff --git a/package-lock.json b/package-lock.json index bac9064..3acafe8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,7 @@ "drizzle-orm": "^0.44.7", "lucide": "^0.544.0", "lucide-react": "^0.545.0", + "maplibre-gl": "^5.16.0", "next": "15.5.4", "react": "19.1.0", "react-dom": "19.1.0", @@ -1337,6 +1338,12 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@iarna/toml": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", + "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==", + "license": "ISC" + }, "node_modules/@img/colour": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", @@ -1803,73 +1810,25 @@ "url": "https://opencollective.com/libvips" } }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "20 || >=22" } }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "license": "MIT" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", "license": "MIT", "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" + "@isaacs/balanced-match": "^4.0.1" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": "20 || >=22" } }, "node_modules/@isaacs/fs-minipass": { @@ -1976,10 +1935,43 @@ "react": "^18.x || ^19.x" } }, + "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==", + "license": "ISC", + "dependencies": { + "get-stream": "^6.0.1", + "minimist": "^1.2.6" + }, + "bin": { + "geojson-rewind": "geojson-rewind" + } + }, + "node_modules/@mapbox/geojson-rewind/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/node-pre-gyp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-2.0.0.tgz", - "integrity": "sha512-llMXd39jtP0HpQLVI37Bf1m2ADlEb35GYSh1SDSLsBhR+5iCxiNGlT31yqbNtVHygHAtMy6dWFERpU2JgufhPg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-2.0.3.tgz", + "integrity": "sha512-uwPAhccfFJlsfCxMYTwOdVfOz3xqyj8xYL3zJj8f0pb30tLohnnFPhLuqp4/qoEz8sNxe4SESZedcBojRefIzg==", "license": "BSD-3-Clause", "dependencies": { "consola": "^3.2.3", @@ -2031,9 +2023,9 @@ } }, "node_modules/@mapbox/node-pre-gyp/node_modules/tar": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz", - "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==", + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.6.tgz", + "integrity": "sha512-xqUeu2JAIJpXyvskvU3uvQW8PAmHrtXp2KDuMJwQqW8Sqq0CaZBAQ+dKS3RBXVhU4wC5NjAdKrmh84241gO9cA==", "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/fs-minipass": "^4.0.0", @@ -2055,6 +2047,94 @@ "node": ">=18" } }, + "node_modules/@mapbox/point-geometry": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-1.1.0.tgz", + "integrity": "sha512-YGcBz1cg4ATXDCM/71L9xveh4dynfGmcLDqufR+nQQy3fKwsAZsWd/x4621/6uJaeB9mwOHE6hPeDgXz9uViUQ==", + "license": "ISC" + }, + "node_modules/@mapbox/tiny-sdf": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.7.tgz", + "integrity": "sha512-25gQLQMcpivjOSA40g3gO6qgiFPDpWRoMfd+G/GoppPIeP6JDaMMkMrEJnMZhKyyS6iKwVt5YKu02vCUyJM3Ug==", + "license": "BSD-2-Clause" + }, + "node_modules/@mapbox/unitbezier": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz", + "integrity": "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==", + "license": "BSD-2-Clause" + }, + "node_modules/@mapbox/vector-tile": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-2.0.4.tgz", + "integrity": "sha512-AkOLcbgGTdXScosBWwmmD7cDlvOjkg/DetGva26pIRiZPdeJYjYKarIlb4uxVzi6bwHO6EWH82eZ5Nuv4T5DUg==", + "license": "BSD-3-Clause", + "dependencies": { + "@mapbox/point-geometry": "~1.1.0", + "@types/geojson": "^7946.0.16", + "pbf": "^4.0.1" + } + }, + "node_modules/@mapbox/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==", + "license": "ISC", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@maplibre/geojson-vt": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@maplibre/geojson-vt/-/geojson-vt-5.0.4.tgz", + "integrity": "sha512-KGg9sma45S+stfH9vPCJk1J0lSDLWZgCT9Y8u8qWZJyjFlP8MNP1WGTxIMYJZjDvVT3PDn05kN1C95Sut1HpgQ==", + "license": "ISC" + }, + "node_modules/@maplibre/maplibre-gl-style-spec": { + "version": "24.4.1", + "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-24.4.1.tgz", + "integrity": "sha512-UKhA4qv1h30XT768ccSv5NjNCX+dgfoq2qlLVmKejspPcSQTYD4SrVucgqegmYcKcmwf06wcNAa/kRd0NHWbUg==", + "license": "ISC", + "dependencies": { + "@mapbox/jsonlint-lines-primitives": "~2.0.2", + "@mapbox/unitbezier": "^0.0.1", + "json-stringify-pretty-compact": "^4.0.0", + "minimist": "^1.2.8", + "quickselect": "^3.0.0", + "rw": "^1.3.3", + "tinyqueue": "^3.0.0" + }, + "bin": { + "gl-style-format": "dist/gl-style-format.mjs", + "gl-style-migrate": "dist/gl-style-migrate.mjs", + "gl-style-validate": "dist/gl-style-validate.mjs" + } + }, + "node_modules/@maplibre/mlt": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@maplibre/mlt/-/mlt-1.1.2.tgz", + "integrity": "sha512-SQKdJ909VGROkA6ovJgtHNs9YXV4YXUPS+VaZ50I2Mt951SLlUm2Cv34x5Xwc1HiFlsd3h2Yrs5cn7xzqBmENw==", + "license": "(MIT OR Apache-2.0)", + "dependencies": { + "@mapbox/point-geometry": "^1.1.0" + } + }, + "node_modules/@maplibre/vt-pbf": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@maplibre/vt-pbf/-/vt-pbf-4.2.1.tgz", + "integrity": "sha512-IxZBGq/+9cqf2qdWlFuQ+ZfoMhWpxDUGQZ/poPHOJBvwMUT1GuxLo6HgYTou+xxtsOsjfbcjI8PZaPCtmt97rA==", + "license": "MIT", + "dependencies": { + "@mapbox/point-geometry": "^1.1.0", + "@mapbox/vector-tile": "^2.0.4", + "@maplibre/geojson-vt": "^5.0.4", + "@types/geojson": "^7946.0.16", + "@types/supercluster": "^7.1.3", + "pbf": "^4.0.1", + "supercluster": "^8.0.1" + } + }, "node_modules/@napi-rs/wasm-runtime": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz", @@ -2285,16 +2365,6 @@ "url": "https://github.com/sponsors/Boshen" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, "node_modules/@radix-ui/number": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", @@ -3875,9 +3945,17 @@ "version": "7946.0.16", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", - "dev": true, "license": "MIT" }, + "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==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -3924,6 +4002,15 @@ "@types/react": "^19.2.0" } }, + "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==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.47.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.47.0.tgz", @@ -4226,14 +4313,14 @@ } }, "node_modules/@vercel/backends": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/@vercel/backends/-/backends-0.0.8.tgz", - "integrity": "sha512-knjz9CFa67V6bJLHZFMqdHMRGZQEz/6rrtwCTsIugiG/tmr7VJe31UTS2WhoOEio20gp9Dcl/nta0ZxdinYOFQ==", + "version": "0.0.14", + "resolved": "https://registry.npmjs.org/@vercel/backends/-/backends-0.0.14.tgz", + "integrity": "sha512-4a4LQueJCvwqJhz+B9DBlEOZOdyl+BrIMkC1LZC3++YGbEA9KLhcBwS10WF7hndQR1jizpf7klMQbcU2FwaN/g==", "license": "Apache-2.0", "dependencies": { - "@vercel/cervel": "0.0.4", - "@vercel/introspection": "0.0.2", - "@vercel/nft": "0.30.1", + "@vercel/cervel": "0.0.6", + "@vercel/introspection": "0.0.5", + "@vercel/nft": "1.1.1", "@vercel/static-config": "3.1.2", "fs-extra": "11.1.0", "rolldown": "1.0.0-beta.35" @@ -4256,15 +4343,15 @@ } }, "node_modules/@vercel/build-utils": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/@vercel/build-utils/-/build-utils-13.0.1.tgz", - "integrity": "sha512-RVWYBhVAwoJsuRRq/93OnKVaYEdPlceYnz5f3daRQvrhmjoYRup3y/uy+/SQ5W9I+c0kWdsbogkJ5oBkaWWMOQ==", + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/@vercel/build-utils/-/build-utils-13.2.2.tgz", + "integrity": "sha512-VNGFd/bpjsrpMSHCkRhhcbzdaMJ1tRW9E3BW6uf5SpFB1zn3GUab+aNC6Zq23kHxtNhutjTmttybqWB2hYYTKQ==", "license": "Apache-2.0" }, "node_modules/@vercel/cervel": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/@vercel/cervel/-/cervel-0.0.4.tgz", - "integrity": "sha512-Jk58wDex0lt2RUkSDVHEtszKZNWwHjonAKUNciSD5F30h3JQceP8CAgBa7ZeCNYrmsYPL2bc8sdM0E16ePy4tg==", + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@vercel/cervel/-/cervel-0.0.6.tgz", + "integrity": "sha512-IW/gXg17sozYYb7O/1ycjUj08c2EOCWaTFULNzPs5K2nZEgd1E6CQNmCcyRjHeVzIl3i40YXzytdhUBp2zH9sQ==", "license": "Apache-2.0", "dependencies": { "execa": "3.2.0", @@ -4289,12 +4376,12 @@ } }, "node_modules/@vercel/elysia": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@vercel/elysia/-/elysia-0.1.4.tgz", - "integrity": "sha512-SekuhuK8+EmcMi+ghevSWLIQfjquzipdYVdkYddfifGa5t/fdbzeTh2RQNaDNvjAUcX/E54PLhgRhGSF/OE4Rg==", + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/@vercel/elysia/-/elysia-0.1.12.tgz", + "integrity": "sha512-0+pUbwTP2n6ii2QMafoYhO7F6FcvxYyeKoPmKIeXKCFFRkwI25EJFDOICLviWnZg7ijNJQym/tqQg72eeHb9AQ==", "license": "Apache-2.0", "dependencies": { - "@vercel/node": "5.5.6", + "@vercel/node": "5.5.14", "@vercel/static-config": "3.1.2" } }, @@ -4305,14 +4392,14 @@ "license": "Apache-2.0" }, "node_modules/@vercel/express": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/@vercel/express/-/express-0.1.8.tgz", - "integrity": "sha512-iQ0jzh83zyNIkCsPbU1QywRtoN5Y3vafdkuEp/EXosaXVUyWUuwhuT0iQ+i3e+bUGxad3ErOHszeU2dJiFiYzQ==", + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/@vercel/express/-/express-0.1.17.tgz", + "integrity": "sha512-FS/uBC6aeJJ3IvbUPiAupcgXkrX//3MVdZ7TsqxM7jPYNoJE1JFoonfZ4zIu9WKP5JdvvtnXzxeRsoSgpEMiCQ==", "license": "Apache-2.0", "dependencies": { - "@vercel/cervel": "0.0.4", - "@vercel/nft": "0.30.1", - "@vercel/node": "5.5.6", + "@vercel/cervel": "0.0.6", + "@vercel/nft": "1.1.1", + "@vercel/node": "5.5.14", "@vercel/static-config": "3.1.2", "fs-extra": "11.1.0", "path-to-regexp": "8.3.0", @@ -4322,19 +4409,19 @@ } }, "node_modules/@vercel/fastify": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/@vercel/fastify/-/fastify-0.1.7.tgz", - "integrity": "sha512-3s3Noioc6kkDWXvNk71QzO0GArgUC7wOPcg4JTbGYUHtEieht/I0O0zcGcWyKxQuzt6U2nvEgntkvKjyxTkiqQ==", + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/@vercel/fastify/-/fastify-0.1.15.tgz", + "integrity": "sha512-FVL5pK9MvlT4cxnrJ7ELv1AdERloIGYZbwfbwPMqGasW3nPhoFQfGA8z1XdEvY+NfDJtjAzBfakC91qtDVAUIQ==", "license": "Apache-2.0", "dependencies": { - "@vercel/node": "5.5.6", + "@vercel/node": "5.5.14", "@vercel/static-config": "3.1.2" } }, "node_modules/@vercel/fun": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@vercel/fun/-/fun-1.1.6.tgz", - "integrity": "sha512-xDiM+bD0fSZyzcjsAua3D+guXclvHOSTzr03UcZEQwYzIjwWjLduT7bl2gAaeNIe7fASAIZd0P00clcj0On4rQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@vercel/fun/-/fun-1.2.0.tgz", + "integrity": "sha512-WSmS9qe2R+5roucDEwYB3atKhs9sUbkHV3laJGEUoqz25O83jLE/jqg/B/3yTunB0av1xqiCIBFFVKnXsqSc8w==", "license": "Apache-2.0", "dependencies": { "@tootallnate/once": "2.0.0", @@ -4426,12 +4513,12 @@ } }, "node_modules/@vercel/gatsby-plugin-vercel-builder": { - "version": "2.0.105", - "resolved": "https://registry.npmjs.org/@vercel/gatsby-plugin-vercel-builder/-/gatsby-plugin-vercel-builder-2.0.105.tgz", - "integrity": "sha512-osIn+BzTTbRmDj3zzhTdTkRi4H0g1u5vl7ME4bc11ACjpguxe+dPYOhtzvq/EgFRCLots55ZVqJH1OnxM5XyNA==", + "version": "2.0.112", + "resolved": "https://registry.npmjs.org/@vercel/gatsby-plugin-vercel-builder/-/gatsby-plugin-vercel-builder-2.0.112.tgz", + "integrity": "sha512-35lLBlbXECEAX3BfPNMsT1W1rpz5gARjePjGwiTS/G1T54ptGCiAfxGBqCj0rilAkjhH3L4Oa07FMbkcGExEUQ==", "dependencies": { "@sinclair/typebox": "0.25.24", - "@vercel/build-utils": "13.0.1", + "@vercel/build-utils": "13.2.2", "esbuild": "0.14.47", "etag": "1.8.1", "fs-extra": "11.1.0" @@ -4479,23 +4566,23 @@ "license": "Apache-2.0" }, "node_modules/@vercel/h3": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@vercel/h3/-/h3-0.1.13.tgz", - "integrity": "sha512-tNSpUfitmnjygnDXsdZ6rmii/oYhx2pYevkE+iNr9MnzJWM8GQilH140BmQbWWGx/DbNQzdwolCnK18hiG+O0g==", + "version": "0.1.21", + "resolved": "https://registry.npmjs.org/@vercel/h3/-/h3-0.1.21.tgz", + "integrity": "sha512-a87vIJwqfy3M2eUC7dhFS7WbsE6lo9eaQZmyPGVzFgVrMjSMeko/fI/A4AldgEAUp2Jndo5IB+0HA6Gv8TPu9A==", "license": "Apache-2.0", "dependencies": { - "@vercel/node": "5.5.6", + "@vercel/node": "5.5.14", "@vercel/static-config": "3.1.2" } }, "node_modules/@vercel/hono": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@vercel/hono/-/hono-0.2.7.tgz", - "integrity": "sha512-0PjGXJBFGcb08vMshXZtnPqPjmbS5WUIqBkXzNFblWMNOAkBabPknew5QVTWddmnCVdpF6IK21G9UZOe5eyaBw==", + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/@vercel/hono/-/hono-0.2.15.tgz", + "integrity": "sha512-syASDqf2ssUH92xr2Z0jvoIfmuMibcsYutvL02f6WFrsGyWnmX3JKvsLwieuqRjBBz47/kfArouH7IgLrMcwRw==", "license": "Apache-2.0", "dependencies": { - "@vercel/nft": "0.30.1", - "@vercel/node": "5.5.6", + "@vercel/nft": "1.1.1", + "@vercel/node": "5.5.14", "@vercel/static-config": "3.1.2", "fs-extra": "11.1.0", "path-to-regexp": "8.3.0", @@ -4515,28 +4602,38 @@ } }, "node_modules/@vercel/introspection": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/@vercel/introspection/-/introspection-0.0.2.tgz", - "integrity": "sha512-PcTrmmF31mYYMoY9EntZpBBexPqHy927VdA1XIqXCyjvmQV28Q+3kS/WaTQny9IppNs8itOHwg9/N4I9phcbJw==", + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@vercel/introspection/-/introspection-0.0.5.tgz", + "integrity": "sha512-4v35gsQ7KczLQKXB8XovpPhr3wVT5rpXqK78J5sXpJHSa7LYPO2n4wSCpN+XBQ98Xi56ONZY4VXcBj+kpRxZfg==", "license": "Apache-2.0", "dependencies": { "path-to-regexp": "8.3.0", "zod": "3.22.4" } }, + "node_modules/@vercel/nestjs": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/@vercel/nestjs/-/nestjs-0.2.16.tgz", + "integrity": "sha512-VIpqtGsKy/TJi8VzUfJq+GB/jZ63MWYhUAOzM5kRU82E0+6PApxf3fj3C9xNg8OpeK5tHjx8QckS3wd4GHV8eg==", + "license": "Apache-2.0", + "dependencies": { + "@vercel/node": "5.5.14", + "@vercel/static-config": "3.1.2" + } + }, "node_modules/@vercel/next": { - "version": "4.15.3", - "resolved": "https://registry.npmjs.org/@vercel/next/-/next-4.15.3.tgz", - "integrity": "sha512-YRN6QL7WLNHMqWn+a3MdQ1/kK7NbM9lylgFFwoVVzKiC0WhTpahWUQo8iWgSbnhdYl9z8ruF4QLyYJillIXmhw==", + "version": "4.15.7", + "resolved": "https://registry.npmjs.org/@vercel/next/-/next-4.15.7.tgz", + "integrity": "sha512-h2Knaxq4DK0z1yoHdpDPnTIXZx9TvmJOMHU1A//vjoMO6cgq+Xv/pjTEdSn/OUtftFIwqA+hrd5PpXLyEdW/7Q==", "license": "Apache-2.0", "dependencies": { - "@vercel/nft": "0.30.1" + "@vercel/nft": "1.1.1" } }, "node_modules/@vercel/nft": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-0.30.1.tgz", - "integrity": "sha512-2mgJZv4AYBFkD/nJ4QmiX5Ymxi+AisPLPcS/KPXVqniyQNqKXX+wjieAbDXQP3HcogfEbpHoRMs49Cd4pfkk8g==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-1.1.1.tgz", + "integrity": "sha512-mKMGa7CEUcXU75474kOeqHbtvK1kAcu4wiahhmlUenB5JbTQB8wVlDI8CyHR3rpGo0qlzoRWqcDzI41FUoBJCA==", "license": "MIT", "dependencies": { "@mapbox/node-pre-gyp": "^2.0.0", @@ -4546,7 +4643,7 @@ "async-sema": "^3.1.1", "bindings": "^1.4.0", "estree-walker": "2.0.2", - "glob": "^10.4.5", + "glob": "^13.0.0", "graceful-fs": "^4.2.9", "node-gyp-build": "^4.2.2", "picomatch": "^4.0.2", @@ -4556,7 +4653,7 @@ "nft": "out/cli.js" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/@vercel/nft/node_modules/picomatch": { @@ -4581,18 +4678,18 @@ } }, "node_modules/@vercel/node": { - "version": "5.5.6", - "resolved": "https://registry.npmjs.org/@vercel/node/-/node-5.5.6.tgz", - "integrity": "sha512-U8x9Bh70vX74fAE+3G6+M3QdWyTe1gLUzDxG3s2P2SvYRkvtpCkCO2K8UCeceyA7uGH9+XB9nU6PZy08UK+17w==", + "version": "5.5.14", + "resolved": "https://registry.npmjs.org/@vercel/node/-/node-5.5.14.tgz", + "integrity": "sha512-pEhGm8L1qbefx6P7rjm1F7vrWLN4vB1+UOD1xmWiLvWJSyzUDnk2nelaPPmgLSh6dObUnxucoSnWkoLpmZnL/Q==", "license": "Apache-2.0", "dependencies": { "@edge-runtime/node-utils": "2.3.0", "@edge-runtime/primitives": "4.1.0", "@edge-runtime/vm": "3.2.0", "@types/node": "16.18.11", - "@vercel/build-utils": "13.0.1", + "@vercel/build-utils": "13.2.2", "@vercel/error-utils": "2.0.3", - "@vercel/nft": "0.30.1", + "@vercel/nft": "1.1.1", "@vercel/static-config": "3.1.2", "async-listen": "3.0.0", "cjs-module-lexer": "1.2.3", @@ -4756,31 +4853,31 @@ } }, "node_modules/@vercel/python": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/@vercel/python/-/python-6.0.5.tgz", - "integrity": "sha512-c1rrkkYn+eOw8boPTjh6f2T+RPzdKRfYjcCd4Pu3yn6i9j3dlu6kaJT2SCPHCRdhJyAKVL0jGJ313itVG/CFgQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@vercel/python/-/python-6.1.0.tgz", + "integrity": "sha512-I8ddjrpjY0WoJdFpALYqwydj40DM2YRD97798hN0DNnT9fMZtltyJar5mm7lxLWo4evKNqRkqtUeex2t8WxV6w==", "license": "Apache-2.0" }, "node_modules/@vercel/redwood": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@vercel/redwood/-/redwood-2.4.2.tgz", - "integrity": "sha512-6j7mHHf/B/JH8N7mpyuH06fz8aItY2FiTZuYbKxmd/ScfQj6YF7TGJo65muY5+vnAEvc65vESEiJmt3Vc7cfyw==", + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/@vercel/redwood/-/redwood-2.4.5.tgz", + "integrity": "sha512-mFFZSFJ2ND3Sym6PDOISyaOChLY1AIXBW5ZQaSiNg/ZqFtcjwY6cYCw8xTvG/ThlAZE0/XqetLWJJqffoW2lxA==", "license": "Apache-2.0", "dependencies": { - "@vercel/nft": "0.30.1", + "@vercel/nft": "1.1.1", "@vercel/static-config": "3.1.2", "semver": "6.3.1", "ts-morph": "12.0.0" } }, "node_modules/@vercel/remix-builder": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/@vercel/remix-builder/-/remix-builder-5.5.2.tgz", - "integrity": "sha512-+F2ugrKRAkl+NCHfUm/hygZylonucosyiuvf1LSRYNSPZKpnzs+JWpCa9UoeOr0KFD+pTeJ3vJppFvV8XZx9NQ==", + "version": "5.5.5", + "resolved": "https://registry.npmjs.org/@vercel/remix-builder/-/remix-builder-5.5.5.tgz", + "integrity": "sha512-r1Hrt1UM3+GsQ1bpqEGrM/bU2NzbHk8MjyxwxcvdltWHnHvuQCWxUf79hUGa+vWjYJITit6Bqg/978R9vLr5nQ==", "license": "Apache-2.0", "dependencies": { "@vercel/error-utils": "2.0.3", - "@vercel/nft": "0.30.1", + "@vercel/nft": "1.1.1", "@vercel/static-config": "3.1.2", "path-to-regexp": "6.1.0", "path-to-regexp-updated": "npm:path-to-regexp@6.3.0", @@ -4799,14 +4896,89 @@ "integrity": "sha512-PgO3kVPPmYw914BQpgi2/NQSgaXe4xoLLdNhYuI7tJxwfZzVfYizNvhjREyoOvn96LeuB4gNS5P7nbeJjHqb/w==", "license": "Apache-2.0" }, + "node_modules/@vercel/rust": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@vercel/rust/-/rust-1.0.3.tgz", + "integrity": "sha512-u0kh2ZafuxTelXPRbv2/tR6cC9MBjgRhrzVuKxOZk7GyLnFwNTDuNwoP4iJ03yF1k5GqGYB0v+0Zvty67isAAQ==", + "license": "Apache-2.0", + "dependencies": { + "@iarna/toml": "^2.2.5", + "execa": "5" + } + }, + "node_modules/@vercel/rust/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/@vercel/rust/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vercel/rust/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/@vercel/rust/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vercel/rust/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, "node_modules/@vercel/static-build": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/@vercel/static-build/-/static-build-2.8.6.tgz", - "integrity": "sha512-mQEeOH1CttfRAKfQ7Z1YlHZjsnln6EiMf1MeUQdsyNeoge4rKebrDq4rwrBv+qGeVqssW74p8tFc7ajy1DkJqQ==", + "version": "2.8.13", + "resolved": "https://registry.npmjs.org/@vercel/static-build/-/static-build-2.8.13.tgz", + "integrity": "sha512-kppgO752q0ri+UKCFps91gSSwrEyXybqsJWfF/Amtavp0agx378wKHFXi/GhUS8FgidZe4928XFa2/tCjePWlw==", "license": "Apache-2.0", "dependencies": { "@vercel/gatsby-plugin-vercel-analytics": "1.0.11", - "@vercel/gatsby-plugin-vercel-builder": "2.0.105", + "@vercel/gatsby-plugin-vercel-builder": "2.0.112", "@vercel/static-config": "3.1.2", "ts-morph": "12.0.0" } @@ -5155,6 +5327,7 @@ "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -5167,6 +5340,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -6364,6 +6538,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -6376,6 +6551,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, "license": "MIT" }, "node_modules/colorette": { @@ -7228,9 +7404,9 @@ "license": "MIT" }, "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" @@ -7289,9 +7465,9 @@ } }, "node_modules/drizzle-kit": { - "version": "0.31.7", - "resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.31.7.tgz", - "integrity": "sha512-hOzRGSdyKIU4FcTSFYGKdXEjFsncVwHZ43gY3WU5Bz9j5Iadp6Rh6hxLSQ1IWXpKLBKt/d5y1cpSPcV+FcoQ1A==", + "version": "0.31.8", + "resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.31.8.tgz", + "integrity": "sha512-O9EC/miwdnRDY10qRxM8P3Pg8hXe3LyU4ZipReKOgTwn4OqANmftj8XJz1UPUAS6NMHf0E2htjsbQujUTkncCg==", "dev": true, "license": "MIT", "dependencies": { @@ -7455,11 +7631,11 @@ "stream-shift": "^1.0.0" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "license": "MIT" + "node_modules/earcut": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.2.tgz", + "integrity": "sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==", + "license": "ISC" }, "node_modules/edge-runtime": { "version": "2.5.9", @@ -7538,12 +7714,6 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, "node_modules/emojis-list": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", @@ -8807,22 +8977,6 @@ "node": ">=0.10.0" } }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/frac": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", @@ -8984,6 +9138,12 @@ "node": ">= 4" } }, + "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==", + "license": "ISC" + }, "node_modules/get-east-asian-width": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", @@ -9097,21 +9257,24 @@ "node": ">=0.10.0" } }, + "node_modules/gl-matrix": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.4.tgz", + "integrity": "sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==", + "license": "MIT" + }, "node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "license": "ISC", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.0.tgz", + "integrity": "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==", + "license": "BlueOak-1.0.0", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", + "minimatch": "^10.1.1", "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "path-scurry": "^2.0.0" }, - "bin": { - "glob": "dist/esm/bin.mjs" + "engines": { + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -9130,25 +9293,16 @@ "node": ">=10.13.0" } }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/glob/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "license": "ISC", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", + "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.1" + "@isaacs/brace-expansion": "^5.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -10167,21 +10321,6 @@ "node": ">= 0.4" } }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, "node_modules/jiti": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", @@ -10256,6 +10395,12 @@ "dev": true, "license": "MIT" }, + "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==", + "license": "MIT" + }, "node_modules/json5": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", @@ -10296,6 +10441,12 @@ "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==", + "license": "ISC" + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -10722,10 +10873,13 @@ } }, "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" + "version": "11.2.4", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", + "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } }, "node_modules/lucide": { "version": "0.544.0", @@ -10801,6 +10955,44 @@ "node": ">=0.10.0" } }, + "node_modules/maplibre-gl": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-5.16.0.tgz", + "integrity": "sha512-/VDY89nr4jgLJyzmhy325cG6VUI02WkZ/UfVuDbG/piXzo6ODnM+omDFIwWY8tsEsBG26DNDmNMn3Y2ikHsBiA==", + "license": "BSD-3-Clause", + "dependencies": { + "@mapbox/geojson-rewind": "^0.5.2", + "@mapbox/jsonlint-lines-primitives": "^2.0.2", + "@mapbox/point-geometry": "^1.1.0", + "@mapbox/tiny-sdf": "^2.0.7", + "@mapbox/unitbezier": "^0.0.1", + "@mapbox/vector-tile": "^2.0.4", + "@mapbox/whoots-js": "^3.1.0", + "@maplibre/maplibre-gl-style-spec": "^24.4.1", + "@maplibre/mlt": "^1.1.2", + "@maplibre/vt-pbf": "^4.2.0", + "@types/geojson": "^7946.0.16", + "@types/geojson-vt": "3.2.5", + "@types/supercluster": "^7.1.3", + "earcut": "^3.0.2", + "geojson-vt": "^4.0.2", + "gl-matrix": "^3.4.4", + "kdbush": "^4.0.2", + "murmurhash-js": "^1.0.0", + "pbf": "^4.0.1", + "potpack": "^2.1.0", + "quickselect": "^3.0.0", + "supercluster": "^8.0.1", + "tinyqueue": "^3.0.0" + }, + "engines": { + "node": ">=16.14.0", + "npm": ">=8.1.0" + }, + "funding": { + "url": "https://github.com/maplibre/maplibre-gl-js?sponsor=1" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -11093,6 +11285,12 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "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==", + "license": "MIT" + }, "node_modules/nan": { "version": "2.23.1", "resolved": "https://registry.npmjs.org/nan/-/nan-2.23.1.tgz", @@ -11668,12 +11866,6 @@ "node": ">=6" } }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "license": "BlueOak-1.0.0" - }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -11819,16 +12011,16 @@ "license": "MIT" }, "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz", + "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==", "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, "engines": { - "node": ">=16 || 14 >=14.18" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -11851,6 +12043,18 @@ "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", "license": "MIT" }, + "node_modules/pbf": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pbf/-/pbf-4.0.1.tgz", + "integrity": "sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA==", + "license": "BSD-3-Clause", + "dependencies": { + "resolve-protobuf-schema": "^2.1.0" + }, + "bin": { + "pbf": "bin/pbf" + } + }, "node_modules/pbkdf2": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.5.tgz", @@ -12104,6 +12308,12 @@ "node": ">=0.10.0" } }, + "node_modules/potpack": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/potpack/-/potpack-2.1.0.tgz", + "integrity": "sha512-pcaShQc1Shq0y+E7GqJqvZj8DTthWV1KeHGdi0Z6IAin2Oi3JnLCOfwnCo84qc+HAp52wT9nK9H7FAJp5a44GQ==", + "license": "ISC" + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -12195,6 +12405,12 @@ "react-is": "^16.13.1" } }, + "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==", + "license": "MIT" + }, "node_modules/prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", @@ -12268,9 +12484,9 @@ } }, "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -12310,6 +12526,12 @@ ], "license": "MIT" }, + "node_modules/quickselect": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz", + "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==", + "license": "ISC" + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -12798,6 +13020,15 @@ "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==", + "license": "MIT", + "dependencies": { + "protocol-buffers-schema": "^3.3.1" + } + }, "node_modules/resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", @@ -13405,6 +13636,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, "license": "ISC", "engines": { "node": ">=14" @@ -13871,51 +14103,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/string.prototype.matchall": { "version": "4.0.12", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", @@ -14018,6 +14205,7 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" @@ -14029,28 +14217,6 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -14096,6 +14262,15 @@ } } }, + "node_modules/supercluster": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz", + "integrity": "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==", + "license": "ISC", + "dependencies": { + "kdbush": "^4.0.2" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -14171,6 +14346,7 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exhorbitant rates) by contacting i@izs.me", "license": "ISC", "dependencies": { "chownr": "^2.0.0", @@ -14294,6 +14470,12 @@ "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", "license": "MIT" }, + "node_modules/tinyqueue": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-3.0.0.tgz", + "integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==", + "license": "ISC" + }, "node_modules/to-arraybuffer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", @@ -15365,30 +15547,32 @@ "license": "MIT" }, "node_modules/vercel": { - "version": "48.10.3", - "resolved": "https://registry.npmjs.org/vercel/-/vercel-48.10.3.tgz", - "integrity": "sha512-aT8usdP3pqUs2ubV3vPZTT/GpV7FIvEypofa6LceSAC04Ou8heCAx4pKVgMfzWK1vjX6WbAvcRBDX6zrqDcIWg==", + "version": "48.12.1", + "resolved": "https://registry.npmjs.org/vercel/-/vercel-48.12.1.tgz", + "integrity": "sha512-+lMj+qIXI/Iy7UXKu1wpFCwCaeV1lmrUdBbYQWXBM1/9XsX8vUfohHLkPrPSam8tDyVghKmaYu1ZD5uuHgo5uw==", "license": "Apache-2.0", "dependencies": { - "@vercel/backends": "0.0.8", + "@vercel/backends": "0.0.14", "@vercel/blob": "1.0.2", - "@vercel/build-utils": "13.0.1", + "@vercel/build-utils": "13.2.2", "@vercel/detect-agent": "1.0.0", - "@vercel/elysia": "0.1.4", - "@vercel/express": "0.1.8", - "@vercel/fastify": "0.1.7", - "@vercel/fun": "1.1.6", + "@vercel/elysia": "0.1.12", + "@vercel/express": "0.1.17", + "@vercel/fastify": "0.1.15", + "@vercel/fun": "1.2.0", "@vercel/go": "3.2.3", - "@vercel/h3": "0.1.13", - "@vercel/hono": "0.2.7", + "@vercel/h3": "0.1.21", + "@vercel/hono": "0.2.15", "@vercel/hydrogen": "1.3.2", - "@vercel/next": "4.15.3", - "@vercel/node": "5.5.6", - "@vercel/python": "6.0.5", - "@vercel/redwood": "2.4.2", - "@vercel/remix-builder": "5.5.2", + "@vercel/nestjs": "0.2.16", + "@vercel/next": "4.15.7", + "@vercel/node": "5.5.14", + "@vercel/python": "6.1.0", + "@vercel/redwood": "2.4.5", + "@vercel/remix-builder": "5.5.5", "@vercel/ruby": "2.2.2", - "@vercel/static-build": "2.8.6", + "@vercel/rust": "1.0.3", + "@vercel/static-build": "2.8.13", "chokidar": "4.0.0", "jose": "5.9.6" }, @@ -16235,68 +16419,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/wrap-ansi/node_modules/ansi-styles": { "version": "6.2.3", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", diff --git a/package.json b/package.json index 51bf536..ca6af40 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "drizzle-orm": "^0.44.7", "lucide": "^0.544.0", "lucide-react": "^0.545.0", + "maplibre-gl": "^5.16.0", "next": "15.5.4", "react": "19.1.0", "react-dom": "19.1.0", diff --git a/src/app/globals.css b/src/app/globals.css index af5d1c3..1f45e4f 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -139,4 +139,10 @@ body { @apply bg-background text-foreground; } + .maplibregl-popup-content { + @apply bg-transparent! shadow-none! p-0! rounded-none!; + } + .maplibregl-popup-tip { + @apply hidden!; + } } diff --git a/src/app/heat-map/counties.json b/src/app/heat-map/counties.json new file mode 100644 index 0000000..3453459 --- /dev/null +++ b/src/app/heat-map/counties.json @@ -0,0 +1,656 @@ +{ + "Barnstable": { + "name": "Barnstable", + "fips": "001", + "coordinates": [ + [-70.698197, 41.559001999063376], + [-70.695392, 41.60254599906321], + [-70.715979844668794, 41.614013267827367], + [-70.690964, 41.660456999062987], + [-70.620785, 41.747529999062657], + [-70.632555, 41.762903999062608], + [-70.565363, 41.786668999062513], + [-70.536407254124796, 41.811634144808821], + [-70.494048, 41.773882999062558], + [-70.441718, 41.752897999062633], + [-70.323819, 41.736057999062702], + [-70.272289, 41.721345999062748], + [-70.216073, 41.742980999062681], + [-70.121978, 41.75884099906262], + [-70.024734, 41.787363999062507], + [-70.003842, 41.808519999062433], + [-70.006111, 41.852395999062274], + [-70.06901, 41.884923999062167], + [-70.074006, 41.938649999061958], + [-70.076573999136698, 41.957942457311916], + [-70.083775, 42.012040999061718], + [-70.15076, 42.026568999061681], + [-70.190834, 42.020027999061696], + [-70.245385, 42.06373299906155], + [-70.189305, 42.082336999061489], + [-70.138942, 42.092906999061462], + [-70.049382, 42.064688999061552], + [-69.994136, 41.999257999061768], + [-69.980231361587698, 41.945986459295256], + [-69.974781, 41.925104999062029], + [-69.935952, 41.809421999062423], + [-69.928261, 41.691699999062863], + [-69.931129, 41.622658999063127], + [-69.964982, 41.551109999063407], + [-70.011229, 41.543930999063434], + [-70.011961, 41.619796999063141], + [-70.007011, 41.671578999062945], + [-70.055523, 41.664842999062969], + [-70.158621, 41.650437999063037], + [-70.269687, 41.617774999063151], + [-70.321588, 41.630507999063099], + [-70.400581, 41.606381999063181], + [-70.445289, 41.591814999063246], + [-70.476256, 41.558501999063367], + [-70.559689, 41.548329999063412], + [-70.654104, 41.519024999063532], + [-70.669058366600495, 41.512929661738156], + [-70.697498, 41.527157999063505], + [-70.726088944807003, 41.543236930981038], + [-70.698197, 41.559001999063376] + ] + }, + "Berkshire": { + "name": "Berkshire", + "fips": "003", + "coordinates": [ + [-73.508142, 42.086256999061476], + [-73.410644407427284, 42.351746298708541], + [-73.383505657283607, 42.425646049244229], + [-73.352527, 42.510001999060187], + [-73.352524954944201, 42.510007509010784], + [-73.307004076180576, 42.632653450471359], + [-73.264957, 42.745939999059559], + [-73.142495120514397, 42.743425716256461], + [-73.023017893290088, 42.740972711786476], + [-72.948758, 42.704091999059663], + [-72.951039, 42.641005999059843], + [-72.975409, 42.556036999060055], + [-72.979284, 42.497856999060218], + [-73.009085, 42.387733999060544], + [-73.065772, 42.389110999060541], + [-73.062886, 42.328950999060709], + [-73.000173, 42.312677999060753], + [-73.001529, 42.26266099906092], + [-73.007867, 42.238408999060987], + [-73.071113, 42.148205999061275], + [-73.053362673748097, 42.040115570971935], + [-73.127229170878394, 42.042123147416426], + [-73.231056, 42.04494499906162], + [-73.487314, 42.049637999061595], + [-73.489679942032097, 42.053797708648283], + [-73.508142, 42.086256999061476] + ] + }, + "Bristol": { + "name": "Bristol", + "fips": "005", + "coordinates": [ + [-71.381461, 41.952140999061925], + [-71.381431346893095, 41.985084118191317], + [-71.353265, 41.989539999061805], + [-71.288265, 42.014461999061709], + [-71.138619, 42.072812999061519], + [-71.080483, 42.095538999061439], + [-71.054718, 41.985056999061811], + [-70.999773, 41.929670999062004], + [-71.000002, 41.928817999061998], + [-70.999669, 41.92843899906201], + [-70.997782, 41.927953999062012], + [-70.996804, 41.927403999062008], + [-70.995908, 41.926351999062028], + [-70.995631, 41.925587999062031], + [-70.995744, 41.924424999062026], + [-70.997232, 41.923233999062035], + [-70.996907, 41.920907999062038], + [-70.997436, 41.918884999062044], + [-70.997395, 41.917401999062051], + [-70.996347, 41.915626999062056], + [-70.994834, 41.91492299906205], + [-70.994341, 41.915235999062062], + [-70.993955, 41.916348999062045], + [-70.992507, 41.916175999062048], + [-70.991978, 41.915601999062055], + [-70.990984, 41.910058999062073], + [-70.991455, 41.908339999062086], + [-70.992307, 41.90705899906208], + [-70.993152, 41.906444999062089], + [-70.987256, 41.905807999062091], + [-70.973717, 41.860878999062251], + [-71.03657, 41.816524999062402], + [-71.014591, 41.799567999062468], + [-71.026288, 41.77888799906254], + [-70.921782, 41.791243999062502], + [-70.907184, 41.763542999062594], + [-70.88497, 41.756114999062625], + [-70.865003, 41.700163999062831], + [-70.839175, 41.614760999063165], + [-70.803962613207901, 41.601515540063502], + [-70.82191, 41.582840999063272], + [-70.853121, 41.587320999063259], + [-70.85762366603339, 41.586512091526771], + [-70.9090917764468, 41.577265804528793], + [-70.910165, 41.577072999063304], + [-70.941785, 41.540120999063447], + [-70.981708, 41.51006999906356], + [-71.02032123222439, 41.502159474980608], + [-71.035514, 41.499046999063602], + [-71.085663, 41.509291999063556], + [-71.12057, 41.497447999063603], + [-71.137492, 41.602560999063208], + [-71.132888, 41.660101999062988], + [-71.19564, 41.675089999062934], + [-71.201327187524896, 41.681768327265601], + [-71.20860053289509, 41.69030924266557], + [-71.261392, 41.752300999062641], + [-71.317402007931207, 41.777256108031253], + [-71.329396, 41.782599999062541], + [-71.339597, 41.831999999062354], + [-71.339298, 41.893398999062136], + [-71.3817, 41.893198999062129], + [-71.381461, 41.952140999061925] + ] + }, + "Dukes East": { + "name": "Dukes East", + "fips": "007", + "coordinates": [ + [-70.934986, 41.454698999063787], + [-70.806861, 41.49758299906361], + [-70.726088944807003, 41.543236930981038], + [-70.697498, 41.527157999063505], + [-70.669058366600495, 41.512929661738156], + [-70.734306, 41.486334999063658], + [-70.79027, 41.446338999063826], + [-70.857528, 41.425766999063896], + [-70.948431, 41.409192999063954], + [-70.934986, 41.454698999063787] + ] + }, + "Dukes West": { + "name": "Dukes West", + "fips": "007", + "coordinates": [ + [-70.833802, 41.353385999064187], + [-70.757797, 41.365701999064143], + [-70.686881, 41.441333999063843], + [-70.603555, 41.482383999063671], + [-70.553277, 41.452954999063785], + [-70.496197, 41.42490799906389], + [-70.463833, 41.419144999063924], + [-70.448262, 41.3536509990642], + [-70.577454, 41.349162999064212], + [-70.693635, 41.34283299906425], + [-70.768687, 41.303701999064408], + [-70.821284, 41.251013999064625], + [-70.833397, 41.31677799906435], + [-70.833802, 41.353385999064187] + ] + }, + "Essex": { + "name": "Essex", + "fips": "009", + "coordinates": [ + [-71.238551, 42.671249999059754], + [-71.255110121810603, 42.736397052346774], + [-71.245384032920697, 42.736555327634186], + [-71.181803, 42.737589999059573], + [-71.186104, 42.790688999059448], + [-71.149703, 42.81548899905939], + [-71.116375012177485, 42.811902913494492], + [-71.064201, 42.806288999059412], + [-71.048716413698585, 42.831064337141548], + [-71.031201, 42.859088999059267], + [-70.9665, 42.868988999059255], + [-70.930799, 42.884588999059211], + [-70.86475, 42.870257999059248], + [-70.817296, 42.872289999059255], + [-70.80522, 42.781797999059464], + [-70.772267, 42.711063999059654], + [-70.72982, 42.669601999059765], + [-70.681594, 42.66234199905977], + [-70.645101, 42.689422999059694], + [-70.602506, 42.677701999059735], + [-70.594014, 42.635029999059853], + [-70.654727, 42.582233999059994], + [-70.698574, 42.577392999059995], + [-70.804091, 42.561594999060041], + [-70.848492, 42.550194999060068], + [-70.835991, 42.490495999060244], + [-70.886493, 42.470196999060299], + [-70.899233979101794, 42.44991565474686], + [-70.905993, 42.43915651065258], + [-70.913192, 42.427696999060423], + [-70.955211061463288, 42.425469089452129], + [-70.982994, 42.423995999060438], + [-71.053365, 42.475924999060283], + [-71.033998, 42.58549299905998], + [-71.145942, 42.60833499905992], + [-71.164879, 42.598020999059933], + [-71.255749, 42.656928999059787], + [-71.238551, 42.671249999059754] + ] + }, + "Franklin": { + "name": "Franklin", + "fips": "011", + "coordinates": [ + [-73.018648613350393, 42.740883005459374], + [-72.930262612517993, 42.739068339517175], + [-72.864291854845391, 42.73771388421877], + [-72.809113, 42.736580999059584], + [-72.516711182782203, 42.72846766888879], + [-72.458519, 42.726852999059609], + [-72.451195051822694, 42.726650884553209], + [-72.412030064752301, 42.725570072422904], + [-72.283034092510903, 42.722010249683628], + [-72.271956, 42.67467199905974], + [-72.224932, 42.638936999059844], + [-72.227752, 42.615866999059904], + [-72.271469, 42.547229999060079], + [-72.244868, 42.513438999060178], + [-72.291288, 42.479524999060281], + [-72.314599, 42.343525999060667], + [-72.356086, 42.303279999060784], + [-72.375022, 42.420818999060451], + [-72.483696, 42.407480999060468], + [-72.489891, 42.433815999060407], + [-72.669138, 42.409710999060472], + [-72.700877, 42.452919999060342], + [-72.757758, 42.445882999060366], + [-72.871136, 42.484040999060255], + [-72.876849, 42.541196999060098], + [-72.975409, 42.556036999060055], + [-72.951039, 42.641005999059843], + [-72.948758, 42.704091999059663], + [-73.023017893290088, 42.740972711786476], + [-73.018648613350393, 42.740883005459374] + ] + }, + "Hampden": { + "name": "Hampden", + "fips": "013", + "coordinates": [ + [-73.071113, 42.148205999061275], + [-73.007867, 42.238408999060987], + [-73.001529, 42.26266099906092], + [-73.000173, 42.312677999060753], + [-72.953254, 42.343828999060669], + [-72.885184, 42.332614999060695], + [-72.912302, 42.239132999060985], + [-72.793414, 42.236853999060997], + [-72.686861, 42.183389999061156], + [-72.613138, 42.286264999060855], + [-72.609202, 42.285476999060847], + [-72.598933, 42.268089999060905], + [-72.623934, 42.233690999061004], + [-72.607933, 42.216290999061059], + [-72.589938, 42.21197499906107], + [-72.44712, 42.227253999061027], + [-72.395478, 42.185736999061149], + [-72.333875, 42.220718999061049], + [-72.291567, 42.232220999061013], + [-72.221218, 42.245251999060976], + [-72.263924, 42.183830999061165], + [-72.135011, 42.161783999061228], + [-72.135731461946691, 42.029141929003266], + [-72.198845978131999, 42.030103892343668], + [-72.317148, 42.031906999061654], + [-72.397476255195897, 42.032816190197458], + [-72.509179336298303, 42.034080495651757], + [-72.528131, 42.034294999061657], + [-72.607933, 42.030794999061669], + [-72.735496, 42.036398999061639], + [-72.766739, 42.002994999061748], + [-72.774759316078601, 42.002129103242055], + [-72.810078, 41.998315999061774], + [-72.847142, 42.03689399906164], + [-72.999549, 42.038652999061632], + [-73.008762781630693, 42.038903415308326], + [-73.053362673748097, 42.040115570971935], + [-73.071113, 42.148205999061275] + ] + }, + "Hampshire": { + "name": "Hampshire", + "fips": "015", + "coordinates": [ + [-73.065772, 42.389110999060541], + [-73.009085, 42.387733999060544], + [-72.979284, 42.497856999060218], + [-72.975409, 42.556036999060055], + [-72.876849, 42.541196999060098], + [-72.871136, 42.484040999060255], + [-72.757758, 42.445882999060366], + [-72.700877, 42.452919999060342], + [-72.669138, 42.409710999060472], + [-72.489891, 42.433815999060407], + [-72.483696, 42.407480999060468], + [-72.375022, 42.420818999060451], + [-72.356086, 42.303279999060784], + [-72.314599, 42.343525999060667], + [-72.288734, 42.352023999060648], + [-72.210795, 42.311379999060762], + [-72.213973, 42.294256999060806], + [-72.211079, 42.251261999060951], + [-72.221218, 42.245251999060976], + [-72.291567, 42.232220999061013], + [-72.333875, 42.220718999061049], + [-72.395478, 42.185736999061149], + [-72.44712, 42.227253999061027], + [-72.589938, 42.21197499906107], + [-72.607933, 42.216290999061059], + [-72.623934, 42.233690999061004], + [-72.598933, 42.268089999060905], + [-72.609202, 42.285476999060847], + [-72.613138, 42.286264999060855], + [-72.686861, 42.183389999061156], + [-72.793414, 42.236853999060997], + [-72.912302, 42.239132999060985], + [-72.885184, 42.332614999060695], + [-72.953254, 42.343828999060669], + [-73.000173, 42.312677999060753], + [-73.062886, 42.328950999060709], + [-73.065772, 42.389110999060541] + ] + }, + "Middlesex": { + "name": "Middlesex", + "fips": "017", + "coordinates": [ + [-71.898768771936588, 42.711466719868447], + [-71.805389602738288, 42.708914942588144], + [-71.745817, 42.707286999059662], + [-71.636214, 42.70488799905965], + [-71.351874, 42.698153999059677], + [-71.294205, 42.696989999059696], + [-71.255605, 42.736388999059578], + [-71.255110121810603, 42.736397052346774], + [-71.238551, 42.671249999059754], + [-71.255749, 42.656928999059787], + [-71.164879, 42.598020999059933], + [-71.145942, 42.60833499905992], + [-71.033998, 42.58549299905998], + [-71.053365, 42.475924999060283], + [-70.982994, 42.423995999060438], + [-70.982545601587901, 42.42022179911244], + [-71.080947, 42.382377999060544], + [-71.064059, 42.369000999060582], + [-71.077095, 42.358696999060626], + [-71.117193, 42.3642289990606], + [-71.167625, 42.36007299906062], + [-71.156887, 42.330240999060713], + [-71.191155, 42.283058999060863], + [-71.23106, 42.320735999060737], + [-71.269958, 42.328085999060718], + [-71.32975, 42.312990999060759], + [-71.302922, 42.248273999060963], + [-71.344083, 42.200978999061107], + [-71.478119, 42.156781999061252], + [-71.502626, 42.191415999061135], + [-71.555738, 42.182502999061157], + [-71.586759, 42.259544999060921], + [-71.549453, 42.266094999060897], + [-71.522178, 42.266453999060893], + [-71.506478, 42.264275999060906], + [-71.486797, 42.330188999060702], + [-71.551126, 42.326394999060717], + [-71.585168, 42.31097699906077], + [-71.624702, 42.350464999060648], + [-71.5591, 42.411942999060464], + [-71.53878, 42.543029999060096], + [-71.635869, 42.524023999060155], + [-71.678969, 42.530429999060125], + [-71.664601, 42.61159799905991], + [-71.775755, 42.644229999059817], + [-71.858483, 42.633814999059837], + [-71.898768771936588, 42.711466719868447] + ] + }, + "Nantucket": { + "name": "Nantucket", + "fips": "019", + "coordinates": [ + [-70.275526, 41.310463999064375], + [-70.193712, 41.313786999064355], + [-70.079133, 41.319503999064331], + [-70.049053, 41.39170199906404], + [-69.984869, 41.358817999064165], + [-69.960181, 41.264545999064559], + [-70.015225, 41.237963999064682], + [-70.096967, 41.240849999064658], + [-70.211479, 41.24876499906464], + [-70.275526, 41.310463999064375] + ] + }, + "Norfolk West": { + "name": "Norfolk West", + "fips": "021", + "coordinates": [ + [-71.499689, 42.103489999061424], + [-71.478522, 42.131377999061336], + [-71.478119, 42.156781999061252], + [-71.344083, 42.200978999061107], + [-71.302922, 42.248273999060963], + [-71.32975, 42.312990999060759], + [-71.269958, 42.328085999060718], + [-71.23106, 42.320735999060737], + [-71.191155, 42.283058999060863], + [-71.188167, 42.28041199906086], + [-71.146642, 42.25575499906094], + [-71.130771, 42.227925999061029], + [-71.122856, 42.235224999061003], + [-71.126377, 42.23916199906099], + [-71.109347, 42.247989999060962], + [-71.109544, 42.255411999060939], + [-71.11326, 42.258925999060928], + [-71.111737, 42.260468999060926], + [-71.102691, 42.259883999060932], + [-71.093737, 42.2671069990609], + [-71.053284, 42.277501999060867], + [-71.041694, 42.305297999060784], + [-70.9930596399482, 42.312892279473168], + [-70.967351, 42.268167999060907], + [-70.948497043534289, 42.28235469284315], + [-70.914091, 42.288799999060842], + [-70.924877, 42.157579999061241], + [-70.948963, 42.148155999061288], + [-71.080483, 42.095538999061439], + [-71.138619, 42.072812999061519], + [-71.288265, 42.014461999061709], + [-71.353265, 41.989539999061805], + [-71.381431346893095, 41.985084118191317], + [-71.381401, 42.018797999061704], + [-71.458080994556099, 42.0168788241904], + [-71.498224013124698, 42.015874110080205], + [-71.498287, 42.064877999061558], + [-71.499689, 42.103489999061424] + ] + }, + "Norfolk East": { + "name": "Norfolk East", + "fips": "021", + "coordinates": [ + [-70.85338, 42.239606999060989], + [-70.835851775394389, 42.264763263774306], + [-70.788724, 42.253919999060948], + [-70.781574545568489, 42.248636945084868], + [-70.785081, 42.22527599906104], + [-70.827, 42.20079199906111], + [-70.8408, 42.213117999061076], + [-70.85338, 42.239606999060989] + ] + }, + + "Plymouth": { + "name": "Plymouth", + "fips": "023", + "coordinates": [ + [-71.080483, 42.095538999061439], + [-70.948963, 42.148155999061288], + [-70.924877, 42.157579999061241], + [-70.914091, 42.288799999060842], + [-70.948497043534289, 42.28235469284315], + [-70.91749, 42.305685999060792], + [-70.881242, 42.300662999060798], + [-70.851093, 42.268269999060898], + [-70.835851775394389, 42.264763263774306], + [-70.85338, 42.239606999060989], + [-70.8408, 42.213117999061076], + [-70.827, 42.20079199906111], + [-70.785081, 42.22527599906104], + [-70.781574545568489, 42.248636945084868], + [-70.73056, 42.210939999061075], + [-70.685315, 42.13302499906132], + [-70.679237774502496, 42.126349458483837], + [-70.63848, 42.081578999061492], + [-70.644337, 42.045894999061602], + [-70.678798, 42.005509999061751], + [-70.662476, 41.960591999061897], + [-70.608166, 41.94070099906196], + [-70.583572, 41.950006999061934], + [-70.546386, 41.916750999062053], + [-70.525567, 41.858729999062255], + [-70.54103, 41.815753999062409], + [-70.536407254124796, 41.811634144808821], + [-70.565363, 41.786668999062513], + [-70.632555, 41.762903999062608], + [-70.620785, 41.747529999062657], + [-70.690964, 41.660456999062987], + [-70.715979844668794, 41.614013267827367], + [-70.761770079788405, 41.639518071333455], + [-70.765463, 41.641574999063053], + [-70.803962613207901, 41.601515540063502], + [-70.839175, 41.614760999063165], + [-70.865003, 41.700163999062831], + [-70.88497, 41.756114999062625], + [-70.907184, 41.763542999062594], + [-70.921782, 41.791243999062502], + [-71.026288, 41.77888799906254], + [-71.014591, 41.799567999062468], + [-71.03657, 41.816524999062402], + [-70.973717, 41.860878999062251], + [-70.987256, 41.905807999062091], + [-70.993152, 41.906444999062089], + [-70.992307, 41.90705899906208], + [-70.991455, 41.908339999062086], + [-70.990984, 41.910058999062073], + [-70.991978, 41.915601999062055], + [-70.992507, 41.916175999062048], + [-70.993955, 41.916348999062045], + [-70.994341, 41.915235999062062], + [-70.994834, 41.91492299906205], + [-70.996347, 41.915626999062056], + [-70.997395, 41.917401999062051], + [-70.997436, 41.918884999062044], + [-70.996907, 41.920907999062038], + [-70.997232, 41.923233999062035], + [-70.995744, 41.924424999062026], + [-70.995631, 41.925587999062031], + [-70.995908, 41.926351999062028], + [-70.996804, 41.927403999062008], + [-70.997782, 41.927953999062012], + [-70.999669, 41.92843899906201], + [-71.000002, 41.928817999061998], + [-70.999773, 41.929670999062004], + [-71.054718, 41.985056999061811], + [-71.080483, 42.095538999061439] + ] + }, + "Suffolk": { + "name": "Suffolk", + "fips": "025", + "coordinates": [ + [-71.191155, 42.283058999060863], + [-71.156887, 42.330240999060713], + [-71.167625, 42.36007299906062], + [-71.117193, 42.3642289990606], + [-71.077095, 42.358696999060626], + [-71.064059, 42.369000999060582], + [-71.080947, 42.382377999060544], + [-70.982545601587901, 42.42022179911244], + [-70.974897, 42.355842999060641], + [-70.975892942126109, 42.354339252782545], + [-70.983093585947202, 42.343467193993753], + [-70.997838, 42.321204999060726], + [-70.9930596399482, 42.312892279473168], + [-71.041694, 42.305297999060784], + [-71.053284, 42.277501999060867], + [-71.093737, 42.2671069990609], + [-71.102691, 42.259883999060932], + [-71.111737, 42.260468999060926], + [-71.11326, 42.258925999060928], + [-71.109544, 42.255411999060939], + [-71.109347, 42.247989999060962], + [-71.126377, 42.23916199906099], + [-71.122856, 42.235224999061003], + [-71.130771, 42.227925999061029], + [-71.146642, 42.25575499906094], + [-71.188167, 42.28041199906086], + [-71.191155, 42.283058999060863] + ] + }, + "Worcester": { + "name": "Worcester", + "fips": "027", + "coordinates": [ + [-72.291288, 42.479524999060281], + [-72.244868, 42.513438999060178], + [-72.271469, 42.547229999060079], + [-72.227752, 42.615866999059904], + [-72.224932, 42.638936999059844], + [-72.271956, 42.67467199905974], + [-72.283034092510903, 42.722010249683628], + [-72.203613479806791, 42.71981852773952], + [-72.124526, 42.717635999059624], + [-72.081365050187998, 42.716456537600834], + [-71.929030059053503, 42.712293671592249], + [-71.898768771936588, 42.711466719868447], + [-71.858483, 42.633814999059837], + [-71.775755, 42.644229999059817], + [-71.664601, 42.61159799905991], + [-71.678969, 42.530429999060125], + [-71.635869, 42.524023999060155], + [-71.53878, 42.543029999060096], + [-71.5591, 42.411942999060464], + [-71.624702, 42.350464999060648], + [-71.585168, 42.31097699906077], + [-71.551126, 42.326394999060717], + [-71.486797, 42.330188999060702], + [-71.506478, 42.264275999060906], + [-71.522178, 42.266453999060893], + [-71.549453, 42.266094999060897], + [-71.586759, 42.259544999060921], + [-71.555738, 42.182502999061157], + [-71.502626, 42.191415999061135], + [-71.478119, 42.156781999061252], + [-71.478522, 42.131377999061336], + [-71.499689, 42.103489999061424], + [-71.498287, 42.064877999061558], + [-71.498224013124698, 42.015874110080205], + [-71.559439, 42.01434199906172], + [-71.591096527631009, 42.013513342618907], + [-71.606203441134099, 42.013117909300526], + [-71.799242, 42.008064999061745], + [-71.80065, 42.023568999061695], + [-71.884676835903704, 42.025059350545988], + [-71.987326, 42.02687999906167], + [-72.102159434526797, 42.028630239116474], + [-72.135731461946691, 42.029141929003266], + [-72.135011, 42.161783999061228], + [-72.263924, 42.183830999061165], + [-72.221218, 42.245251999060976], + [-72.211079, 42.251261999060951], + [-72.213973, 42.294256999060806], + [-72.210795, 42.311379999060762], + [-72.288734, 42.352023999060648], + [-72.314599, 42.343525999060667], + [-72.291288, 42.479524999060281] + ] + } +} diff --git a/src/app/heat-map/data.json b/src/app/heat-map/data.json new file mode 100644 index 0000000..f40ff0c --- /dev/null +++ b/src/app/heat-map/data.json @@ -0,0 +1,197 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { "name": "Boston", "population": "673458" }, + "geometry": { + "type": "Point", + "coordinates": [-71.0589, 42.3601] + } + }, + { + "type": "Feature", + "properties": { "name": "Worcester", "population": "211286" }, + "geometry": { + "type": "Point", + "coordinates": [-71.802, 42.263] + } + }, + { + "type": "Feature", + "properties": { "name": "Worcester", "population": "211286" }, + "geometry": { + "type": "Point", + "coordinates": [-71.802, 42.263] + } + }, + { + "type": "Feature", + "properties": { "name": "Springfield", "population": "154888" }, + "geometry": { + "type": "Point", + "coordinates": [-72.590279, 42.101391] + } + }, + { + "type": "Feature", + "properties": { "name": "Cambridge", "population": "121186" }, + "geometry": { + "type": "Point", + "coordinates": [-71.106, 42.375] + } + }, + { + "type": "Feature", + "properties": { "name": "Lowell", "population": "120418" }, + "geometry": { + "type": "Point", + "coordinates": [-71.316, 42.633] + } + }, + { + "type": "Feature", + "properties": { "name": "Brockton", "population": "105788" }, + "geometry": { + "type": "Point", + "coordinates": [-71.0184, 42.0834] + } + }, + { + "type": "Feature", + "properties": { "name": "Quincy", "population": "103434" }, + "geometry": { + "type": "Point", + "coordinates": [-71.0023, 42.2529] + } + }, + { + "type": "Feature", + "properties": { "name": "Lynn", "population": "103489" }, + "geometry": { + "type": "Point", + "coordinates": [-70.9495, 42.4668] + } + }, + { + "type": "Feature", + "properties": { "name": "New Bedford", "population": "101318" }, + "geometry": { + "type": "Point", + "coordinates": [-70.927, 41.6362] + } + }, + { + "type": "Feature", + "properties": { "name": "Fall River", "population": "94689" }, + "geometry": { + "type": "Point", + "coordinates": [-71.155, 41.7015] + } + }, + { + "type": "Feature", + "properties": { "name": "Newton", "population": "90700" }, + "geometry": { + "type": "Point", + "coordinates": [-71.2092, 42.337] + } + }, + { + "type": "Feature", + "properties": { "name": "Lawrence", "population": "89332" }, + "geometry": { + "type": "Point", + "coordinates": [-71.1605, 42.707] + } + }, + { + "type": "Feature", + "properties": { "name": "Somerville", "population": "82149" }, + "geometry": { + "type": "Point", + "coordinates": [-71.0995, 42.3876] + } + }, + { + "type": "Feature", + "properties": { "name": "Framingham", "population": "73361" }, + "geometry": { + "type": "Point", + "coordinates": [-71.4162, 42.2793] + } + }, + { + "type": "Feature", + "properties": { "name": "Haverhill", "population": "68291" }, + "geometry": { + "type": "Point", + "coordinates": [-71.0773, 42.7762] + } + }, + { + "type": "Feature", + "properties": { "name": "Malden", "population": "66693" }, + "geometry": { + "type": "Point", + "coordinates": [-71.074, 42.4251] + } + }, + { + "type": "Feature", + "properties": { "name": "Waltham", "population": "65849" }, + "geometry": { + "type": "Point", + "coordinates": [-71.2356, 42.3765] + } + }, + { + "type": "Feature", + "properties": { "name": "Revere", "population": "60702" }, + "geometry": { + "type": "Point", + "coordinates": [-71.011, 42.4084] + } + }, + { + "type": "Feature", + "properties": { "name": "Taunton", "population": "61936" }, + "geometry": { + "type": "Point", + "coordinates": [-71.0898, 41.9001] + } + }, + { + "type": "Feature", + "properties": { "name": "Peabody", "population": "55418" }, + "geometry": { + "type": "Point", + "coordinates": [-70.9858, 42.5365] + } + }, + { + "type": "Feature", + "properties": { "name": "Westfield", "population": "41741" }, + "geometry": { + "type": "Point", + "coordinates": [-72.7562, 42.1251] + } + }, + { + "type": "Feature", + "properties": { "name": "Chicopee", "population": "55347" }, + "geometry": { + "type": "Point", + "coordinates": [-72.6071, 42.1487] + } + }, + { + "type": "Feature", + "properties": { "name": "Holyoke", "population": "40121" }, + "geometry": { + "type": "Point", + "coordinates": [-72.6142, 42.2043] + } + } + ] +} diff --git a/src/app/heat-map/page.tsx b/src/app/heat-map/page.tsx new file mode 100644 index 0000000..45fabd6 --- /dev/null +++ b/src/app/heat-map/page.tsx @@ -0,0 +1,75 @@ +/*************************************************************** + * + * src/app/heat-map/page.tsx + * + * Author: Chiara & Elki + * Date: 1/27/26 + * + * Summary: Heatmap + Clusters POC with MA region + * + **************************************************************/ + +import { + Map, + MapMarker, + MarkerContent, + MarkerTooltip, + MapClusterLayer, + MapPopup, + MapRoute, +} from "@/components/ui/map"; + +import heatmapData from "../heat-map/data.json"; +import countiesData from "../heat-map/counties.json"; + +const COUNTY_COLORS = [ + "#3b82f6", + "#ef4444", + "#22c55e", + "#eab308", + "#eab308", + "#8b5cf6", + "#ec4899", + "#f97316", + "#14b8a6", + "#6366f1", + "#84cc16", + "#06b6d4", + "#06b6d4", + "#f43f5e", + "#a855f7", + "#10b981", +]; + +const counties = Object.values(countiesData).map((county, i) => ({ + name: county.name, + coordinates: county.coordinates as [number, number][], + color: COUNTY_COLORS[i % COUNTY_COLORS.length], +})); + +export default function HeatMapPage() { + return ( +
+
+ + {counties.map((county) => ( + + ))} + + +
+
+ ); +} diff --git a/src/components/ui/map.tsx b/src/components/ui/map.tsx new file mode 100644 index 0000000..6c9f341 --- /dev/null +++ b/src/components/ui/map.tsx @@ -0,0 +1,1390 @@ +"use client"; + +import MapLibreGL, { type PopupOptions, type MarkerOptions } from "maplibre-gl"; +import "maplibre-gl/dist/maplibre-gl.css"; +import { + createContext, + forwardRef, + useCallback, + useContext, + useEffect, + useId, + useImperativeHandle, + useMemo, + useRef, + useState, + type ReactNode, +} from "react"; +import { createPortal } from "react-dom"; +import { X, Minus, Plus, Locate, Maximize, Loader2 } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +// Check document class for theme (works with next-themes, etc.) +function getDocumentTheme(): Theme | null { + if (typeof document === "undefined") return null; + if (document.documentElement.classList.contains("dark")) return "dark"; + if (document.documentElement.classList.contains("light")) return "light"; + return null; +} + +// Get system preference +function getSystemTheme(): Theme { + if (typeof window === "undefined") return "light"; + return window.matchMedia("(prefers-color-scheme: dark)").matches + ? "dark" + : "light"; +} + +function useResolvedTheme(themeProp?: "light" | "dark"): "light" | "dark" { + const [detectedTheme, setDetectedTheme] = useState<"light" | "dark">( + () => getDocumentTheme() ?? getSystemTheme(), + ); + + useEffect(() => { + if (themeProp) return; // Skip detection if theme is provided via prop + + // Watch for document class changes (e.g., next-themes toggling dark class) + const observer = new MutationObserver(() => { + const docTheme = getDocumentTheme(); + if (docTheme) { + setDetectedTheme(docTheme); + } + }); + observer.observe(document.documentElement, { + attributes: true, + attributeFilter: ["class"], + }); + + // Also watch for system preference changes + const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); + const handleSystemChange = (e: MediaQueryListEvent) => { + // Only use system preference if no document class is set + if (!getDocumentTheme()) { + setDetectedTheme(e.matches ? "dark" : "light"); + } + }; + mediaQuery.addEventListener("change", handleSystemChange); + + return () => { + observer.disconnect(); + mediaQuery.removeEventListener("change", handleSystemChange); + }; + }, [themeProp]); + + return themeProp ?? detectedTheme; +} + +type MapContextValue = { + map: MapLibreGL.Map | null; + isLoaded: boolean; +}; + +const MapContext = createContext(null); + +function useMap() { + const context = useContext(MapContext); + if (!context) { + throw new Error("useMap must be used within a Map component"); + } + return context; +} + +const defaultStyles = { + dark: "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json", + light: "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json", +}; + +type MapStyleOption = string | MapLibreGL.StyleSpecification; + +type Theme = "light" | "dark"; + +type MapProps = { + children?: ReactNode; + /** + * Theme for the map. If not provided, automatically detects system preference. + * Pass your theme value here. + */ + theme?: Theme; + /** Custom map styles for light and dark themes. Overrides the default Carto styles. */ + styles?: { + light?: MapStyleOption; + dark?: MapStyleOption; + }; + /** Map projection type. Use `{ type: "globe" }` for 3D globe view. */ + projection?: MapLibreGL.ProjectionSpecification; +} & Omit; + +type MapRef = MapLibreGL.Map; + +const DefaultLoader = () => ( +
+
+ + + +
+
+); + +const Map = forwardRef(function Map( + { children, theme: themeProp, styles, projection, ...props }, + ref, +) { + const containerRef = useRef(null); + const [mapInstance, setMapInstance] = useState(null); + const [isLoaded, setIsLoaded] = useState(false); + const [isStyleLoaded, setIsStyleLoaded] = useState(false); + const currentStyleRef = useRef(null); + const styleTimeoutRef = useRef | null>(null); + const resolvedTheme = useResolvedTheme(themeProp); + + const mapStyles = useMemo( + () => ({ + dark: styles?.dark ?? defaultStyles.dark, + light: styles?.light ?? defaultStyles.light, + }), + [styles], + ); + + useImperativeHandle(ref, () => mapInstance as MapLibreGL.Map, [ + mapInstance, + ]); + + const clearStyleTimeout = useCallback(() => { + if (styleTimeoutRef.current) { + clearTimeout(styleTimeoutRef.current); + styleTimeoutRef.current = null; + } + }, []); + + useEffect(() => { + if (!containerRef.current) return; + + const initialStyle = + resolvedTheme === "dark" ? mapStyles.dark : mapStyles.light; + currentStyleRef.current = initialStyle; + + const map = new MapLibreGL.Map({ + container: containerRef.current, + style: initialStyle, + renderWorldCopies: false, + attributionControl: { + compact: true, + }, + ...props, + }); + + const styleDataHandler = () => { + clearStyleTimeout(); + // Delay to ensure style is fully processed before allowing layer operations + // This is a workaround to avoid race conditions with the style loading + // else we have to force update every layer on setStyle change + styleTimeoutRef.current = setTimeout(() => { + setIsStyleLoaded(true); + if (projection) { + map.setProjection(projection); + } + }, 100); + }; + const loadHandler = () => setIsLoaded(true); + + map.on("load", loadHandler); + map.on("styledata", styleDataHandler); + setMapInstance(map); + + return () => { + clearStyleTimeout(); + map.off("load", loadHandler); + map.off("styledata", styleDataHandler); + map.remove(); + setIsLoaded(false); + setIsStyleLoaded(false); + setMapInstance(null); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + if (!mapInstance || !resolvedTheme) return; + + const newStyle = + resolvedTheme === "dark" ? mapStyles.dark : mapStyles.light; + + if (currentStyleRef.current === newStyle) return; + + clearStyleTimeout(); + currentStyleRef.current = newStyle; + setIsStyleLoaded(false); + + mapInstance.setStyle(newStyle, { diff: true }); + }, [mapInstance, resolvedTheme, mapStyles, clearStyleTimeout]); + + const contextValue = useMemo( + () => ({ + map: mapInstance, + isLoaded: isLoaded && isStyleLoaded, + }), + [mapInstance, isLoaded, isStyleLoaded], + ); + + return ( + +
+ {!isLoaded && } + {/* SSR-safe: children render only when map is loaded on client */} + {mapInstance && children} +
+
+ ); +}); + +type MarkerContextValue = { + marker: MapLibreGL.Marker; + map: MapLibreGL.Map | null; +}; + +const MarkerContext = createContext(null); + +function useMarkerContext() { + const context = useContext(MarkerContext); + if (!context) { + throw new Error("Marker components must be used within MapMarker"); + } + return context; +} + +type MapMarkerProps = { + /** Longitude coordinate for marker position */ + longitude: number; + /** Latitude coordinate for marker position */ + latitude: number; + /** Marker subcomponents (MarkerContent, MarkerPopup, MarkerTooltip, MarkerLabel) */ + children: ReactNode; + /** Callback when marker is clicked */ + onClick?: (e: MouseEvent) => void; + /** Callback when mouse enters marker */ + onMouseEnter?: (e: MouseEvent) => void; + /** Callback when mouse leaves marker */ + onMouseLeave?: (e: MouseEvent) => void; + /** Callback when marker drag starts (requires draggable: true) */ + onDragStart?: (lngLat: { lng: number; lat: number }) => void; + /** Callback during marker drag (requires draggable: true) */ + onDrag?: (lngLat: { lng: number; lat: number }) => void; + /** Callback when marker drag ends (requires draggable: true) */ + onDragEnd?: (lngLat: { lng: number; lat: number }) => void; +} & Omit; + +function MapMarker({ + longitude, + latitude, + children, + onClick, + onMouseEnter, + onMouseLeave, + onDragStart, + onDrag, + onDragEnd, + draggable = false, + ...markerOptions +}: MapMarkerProps) { + const { map } = useMap(); + + const marker = useMemo(() => { + const markerInstance = new MapLibreGL.Marker({ + ...markerOptions, + element: document.createElement("div"), + draggable, + }).setLngLat([longitude, latitude]); + + const handleClick = (e: MouseEvent) => onClick?.(e); + const handleMouseEnter = (e: MouseEvent) => onMouseEnter?.(e); + const handleMouseLeave = (e: MouseEvent) => onMouseLeave?.(e); + + markerInstance.getElement()?.addEventListener("click", handleClick); + markerInstance + .getElement() + ?.addEventListener("mouseenter", handleMouseEnter); + markerInstance + .getElement() + ?.addEventListener("mouseleave", handleMouseLeave); + + const handleDragStart = () => { + const lngLat = markerInstance.getLngLat(); + onDragStart?.({ lng: lngLat.lng, lat: lngLat.lat }); + }; + const handleDrag = () => { + const lngLat = markerInstance.getLngLat(); + onDrag?.({ lng: lngLat.lng, lat: lngLat.lat }); + }; + const handleDragEnd = () => { + const lngLat = markerInstance.getLngLat(); + onDragEnd?.({ lng: lngLat.lng, lat: lngLat.lat }); + }; + + markerInstance.on("dragstart", handleDragStart); + markerInstance.on("drag", handleDrag); + markerInstance.on("dragend", handleDragEnd); + + return markerInstance; + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + if (!map) return; + + marker.addTo(map); + + return () => { + marker.remove(); + }; + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [map]); + + if ( + marker.getLngLat().lng !== longitude || + marker.getLngLat().lat !== latitude + ) { + marker.setLngLat([longitude, latitude]); + } + if (marker.isDraggable() !== draggable) { + marker.setDraggable(draggable); + } + + const currentOffset = marker.getOffset(); + const newOffset = markerOptions.offset ?? [0, 0]; + const [newOffsetX, newOffsetY] = Array.isArray(newOffset) + ? newOffset + : [newOffset.x, newOffset.y]; + if (currentOffset.x !== newOffsetX || currentOffset.y !== newOffsetY) { + marker.setOffset(newOffset); + } + + if (marker.getRotation() !== markerOptions.rotation) { + marker.setRotation(markerOptions.rotation ?? 0); + } + if (marker.getRotationAlignment() !== markerOptions.rotationAlignment) { + marker.setRotationAlignment(markerOptions.rotationAlignment ?? "auto"); + } + if (marker.getPitchAlignment() !== markerOptions.pitchAlignment) { + marker.setPitchAlignment(markerOptions.pitchAlignment ?? "auto"); + } + + return ( + + {children} + + ); +} + +type MarkerContentProps = { + /** Custom marker content. Defaults to a blue dot if not provided */ + children?: ReactNode; + /** Additional CSS classes for the marker container */ + className?: string; +}; + +function MarkerContent({ children, className }: MarkerContentProps) { + const { marker } = useMarkerContext(); + + return createPortal( +
+ {children || } +
, + marker.getElement(), + ); +} + +function DefaultMarkerIcon() { + return ( +
+ ); +} + +type MarkerPopupProps = { + /** Popup content */ + children: ReactNode; + /** Additional CSS classes for the popup container */ + className?: string; + /** Show a close button in the popup (default: false) */ + closeButton?: boolean; +} & Omit; + +function MarkerPopup({ + children, + className, + closeButton = false, + ...popupOptions +}: MarkerPopupProps) { + const { marker, map } = useMarkerContext(); + const container = useMemo(() => document.createElement("div"), []); + const prevPopupOptions = useRef(popupOptions); + + const popup = useMemo(() => { + const popupInstance = new MapLibreGL.Popup({ + offset: 16, + ...popupOptions, + closeButton: false, + }) + .setMaxWidth("none") + .setDOMContent(container); + + return popupInstance; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + if (!map) return; + + popup.setDOMContent(container); + marker.setPopup(popup); + + return () => { + marker.setPopup(null); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [map]); + + if (popup.isOpen()) { + const prev = prevPopupOptions.current; + + if (prev.offset !== popupOptions.offset) { + popup.setOffset(popupOptions.offset ?? 16); + } + if (prev.maxWidth !== popupOptions.maxWidth && popupOptions.maxWidth) { + popup.setMaxWidth(popupOptions.maxWidth ?? "none"); + } + + prevPopupOptions.current = popupOptions; + } + + const handleClose = () => popup.remove(); + + return createPortal( +
+ {closeButton && ( + + )} + {children} +
, + container, + ); +} + +type MarkerTooltipProps = { + /** Tooltip content */ + children: ReactNode; + /** Additional CSS classes for the tooltip container */ + className?: string; +} & Omit; + +function MarkerTooltip({ + children, + className, + ...popupOptions +}: MarkerTooltipProps) { + const { marker, map } = useMarkerContext(); + const container = useMemo(() => document.createElement("div"), []); + const prevTooltipOptions = useRef(popupOptions); + + const tooltip = useMemo(() => { + const tooltipInstance = new MapLibreGL.Popup({ + offset: 16, + ...popupOptions, + closeOnClick: true, + closeButton: false, + }).setMaxWidth("none"); + + return tooltipInstance; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + if (!map) return; + + tooltip.setDOMContent(container); + + const handleMouseEnter = () => { + tooltip.setLngLat(marker.getLngLat()).addTo(map); + }; + const handleMouseLeave = () => tooltip.remove(); + + marker.getElement()?.addEventListener("mouseenter", handleMouseEnter); + marker.getElement()?.addEventListener("mouseleave", handleMouseLeave); + + return () => { + marker + .getElement() + ?.removeEventListener("mouseenter", handleMouseEnter); + marker + .getElement() + ?.removeEventListener("mouseleave", handleMouseLeave); + tooltip.remove(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [map]); + + if (tooltip.isOpen()) { + const prev = prevTooltipOptions.current; + + if (prev.offset !== popupOptions.offset) { + tooltip.setOffset(popupOptions.offset ?? 16); + } + if (prev.maxWidth !== popupOptions.maxWidth && popupOptions.maxWidth) { + tooltip.setMaxWidth(popupOptions.maxWidth ?? "none"); + } + + prevTooltipOptions.current = popupOptions; + } + + return createPortal( +
+ {children} +
, + container, + ); +} + +type MarkerLabelProps = { + /** Label text content */ + children: ReactNode; + /** Additional CSS classes for the label */ + className?: string; + /** Position of the label relative to the marker (default: "top") */ + position?: "top" | "bottom"; +}; + +function MarkerLabel({ + children, + className, + position = "top", +}: MarkerLabelProps) { + const positionClasses = { + top: "bottom-full mb-1", + bottom: "top-full mt-1", + }; + + return ( +
+ {children} +
+ ); +} + +type MapControlsProps = { + /** Position of the controls on the map (default: "bottom-right") */ + position?: "top-left" | "top-right" | "bottom-left" | "bottom-right"; + /** Show zoom in/out buttons (default: true) */ + showZoom?: boolean; + /** Show compass button to reset bearing (default: false) */ + showCompass?: boolean; + /** Show locate button to find user's location (default: false) */ + showLocate?: boolean; + /** Show fullscreen toggle button (default: false) */ + showFullscreen?: boolean; + /** Additional CSS classes for the controls container */ + className?: string; + /** Callback with user coordinates when located */ + onLocate?: (coords: { longitude: number; latitude: number }) => void; +}; + +const positionClasses = { + "top-left": "top-2 left-2", + "top-right": "top-2 right-2", + "bottom-left": "bottom-2 left-2", + "bottom-right": "bottom-10 right-2", +}; + +function ControlGroup({ children }: { children: React.ReactNode }) { + return ( +
+ {children} +
+ ); +} + +function ControlButton({ + onClick, + label, + children, + disabled = false, +}: { + onClick: () => void; + label: string; + children: React.ReactNode; + disabled?: boolean; +}) { + return ( + + ); +} + +function MapControls({ + position = "bottom-right", + showZoom = true, + showCompass = false, + showLocate = false, + showFullscreen = false, + className, + onLocate, +}: MapControlsProps) { + const { map } = useMap(); + const [waitingForLocation, setWaitingForLocation] = useState(false); + + const handleZoomIn = useCallback(() => { + map?.zoomTo(map.getZoom() + 1, { duration: 300 }); + }, [map]); + + const handleZoomOut = useCallback(() => { + map?.zoomTo(map.getZoom() - 1, { duration: 300 }); + }, [map]); + + const handleResetBearing = useCallback(() => { + map?.resetNorthPitch({ duration: 300 }); + }, [map]); + + const handleLocate = useCallback(() => { + setWaitingForLocation(true); + if ("geolocation" in navigator) { + navigator.geolocation.getCurrentPosition( + (pos) => { + const coords = { + longitude: pos.coords.longitude, + latitude: pos.coords.latitude, + }; + map?.flyTo({ + center: [coords.longitude, coords.latitude], + zoom: 14, + duration: 1500, + }); + onLocate?.(coords); + setWaitingForLocation(false); + }, + (error) => { + console.error("Error getting location:", error); + setWaitingForLocation(false); + }, + ); + } + }, [map, onLocate]); + + const handleFullscreen = useCallback(() => { + const container = map?.getContainer(); + if (!container) return; + if (document.fullscreenElement) { + document.exitFullscreen(); + } else { + container.requestFullscreen(); + } + }, [map]); + + return ( +
+ {showZoom && ( + + + + + + + + + )} + {showCompass && ( + + + + )} + {showLocate && ( + + + {waitingForLocation ? ( + + ) : ( + + )} + + + )} + {showFullscreen && ( + + + + + + )} +
+ ); +} + +function CompassButton({ onClick }: { onClick: () => void }) { + const { map } = useMap(); + const compassRef = useRef(null); + + useEffect(() => { + if (!map || !compassRef.current) return; + + const compass = compassRef.current; + + const updateRotation = () => { + const bearing = map.getBearing(); + const pitch = map.getPitch(); + compass.style.transform = `rotateX(${pitch}deg) rotateZ(${-bearing}deg)`; + }; + + map.on("rotate", updateRotation); + map.on("pitch", updateRotation); + updateRotation(); + + return () => { + map.off("rotate", updateRotation); + map.off("pitch", updateRotation); + }; + }, [map]); + + return ( + + + + + + + + + ); +} + +type MapPopupProps = { + /** Longitude coordinate for popup position */ + longitude: number; + /** Latitude coordinate for popup position */ + latitude: number; + /** Callback when popup is closed */ + onClose?: () => void; + /** Popup content */ + children: ReactNode; + /** Additional CSS classes for the popup container */ + className?: string; + /** Show a close button in the popup (default: false) */ + closeButton?: boolean; +} & Omit; + +function MapPopup({ + longitude, + latitude, + onClose, + children, + className, + closeButton = false, + ...popupOptions +}: MapPopupProps) { + const { map } = useMap(); + const popupOptionsRef = useRef(popupOptions); + const container = useMemo(() => document.createElement("div"), []); + + const popup = useMemo(() => { + const popupInstance = new MapLibreGL.Popup({ + offset: 16, + ...popupOptions, + closeButton: false, + }) + .setMaxWidth("none") + .setLngLat([longitude, latitude]); + + return popupInstance; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + if (!map) return; + + const onCloseProp = () => onClose?.(); + popup.on("close", onCloseProp); + + popup.setDOMContent(container); + popup.addTo(map); + + return () => { + popup.off("close", onCloseProp); + if (popup.isOpen()) { + popup.remove(); + } + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [map]); + + if (popup.isOpen()) { + const prev = popupOptionsRef.current; + + if ( + popup.getLngLat().lng !== longitude || + popup.getLngLat().lat !== latitude + ) { + popup.setLngLat([longitude, latitude]); + } + + if (prev.offset !== popupOptions.offset) { + popup.setOffset(popupOptions.offset ?? 16); + } + if (prev.maxWidth !== popupOptions.maxWidth && popupOptions.maxWidth) { + popup.setMaxWidth(popupOptions.maxWidth ?? "none"); + } + popupOptionsRef.current = popupOptions; + } + + const handleClose = () => { + popup.remove(); + onClose?.(); + }; + + return createPortal( +
+ {closeButton && ( + + )} + {children} +
, + container, + ); +} + +type MapRouteProps = { + /** Optional unique identifier for the route layer */ + id?: string; + /** Array of [longitude, latitude] coordinate pairs defining the route */ + coordinates: [number, number][]; + /** Line color as CSS color value (default: "#4285F4") */ + color?: string; + /** Line width in pixels (default: 3) */ + width?: number; + /** Line opacity from 0 to 1 (default: 0.8) */ + opacity?: number; + /** Dash pattern [dash length, gap length] for dashed lines */ + dashArray?: [number, number]; + /** Callback when the route line is clicked */ + onClick?: () => void; + /** Callback when mouse enters the route line */ + onMouseEnter?: () => void; + /** Callback when mouse leaves the route line */ + onMouseLeave?: () => void; + /** Whether the route is interactive - shows pointer cursor on hover (default: true) */ + interactive?: boolean; +}; + +function MapRoute({ + id: propId, + coordinates, + color = "#4285F4", + width = 3, + opacity = 0.8, + dashArray, + onClick, + onMouseEnter, + onMouseLeave, + interactive = true, +}: MapRouteProps) { + const { map, isLoaded } = useMap(); + const autoId = useId(); + const id = propId ?? autoId; + const sourceId = `route-source-${id}`; + const layerId = `route-layer-${id}`; + + // Add source and layer on mount + useEffect(() => { + if (!isLoaded || !map) return; + + map.addSource(sourceId, { + type: "geojson", + data: { + type: "Feature", + properties: {}, + geometry: { type: "LineString", coordinates: [] }, + }, + }); + + map.addLayer({ + id: layerId, + type: "line", + source: sourceId, + layout: { "line-join": "round", "line-cap": "round" }, + paint: { + "line-color": color, + "line-width": width, + "line-opacity": opacity, + ...(dashArray && { "line-dasharray": dashArray }), + }, + }); + + return () => { + try { + if (map.getLayer(layerId)) map.removeLayer(layerId); + if (map.getSource(sourceId)) map.removeSource(sourceId); + } catch { + // ignore + } + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isLoaded, map]); + + // When coordinates change, update the source data + useEffect(() => { + if (!isLoaded || !map || coordinates.length < 2) return; + + const source = map.getSource(sourceId) as MapLibreGL.GeoJSONSource; + if (source) { + source.setData({ + type: "Feature", + properties: {}, + geometry: { type: "LineString", coordinates }, + }); + } + }, [isLoaded, map, coordinates, sourceId]); + + useEffect(() => { + if (!isLoaded || !map || !map.getLayer(layerId)) return; + + map.setPaintProperty(layerId, "line-color", color); + map.setPaintProperty(layerId, "line-width", width); + map.setPaintProperty(layerId, "line-opacity", opacity); + if (dashArray) { + map.setPaintProperty(layerId, "line-dasharray", dashArray); + } + }, [isLoaded, map, layerId, color, width, opacity, dashArray]); + + // Handle click and hover events + useEffect(() => { + if (!isLoaded || !map || !interactive) return; + + const handleClick = () => { + onClick?.(); + }; + const handleMouseEnter = () => { + map.getCanvas().style.cursor = "pointer"; + onMouseEnter?.(); + }; + const handleMouseLeave = () => { + map.getCanvas().style.cursor = ""; + onMouseLeave?.(); + }; + + map.on("click", layerId, handleClick); + map.on("mouseenter", layerId, handleMouseEnter); + map.on("mouseleave", layerId, handleMouseLeave); + + return () => { + map.off("click", layerId, handleClick); + map.off("mouseenter", layerId, handleMouseEnter); + map.off("mouseleave", layerId, handleMouseLeave); + }; + }, [ + isLoaded, + map, + layerId, + onClick, + onMouseEnter, + onMouseLeave, + interactive, + ]); + + return null; +} + +type MapClusterLayerProps< + P extends GeoJSON.GeoJsonProperties = GeoJSON.GeoJsonProperties, +> = { + /** GeoJSON FeatureCollection data or URL to fetch GeoJSON from */ + data: string | GeoJSON.FeatureCollection; + /** Maximum zoom level to cluster points on (default: 14) */ + clusterMaxZoom?: number; + /** Radius of each cluster when clustering points in pixels (default: 50) */ + clusterRadius?: number; + /** Colors for cluster circles: [small, medium, large] based on point count (default: ["#51bbd6", "#f1f075", "#f28cb1"]) */ + clusterColors?: [string, string, string]; + /** Point count thresholds for color/size steps: [medium, large] (default: [100, 750]) */ + clusterThresholds?: [number, number]; + /** Color for unclustered individual points (default: "#3b82f6") */ + pointColor?: string; + /** Callback when an unclustered point is clicked */ + onPointClick?: ( + feature: GeoJSON.Feature, + coordinates: [number, number], + ) => void; + /** Callback when a cluster is clicked. If not provided, zooms into the cluster */ + onClusterClick?: ( + clusterId: number, + coordinates: [number, number], + pointCount: number, + ) => void; +}; + +function MapClusterLayer< + P extends GeoJSON.GeoJsonProperties = GeoJSON.GeoJsonProperties, +>({ + data, + clusterMaxZoom = 14, + clusterRadius = 50, + clusterColors = ["#51bbd6", "#f1f075", "#f28cb1"], + clusterThresholds = [100, 750], + pointColor = "#3b82f6", + onPointClick, + onClusterClick, +}: MapClusterLayerProps

) { + const { map, isLoaded } = useMap(); + const id = useId(); + const sourceId = `cluster-source-${id}`; + const clusterLayerId = `clusters-${id}`; + const clusterCountLayerId = `cluster-count-${id}`; + const unclusteredLayerId = `unclustered-point-${id}`; + + const stylePropsRef = useRef({ + clusterColors, + clusterThresholds, + pointColor, + }); + + // Add source and layers on mount + useEffect(() => { + if (!isLoaded || !map) return; + + // Add clustered GeoJSON source + map.addSource(sourceId, { + type: "geojson", + data, + cluster: true, + clusterMaxZoom, + clusterRadius, + }); + + // Add cluster circles layer + map.addLayer({ + id: clusterLayerId, + type: "circle", + source: sourceId, + filter: ["has", "point_count"], + paint: { + "circle-color": [ + "step", + ["get", "point_count"], + clusterColors[0], + clusterThresholds[0], + clusterColors[1], + clusterThresholds[1], + clusterColors[2], + ], + "circle-radius": [ + "step", + ["get", "point_count"], + 20, + clusterThresholds[0], + 30, + clusterThresholds[1], + 40, + ], + }, + }); + + // Add cluster count text layer + map.addLayer({ + id: clusterCountLayerId, + type: "symbol", + source: sourceId, + filter: ["has", "point_count"], + layout: { + "text-field": "{point_count_abbreviated}", + "text-size": 12, + }, + paint: { + "text-color": "#fff", + }, + }); + + // Add unclustered point layer + map.addLayer({ + id: unclusteredLayerId, + type: "circle", + source: sourceId, + filter: ["!", ["has", "point_count"]], + paint: { + "circle-color": pointColor, + "circle-radius": 6, + }, + }); + + return () => { + try { + if (map.getLayer(clusterCountLayerId)) + map.removeLayer(clusterCountLayerId); + if (map.getLayer(unclusteredLayerId)) + map.removeLayer(unclusteredLayerId); + if (map.getLayer(clusterLayerId)) + map.removeLayer(clusterLayerId); + if (map.getSource(sourceId)) map.removeSource(sourceId); + } catch { + // ignore + } + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isLoaded, map, sourceId]); + + // Update source data when data prop changes (only for non-URL data) + useEffect(() => { + if (!isLoaded || !map || typeof data === "string") return; + + const source = map.getSource(sourceId) as MapLibreGL.GeoJSONSource; + if (source) { + source.setData(data); + } + }, [isLoaded, map, data, sourceId]); + + // Update layer styles when props change + useEffect(() => { + if (!isLoaded || !map) return; + + const prev = stylePropsRef.current; + const colorsChanged = + prev.clusterColors !== clusterColors || + prev.clusterThresholds !== clusterThresholds; + + // Update cluster layer colors and sizes + if (map.getLayer(clusterLayerId) && colorsChanged) { + map.setPaintProperty(clusterLayerId, "circle-color", [ + "step", + ["get", "point_count"], + clusterColors[0], + clusterThresholds[0], + clusterColors[1], + clusterThresholds[1], + clusterColors[2], + ]); + map.setPaintProperty(clusterLayerId, "circle-radius", [ + "step", + ["get", "point_count"], + 20, + clusterThresholds[0], + 30, + clusterThresholds[1], + 40, + ]); + } + + // Update unclustered point layer color + if ( + map.getLayer(unclusteredLayerId) && + prev.pointColor !== pointColor + ) { + map.setPaintProperty( + unclusteredLayerId, + "circle-color", + pointColor, + ); + } + + stylePropsRef.current = { + clusterColors, + clusterThresholds, + pointColor, + }; + }, [ + isLoaded, + map, + clusterLayerId, + unclusteredLayerId, + clusterColors, + clusterThresholds, + pointColor, + ]); + + // Handle click events + useEffect(() => { + if (!isLoaded || !map) return; + + // Cluster click handler - zoom into cluster + const handleClusterClick = async ( + e: MapLibreGL.MapMouseEvent & { + features?: MapLibreGL.MapGeoJSONFeature[]; + }, + ) => { + const features = map.queryRenderedFeatures(e.point, { + layers: [clusterLayerId], + }); + if (!features.length) return; + + const feature = features[0]; + const clusterId = feature.properties?.cluster_id as number; + const pointCount = feature.properties?.point_count as number; + const coordinates = (feature.geometry as GeoJSON.Point) + .coordinates as [number, number]; + + if (onClusterClick) { + onClusterClick(clusterId, coordinates, pointCount); + } else { + // Default behavior: zoom to cluster expansion zoom + const source = map.getSource( + sourceId, + ) as MapLibreGL.GeoJSONSource; + const zoom = await source.getClusterExpansionZoom(clusterId); + map.easeTo({ + center: coordinates, + zoom, + }); + } + }; + + // Unclustered point click handler + const handlePointClick = ( + e: MapLibreGL.MapMouseEvent & { + features?: MapLibreGL.MapGeoJSONFeature[]; + }, + ) => { + if (!onPointClick || !e.features?.length) return; + + const feature = e.features[0]; + const coordinates = ( + feature.geometry as GeoJSON.Point + ).coordinates.slice() as [number, number]; + + // Handle world copies + while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) { + coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360; + } + + onPointClick( + feature as unknown as GeoJSON.Feature, + coordinates, + ); + }; + + // Cursor style handlers + const handleMouseEnterCluster = () => { + map.getCanvas().style.cursor = "pointer"; + }; + const handleMouseLeaveCluster = () => { + map.getCanvas().style.cursor = ""; + }; + const handleMouseEnterPoint = () => { + if (onPointClick) { + map.getCanvas().style.cursor = "pointer"; + } + }; + const handleMouseLeavePoint = () => { + map.getCanvas().style.cursor = ""; + }; + + map.on("click", clusterLayerId, handleClusterClick); + map.on("click", unclusteredLayerId, handlePointClick); + map.on("mouseenter", clusterLayerId, handleMouseEnterCluster); + map.on("mouseleave", clusterLayerId, handleMouseLeaveCluster); + map.on("mouseenter", unclusteredLayerId, handleMouseEnterPoint); + map.on("mouseleave", unclusteredLayerId, handleMouseLeavePoint); + + return () => { + map.off("click", clusterLayerId, handleClusterClick); + map.off("click", unclusteredLayerId, handlePointClick); + map.off("mouseenter", clusterLayerId, handleMouseEnterCluster); + map.off("mouseleave", clusterLayerId, handleMouseLeaveCluster); + map.off("mouseenter", unclusteredLayerId, handleMouseEnterPoint); + map.off("mouseleave", unclusteredLayerId, handleMouseLeavePoint); + }; + }, [ + isLoaded, + map, + clusterLayerId, + unclusteredLayerId, + sourceId, + onClusterClick, + onPointClick, + ]); + + return null; +} + +export { + Map, + useMap, + MapMarker, + MarkerContent, + MarkerPopup, + MarkerTooltip, + MarkerLabel, + MapPopup, + MapControls, + MapRoute, + MapClusterLayer, +}; + +export type { MapRef };