diff --git a/docs/01_Problem_Definition/Requirements.md b/docs/01_Problem_Definition/Requirements.md index eb3ce23..fca3d41 100644 --- a/docs/01_Problem_Definition/Requirements.md +++ b/docs/01_Problem_Definition/Requirements.md @@ -1,8 +1,8 @@ -# Project Requirements — Biochar Placement Optimization Tool +# Project Requirements - Biochar Placement Optimization Tool ## Table of Contents -- [Project Requirements — Biochar Placement Optimization Tool](#project-requirements--biochar-placement-optimization-tool) +- [Project Requirements - Biochar Placement Optimization Tool](#project-requirements--biochar-placement-optimization-tool) - [Table of Contents](#table-of-contents) - [Functional Requirements](#functional-requirements) - [User-Facing Input/Output](#user-facing-inputoutput) @@ -65,11 +65,11 @@ The total budget of the product shall not exceed $1000. The following schedule outline shall be followed: -- Approval of Requirements — Sept. 30, 2025 -- Concept Design Review — Nov. 30, 2025 -- EPO of long lead parts — Dec. 8, 2025 -- Detailed Design Review — Feb. 9, 2026 -- ER of drawing package — Mar. 2, 2026 -- Complete Prototype Build — Apr. 5, 2026 -- UI Design EXPO — Apr. 26, 2026 -- Final Report / Drawings — May 4, 2026 \ No newline at end of file +- Approval of Requirements - Sept. 30, 2025 +- Concept Design Review - Nov. 30, 2025 +- EPO of long lead parts - Dec. 8, 2025 +- Detailed Design Review - Feb. 9, 2026 +- ER of drawing package - Mar. 2, 2026 +- Complete Prototype Build - Apr. 5, 2026 +- UI Design EXPO - Apr. 26, 2026 +- Final Report / Drawings - May 4, 2026 \ No newline at end of file diff --git a/docs/03_Project_Management/Schedule/Schedule.md b/docs/03_Project_Management/Schedule/Schedule.md index df303de..c2ea51f 100644 --- a/docs/03_Project_Management/Schedule/Schedule.md +++ b/docs/03_Project_Management/Schedule/Schedule.md @@ -4,7 +4,7 @@ The project schedule outlines the planned timeline and major milestones for Char The schedule provides a high-level view of dependencies between tasks, showing how progress in one area (such as dataset preparation) enables the next (such as model development). By visualizing these relationships, the schedule helps identify potential bottlenecks early and ensures efficient coordination between contributors. -The Gantt chart accompanying this document serves as the main visual reference for the project’s timeline. It includes major milestones—such as Snapshot Days—to track deliverable progress and measure readiness for deployment. +The Gantt chart accompanying this document serves as the main visual reference for the project's timeline. It includes major milestones-such as Snapshot Days-to track deliverable progress and measure readiness for deployment. In summary, the schedule acts as both a **planning tool** and a **communication aid**, helping the team maintain focus, manage dependencies, and make informed adjustments as the project evolves. diff --git a/frontend/README.md b/frontend/README.md index c178e59..be2ea38 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -110,14 +110,14 @@ The frontend integrates with Django REST Framework using **TokenAuthentication** ### Key Files -- `src/services/authService.ts` — API calls and token management -- `src/contexts/AuthContext.tsx` — Auth state and methods -- `src/types/auth.ts` — TypeScript types for auth -- `src/components/ProtectedRoute.tsx` — Guards authenticated pages -- `src/components/PublicRoute.tsx` — Prevents logged-in users from seeing login/signup -- `src/pages/LoginPage.tsx` — Login form -- `src/pages/SignupPage.tsx` — Registration form -- `src/pages/HomePage.tsx` — Protected home page (shown only to authenticated users) +- `src/services/authService.ts` - API calls and token management +- `src/contexts/AuthContext.tsx` - Auth state and methods +- `src/types/auth.ts` - TypeScript types for auth +- `src/components/ProtectedRoute.tsx` - Guards authenticated pages +- `src/components/PublicRoute.tsx` - Prevents logged-in users from seeing login/signup +- `src/pages/LoginPage.tsx` - Login form +- `src/pages/SignupPage.tsx` - Registration form +- `src/pages/HomePage.tsx` - Protected home page (shown only to authenticated users) --- @@ -172,10 +172,10 @@ Commit both `package.json` and the lock file to version control. - **Package manager**: `npm` - **Project root**: `src/` - **Main commands**: - - `npm run dev` — start development server - - `npm run build` — build for production and type check - - `npm run preview` — preview production build - - `npm run lint` — lint codebase + - `npm run dev` - start development server + - `npm run build` - build for production and type check + - `npm run preview` - preview production build + - `npm run lint` - lint codebase - **Configuration**: - TypeScript: `tsconfig.json` - Vite: `vite.config.ts` diff --git a/frontend/index.html b/frontend/index.html index 65f5e81..2b0e1ac 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -3,6 +3,13 @@ + + + CharAI diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 8a611fc..bbc72be 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -10,13 +10,19 @@ "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.1", + "@mui/icons-material": "^7.3.5", "@mui/material": "^7.3.5", - "react": "^19.1.1", - "react-dom": "^19.1.1", + "@turf/turf": "^7.3.1", + "leaflet": "^1.9.4", + "maplibre-gl": "^5.15.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "react-leaflet": "^5.0.0-rc.2", "react-router": "^7.9.5" }, "devDependencies": { "@eslint/js": "^9.36.0", + "@types/leaflet": "^1.9.21", "@types/node": "^24.6.0", "@types/react": "^19.1.16", "@types/react-dom": "^19.1.9", @@ -752,6 +758,109 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "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/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/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/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.0", + "resolved": "https://registry.npmjs.org/@maplibre/vt-pbf/-/vt-pbf-4.2.0.tgz", + "integrity": "sha512-bxrk/kQUwWXZgmqYgwOCnZCMONCRi3MJMqJdza4T3E4AeR5i+VyMnaJ8iDWtWxdfEAJRtrzIOeJtxZSy5mFrFA==", + "license": "MIT", + "dependencies": { + "@mapbox/point-geometry": "^1.1.0", + "@mapbox/vector-tile": "^2.0.4", + "@types/geojson-vt": "3.2.5", + "@types/supercluster": "^7.1.3", + "geojson-vt": "^4.0.2", + "pbf": "^4.0.1", + "supercluster": "^8.0.1" + } + }, "node_modules/@mui/core-downloads-tracker": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.5.tgz", @@ -762,6 +871,32 @@ "url": "https://opencollective.com/mui-org" } }, + "node_modules/@mui/icons-material": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.3.5.tgz", + "integrity": "sha512-LciL1GLMZ+VlzyHAALSVAR22t8IST4LCXmljcUSx2NOutgO2XnxdIp8ilFbeNf9wpo0iUFbAuoQcB7h+HHIf3A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^7.3.5", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@mui/material": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.5.tgz", @@ -1040,6 +1175,17 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@react-leaflet/core": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-3.0.0.tgz", + "integrity": "sha512-3EWmekh4Nz+pGcr+xjf0KNyYfC3U2JjnkWsh0zcqaexYqmmB5ZhH37kz41JXGmKzpaMZCnPofBBm64i+YrEvGQ==", + "license": "Hippocratic-2.1", + "peerDependencies": { + "leaflet": "^1.9.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" + } + }, "node_modules/@rolldown/binding-android-arm64": { "version": "1.0.0-beta.41", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.41.tgz", @@ -1221,69 +1367,2101 @@ "license": "MIT", "optional": true, "dependencies": { - "@napi-rs/wasm-runtime": "^1.0.5" + "@napi-rs/wasm-runtime": "^1.0.5" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-beta.41", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.41.tgz", + "integrity": "sha512-NIYGuCcuXaq5BC4Q3upbiMBvmZsTsEPG9k/8QKQdmrch+ocSy5Jv9tdpdmXJyighKqm182nh/zBt+tSJkYoNlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-ia32-msvc": { + "version": "1.0.0-beta.41", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.0.0-beta.41.tgz", + "integrity": "sha512-kANdsDbE5FkEOb5NrCGBJBCaZ2Sabp3D7d4PRqMYJqyLljwh9mDyYyYSv5+QNvdAmifj+f3lviNEUUuUZPEFPw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-beta.41", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.41.tgz", + "integrity": "sha512-UlpxKmFdik0Y2VjZrgUCgoYArZJiZllXgIipdBRV1hw6uK45UbQabSTW6Kp6enuOu7vouYWftwhuxfpE8J2JAg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.43", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.43.tgz", + "integrity": "sha512-5Uxg7fQUCmfhax7FJke2+8B6cqgeUJUD9o2uXIKXhD+mG0mL6NObmVoi9wXEU1tY89mZKgAYA6fTbftx3q2ZPQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@turf/along": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/along/-/along-7.3.1.tgz", + "integrity": "sha512-z84b9PKsUB69BhkeHA6oPqRO7VaJHwTid1SpuIbwWzDqHTpq8buJBKlrKgHIIthuVr5P/AZiEXmf3R4ifRhDmw==", + "license": "MIT", + "dependencies": { + "@turf/bearing": "7.3.1", + "@turf/destination": "7.3.1", + "@turf/distance": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/angle": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/angle/-/angle-7.3.1.tgz", + "integrity": "sha512-Pcb0Fg8WHsOMKFvIPaYfORrlLYdytWjVAkVTnAqJdmGI+2n+eLROPjJO2sJbpX9yU/dlBgujOB7a1d0PJjhHyQ==", + "license": "MIT", + "dependencies": { + "@turf/bearing": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@turf/rhumb-bearing": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/area": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/area/-/area-7.3.1.tgz", + "integrity": "sha512-9nSiwt4zB5QDMcSoTxF28WpK1f741MNKcpUJDiHVRX08CZ4qfGWGV9ZIPQ8TVEn5RE4LyYkFuQ47Z9pdEUZE9Q==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/bbox": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/bbox/-/bbox-7.3.1.tgz", + "integrity": "sha512-/IyMKoS7P9B0ch5PIlQ6gMfoE8gRr48+cSbzlyexvEjuDuaAV1VURjH1jAthS0ipFG8RrFxFJKnp7TLL1Skong==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/bbox-clip": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/bbox-clip/-/bbox-clip-7.3.1.tgz", + "integrity": "sha512-YUeITFtp5QLbpSS0XyQa0GlgMqK4PMgjOeOGOTlWsfDYaqc5SErf7o5UyCOsLAPQW16QZVxJ26uTAE20YkluAA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/bbox-polygon": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/bbox-polygon/-/bbox-polygon-7.3.1.tgz", + "integrity": "sha512-2NvwPfuRtwJk7w5HIC/Knei3mUXrVT+t/0FB1zStgDbakmXrqKISaftlIh4YTOVlUsVnvq0tggjFMLZ/Xxo+lQ==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/bearing": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/bearing/-/bearing-7.3.1.tgz", + "integrity": "sha512-ex78l/LiY6uO6jO8AJepyWE6/tiWEbXjKLOgqUfJSkW23UcMVlhbAKzXDjbsdz9T66sXFC/6QNAh8oaZzmoo6w==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/bezier-spline": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/bezier-spline/-/bezier-spline-7.3.1.tgz", + "integrity": "sha512-7Mal/d8ttTQ5eu/mwgC53iH9eYBRTBHXsIqEEiTVHChh1iajNuS4/bwYdaxsQsRXKVaFfx+4dCy0cRmqhjgTrw==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-clockwise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/boolean-clockwise/-/boolean-clockwise-7.3.1.tgz", + "integrity": "sha512-ik9j0CCrsp/JZ42tbCnyZg86YFoavEU/nyal3HsEgdY5WFYq43aMYqLPRi6yNqE48THEk3fl1BcfgJqAiUhDFA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-concave": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/boolean-concave/-/boolean-concave-7.3.1.tgz", + "integrity": "sha512-jAAt5MhqXSKmRmX7l09oeo9dObf7bMDuzfeUSSNAK+yAi9TE5QWlP4JtzOWC5+gKXsL8dvzE8mvsQj38FzQdEA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-contains": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/boolean-contains/-/boolean-contains-7.3.1.tgz", + "integrity": "sha512-VvytV9ZcUgnitzm5ILVWIoOhoZOh8VZ4dnweUJM3N+A77CzXXFk8e4NqPNZ6tZVPY3ehxzDXrq1+iN87pMcB7g==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "7.3.1", + "@turf/boolean-point-in-polygon": "7.3.1", + "@turf/boolean-point-on-line": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-crosses": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/boolean-crosses/-/boolean-crosses-7.3.1.tgz", + "integrity": "sha512-Fn99AxTXQORiQjclUqUYQcA40oJJoJxMBFx/Vycd7v949Lnplt1qrUkBpbZNXQlvHF2gxrgirSfgBDaUnUJjzQ==", + "license": "MIT", + "dependencies": { + "@turf/boolean-point-in-polygon": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@turf/line-intersect": "7.3.1", + "@turf/polygon-to-line": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-disjoint": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/boolean-disjoint/-/boolean-disjoint-7.3.1.tgz", + "integrity": "sha512-bqVo+eAYaCq0lcr09zsZdWIAdv22UzGc/h2CCfaBwP5r4o/rFudNFLU9gb9BcM6dBUzrtTgBguShAZr7k3cGbw==", + "license": "MIT", + "dependencies": { + "@turf/boolean-point-in-polygon": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/line-intersect": "7.3.1", + "@turf/meta": "7.3.1", + "@turf/polygon-to-line": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-equal": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/boolean-equal/-/boolean-equal-7.3.1.tgz", + "integrity": "sha512-nEsmmNdwD1nzYZLsO6hPC/X/Uag+eT0yuWamD0XxJAQhXBsnSATxKisCJXVJgXvO8M0qvEMW1zZrUGB6Fjfzzw==", + "license": "MIT", + "dependencies": { + "@turf/clean-coords": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@types/geojson": "^7946.0.10", + "geojson-equality-ts": "^1.0.2", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-intersects": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/boolean-intersects/-/boolean-intersects-7.3.1.tgz", + "integrity": "sha512-nc6W8qFdzFkfsR6p506HINGu85nHk/Skm+cw3TRQZ5/A44hjf0kYnbhvS3qrCAws3bR+/FKK8O1bsO/Udk8kkg==", + "license": "MIT", + "dependencies": { + "@turf/boolean-disjoint": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-overlap": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/boolean-overlap/-/boolean-overlap-7.3.1.tgz", + "integrity": "sha512-QhhsgCLzkwXIeZhaCmgE3H8yTANJGZatJ5IzQG3xnPTx7LiNAaa/ReN2/NroEv++8Yc0sr5Bkh6xWZOtew1dvQ==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@turf/line-intersect": "7.3.1", + "@turf/line-overlap": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10", + "geojson-equality-ts": "^1.0.2", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-parallel": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/boolean-parallel/-/boolean-parallel-7.3.1.tgz", + "integrity": "sha512-SXPyYiuaRB1ES/LtcUP11HWyloMJGzN1nYaCLG7H+6l2OKjVJl025qR6uxVElWCzAdElek9nGNeNya1hd9ZHaw==", + "license": "MIT", + "dependencies": { + "@turf/clean-coords": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/line-segment": "7.3.1", + "@turf/rhumb-bearing": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-point-in-polygon": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/boolean-point-in-polygon/-/boolean-point-in-polygon-7.3.1.tgz", + "integrity": "sha512-BUPW63vE43LctwkgannjmEFTX1KFR/18SS7WzFahJWK1ZoP0s1jrfxGX+pi0BH/3Dd9mA71hkGKDDnj1Ndcz0g==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@types/geojson": "^7946.0.10", + "point-in-polygon-hao": "^1.1.0", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-point-on-line": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/boolean-point-on-line/-/boolean-point-on-line-7.3.1.tgz", + "integrity": "sha512-8Hywuv7XFpSc8nfH0BJBtt+XTcJ7OjfjpX2Sz+ty8gyiY/2nCLLqq6amu3ebr67ruqZTDpPNQoGGUbUePjF3rA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-touches": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/boolean-touches/-/boolean-touches-7.3.1.tgz", + "integrity": "sha512-XqrQzYGTakoTWeTWT274pfObpbIpAM7L8CzGUa04rJD0l3bv3VK4TUw0v6+bywi5ea6TnJzvOzgvzTb1DtvBKA==", + "license": "MIT", + "dependencies": { + "@turf/boolean-point-in-polygon": "7.3.1", + "@turf/boolean-point-on-line": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-valid": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/boolean-valid/-/boolean-valid-7.3.1.tgz", + "integrity": "sha512-lpw4J5HaV4Tv033s2j/i6QHt6Zx/8Lc90DTfOU0axgRSrs127kbKNJsmDEGvtmV7YjNp8aPbIG1wwAX9wg/dMA==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "7.3.1", + "@turf/boolean-crosses": "7.3.1", + "@turf/boolean-disjoint": "7.3.1", + "@turf/boolean-overlap": "7.3.1", + "@turf/boolean-point-in-polygon": "7.3.1", + "@turf/boolean-point-on-line": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@turf/line-intersect": "7.3.1", + "@types/geojson": "^7946.0.10", + "geojson-polygon-self-intersections": "^1.2.1", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-within": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/boolean-within/-/boolean-within-7.3.1.tgz", + "integrity": "sha512-oxP4VU81RRCf59TXCBhVWEyJ5Lsr+wrqvqSAFxyBuur5oLmBqZdYyvL7FQJmYvG0uOxX7ohyHmSJMaTe4EhGDA==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "7.3.1", + "@turf/boolean-point-in-polygon": "7.3.1", + "@turf/boolean-point-on-line": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/buffer": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/buffer/-/buffer-7.3.1.tgz", + "integrity": "sha512-jtdI0Ir3GwPyY1V2dFX039HNhD8MIYLX39c7b9AZdLh7kBuD2VgXJmPvhtnivqMV2SmRlS4fd9cKzNj369/cGg==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "7.3.1", + "@turf/center": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/jsts": "^2.7.1", + "@turf/meta": "7.3.1", + "@turf/projection": "7.3.1", + "@types/geojson": "^7946.0.10", + "d3-geo": "1.7.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/center": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/center/-/center-7.3.1.tgz", + "integrity": "sha512-czqNKLGGdik3phYsWCK5SHKBRkDulUArMlG4v62IQcNcRFq9MbOGqyN21GSshSMO792ynDeWzdXdcKmycQ14Yg==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "7.3.1", + "@turf/helpers": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/center-mean": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/center-mean/-/center-mean-7.3.1.tgz", + "integrity": "sha512-koVenhCl8JPEvtDwH6nhZpLAm9+7XOXosqKdkXyK1uDae3NRyoQQeIYD7nIJHJPCOyeacw6buWzAEoAleBj0XA==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/center-median": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/center-median/-/center-median-7.3.1.tgz", + "integrity": "sha512-XIvxqnSdcUFOev4WO8AEQth4U3uzfQkxYVkKhZrxpVitqEeSDm5v3ANUeVGYqQ/QNTWvFAFn4zB5+XRRd8tayA==", + "license": "MIT", + "dependencies": { + "@turf/center-mean": "7.3.1", + "@turf/centroid": "7.3.1", + "@turf/distance": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/center-of-mass": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/center-of-mass/-/center-of-mass-7.3.1.tgz", + "integrity": "sha512-w2O7RLc0tSs+eEsZCaWa1lYiACsaQTJtie/a4bj5ta1TDTAEjyxC6Rp6br4mN1XPzeSFbEuNw+q9/VdSXU/mGA==", + "license": "MIT", + "dependencies": { + "@turf/centroid": "7.3.1", + "@turf/convex": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/centroid": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/centroid/-/centroid-7.3.1.tgz", + "integrity": "sha512-hRnsDdVBH4pX9mAjYympb2q5W8TCMUMNEjcRrAF7HTCyjIuRmjJf8vUtlzf7TTn9RXbsvPc1vtm3kLw20Jm8DQ==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/circle": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/circle/-/circle-7.3.1.tgz", + "integrity": "sha512-UY2OM1OK7IuyrtN3YE8026ZM3xM9VIkqZ0vRZln8g33D0AogrJVJ/I9T81/VpRPlxTnrbDpzQxJQBH+3vPG/Ow==", + "license": "MIT", + "dependencies": { + "@turf/destination": "7.3.1", + "@turf/helpers": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/clean-coords": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/clean-coords/-/clean-coords-7.3.1.tgz", + "integrity": "sha512-uNo4lnTekvkw8dUCXIVCc38nZiHBrpy5jn0T8hlodZo/A4XAChFtLQi8NLcX8rtXcaNxeJo+yaPfpP3PSVI2jw==", + "license": "MIT", + "dependencies": { + "@turf/boolean-point-on-line": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/clone": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/clone/-/clone-7.3.1.tgz", + "integrity": "sha512-r7xDOfw9ohA7PhZW+8X9RMsO4szB4YqkhEROaELJyLtQ1bo8VNFtndpZdE6YHQpD7Pjlvlb6i99q8w1QLisEPg==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/clusters": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/clusters/-/clusters-7.3.1.tgz", + "integrity": "sha512-ZELehyYnsozw+AHOc426abmPaGJOt46BHnCN+hwtPOkqEbvdZYu+16Y+cjiFnY7FwbvzBjDMb9HRtKJFlAmupg==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/clusters-dbscan": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/clusters-dbscan/-/clusters-dbscan-7.3.1.tgz", + "integrity": "sha512-rY1wbQlljRhX5e+XM/yw4dKs2HniN45v+Xf5Xde6nv23WyEf/LLjpyD5yrsLa1awfJjD/NmD6axGVebnBBn9YA==", + "license": "MIT", + "dependencies": { + "@turf/clone": "7.3.1", + "@turf/distance": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10", + "rbush": "^3.0.1", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/clusters-kmeans": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/clusters-kmeans/-/clusters-kmeans-7.3.1.tgz", + "integrity": "sha512-HYvRninBY/b5ftkIkoVWjV/wHilNE56cdr6gTlrxuvm4EClilsLDSVYjeiMYU0pjI3xDTc7PlicQDGdnIavUqQ==", + "license": "MIT", + "dependencies": { + "@turf/clone": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10", + "skmeans": "0.9.7", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/collect": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/collect/-/collect-7.3.1.tgz", + "integrity": "sha512-yVDz5YLcRGFipttb60Y4IAd7zWfbQk6mNW5Kt6/wa8+YueHFzsKJdtbErWfozCVuiKplQZWT5r+9J9g6RnhpjQ==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "7.3.1", + "@turf/boolean-point-in-polygon": "7.3.1", + "@turf/helpers": "7.3.1", + "@types/geojson": "^7946.0.10", + "rbush": "^3.0.1", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/combine": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/combine/-/combine-7.3.1.tgz", + "integrity": "sha512-iZBe36sKRq08fY3Ars0JpfYJm8N3LtLLnNzdTxHp8Ry2ORJGHvZHpcv3lQXWL7gyJwDPAye7pyrX7S99IB/1VA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/concave": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/concave/-/concave-7.3.1.tgz", + "integrity": "sha512-vZWqyAYH4qzOuiqPb+bj2jvpIGzYAH8byUhfFJ2gRFRL3/RfV8jdXL2r0Y6VFScqE6OLVGvtM3ITzXX1/9wTaA==", + "license": "MIT", + "dependencies": { + "@turf/clone": "7.3.1", + "@turf/distance": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@turf/meta": "7.3.1", + "@turf/tin": "7.3.1", + "@types/geojson": "^7946.0.10", + "topojson-client": "3.x", + "topojson-server": "3.x", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/convex": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/convex/-/convex-7.3.1.tgz", + "integrity": "sha512-k2T8QVSie4w+KhwUxjzi/6S6VFr33H9gnUawOh4chCGAgje9PljUZLCGbktHgDfAjX1FVzyUyriH+dm86Z7njQ==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10", + "concaveman": "^1.2.1", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/destination": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/destination/-/destination-7.3.1.tgz", + "integrity": "sha512-yyiJtbQJ4AB9Ny/FKDDNuWI9Sg4Jtd2PMpQPqOV3AFq8NNkg0xJSNmDHDxupb3oPqPWYPxyfVI3tBoF+Xhhoig==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/difference": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/difference/-/difference-7.3.1.tgz", + "integrity": "sha512-Ne2AR+1AdeH8aqY2VHcws+Z/1MHl8SlSbSWHBNVZUVEfvyzTrRg8/E+OC5vFaSUvNZXkB/OUufTCM9xsatLKXQ==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10", + "polyclip-ts": "^0.16.8", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/dissolve": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/dissolve/-/dissolve-7.3.1.tgz", + "integrity": "sha512-Xmjl4E1aGRMdJjq+HfsiAXZtfMKruq7O+8xvsqnHM6E8iBWlJNSw8ucrNB5RZME8BUojx0q8bvXgS3k68koGyw==", + "license": "MIT", + "dependencies": { + "@turf/flatten": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10", + "polyclip-ts": "^0.16.8", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/distance": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/distance/-/distance-7.3.1.tgz", + "integrity": "sha512-DK//doTGgYYjBkcWUywAe7wbZYcdP97hdEJ6rXYVYRoULwGGR3lhY96GNjozg6gaW9q2eSNYnZLpcL5iFVHqgw==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/distance-weight": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/distance-weight/-/distance-weight-7.3.1.tgz", + "integrity": "sha512-h82qLPeMxOfgN62ZysscQCu9IYB5AO+duw7peAQnMtFobpbcQK58158P0cNzxAoTVJXSO/mfR9dI9Zdz7NF75w==", + "license": "MIT", + "dependencies": { + "@turf/centroid": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/ellipse": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/ellipse/-/ellipse-7.3.1.tgz", + "integrity": "sha512-tcGbS+U7EktZg+UJad17LRU+8C067XDWdmURPCmycaib2zRxeNrImh2Y/589us6wsldlYYoBYRxDY/c1oxIUCA==", + "license": "MIT", + "dependencies": { + "@turf/destination": "7.3.1", + "@turf/distance": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@turf/transform-rotate": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/envelope": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/envelope/-/envelope-7.3.1.tgz", + "integrity": "sha512-Sp3ct/LpWyHN5tTfPOcKXFoVDI1QH9BXtQ+aQzABFp3U5nY2Sz8LFg8SeFQm3K7PpoCnUwSfwDFA4aa+z+4l1g==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "7.3.1", + "@turf/bbox-polygon": "7.3.1", + "@turf/helpers": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/explode": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/explode/-/explode-7.3.1.tgz", + "integrity": "sha512-H0Q8NnmrPoWKhsYYmVmkuT5F4t50N53ByGBf6Ys1n5B9YrFyrT+/aLDXF2C05r+QnW8nFtkM4lFG3ZSBHiq4Xg==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/flatten": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/flatten/-/flatten-7.3.1.tgz", + "integrity": "sha512-cM/uuQP8oZ4IDJG342uOlqQ8yD9RsAY9Gg9nsDOgJn6tN065aigRCNy2lfrNyLdK/CPTVEWQzx1EQa+zXGSgAg==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/flip": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/flip/-/flip-7.3.1.tgz", + "integrity": "sha512-6sF41pWY8Tw7w72hYc87sR9zzDei7UZ4Db/z0mKuNKueyzl4iTQ/H2JVd/XLZ7Tasz7H8htmrbUO0GR8GY7qiQ==", + "license": "MIT", + "dependencies": { + "@turf/clone": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/geojson-rbush": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/geojson-rbush/-/geojson-rbush-7.3.1.tgz", + "integrity": "sha512-EsrBBftZS5TvzRP2opLzwfnPXfmJi45KkGUcKSSFD0bxQe3BQUSmBrZbHMT8avB2s/XHrS/MniqsyeVOMwc35Q==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10", + "rbush": "^3.0.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/great-circle": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/great-circle/-/great-circle-7.3.1.tgz", + "integrity": "sha512-pfs7PzBRgYEEyecM0ni6iEF19grn9FmbHyaLz7voYInmc2ZHfWQaxuY4dcf9cziWDaiPlbuyr/RyE6envg1xpw==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@types/geojson": "^7946.0.10" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/helpers": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-7.3.1.tgz", + "integrity": "sha512-zkL34JVhi5XhsuMEO0MUTIIFEJ8yiW1InMu4hu/oRqamlY4mMoZql0viEmH6Dafh/p+zOl8OYvMJ3Vm3rFshgg==", + "license": "MIT", + "dependencies": { + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/hex-grid": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/hex-grid/-/hex-grid-7.3.1.tgz", + "integrity": "sha512-cWAKxlU1aa06976C3RhpcilDzLnWwXkH/atNIWKGpLV/HubHrMXxhp9VMBKWaqsLbdn5x2uJjv4MxwWw9/373g==", + "license": "MIT", + "dependencies": { + "@turf/distance": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/intersect": "7.3.1", + "@turf/invariant": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/interpolate": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/interpolate/-/interpolate-7.3.1.tgz", + "integrity": "sha512-dquwDplzkSANMQdvxAu0dRF69EBIIlW/1zTPOB/BQfb/s7j6t8RskgbuV8ew1KpJPMmj7EbexejiMBtRWXTu4Q==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "7.3.1", + "@turf/centroid": "7.3.1", + "@turf/clone": "7.3.1", + "@turf/distance": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/hex-grid": "7.3.1", + "@turf/invariant": "7.3.1", + "@turf/meta": "7.3.1", + "@turf/point-grid": "7.3.1", + "@turf/square-grid": "7.3.1", + "@turf/triangle-grid": "7.3.1", + "@types/geojson": "^7946.0.10" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/intersect": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/intersect/-/intersect-7.3.1.tgz", + "integrity": "sha512-676688YnF9wpprMioQWvxPlUMhtTvYITzw4XoG3lQmLjd/yt2cByanQHWpzWauLfYUlfuL13AeRGdqXRhSkhTQ==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10", + "polyclip-ts": "^0.16.8", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/invariant": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/invariant/-/invariant-7.3.1.tgz", + "integrity": "sha512-IdZJfDjIDCLH+Gu2yLFoSM7H23sdetIo5t4ET1/25X8gi3GE2XSqbZwaGjuZgNh02nisBewLqNiJs2bo+hrqZA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/isobands": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/isobands/-/isobands-7.3.1.tgz", + "integrity": "sha512-An6+yUSrOStQSpZwKW9XN891kCW6eagtuofyudZ2BkoxcYRJ0vcDXo7RoiXuf9nHaG4k/xwhAzTqe8hdO1ltWA==", + "license": "MIT", + "dependencies": { + "@turf/area": "7.3.1", + "@turf/bbox": "7.3.1", + "@turf/boolean-point-in-polygon": "7.3.1", + "@turf/explode": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/isolines": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/isolines/-/isolines-7.3.1.tgz", + "integrity": "sha512-TcwbTd7Z4BffYe1PtpXUtZvWCwTffta8VxqryGU30CbqKjNJYqrFbEQXS0mo4l3BEPPmT1lfMskUQ2g97O2MWQ==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/jsts": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@turf/jsts/-/jsts-2.7.2.tgz", + "integrity": "sha512-zAezGlwWHPyU0zxwcX2wQY3RkRpwuoBmhhNE9HY9kWhFDkCxZ3aWK5URKwa/SWKJbj9aztO+8vtdiBA28KVJFg==", + "license": "(EDL-1.0 OR EPL-1.0)", + "dependencies": { + "jsts": "2.7.1" + } + }, + "node_modules/@turf/kinks": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/kinks/-/kinks-7.3.1.tgz", + "integrity": "sha512-gGXNrhlF7zvLwRX672S0Be7bmYjbZEoZYnOGN6RvhyBFSSLFIbne+I74I+lWRzAzG/NhAMBXma5TpB09iTH06Q==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/length": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/length/-/length-7.3.1.tgz", + "integrity": "sha512-QOr4qS3yi6qWIfQ/KLcy4rDLdemGCYpqz2YDh29R46seE+arSvlBI0KXvI36rPzgEMcUbQuVQyO65sOSqPaEjQ==", + "license": "MIT", + "dependencies": { + "@turf/distance": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-arc": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/line-arc/-/line-arc-7.3.1.tgz", + "integrity": "sha512-QSuVP0YWcfl76QjPb5Y2GJqXnziSJ2AuaJm5RKEFt5ELugXdEcHkRtydkGov+ZRPmI93jVmXoEE0UXwQx7aYHA==", + "license": "MIT", + "dependencies": { + "@turf/circle": "7.3.1", + "@turf/destination": "7.3.1", + "@turf/helpers": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-chunk": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/line-chunk/-/line-chunk-7.3.1.tgz", + "integrity": "sha512-fbJw/7Qlqz0XRMz0TgtFUivFHr51+++ZUBrARgs3w/pogeAdkrcWKBbuT2cowEsUkXDHaQ7MMpmuV8Uteru1qw==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@turf/length": "7.3.1", + "@turf/line-slice-along": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-intersect": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/line-intersect/-/line-intersect-7.3.1.tgz", + "integrity": "sha512-HFPH4Hi+rG7XZ5rijkYL5C9JGVKd6gz6TToShVfqOt/qgGY9/bLYQxymgum/MG7sRhIa8xcKff2d57JrIVuSWA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@types/geojson": "^7946.0.10", + "sweepline-intersections": "^1.5.0", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-offset": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/line-offset/-/line-offset-7.3.1.tgz", + "integrity": "sha512-PyElfSyXETXcI8OKRsAJNdOcxlM718EG0d+b9zeO2uRztf2IlSb5w3lYiTIUSslEDA1gMQE31cJE8sAW40+nhg==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-overlap": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/line-overlap/-/line-overlap-7.3.1.tgz", + "integrity": "sha512-xIhTfPhJMwz57DvM+/JuzG2BUL/gR/pJfH6w+vofI3akej33LTR8b296h2dhcJjDixxprVVH062AD1Q3AGKyfg==", + "license": "MIT", + "dependencies": { + "@turf/boolean-point-on-line": "7.3.1", + "@turf/geojson-rbush": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@turf/line-segment": "7.3.1", + "@turf/meta": "7.3.1", + "@turf/nearest-point-on-line": "7.3.1", + "@types/geojson": "^7946.0.10", + "fast-deep-equal": "^3.1.3", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-segment": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/line-segment/-/line-segment-7.3.1.tgz", + "integrity": "sha512-hHz1fM2LigNKmnhyHDXtbRrkBqltH/lYEvhgSmv3laZ9PsEYL8jvA3o7+IhLM9B4KPa8N6VGim6ZR5YA5bhLvQ==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-slice": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/line-slice/-/line-slice-7.3.1.tgz", + "integrity": "sha512-bp1L4sc7ZOYC4fwxpfWu+IR/COvLFGm5mjbLPK8VBJYa+kUNrzNcB3QE3A8yFRjwPtlUTCm5fDMLSoGtiJcy2g==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@turf/nearest-point-on-line": "7.3.1", + "@types/geojson": "^7946.0.10" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-slice-along": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/line-slice-along/-/line-slice-along-7.3.1.tgz", + "integrity": "sha512-RizIhPytHxEewCyUCSMrZ5a58sQev0kZ0jzAV/9iTzvGfRD1VU/RG2ThLpSEqXYKBBSty98rTeSlnwsvZpAraA==", + "license": "MIT", + "dependencies": { + "@turf/bearing": "7.3.1", + "@turf/destination": "7.3.1", + "@turf/distance": "7.3.1", + "@turf/helpers": "7.3.1", + "@types/geojson": "^7946.0.10" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-split": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/line-split/-/line-split-7.3.1.tgz", + "integrity": "sha512-Ee4NRN+eYKYX8vJDNvMpyZFjOntKFokQ/E8yFtKMcN++vG7RbnPOo2/ag6TMZaIHsahj4UR2yhqJbHTaB6Dp+g==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "7.3.1", + "@turf/geojson-rbush": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@turf/line-intersect": "7.3.1", + "@turf/line-segment": "7.3.1", + "@turf/meta": "7.3.1", + "@turf/nearest-point-on-line": "7.3.1", + "@turf/truncate": "7.3.1", + "@types/geojson": "^7946.0.10" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-to-polygon": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/line-to-polygon/-/line-to-polygon-7.3.1.tgz", + "integrity": "sha512-GL4fjbdYYjfOmwTu4dtllNHm18E7+hoXqyca2Rqb2ZzXj++NHvifJ9iYHUSdpV4/mkvVD3U2rU6jzNkjQeXIaA==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "7.3.1", + "@turf/clone": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/mask": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/mask/-/mask-7.3.1.tgz", + "integrity": "sha512-rSNS6wNuBiaUR1aU7tobgkzHpot5v9GKCn+n5gQ3ad7KWqwwqLWfcCPeyHBWkWEoEwc2yfPqikMQugZbmxrorg==", + "license": "MIT", + "dependencies": { + "@turf/clone": "7.3.1", + "@turf/helpers": "7.3.1", + "@types/geojson": "^7946.0.10", + "polyclip-ts": "^0.16.8", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/meta": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/meta/-/meta-7.3.1.tgz", + "integrity": "sha512-NWsfOE5RVtWpLQNkfOF/RrYvLRPwwruxhZUV0UFIzHqfiRJ50aO9Y6uLY4bwCUe2TumLJQSR4yaoA72Rmr2mnQ==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@types/geojson": "^7946.0.10" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/midpoint": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/midpoint/-/midpoint-7.3.1.tgz", + "integrity": "sha512-hx3eT9ut0Qyl8fyitCREp9l+v5Q4uBILht5+VKQS3p5eK2ijLEsKw4VikNZhh2rZ7bHGrs6obG5/P5ZqDTObiA==", + "license": "MIT", + "dependencies": { + "@turf/bearing": "7.3.1", + "@turf/destination": "7.3.1", + "@turf/distance": "7.3.1", + "@turf/helpers": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/moran-index": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/moran-index/-/moran-index-7.3.1.tgz", + "integrity": "sha512-9t70AjBB0bycJWLVprqS7mtRU+Ha+U4ji5lkKzyg31ZWAr0IwuawY2VQ/ydsodFMLCqmIf8QbWsltV/I/bRdjQ==", + "license": "MIT", + "dependencies": { + "@turf/distance-weight": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/nearest-neighbor-analysis": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/nearest-neighbor-analysis/-/nearest-neighbor-analysis-7.3.1.tgz", + "integrity": "sha512-qwZON/7v1NbD1H1v3kTHJfLLml2/TNj5QQFRFBJiXRSCydMJT1sKEs5BwJe/9cBbmd0ln3gBWXCkG7Sk3sPgOQ==", + "license": "MIT", + "dependencies": { + "@turf/area": "7.3.1", + "@turf/bbox": "7.3.1", + "@turf/bbox-polygon": "7.3.1", + "@turf/centroid": "7.3.1", + "@turf/distance": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/meta": "7.3.1", + "@turf/nearest-point": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/nearest-point": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/nearest-point/-/nearest-point-7.3.1.tgz", + "integrity": "sha512-hLKGFzwAEop5z04X5BeurJvz0oVPHQX0rjeL3v83kgIjR/eavQucXKO3XkJBoF1AaT9Dv0mgB8rmj/qrwroWgg==", + "license": "MIT", + "dependencies": { + "@turf/clone": "7.3.1", + "@turf/distance": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/nearest-point-on-line": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/nearest-point-on-line/-/nearest-point-on-line-7.3.1.tgz", + "integrity": "sha512-FialyHfXXZWLayKQcUtdOtKv3ulOQ9FSI45kSmkDl8b96+VFWHX983Pc94tTrSTSg89+XX7MDr6gRl0yowmF4Q==", + "license": "MIT", + "dependencies": { + "@turf/distance": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/nearest-point-to-line": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/nearest-point-to-line/-/nearest-point-to-line-7.3.1.tgz", + "integrity": "sha512-7zvhE15vlKBW7F3gYmxZMrnsS2HhXIt0Mpdymy6Y1oMWAXrYIqSeHl1Y/h2CiDh0v91K1KJXf2WyRYacosWiNA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@turf/meta": "7.3.1", + "@turf/point-to-line-distance": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/planepoint": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/planepoint/-/planepoint-7.3.1.tgz", + "integrity": "sha512-/DVTAZcOsSW54B9XDYUXyiL000vJ8WfONCF4FoM71VMeLS7PM3e+4W9gzN21q15XRn3nUftH12tJhqKEqDouvw==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/point-grid": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/point-grid/-/point-grid-7.3.1.tgz", + "integrity": "sha512-KqBlGgBzI/M7/awK25o9p8Q+mRjQDRU4mpHtqNzqNxgidk4JxnUnGybYTnsjp3n1Zid3yASv5kARJ4i/Jc5F7w==", + "license": "MIT", + "dependencies": { + "@turf/boolean-within": "7.3.1", + "@turf/distance": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/point-on-feature": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/point-on-feature/-/point-on-feature-7.3.1.tgz", + "integrity": "sha512-uX15wjujBMeMKAN7OLK4RV6KCLxsoQiFRB9kMtbTeZj13mDo+Bz5SyNN+M2AXqrdsQI9+4h0UTwu3EjcXj/nEw==", + "license": "MIT", + "dependencies": { + "@turf/boolean-point-in-polygon": "7.3.1", + "@turf/center": "7.3.1", + "@turf/explode": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/nearest-point": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/point-to-line-distance": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/point-to-line-distance/-/point-to-line-distance-7.3.1.tgz", + "integrity": "sha512-vynnX3zIMmJY633fyAIKnzlsmL7OBhbk05YhWVSjCKvSQV8C2xMA9pWaLFacn1xu4nfMSVDUaNOrcAqwubN9pg==", + "license": "MIT", + "dependencies": { + "@turf/bearing": "7.3.1", + "@turf/distance": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@turf/meta": "7.3.1", + "@turf/nearest-point-on-line": "7.3.1", + "@turf/projection": "7.3.1", + "@turf/rhumb-bearing": "7.3.1", + "@turf/rhumb-distance": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/point-to-polygon-distance": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/point-to-polygon-distance/-/point-to-polygon-distance-7.3.1.tgz", + "integrity": "sha512-A2hTQjMKO2VEMdgOariICLCjt0BDc1wAQ7Mzqc4vFuol1/GlAed4JqyLg1zXuOVlZcojvXDk/XRuZwXDlRJkBA==", + "license": "MIT", + "dependencies": { + "@turf/boolean-point-in-polygon": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@turf/meta": "7.3.1", + "@turf/point-to-line-distance": "7.3.1", + "@turf/polygon-to-line": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/points-within-polygon": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/points-within-polygon/-/points-within-polygon-7.3.1.tgz", + "integrity": "sha512-tVcQVykc1vvSqz+l/PA4EKVWfMrGtA3ZUxDYBoD2tSaM79EpdTcY1BzfxT5O2582SQ0AdNFXDXRTf7VI6u/+2Q==", + "license": "MIT", + "dependencies": { + "@turf/boolean-point-in-polygon": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/polygon-smooth": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/polygon-smooth/-/polygon-smooth-7.3.1.tgz", + "integrity": "sha512-CNi4SdpOycZRSBr4o0MlrFdC6x5xcXP6jKx2yXZf9FPrOWamHsDXa+NrywCOAPhgZKnBodRF6usKWudVMyPIgg==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/polygon-tangents": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/polygon-tangents/-/polygon-tangents-7.3.1.tgz", + "integrity": "sha512-XPLeCLQAcU2xco+3kS5Mp4AKmCKjOGzyZoC6oy8BuvHg1HaaEs0ZRzcmf0x17cq7bruhJ7n/QkcudnAueae5mg==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "7.3.1", + "@turf/boolean-within": "7.3.1", + "@turf/explode": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@turf/nearest-point": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/polygon-to-line": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/polygon-to-line/-/polygon-to-line-7.3.1.tgz", + "integrity": "sha512-qTOFzn7SLQ0TcKBsPFAFYz7iiq34ijqinpjyr9fHQlFHRHeWzUXiWyIn5a2uOHazkdhHCEXNX8JPkt6hjdZ/fQ==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/polygonize": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/polygonize/-/polygonize-7.3.1.tgz", + "integrity": "sha512-BSamH4eDSbREtye/RZiIyt488KI/hO3+2FiDB8JUoHNESe3VNWk4KEy+sL6oqfhOZcRWndHtJ6MOi3HFptyJrw==", + "license": "MIT", + "dependencies": { + "@turf/boolean-point-in-polygon": "7.3.1", + "@turf/envelope": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/projection": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/projection/-/projection-7.3.1.tgz", + "integrity": "sha512-nDM3LG2j37B1tCpF4xL4rUBrQJcG585IRyDIxL2QEvP1LLv6dcm4fodw70HcGAj05Ux8bJr7IOXQXnobOJrlRA==", + "license": "MIT", + "dependencies": { + "@turf/clone": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/quadrat-analysis": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/quadrat-analysis/-/quadrat-analysis-7.3.1.tgz", + "integrity": "sha512-Kwqtih5CnijULGoTobS0pXdzh/Yr3iGatJcKks4IaxA4+hlJ6Z+Mj47QfKvUtl/IP3lZpVzezewJ51Y989YtVg==", + "license": "MIT", + "dependencies": { + "@turf/area": "7.3.1", + "@turf/bbox": "7.3.1", + "@turf/bbox-polygon": "7.3.1", + "@turf/centroid": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@turf/point-grid": "7.3.1", + "@turf/random": "7.3.1", + "@turf/square-grid": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/random": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/random/-/random-7.3.1.tgz", + "integrity": "sha512-Iruica0gfdAuuqWG3SLe1MQOEP4IOGelPp81Cu552AamhHJmkEZCaiis2n28qdOlAbDs1NJZeJhRFNkiopiy+Q==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/rectangle-grid": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/rectangle-grid/-/rectangle-grid-7.3.1.tgz", + "integrity": "sha512-3/fwd1dzeGApxGXAzyVINFylmn8trYTPLG6jtqOgriAdiHPMTtPqSW58wpScC43oKbK3Bps9dSZ43jvcbrfGxw==", + "license": "MIT", + "dependencies": { + "@turf/boolean-intersects": "7.3.1", + "@turf/distance": "7.3.1", + "@turf/helpers": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/rewind": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/rewind/-/rewind-7.3.1.tgz", + "integrity": "sha512-gD2TGPNq3SE6IlpDwkVHQthZ2U2MElh6X4Vfld3K7VsBHJv4eBct6OOgSWZLkVVPHuWNlVFTNtcRh2LAznMtgw==", + "license": "MIT", + "dependencies": { + "@turf/boolean-clockwise": "7.3.1", + "@turf/clone": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/rhumb-bearing": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/rhumb-bearing/-/rhumb-bearing-7.3.1.tgz", + "integrity": "sha512-GA/EUSOMapLp6qK5kOX+PkFg2MMUHzUSm/jVezv6Fted0dAlCgXHOrKgLm0tN8PqbH7Oj9xQhv9+3/1ze7W8YA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/rhumb-destination": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/rhumb-destination/-/rhumb-destination-7.3.1.tgz", + "integrity": "sha512-HjtAFr5DTISUn9b4oaZpX79tYl72r4EyAj40HKwjQeV6KkwIe5/h4zryOSEpnvAK2Gnkmu1GxYeTGfM5z3J9JA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/rhumb-distance": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/rhumb-distance/-/rhumb-distance-7.3.1.tgz", + "integrity": "sha512-9ZvXU0ii2aywdphLhiawl3uxMEHucMmXCBiRj3WhmssTY9CZkFii9iImbJEqz5glxh6/gzXDcz1CCFQUdNP2xA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/sample": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/sample/-/sample-7.3.1.tgz", + "integrity": "sha512-s9IkXrrtaHRllgk9X2tmg8+SJKLG6orQwf0p1wZX8WxnHXvmnHaju465A3nmtGGVDI/RSD8KwU9aqPcc4AinNw==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/sector": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/sector/-/sector-7.3.1.tgz", + "integrity": "sha512-3BYJk7pQaqVr1Ji1ors6FUnhCJVHuobNf4bYW2yAUW1rxL+snuo6aTCsu39hpkwLj4BBknYt5w4MIOy5b8+QKg==", + "license": "MIT", + "dependencies": { + "@turf/circle": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@turf/line-arc": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/shortest-path": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/shortest-path/-/shortest-path-7.3.1.tgz", + "integrity": "sha512-B0j6MoTSeGw1inRJPfj+6lU4WVXBNFAafqs/BkccScnCHLLK+vMnsOkyQoDX2vdZnhPTEaGj7TEL1SIjV6IMgA==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "7.3.1", + "@turf/bbox-polygon": "7.3.1", + "@turf/boolean-point-in-polygon": "7.3.1", + "@turf/clean-coords": "7.3.1", + "@turf/distance": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@turf/meta": "7.3.1", + "@turf/transform-scale": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/simplify": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/simplify/-/simplify-7.3.1.tgz", + "integrity": "sha512-8LRITQAyNAdvVInjm8pal3J7ZAZZBYrYd5oApXqHlIFK7gEiE21Hx9CZyog6AHDjxZCinwnEoGkzDxORh/mNMg==", + "license": "MIT", + "dependencies": { + "@turf/clean-coords": "7.3.1", + "@turf/clone": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/square": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/square/-/square-7.3.1.tgz", + "integrity": "sha512-LvMkII6bbHaFHp67jI029xHjWFK3pnqwF8c2pUNU+0dL+45KgrO2jaFTnNQdsjexPymI+uaNLlG809Y0aGGQlw==", + "license": "MIT", + "dependencies": { + "@turf/distance": "7.3.1", + "@turf/helpers": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/square-grid": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/square-grid/-/square-grid-7.3.1.tgz", + "integrity": "sha512-WYCX8+nrqHyAhKBSBHFp1eU1gWrcojz9uVvhCbDO8NO14SLHowzWOgB61Gv8KlLXCUBjDr+rYWCt3ymyPzU5TA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@turf/rectangle-grid": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/standard-deviational-ellipse": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/standard-deviational-ellipse/-/standard-deviational-ellipse-7.3.1.tgz", + "integrity": "sha512-u9ojpWyv3rnFioYZyya6VXVDrRPYymNROVKwGqnQzffYE1MdxhJ6ik/CvdcChzCNvSNVBJQUvnjjPq2C2uOsLA==", + "license": "MIT", + "dependencies": { + "@turf/center-mean": "7.3.1", + "@turf/ellipse": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@turf/meta": "7.3.1", + "@turf/points-within-polygon": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/tag": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/tag/-/tag-7.3.1.tgz", + "integrity": "sha512-Y7G2EWm0/j78ss5wCnjGWKfmPbXw9yKJFg93EuMnwggIsDfKdQi/vdUInjQ0462RIQA87StlydPG09X/8bquwQ==", + "license": "MIT", + "dependencies": { + "@turf/boolean-point-in-polygon": "7.3.1", + "@turf/clone": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/tesselate": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/tesselate/-/tesselate-7.3.1.tgz", + "integrity": "sha512-iJnatp9RcJvyffBjqJaw5GbKE/PQosT8DH2kgG7pv4Re0xl3h/QvCjvTlCTEmJ5cNY4geZVKUXDvkkCkgQQVuA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@types/geojson": "^7946.0.10", + "earcut": "^2.2.4", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/tesselate/node_modules/earcut": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", + "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==", + "license": "ISC" + }, + "node_modules/@turf/tin": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/tin/-/tin-7.3.1.tgz", + "integrity": "sha512-pDtHE8rLXvV4zAC9mWmwToDDda2ZTty8IZqZIoUqTnlf6AJjzF7TJrhoE3a+zukRTUI1wowTFqe2NvwgNX0yew==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/transform-rotate": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/transform-rotate/-/transform-rotate-7.3.1.tgz", + "integrity": "sha512-KAYebOkk7IT2j7S8M+ZxDAmyqeni9ZZGU9ouD6mvd/hTpDOlGG+ORRmg312RxG0NiThzCHLyeG1Nea1nEud6bg==", + "license": "MIT", + "dependencies": { + "@turf/centroid": "7.3.1", + "@turf/clone": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@turf/meta": "7.3.1", + "@turf/rhumb-bearing": "7.3.1", + "@turf/rhumb-destination": "7.3.1", + "@turf/rhumb-distance": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/transform-scale": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/transform-scale/-/transform-scale-7.3.1.tgz", + "integrity": "sha512-e8jBSWEn0BMxG0HR8ZMvkHgBgdwNrFRzbhy8DqQwZDgUN59fMeWGbjX5QR5Exl2gZBPaBXkgbDgEhh/JD3kYhw==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "7.3.1", + "@turf/center": "7.3.1", + "@turf/centroid": "7.3.1", + "@turf/clone": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@turf/meta": "7.3.1", + "@turf/rhumb-bearing": "7.3.1", + "@turf/rhumb-destination": "7.3.1", + "@turf/rhumb-distance": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/transform-translate": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/transform-translate/-/transform-translate-7.3.1.tgz", + "integrity": "sha512-yeaW1EqfuuY4l5VBWSsItglaZ9qdTFD0QEIUW1ooOYuQvtKQ2MTKrcQIKLXZckxQrrNq4TXsZDaBbFs+U1wtcQ==", + "license": "MIT", + "dependencies": { + "@turf/clone": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@turf/meta": "7.3.1", + "@turf/rhumb-destination": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/triangle-grid": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/triangle-grid/-/triangle-grid-7.3.1.tgz", + "integrity": "sha512-lhZyqnQC/M8x8DgQURHNZP/HaJIqrL5We5ZvzJBX+lrH2u4DO831awJcuDniRuJ5e0QE5n4yMsBJO77KMNdKfw==", + "license": "MIT", + "dependencies": { + "@turf/distance": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/intersect": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/truncate": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/truncate/-/truncate-7.3.1.tgz", + "integrity": "sha512-rcXHM2m17hyKoW1dJpOvTgUUWFOKluTKKsoLmhEE6aRAYwtuVetkcInt4qBtS1bv7MaL//glbvq0kdEGR0YaOA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" }, - "engines": { - "node": ">=14.0.0" + "funding": { + "url": "https://opencollective.com/turf" } }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-beta.41", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.41.tgz", - "integrity": "sha512-NIYGuCcuXaq5BC4Q3upbiMBvmZsTsEPG9k/8QKQdmrch+ocSy5Jv9tdpdmXJyighKqm182nh/zBt+tSJkYoNlg==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@turf/turf": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/turf/-/turf-7.3.1.tgz", + "integrity": "sha512-0uKkNnM6Bo6cIzZcJ6wQ+FjFioTFXWS3woGDvQ5R7EPehNfdr4HTS39m1seE+HdI8lGItMZehb6fb0jtjP4Clg==", "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "dependencies": { + "@turf/along": "7.3.1", + "@turf/angle": "7.3.1", + "@turf/area": "7.3.1", + "@turf/bbox": "7.3.1", + "@turf/bbox-clip": "7.3.1", + "@turf/bbox-polygon": "7.3.1", + "@turf/bearing": "7.3.1", + "@turf/bezier-spline": "7.3.1", + "@turf/boolean-clockwise": "7.3.1", + "@turf/boolean-concave": "7.3.1", + "@turf/boolean-contains": "7.3.1", + "@turf/boolean-crosses": "7.3.1", + "@turf/boolean-disjoint": "7.3.1", + "@turf/boolean-equal": "7.3.1", + "@turf/boolean-intersects": "7.3.1", + "@turf/boolean-overlap": "7.3.1", + "@turf/boolean-parallel": "7.3.1", + "@turf/boolean-point-in-polygon": "7.3.1", + "@turf/boolean-point-on-line": "7.3.1", + "@turf/boolean-touches": "7.3.1", + "@turf/boolean-valid": "7.3.1", + "@turf/boolean-within": "7.3.1", + "@turf/buffer": "7.3.1", + "@turf/center": "7.3.1", + "@turf/center-mean": "7.3.1", + "@turf/center-median": "7.3.1", + "@turf/center-of-mass": "7.3.1", + "@turf/centroid": "7.3.1", + "@turf/circle": "7.3.1", + "@turf/clean-coords": "7.3.1", + "@turf/clone": "7.3.1", + "@turf/clusters": "7.3.1", + "@turf/clusters-dbscan": "7.3.1", + "@turf/clusters-kmeans": "7.3.1", + "@turf/collect": "7.3.1", + "@turf/combine": "7.3.1", + "@turf/concave": "7.3.1", + "@turf/convex": "7.3.1", + "@turf/destination": "7.3.1", + "@turf/difference": "7.3.1", + "@turf/dissolve": "7.3.1", + "@turf/distance": "7.3.1", + "@turf/distance-weight": "7.3.1", + "@turf/ellipse": "7.3.1", + "@turf/envelope": "7.3.1", + "@turf/explode": "7.3.1", + "@turf/flatten": "7.3.1", + "@turf/flip": "7.3.1", + "@turf/geojson-rbush": "7.3.1", + "@turf/great-circle": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/hex-grid": "7.3.1", + "@turf/interpolate": "7.3.1", + "@turf/intersect": "7.3.1", + "@turf/invariant": "7.3.1", + "@turf/isobands": "7.3.1", + "@turf/isolines": "7.3.1", + "@turf/kinks": "7.3.1", + "@turf/length": "7.3.1", + "@turf/line-arc": "7.3.1", + "@turf/line-chunk": "7.3.1", + "@turf/line-intersect": "7.3.1", + "@turf/line-offset": "7.3.1", + "@turf/line-overlap": "7.3.1", + "@turf/line-segment": "7.3.1", + "@turf/line-slice": "7.3.1", + "@turf/line-slice-along": "7.3.1", + "@turf/line-split": "7.3.1", + "@turf/line-to-polygon": "7.3.1", + "@turf/mask": "7.3.1", + "@turf/meta": "7.3.1", + "@turf/midpoint": "7.3.1", + "@turf/moran-index": "7.3.1", + "@turf/nearest-neighbor-analysis": "7.3.1", + "@turf/nearest-point": "7.3.1", + "@turf/nearest-point-on-line": "7.3.1", + "@turf/nearest-point-to-line": "7.3.1", + "@turf/planepoint": "7.3.1", + "@turf/point-grid": "7.3.1", + "@turf/point-on-feature": "7.3.1", + "@turf/point-to-line-distance": "7.3.1", + "@turf/point-to-polygon-distance": "7.3.1", + "@turf/points-within-polygon": "7.3.1", + "@turf/polygon-smooth": "7.3.1", + "@turf/polygon-tangents": "7.3.1", + "@turf/polygon-to-line": "7.3.1", + "@turf/polygonize": "7.3.1", + "@turf/projection": "7.3.1", + "@turf/quadrat-analysis": "7.3.1", + "@turf/random": "7.3.1", + "@turf/rectangle-grid": "7.3.1", + "@turf/rewind": "7.3.1", + "@turf/rhumb-bearing": "7.3.1", + "@turf/rhumb-destination": "7.3.1", + "@turf/rhumb-distance": "7.3.1", + "@turf/sample": "7.3.1", + "@turf/sector": "7.3.1", + "@turf/shortest-path": "7.3.1", + "@turf/simplify": "7.3.1", + "@turf/square": "7.3.1", + "@turf/square-grid": "7.3.1", + "@turf/standard-deviational-ellipse": "7.3.1", + "@turf/tag": "7.3.1", + "@turf/tesselate": "7.3.1", + "@turf/tin": "7.3.1", + "@turf/transform-rotate": "7.3.1", + "@turf/transform-scale": "7.3.1", + "@turf/transform-translate": "7.3.1", + "@turf/triangle-grid": "7.3.1", + "@turf/truncate": "7.3.1", + "@turf/union": "7.3.1", + "@turf/unkink-polygon": "7.3.1", + "@turf/voronoi": "7.3.1", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" } }, - "node_modules/@rolldown/binding-win32-ia32-msvc": { - "version": "1.0.0-beta.41", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.0.0-beta.41.tgz", - "integrity": "sha512-kANdsDbE5FkEOb5NrCGBJBCaZ2Sabp3D7d4PRqMYJqyLljwh9mDyYyYSv5+QNvdAmifj+f3lviNEUUuUZPEFPw==", - "cpu": [ - "ia32" - ], - "dev": true, + "node_modules/@turf/union": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/union/-/union-7.3.1.tgz", + "integrity": "sha512-Fk8HvP2gRrRJz8xefeoFJJUeLwhih3HoPPKlqaDf/6L43jwAzBD6BPu59+AwRXOlaZeOUMNMGzgSgx0KKrBwBg==", "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "dependencies": { + "@turf/helpers": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10", + "polyclip-ts": "^0.16.8", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" } }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-beta.41", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.41.tgz", - "integrity": "sha512-UlpxKmFdik0Y2VjZrgUCgoYArZJiZllXgIipdBRV1hw6uK45UbQabSTW6Kp6enuOu7vouYWftwhuxfpE8J2JAg==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@turf/unkink-polygon": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/unkink-polygon/-/unkink-polygon-7.3.1.tgz", + "integrity": "sha512-6NVFkCpJUT2P4Yf3z/FI2uGDXqVdEqZqKGl2hYitmH7mNiKhU4bAvvcw7nCSfNG3sUyNhibbtOEopYMRgwimPw==", "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "dependencies": { + "@turf/area": "7.3.1", + "@turf/boolean-point-in-polygon": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/meta": "7.3.1", + "@types/geojson": "^7946.0.10", + "rbush": "^3.0.1", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" } }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.43", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.43.tgz", - "integrity": "sha512-5Uxg7fQUCmfhax7FJke2+8B6cqgeUJUD9o2uXIKXhD+mG0mL6NObmVoi9wXEU1tY89mZKgAYA6fTbftx3q2ZPQ==", - "dev": true, - "license": "MIT" + "node_modules/@turf/voronoi": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@turf/voronoi/-/voronoi-7.3.1.tgz", + "integrity": "sha512-yS+0EDwSIOizEXI+05qixw/OGZalpfsz9xzBWbCBA3Gu2boLMXErFZ73qzfu39Vwk+ILbu5em0p+VhULBzvH9w==", + "license": "MIT", + "dependencies": { + "@turf/clone": "7.3.1", + "@turf/helpers": "7.3.1", + "@turf/invariant": "7.3.1", + "@types/d3-voronoi": "^1.1.12", + "@types/geojson": "^7946.0.10", + "d3-voronoi": "1.1.2", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } }, "node_modules/@tybys/wasm-util": { "version": "0.10.1", @@ -1341,6 +3519,12 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/d3-voronoi": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@types/d3-voronoi/-/d3-voronoi-1.1.12.tgz", + "integrity": "sha512-DauBl25PKZZ0WVJr42a6CNvI6efsdzofl9sajqZr2Gf5Gu733WkDdUGiPkUHXiUvYGzNNlFQde2wdZdfQPG+yw==", + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1348,6 +3532,21 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "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", @@ -1355,6 +3554,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/leaflet": { + "version": "1.9.21", + "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.21.tgz", + "integrity": "sha512-TbAd9DaPGSnzp6QvtYngntMZgcRk+igFELwR2N99XZn7RXUdKgsXMR+28bUO0rPsWp8MIu/f47luLIQuSLYv/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/node": { "version": "24.10.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz", @@ -1405,6 +3614,15 @@ "@types/react": "*" } }, + "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.46.3", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.3.tgz", @@ -1802,6 +4020,15 @@ "baseline-browser-mapping": "dist/cli.js" } }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -1936,6 +4163,12 @@ "dev": true, "license": "MIT" }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1943,6 +4176,24 @@ "dev": true, "license": "MIT" }, + "node_modules/concaveman": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/concaveman/-/concaveman-1.2.1.tgz", + "integrity": "sha512-PwZYKaM/ckQSa8peP5JpVr7IMJ4Nn/MHIaWUjP4be+KoZ7Botgs8seAZGpmaOM+UZXawcdYRao/px9ycrCihHw==", + "license": "ISC", + "dependencies": { + "point-in-polygon": "^1.1.0", + "rbush": "^3.0.1", + "robust-predicates": "^2.0.4", + "tinyqueue": "^2.0.3" + } + }, + "node_modules/concaveman/node_modules/tinyqueue": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz", + "integrity": "sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==", + "license": "ISC" + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -2005,6 +4256,27 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, + "node_modules/d3-array": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", + "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-geo": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.7.1.tgz", + "integrity": "sha512-O4AempWAr+P5qbk2bC2FuN/sDW4z+dN2wDf9QV3bxQt4M5HfOEeXLgJ/UKQW0+o1Dj8BE+L5kiDbdWUMjsmQpw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1" + } + }, + "node_modules/d3-voronoi": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.2.tgz", + "integrity": "sha512-RhGS1u2vavcO7ay7ZNAPo4xeDh/VYeGof3x5ZLJBQgYhLegxr3s5IykvWmJ94FTU6mcbtp4sloqZ54mP6R4Utw==", + "license": "BSD-3-Clause" + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -2049,6 +4321,12 @@ "csstype": "^3.0.2" } }, + "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/electron-to-chromium": { "version": "1.5.244", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.244.tgz", @@ -2268,7 +4546,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -2429,6 +4706,63 @@ "node": ">=6.9.0" } }, + "node_modules/geojson-equality-ts": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/geojson-equality-ts/-/geojson-equality-ts-1.0.2.tgz", + "integrity": "sha512-h3Ryq+0mCSN/7yLs0eDgrZhvc9af23o/QuC4aTiuuzP/MRCtd6mf5rLsLRY44jX0RPUfM8c4GqERQmlUxPGPoQ==", + "license": "MIT", + "dependencies": { + "@types/geojson": "^7946.0.14" + } + }, + "node_modules/geojson-polygon-self-intersections": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/geojson-polygon-self-intersections/-/geojson-polygon-self-intersections-1.2.2.tgz", + "integrity": "sha512-6XRNF4CsRHYmR9z5YuIk5f/aOototnDf0dgMqYGcS7y1l57ttt6MAIAxl3rXyas6lq1HEbTuLMh4PgvO+OV42w==", + "license": "MIT", + "dependencies": { + "rbush": "^2.0.1" + } + }, + "node_modules/geojson-polygon-self-intersections/node_modules/quickselect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-1.1.1.tgz", + "integrity": "sha512-qN0Gqdw4c4KGPsBOQafj6yj/PA6c/L63f6CaZ/DCF/xF4Esu3jVmKLUDYxghFx8Kb/O7y9tI7x2RjTSXwdK1iQ==", + "license": "ISC" + }, + "node_modules/geojson-polygon-self-intersections/node_modules/rbush": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/rbush/-/rbush-2.0.2.tgz", + "integrity": "sha512-XBOuALcTm+O/H8G90b6pzu6nX6v2zCKiFG4BJho8a+bY6AER6t8uQUZdi5bomQc0AprCWhEGa7ncAbbRap0bRA==", + "license": "MIT", + "dependencies": { + "quickselect": "^1.0.1" + } + }, + "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-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/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-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -2603,9 +4937,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", "dependencies": { @@ -2654,6 +4988,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": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -2667,6 +5007,21 @@ "node": ">=6" } }, + "node_modules/jsts": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/jsts/-/jsts-2.7.1.tgz", + "integrity": "sha512-x2wSZHEBK20CY+Wy+BPE7MrFQHW6sIsdaGUMEqmGAio+3gFzQaBYPwLRonUfQf9Ak8pBieqj9tUofX1+WtAEIg==", + "license": "(EDL-1.0 OR EPL-1.0)", + "engines": { + "node": ">= 12" + } + }, + "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", @@ -2677,6 +5032,12 @@ "json-buffer": "3.0.1" } }, + "node_modules/leaflet": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", + "license": "BSD-2-Clause" + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -3003,6 +5364,44 @@ "yallist": "^3.0.2" } }, + "node_modules/maplibre-gl": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-5.15.0.tgz", + "integrity": "sha512-pPeu/t4yPDX/+Uf9ibLUdmaKbNMlGxMAX+tBednYukol2qNk2TZXAlhdohWxjVvTO3is8crrUYv3Ok02oAaKzA==", + "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/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -3040,12 +5439,27 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "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/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -3203,6 +5617,18 @@ "node": ">=8" } }, + "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/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -3222,6 +5648,37 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/point-in-polygon": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/point-in-polygon/-/point-in-polygon-1.1.0.tgz", + "integrity": "sha512-3ojrFwjnnw8Q9242TzgXuTD+eKiutbzyslcq1ydfu82Db2y+Ogbmyrkpv0Hgj31qwT3lbS9+QAAO/pIQM35XRw==", + "license": "MIT" + }, + "node_modules/point-in-polygon-hao": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/point-in-polygon-hao/-/point-in-polygon-hao-1.2.4.tgz", + "integrity": "sha512-x2pcvXeqhRHlNRdhLs/tgFapAbSSe86wa/eqmj1G6pWftbEs5aVRJhRGM6FYSUERKu0PjekJzMq0gsI2XyiclQ==", + "license": "MIT", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/point-in-polygon-hao/node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "license": "Unlicense" + }, + "node_modules/polyclip-ts": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/polyclip-ts/-/polyclip-ts-0.16.8.tgz", + "integrity": "sha512-JPtKbDRuPEuAjuTdhR62Gph7Is2BS1Szx69CFOO3g71lpJDFo78k4tFyi+qFOMVPePEzdSKkpGU3NBXPHHjvKQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.1.0", + "splaytree-ts": "^1.0.2" + } + }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -3251,6 +5708,12 @@ "node": "^10 || ^12 || >=14" } }, + "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", @@ -3278,6 +5741,12 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "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/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -3309,6 +5778,27 @@ ], "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/rbush": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/rbush/-/rbush-3.0.1.tgz", + "integrity": "sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==", + "license": "MIT", + "dependencies": { + "quickselect": "^2.0.0" + } + }, + "node_modules/rbush/node_modules/quickselect": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", + "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==", + "license": "ISC" + }, "node_modules/react": { "version": "19.2.0", "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", @@ -3336,6 +5826,20 @@ "integrity": "sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA==", "license": "MIT" }, + "node_modules/react-leaflet": { + "version": "5.0.0-rc.2", + "resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-5.0.0-rc.2.tgz", + "integrity": "sha512-1xQGYG9mEIW+nfkQhqgHImwUuB1UDlnzYFSzv6PrBFDBeYrFmv0BbpwpNAFdJg/UQ2yz5UZSL7ZwlUxjwb8MZw==", + "license": "Hippocratic-2.1", + "dependencies": { + "@react-leaflet/core": "^3.0.0-rc.2" + }, + "peerDependencies": { + "leaflet": "^1.9.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" + } + }, "node_modules/react-refresh": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", @@ -3413,6 +5917,15 @@ "node": ">=4" } }, + "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/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -3424,6 +5937,12 @@ "node": ">=0.10.0" } }, + "node_modules/robust-predicates": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-2.0.4.tgz", + "integrity": "sha512-l4NwboJM74Ilm4VKfbAtFeGq7aEjWL+5kVFcmgFA2MrdnQWx9iE/tUGvxY5HyMI7o/WpSIUFLbC5fbeaHgSCYg==", + "license": "Unlicense" + }, "node_modules/rolldown": { "version": "1.0.0-beta.41", "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.41.tgz", @@ -3489,6 +6008,12 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, "node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", @@ -3534,6 +6059,12 @@ "node": ">=8" } }, + "node_modules/skmeans": { + "version": "0.9.7", + "resolved": "https://registry.npmjs.org/skmeans/-/skmeans-0.9.7.tgz", + "integrity": "sha512-hNj1/oZ7ygsfmPZ7ZfN5MUBRoGg1gtpnImuJBgLO0ljQ67DtJuiQaiYdS4lUA6s0KCwnPhGivtC/WRwIZLkHyg==", + "license": "MIT" + }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -3553,6 +6084,12 @@ "node": ">=0.10.0" } }, + "node_modules/splaytree-ts": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/splaytree-ts/-/splaytree-ts-1.0.2.tgz", + "integrity": "sha512-0kGecIZNIReCSiznK3uheYB8sbstLjCZLiwcQwbmLhgHJj2gz6OnSPkVzJQCMnmEz1BQ4gPK59ylhBoEWOhGNA==", + "license": "BDS-3-Clause" + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -3572,6 +6109,15 @@ "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", "license": "MIT" }, + "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", @@ -3597,6 +6143,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sweepline-intersections": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/sweepline-intersections/-/sweepline-intersections-1.5.0.tgz", + "integrity": "sha512-AoVmx72QHpKtItPu72TzFL+kcYjd67BPLDoR0LarIk+xyaRg+pDTMFXndIEvZf9xEKnJv6JdhgRMnocoG0D3AQ==", + "license": "MIT", + "dependencies": { + "tinyqueue": "^2.0.0" + } + }, + "node_modules/sweepline-intersections/node_modules/tinyqueue": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz", + "integrity": "sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==", + "license": "ISC" + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -3645,6 +6206,12 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "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-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -3658,6 +6225,32 @@ "node": ">=8.0" } }, + "node_modules/topojson-client": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/topojson-client/-/topojson-client-3.1.0.tgz", + "integrity": "sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==", + "license": "ISC", + "dependencies": { + "commander": "2" + }, + "bin": { + "topo2geo": "bin/topo2geo", + "topomerge": "bin/topomerge", + "topoquantize": "bin/topoquantize" + } + }, + "node_modules/topojson-server": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/topojson-server/-/topojson-server-3.0.1.tgz", + "integrity": "sha512-/VS9j/ffKr2XAOjlZ9CgyyeLmgJ9dMwq6Y0YEON8O7p/tGGk+dCWnrE03zEdu7i4L7YsFZLEPZPzCvcB7lEEXw==", + "license": "ISC", + "dependencies": { + "commander": "2" + }, + "bin": { + "geo2topo": "bin/geo2topo" + } + }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -3675,9 +6268,7 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true + "license": "0BSD" }, "node_modules/type-check": { "version": "0.4.0", @@ -3919,21 +6510,6 @@ "dev": true, "license": "ISC" }, - "node_modules/yaml": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", - "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index a13b4e3..6a3bebc 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,13 +12,19 @@ "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.1", + "@mui/icons-material": "^7.3.5", "@mui/material": "^7.3.5", - "react": "^19.1.1", - "react-dom": "^19.1.1", + "@turf/turf": "^7.3.1", + "leaflet": "^1.9.4", + "maplibre-gl": "^5.15.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "react-leaflet": "^5.0.0-rc.2", "react-router": "^7.9.5" }, "devDependencies": { "@eslint/js": "^9.36.0", + "@types/leaflet": "^1.9.21", "@types/node": "^24.6.0", "@types/react": "^19.1.16", "@types/react-dom": "^19.1.9", diff --git a/frontend/src/components/AppRoutes.tsx b/frontend/src/components/AppRoutes.tsx index 9e80a36..b3a42b4 100644 --- a/frontend/src/components/AppRoutes.tsx +++ b/frontend/src/components/AppRoutes.tsx @@ -3,42 +3,22 @@ import App from '../pages/LandingPage'; import LoginPage from '../pages/LoginPage'; import SignupPage from '../pages/SignupPage'; import HomePage from '../pages/HomePage'; -import { ProtectedRoute } from './ProtectedRoute'; -import { PublicRoute } from './PublicRoute'; +import { ProtectedRoute, PublicRoute } from '../features/auth'; +import PrescriptionsPage from '../pages/PrescriptionsPage'; -/** - * AppRoutes component orchestrates all routing logic: - * - * ProtectedRoute: Only allows authenticated users - * - If loading: shows spinner - * - If NOT authenticated: shows fallback or renders nothing - * - If authenticated: renders the protected element - * - * PublicRoute: Only allows unauthenticated users - * - If loading: shows spinner - * - If authenticated: redirects to / - * - If NOT authenticated: renders the public element - * - * Route structure: - * "/" → Dynamic based on auth status - * - Authenticated: HomePage (protected) - * - Unauthenticated: App.tsx (landing page, public) - * "/login" -> PublicRoute -> LoginPage (unauthenticated users only) - * "/signup" -> PublicRoute -> SignupPage (unauthenticated users only) - * "*" (catch-all) -> Dynamic based on auth status - * - Authenticated: HomePage (protected) - * - Unauthenticated: App.tsx (landing page, public) - */ export default function AppRoutes() { return ( - {/* Root "/" route - shows HomePage for authenticated, App.tsx for unauthenticated */} + {/* Root "/" route - shows HomePage for authenticated, LandingPage for unauthenticated */} } fallback={} />} /> - + {/* Auth pages - public, redirects authenticated users to home */} } />} /> } />} /> + {/* Prescriptions page - requires authentication */} + } />} /> + {/* Catch-all for unmatched routes */} } fallback={} />} /> diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index 8574713..6c7a22d 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -15,7 +15,7 @@ const Header = () => { }; return ( - + { {isAuthenticated ? ( <> + + ) : ( diff --git a/frontend/src/contexts/CoordinateContext.tsx b/frontend/src/contexts/CoordinateContext.tsx new file mode 100644 index 0000000..0d4831b --- /dev/null +++ b/frontend/src/contexts/CoordinateContext.tsx @@ -0,0 +1,171 @@ +import { createContext, useContext, useState, useEffect } from 'react'; +import type { ReactNode } from 'react'; +import type { FeatureCollection } from 'geojson'; + +const STORAGE_KEY = 'charai_coordinate_data'; // committed/approved coordinates +const PENDING_STORAGE_KEY = 'charai_coordinate_pending'; // latest user input (manual or upload) before submit +const SUBMIT_STORAGE_KEY = 'charai_farm_submitted'; + +type LatLng = { lat: number; lng: number }; + +interface CoordinateContextType { + data: FeatureCollection | null; + pendingData: FeatureCollection | null; + isLoading: boolean; + hasCoordinates: boolean; + hasPendingCoordinates: boolean; + formSubmitted: boolean; + setCoordinateData: (data: FeatureCollection | LatLng[]) => void; // accepts GeoJSON or raw lat/lng array + commitPendingCoordinates: () => void; // promote pending -> committed and mark submitted + setFormSubmitted: (submitted: boolean) => void; + clearCoordinateData: () => void; +} + +const CoordinateContext = createContext(undefined); + +export function CoordinateProvider({ children }: { children: ReactNode }) { + const [data, setData] = useState(null); + const [pendingData, setPendingData] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [formSubmitted, setFormSubmittedState] = useState(false); + + // Helper: convert [{lat,lng}, ...] to a closed GeoJSON FeatureCollection with temporary default properties + const coordsToFeatureCollection = (coords: LatLng[]): FeatureCollection | null => { + if (!coords || coords.length < 3) return null; + const ring: [number, number][] = coords.map(p => [p.lng, p.lat]); + const first = ring[0]; + const last = ring[ring.length - 1]; + if (first[0] !== last[0] || first[1] !== last[1]) { + ring.push([...first]); + } + return { + type: 'FeatureCollection', + features: [ + { + type: 'Feature', + properties: { applicationRate: 5, paybackPeriod: 3, type: 'boundary' }, + geometry: { + type: 'Polygon', + coordinates: [ring], + }, + }, + ], + }; + }; + + // On mount, load coordinate data from localStorage + useEffect(() => { + try { + const stored = localStorage.getItem(STORAGE_KEY); + if (stored) { + setData(JSON.parse(stored)); + } + + const storedPending = localStorage.getItem(PENDING_STORAGE_KEY); + if (storedPending) { + setPendingData(JSON.parse(storedPending)); + } + + const storedSubmitted = localStorage.getItem(SUBMIT_STORAGE_KEY); + if (storedSubmitted) { + setFormSubmittedState(storedSubmitted === 'true'); + } + } catch (err) { + console.debug('Failed to load coordinates from localStorage:', err); + } finally { + setIsLoading(false); + } + }, []); + + const setCoordinateData = (newData: FeatureCollection | LatLng[]) => { + let fc: FeatureCollection | null = null; + if (Array.isArray(newData)) { + fc = coordsToFeatureCollection(newData); + } else { + fc = newData; + } + if (!fc) return; + setPendingData(fc); + try { + localStorage.setItem(PENDING_STORAGE_KEY, JSON.stringify(fc)); + } catch (err) { + console.error('Failed to save pending coordinates to localStorage:', err); + } + }; + + const commitPendingCoordinates = () => { + if (!pendingData) return; + // Ensure temporary default properties exist for hover purposes + const committed: FeatureCollection = { + type: 'FeatureCollection', + features: pendingData.features.map(f => ({ + ...f, + properties: { + applicationRate: f.properties?.applicationRate ?? 5, + paybackPeriod: f.properties?.paybackPeriod ?? 3, + ...f.properties, + }, + })), + }; + + console.log('Committed Coordinates GeoJSON:', JSON.stringify(committed, null, 2)); + + setData(committed); + setFormSubmittedState(true); + try { + localStorage.setItem(STORAGE_KEY, JSON.stringify(committed)); + localStorage.setItem(SUBMIT_STORAGE_KEY, 'true'); + } catch (err) { + console.error('Failed to commit coordinates to localStorage:', err); + } + }; + + const setFormSubmitted = (submitted: boolean) => { + setFormSubmittedState(submitted); + try { + localStorage.setItem(SUBMIT_STORAGE_KEY, submitted ? 'true' : 'false'); + } catch (err) { + console.error('Failed to save submission flag to localStorage:', err); + } + }; + + const clearCoordinateData = () => { + setData(null); + setPendingData(null); + setFormSubmittedState(false); + try { + localStorage.removeItem(STORAGE_KEY); + localStorage.removeItem(PENDING_STORAGE_KEY); + localStorage.setItem(SUBMIT_STORAGE_KEY, 'false'); + } catch (err) { + console.error('Failed to clear coordinates from localStorage:', err); + } + }; + + const value: CoordinateContextType = { + data, + pendingData, + isLoading, + hasCoordinates: !!data, + hasPendingCoordinates: !!pendingData, + formSubmitted, + setCoordinateData, + commitPendingCoordinates, + setFormSubmitted, + clearCoordinateData, + }; + + return ( + + {children} + + ); +} + +export function useCoordinates() { + const context = useContext(CoordinateContext); + if (!context) { + throw new Error('useCoordinates must be used within a CoordinateProvider'); + } + return context; +} diff --git a/frontend/src/components/ProtectedRoute.tsx b/frontend/src/features/auth/ProtectedRoute.tsx similarity index 81% rename from frontend/src/components/ProtectedRoute.tsx rename to frontend/src/features/auth/ProtectedRoute.tsx index b2700ad..f855a9e 100644 --- a/frontend/src/components/ProtectedRoute.tsx +++ b/frontend/src/features/auth/ProtectedRoute.tsx @@ -1,5 +1,5 @@ import { Box, CircularProgress } from '@mui/material'; -import { useAuth } from '../contexts/AuthContext'; +import { useAuth } from '../../contexts/AuthContext'; interface ProtectedRouteProps { element: React.ReactNode; @@ -33,8 +33,6 @@ export function ProtectedRoute({ element, fallback }: ProtectedRouteProps) { if (fallback) { return <>{fallback}; } - // If no fallback provided, redirect would be handled by PublicRoute wrapping - // But since we're in ProtectedRoute, we show nothing (or could redirect) return null; } diff --git a/frontend/src/components/PublicRoute.tsx b/frontend/src/features/auth/PublicRoute.tsx similarity index 93% rename from frontend/src/components/PublicRoute.tsx rename to frontend/src/features/auth/PublicRoute.tsx index 8015492..4fc1a5c 100644 --- a/frontend/src/components/PublicRoute.tsx +++ b/frontend/src/features/auth/PublicRoute.tsx @@ -1,6 +1,6 @@ import { Navigate } from 'react-router'; import { Box, CircularProgress } from '@mui/material'; -import { useAuth } from '../contexts/AuthContext'; +import { useAuth } from '../../contexts/AuthContext'; interface PublicRouteProps { element: React.ReactNode; diff --git a/frontend/src/features/auth/index.ts b/frontend/src/features/auth/index.ts new file mode 100644 index 0000000..e81566e --- /dev/null +++ b/frontend/src/features/auth/index.ts @@ -0,0 +1,2 @@ +export { ProtectedRoute } from './ProtectedRoute'; +export { PublicRoute } from './PublicRoute'; diff --git a/frontend/src/features/farm/BudgetSettings.tsx b/frontend/src/features/farm/BudgetSettings.tsx new file mode 100644 index 0000000..24a6567 --- /dev/null +++ b/frontend/src/features/farm/BudgetSettings.tsx @@ -0,0 +1,53 @@ +import { Box, TextField, Typography, InputAdornment } from '@mui/material'; +import { COLORS } from '../../styles/colors'; + +interface BudgetSettingsProps { + globalMax: number | ''; + onChange: (value: number | '') => void; +} + +export default function BudgetSettings({ globalMax, onChange }: BudgetSettingsProps) { + return ( + + + Budget Settings + + + Set an optional global cap on total biochar spending. This limit will be applied across all fields when distributing budget for biochar applications. + + onChange(e.target.value === '' ? '' : Number(e.target.value))} + size="small" + inputProps={{ maxLength: 10 }} + slotProps={{ + input: { + startAdornment: ( + + $ + + ) + }, + }} + sx={{ + minWidth: 250, + '& .MuiOutlinedInput-root': { + color: COLORS.whiteHigh, + '& fieldset': { borderColor: COLORS.whiteLow }, + '&:hover fieldset': { borderColor: COLORS.indigo }, + }, + '& .MuiFormHelperText-root': { + color: COLORS.whiteMedium, + }, + '& .MuiSvgIcon-root': { color: COLORS.whiteHigh }, + '& .MuiInputLabel-root': { + color: `${COLORS.whiteMedium} !important`, + }, + }} + helperText="Optional - leave blank for no limit" + /> + + ); +} diff --git a/frontend/src/features/farm/CoordinateFileUpload.tsx b/frontend/src/features/farm/CoordinateFileUpload.tsx new file mode 100644 index 0000000..0c6c83c --- /dev/null +++ b/frontend/src/features/farm/CoordinateFileUpload.tsx @@ -0,0 +1,133 @@ +import { Box, Button, IconButton, Stack, Typography, CircularProgress } from "@mui/material"; +import { styled } from "@mui/material/styles"; +import React from "react"; +import CloseIcon from "@mui/icons-material/Close"; +import { parseFileToGeoJSON } from "../../services/coordinateService"; +import { useCoordinates } from "../../contexts/CoordinateContext"; +import { COLORS } from "../../styles/colors"; + +const VisuallyHiddenInput = styled('input')({ + clip: 'rect(0 0 0 0)', + clipPath: 'inset(50%)', + height: 1, + overflow: 'hidden', + position: 'absolute', + bottom: 0, + left: 0, + whiteSpace: 'nowrap', + width: 1, +}); + +export default function CoordinateFileUpload(props: { onSelect?: (file: File | null) => void; onUploadComplete?: () => void }) { + const { onSelect, onUploadComplete } = props || {}; + const { setCoordinateData } = useCoordinates(); + const [selectedFile, setSelectedFile] = React.useState(null); + const [isLoading, setIsLoading] = React.useState(false); + + const handleFileChange = (event: React.ChangeEvent) => { + if (event.target.files === null) { + throw Error("Error: No files selected"); + } + const f = event.target.files[0]; + setSelectedFile(f); + onSelect?.(f); + }; + + const handleFileSubmit = async () => { + if (!selectedFile) return; + + setIsLoading(true); + try { + // Parse file locally and save to context (temporary localStorage-based storage) + const geojson = await parseFileToGeoJSON(selectedFile); + setCoordinateData(geojson); + console.log("Success! Coordinates saved locally"); + setSelectedFile(null); + onSelect?.(null); + onUploadComplete?.(); + } catch (err: any) { + console.error('Failed to process coordinates:', err); + // TODO: Show error message in UI + } finally { + setIsLoading(false); + } + }; + + const handleClearFile = () => { + setSelectedFile(null); + onSelect?.(null); + }; + + return ( + + {!selectedFile ? ( + + ) : ( + + + {selectedFile.name} + + + + + + )} + + {selectedFile && ( + + )} + + ); +} diff --git a/frontend/src/features/farm/FarmBiocharForm.tsx b/frontend/src/features/farm/FarmBiocharForm.tsx new file mode 100644 index 0000000..18ecdd5 --- /dev/null +++ b/frontend/src/features/farm/FarmBiocharForm.tsx @@ -0,0 +1,151 @@ +import React from 'react'; +import { + Box, + Button, + Typography, + IconButton, + Dialog, + DialogTitle, + DialogContent, +} from '@mui/material'; +import CloseIcon from '@mui/icons-material/Close'; +import { COLORS } from '../../styles/colors'; +import BudgetSettings from './BudgetSettings'; +import FieldsList from './FieldsList'; +import type { FieldEntry } from './FieldsList'; +import FileUploadSection from './FileUploadSection'; +import SubmitSection from './SubmitSection'; +import { useCoordinates } from '../../contexts/CoordinateContext'; + +const DEFAULT_FIELD = (): FieldEntry => ({ + id: 'main-field', + cropType: 'Wheat', + customCrop: '', + price: '', + unit: 'bushel', +}); + +export default function FarmBiocharForm() { + const { hasCoordinates, hasPendingCoordinates, setFormSubmitted, commitPendingCoordinates } = useCoordinates(); + const [field, setField] = React.useState(DEFAULT_FIELD()); + const [globalMax, setGlobalMax] = React.useState(''); + const [isModalOpen, setIsModalOpen] = React.useState(false); + + const updateField = (patch: Partial) => { + setField(prev => ({ ...prev, ...patch })); + }; + + // file/manual coordinate state (ready when uploaded OR drawn OR already present) + const [coordUploaded, setCoordUploaded] = React.useState(false); + + // If coordinates already exist in context (from upload or manual draw), mark as ready + React.useEffect(() => { + if (hasCoordinates || hasPendingCoordinates) { + setCoordUploaded(true); + } + }, [hasCoordinates, hasPendingCoordinates]); + + const handleCoordSelect = (file: File | null) => { + setCoordUploaded(!!file); + }; + + const handleCoordUploaded = () => { + setCoordUploaded(true); + }; + + const handleYieldSelect = () => { + // yield file is optional, no action needed on select + }; + + const handleYieldUploaded = () => { + // yield file is optional, no action needed on upload completion + }; + + const openModal = () => setIsModalOpen(true); + const closeModal = () => setIsModalOpen(false); + + const coordsReady = coordUploaded || hasPendingCoordinates || hasCoordinates; + const isPriceValid = field.price !== '' && field.price > 0; + + return ( + <> + {/* Modal Trigger Button */} + + + {/* Modal Dialog */} + + + + + + + + + {/* Title Section */} + + + Farm Configuration + + + Configure your field's crop and selling price, set your biochar budget, and upload boundary coordinates to calculate optimal application rates. + + + + {/* Budget Settings */} + + + {/* Field Configuration */} + + + {/* File Upload Section */} + + + {/* Submit Section */} + { + if (!isPriceValid) { + alert('Please enter a valid crop selling price.'); + return; + } + const payload = { globalMax, field }; + console.log('Submit payload', payload); + commitPendingCoordinates(); + setFormSubmitted(true); + closeModal(); + }} + /> + + + + + ); +} diff --git a/frontend/src/features/farm/FieldsList.tsx b/frontend/src/features/farm/FieldsList.tsx new file mode 100644 index 0000000..03d0df7 --- /dev/null +++ b/frontend/src/features/farm/FieldsList.tsx @@ -0,0 +1,163 @@ +import { Box, Stack, Typography, Accordion, AccordionSummary, AccordionDetails, TextField, Select, MenuItem, InputAdornment } from '@mui/material'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import { COLORS } from '../../styles/colors'; + +export interface FieldEntry { + id: string; + cropType: string; + customCrop?: string; + price: number | ''; + unit: 'ton' | 'kg' | 'bushel'; +} + +interface FieldsListProps { + field: FieldEntry; + onUpdateField: (patch: Partial) => void; +} + +export default function FieldsList({ field, onUpdateField }: FieldsListProps) { + const isPriceValid = field.price !== '' && field.price > 0; + + return ( + + + + Field Configuration + + + Configure your field's crop type and current selling price to calculate biochar ROI. + + + + + } + sx={{ '& .MuiAccordionSummary-content': { alignItems: 'center' } }} + > + + {field.cropType === 'Other' ? (field.customCrop || 'Other') : field.cropType} + + {field.price && ( + + ${field.price}/{field.unit} + + )} + {!isPriceValid && ( + + Price required + + )} + + + + {/* Crop Type */} + + Crop type + + Choose the primary crop grown in your field. + + + + {field.cropType === 'Other' && ( + onUpdateField({ customCrop: e.target.value })} + placeholder="e.g., Alfalfa, Oats" + sx={{ + '& .MuiOutlinedInput-root': { + color: COLORS.whiteHigh, + '& fieldset': { borderColor: COLORS.whiteLow }, + '&:hover fieldset': { borderColor: COLORS.indigo }, + }, + '& .MuiInputLabel-root': { color: `${COLORS.whiteMedium} !important` }, + }} + /> + )} + + + {/* Selling Price */} + + + Selling price * + + + Current market price per unit (required) + + + onUpdateField({ price: e.target.value === '' ? '' : Number(e.target.value) })} + sx={{ + minWidth: 120, + '& .MuiOutlinedInput-root': { + color: COLORS.whiteHigh, + '& fieldset': { borderColor: !isPriceValid ? COLORS.error : COLORS.whiteLow }, + '&:hover fieldset': { borderColor: !isPriceValid ? COLORS.error : COLORS.indigo }, + }, + '& .MuiInputLabel-root': { + color: `${COLORS.whiteMedium} !important`, + }, + }} + slotProps={{ + input: { + startAdornment: ( + + $ + + ) + }, + }} + /> + + + + + + + + ); +} diff --git a/frontend/src/features/farm/FileUploadSection.tsx b/frontend/src/features/farm/FileUploadSection.tsx new file mode 100644 index 0000000..1ae6c30 --- /dev/null +++ b/frontend/src/features/farm/FileUploadSection.tsx @@ -0,0 +1,67 @@ +import { Box, Stack, Typography } from '@mui/material'; +import { COLORS } from '../../styles/colors'; +import CoordinateFileUpload from './CoordinateFileUpload'; +import YieldFileUpload from './YieldFileUpload'; +import ManualCoordinateUpload from './ManualCoordinateUpload'; + +interface FileUploadSectionProps { + onCoordSelect: (file: File | null) => void; + onCoordUploaded: () => void; + onYieldSelect: () => void; + onYieldUploaded: () => void; +} + +export default function FileUploadSection({ + onCoordSelect, + onCoordUploaded, + onYieldSelect, + onYieldUploaded, +}: FileUploadSectionProps) { + return ( + + + Upload Farm Data + + + Upload geographic coordinate data (required) to define your farm boundaries. You can also optionally upload yield data to improve calculations. Supported formats: Shapefile, GeoJSON, CSV, KML, and other standard formats. + + + + {/* Coordinate File Box */} + + + Coordinate file + + Required + + + + Upload a file containing your farm boundary coordinates, or draw them manually. + + + + Accepted formats: Shapefile (.shp, .shx, .dbf), GeoJSON (.geojson), CSV (.csv), KML/KMZ (.kml, .kmz) + + + + + {/* Yield File Box */} + + + Yield file + + Optional + + + + Upload historical yield data to enable yield-based calculations and recommendations. This helps predict potential ROI from biochar applications. + + + + Accepted formats: CSV (.csv), ISOXML (.xml), Shapefile (.shp), TXT (.txt) + + + + + ); +} diff --git a/frontend/src/features/farm/ManualCoordinateUpload.tsx b/frontend/src/features/farm/ManualCoordinateUpload.tsx new file mode 100644 index 0000000..ff2211f --- /dev/null +++ b/frontend/src/features/farm/ManualCoordinateUpload.tsx @@ -0,0 +1,273 @@ +import { Box, Button, Dialog, DialogContent, DialogTitle, IconButton, Typography, Divider, Paper, Stack } from "@mui/material"; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import EditIcon from '@mui/icons-material/Edit'; +import { COLORS } from "../../styles/colors"; +import React from "react"; +import { InteractiveFarmMap } from "../map"; +import { type LatLngLiteral } from "leaflet"; +import { useCoordinates } from "../../contexts/CoordinateContext"; +import type { FeatureCollection, Feature, Polygon } from 'geojson'; + +export default function ManualCoordinateUpload() { + const { data, pendingData, setCoordinateData } = useCoordinates(); + const [isModalOpen, setIsModalOpen] = React.useState(false); + const [markers, setMarkers] = React.useState([]); + + // On mount or when modal opens with existing data, load markers from context + // Check pendingData first, fall back to data (committed coords) + React.useEffect(() => { + if (isModalOpen) { + const coordSource = pendingData || data; + if (coordSource) { + const boundaryFeature = coordSource.features.find(f => f.geometry.type === 'Polygon') as Feature | undefined; + if (boundaryFeature?.geometry.type === 'Polygon') { + const coords = boundaryFeature.geometry.coordinates[0]; + // Remove last coord (which closes the polygon) + const polygonMarkers: LatLngLiteral[] = coords.slice(0, -1).map(([lng, lat]) => ({ + lat, + lng, + })); + setMarkers(polygonMarkers); + } + } + } + }, [isModalOpen, data, pendingData]); + + const openModal = () => setIsModalOpen(true); + const closeModal = () => { + setIsModalOpen(false); + }; + + const handleClearMarkers = () => { + setMarkers([]); + }; + + const handleUndoMarker = () => { + if (markers.length > 0) { + setMarkers(prev => prev.slice(0, -1)); + } + }; + + const handleSubmit = () => { + if (markers.length < 3) return; + + // Convert markers to GeoJSON boundary polygon + const coords: [number, number][] = markers.map(m => [m.lng, m.lat]); + // Close the polygon by adding the first point at the end + coords.push(coords[0]); + + const boundary: Feature = { + type: 'Feature', + properties: { type: 'boundary', applicationRate: 5, paybackPeriod: 3 }, + geometry: { + type: 'Polygon', + coordinates: [coords], + }, + }; + + const geojson: FeatureCollection = { + type: 'FeatureCollection', + features: [boundary], + }; + + // Save to context (which persists to localStorage) + setCoordinateData(geojson); + + console.log('Submitted coordinates:', markers); + closeModal(); + }; + + // Check if coordinates have already been submitted or are pending + const hasCoordinatesReady = (pendingData && pendingData.features.length > 0) || (data && data.features.length > 0); + + return ( + <> + + + + + + + + Back + + + Define Field Boundaries + + {/* Spacer for centering */} + + + + + + + + {/* Map Section - Takes most of the space */} + + + + + {/* Sidebar - Instructions and Controls */} + + {/* Instructions Section */} + + + How to use: + + + + • Click on the map to place boundary markers for your field + + + • Drag markers to adjust their position + + + • Place at least 3 markers to define a field boundary (polygon will appear automatically) + + + • Use Undo to remove the last marker or Clear All to start over + + + • Click Submit when you're satisfied with your field boundaries + + + + + {/* Spacer to push buttons to bottom */} + + + {/* Action Buttons */} + + {/* Undo and Clear buttons in a row */} + + + + + + + + + + + + + + ) +} diff --git a/frontend/src/features/farm/SubmitSection.tsx b/frontend/src/features/farm/SubmitSection.tsx new file mode 100644 index 0000000..aeff7fc --- /dev/null +++ b/frontend/src/features/farm/SubmitSection.tsx @@ -0,0 +1,47 @@ +import { Box, Button, Stack, Typography } from '@mui/material'; +import { COLORS } from '../../styles/colors'; + +interface SubmitSectionProps { + coordsReady: boolean; + onSubmit: () => void; +} + +export default function SubmitSection({ coordsReady, onSubmit }: SubmitSectionProps) { + return ( + + + Ready to proceed? Make sure you've selected all your fields and uploaded at least the coordinate file. + + + + {!coordsReady && ( + + Upload or draw your boundary to enable submission + + )} + {coordsReady && ( + + Boundary received - ready to submit + + )} + + + ); +} diff --git a/frontend/src/features/farm/YieldFileUpload.tsx b/frontend/src/features/farm/YieldFileUpload.tsx new file mode 100644 index 0000000..ac3e80b --- /dev/null +++ b/frontend/src/features/farm/YieldFileUpload.tsx @@ -0,0 +1,133 @@ +import { Box, Button, IconButton, Stack, Typography, CircularProgress } from "@mui/material"; +import { styled } from "@mui/material/styles"; +import React from "react"; +import CloseIcon from "@mui/icons-material/Close"; +import { uploadYieldFile } from "../../services/fileUploadService"; +import { COLORS } from "../../styles/colors"; + +const VisuallyHiddenInput = styled('input')({ + clip: 'rect(0 0 0 0)', + clipPath: 'inset(50%)', + height: 1, + overflow: 'hidden', + position: 'absolute', + bottom: 0, + left: 0, + whiteSpace: 'nowrap', + width: 1, +}); + +export default function YieldFileUpload(props: { onSelect?: (file: File | null) => void; onUploadComplete?: () => void }) { + const { onSelect, onUploadComplete } = props || {}; + const [selectedFile, setSelectedFile] = React.useState(null); + const [isLoading, setIsLoading] = React.useState(false); + + const handleFileChange = (event: React.ChangeEvent) => { + if (event.target.files === null) { + throw Error("Error: No files selected"); + } + const f = event.target.files[0]; + setSelectedFile(f); + onSelect?.(f); + }; + + const handleFileSubmit = async () => { + if (!selectedFile) return; + + setIsLoading(true); + const formData = new FormData(); + formData.append('file', selectedFile); + + try { + const response = await uploadYieldFile(formData); + if (response) { + console.log("Success! File uploaded"); + setSelectedFile(null); + onSelect?.(null); + onUploadComplete?.(); + } + } catch (err: any) { + console.error(err); + } finally { + setIsLoading(false); + } + }; + + const handleClearFile = () => { + setSelectedFile(null); + onSelect?.(null); + }; + + return ( + + {!selectedFile ? ( + + ) : ( + + + {selectedFile.name} + + + + + + )} + + {selectedFile && ( + + )} + + ); +} diff --git a/frontend/src/features/farm/index.ts b/frontend/src/features/farm/index.ts new file mode 100644 index 0000000..cda6ef3 --- /dev/null +++ b/frontend/src/features/farm/index.ts @@ -0,0 +1,9 @@ +export { default as FarmBiocharForm } from './FarmBiocharForm'; +export { default as ManualCoordinateUpload } from './ManualCoordinateUpload'; +export { default as BudgetSettings } from './BudgetSettings'; +export { default as FieldsList } from './FieldsList'; +export type { FieldEntry } from './FieldsList'; +export { default as FileUploadSection } from './FileUploadSection'; +export { default as SubmitSection } from './SubmitSection'; +export { default as CoordinateFileUpload } from './CoordinateFileUpload'; +export { default as YieldFileUpload } from './YieldFileUpload'; diff --git a/frontend/src/features/index.ts b/frontend/src/features/index.ts new file mode 100644 index 0000000..781f92b --- /dev/null +++ b/frontend/src/features/index.ts @@ -0,0 +1,4 @@ +export * from './auth'; +export * from './farm'; +export * from './map'; +export * from './prescriptions'; diff --git a/frontend/src/features/map/InteractiveFarmMap.tsx b/frontend/src/features/map/InteractiveFarmMap.tsx new file mode 100644 index 0000000..f143800 --- /dev/null +++ b/frontend/src/features/map/InteractiveFarmMap.tsx @@ -0,0 +1,132 @@ +import React from "react"; +import { MapContainer, TileLayer, Marker, useMapEvent, Polygon } from "react-leaflet"; +import { type LatLngLiteral } from "leaflet"; +import "leaflet/dist/leaflet.css"; +import L from "leaflet"; +import { Box } from "@mui/material"; +import { COLORS } from "../../styles/colors"; + +// temporary workaround for marker icon clash between Vite and React Leaflet +// eslint-disable-next-line @typescript-eslint/no-explicit-any +delete (L.Icon.Default.prototype as any)._getIconUrl; +L.Icon.Default.mergeOptions({ + iconRetinaUrl: + "https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-icon-2x.png", + iconUrl: + "https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-icon.png", + shadowUrl: + "https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-shadow.png", +}); + +interface ClickHandlerProps { + markers: LatLngLiteral[]; + setMarkers: React.Dispatch>; +} + +const ClickHandler: React.FC = ({ markers, setMarkers }) => { + useMapEvent("click", (e) => { + setMarkers([...markers, e.latlng]); + }); + return null; +}; + +interface DraggableMarkerProps { + position: LatLngLiteral; + index: number; + onDragEnd: (index: number, newPosition: LatLngLiteral) => void; +} + +const DraggableMarker: React.FC = ({ position, index, onDragEnd }) => { + const markerRef = React.useRef(null); + + const eventHandlers = React.useMemo( + () => ({ + dragend() { + const marker = markerRef.current; + if (marker != null) { + const newPos = marker.getLatLng(); + onDragEnd(index, { lat: newPos.lat, lng: newPos.lng }); + } + }, + }), + [index, onDragEnd] + ); + + return ( + + ); +}; + +interface InteractiveFarmMapProps { + markers: LatLngLiteral[]; + setMarkers: React.Dispatch>; +} + +export default function InteractiveFarmMap({ markers, setMarkers }: InteractiveFarmMapProps) { + const handleMarkerDragEnd = React.useCallback( + (index: number, newPosition: LatLngLiteral) => { + setMarkers((prevMarkers) => { + const updated = [...prevMarkers]; + updated[index] = newPosition; + return updated; + }); + }, + [setMarkers] + ); + + return ( + + + {/* ESRI Satellite Imagery */} + + + {/* ESRI Transportation Layer */} + + + {/* ESRI City Labels Layer */} + + + + + {markers.map((position, idx) => ( + + ))} + + {markers.length >= 3 && ( + + )} + + + ); +} diff --git a/frontend/src/features/map/index.ts b/frontend/src/features/map/index.ts new file mode 100644 index 0000000..a7edbb3 --- /dev/null +++ b/frontend/src/features/map/index.ts @@ -0,0 +1 @@ +export { default as InteractiveFarmMap } from './InteractiveFarmMap'; diff --git a/frontend/src/features/prescriptions/PrescriptionMapViewer.tsx b/frontend/src/features/prescriptions/PrescriptionMapViewer.tsx new file mode 100644 index 0000000..4d6b931 --- /dev/null +++ b/frontend/src/features/prescriptions/PrescriptionMapViewer.tsx @@ -0,0 +1,647 @@ +import { Box, Typography, Button, Container, Paper, Stack } from "@mui/material"; +import { COLORS } from '../../styles/colors'; +import React from "react"; +import { useNavigate } from 'react-router'; +import { useCoordinates } from '../../contexts/CoordinateContext'; +import L from 'leaflet'; +import 'leaflet/dist/leaflet.css'; +import MapIcon from '@mui/icons-material/Map'; +import GridOnIcon from '@mui/icons-material/GridOn'; +import RestartAltIcon from '@mui/icons-material/RestartAlt'; +import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; + +type LatLng = { lat: number; lng: number }; + +interface GridCell { + bounds: L.LatLngBounds; + paybackPeriod: number; + applicationRate: number; +} + +// Color scale for payback period (1-10) +const getColorForPayback = (paybackPeriod: number): string => { + if (paybackPeriod <= 2) return COLORS.dataGreen; // green + if (paybackPeriod <= 4) return COLORS.dataLightGreen; // light green + if (paybackPeriod <= 6) return COLORS.dataYellow; // yellow + if (paybackPeriod <= 8) return COLORS.dataOrange; // orange + return COLORS.dataRed; // red +}; + +// Point-in-polygon test using ray casting algorithm +const pointInPolygon = (point: LatLng, polygon: LatLng[]): boolean => { + let inside = false; + const x = point.lng; + const y = point.lat; + + for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) { + const xi = polygon[i].lng; + const yi = polygon[i].lat; + const xj = polygon[j].lng; + const yj = polygon[j].lat; + + if (((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi)) { + inside = !inside; + } + } + + return inside; +}; + +// Convert meters to degrees (approximate) +const metersToDegreesLat = (meters: number): number => meters / 111320; +const metersToDegreesLng = (meters: number, lat: number): number => + meters / (111320 * Math.cos(lat * Math.PI / 180)); + +// Generate grid cells inside a polygon boundary +const generateGridCells = (boundary: LatLng[], cellSizeMeters: number): GridCell[] => { + if (boundary.length < 3) return []; + + // Find bounding box + let minLat = Infinity, maxLat = -Infinity; + let minLng = Infinity, maxLng = -Infinity; + + for (const pt of boundary) { + minLat = Math.min(minLat, pt.lat); + maxLat = Math.max(maxLat, pt.lat); + minLng = Math.min(minLng, pt.lng); + maxLng = Math.max(maxLng, pt.lng); + } + + const centerLat = (minLat + maxLat) / 2; + const cellSizeLat = metersToDegreesLat(cellSizeMeters); + const cellSizeLng = metersToDegreesLng(cellSizeMeters, centerLat); + + const cells: GridCell[] = []; + + // Generate grid cells + for (let lat = minLat; lat < maxLat; lat += cellSizeLat) { + for (let lng = minLng; lng < maxLng; lng += cellSizeLng) { + // Check if cell center is inside polygon + const cellCenter: LatLng = { + lat: lat + cellSizeLat / 2, + lng: lng + cellSizeLng / 2, + }; + + if (pointInPolygon(cellCenter, boundary)) { + const bounds = L.latLngBounds( + [lat, lng], + [lat + cellSizeLat, lng + cellSizeLng] + ); + + cells.push({ + bounds, + paybackPeriod: Math.floor(Math.random() * 10) + 1, + applicationRate: 5, + }); + } + } + } + + return cells; +}; + +class GridCanvasLayer extends L.Layer { + private _canvas: HTMLCanvasElement | null = null; + private _cells: GridCell[] = []; + private _mapInstance: L.Map | null = null; + private _onHover: ((cell: GridCell | null, e: MouseEvent) => void) | null = null; + private _hoveredCell: GridCell | null = null; + + constructor(cells: GridCell[], onHover?: (cell: GridCell | null, e: MouseEvent) => void) { + super(); + this._cells = cells; + this._onHover = onHover || null; + } + + onAdd(map: L.Map): this { + this._mapInstance = map; + this._canvas = L.DomUtil.create('canvas', 'leaflet-grid-canvas') as HTMLCanvasElement; + this._canvas.style.position = 'absolute'; + this._canvas.style.pointerEvents = 'auto'; + + const pane = map.getPane('overlayPane'); + if (pane) pane.appendChild(this._canvas); + + map.on('move moveend zoomend resize', this._reset, this); + this._canvas.addEventListener('mousemove', this._onMouseMove.bind(this)); + this._canvas.addEventListener('mouseout', this._onMouseOut.bind(this)); + + this._reset(); + return this; + } + + onRemove(map: L.Map): this { + if (this._canvas?.parentNode) this._canvas.parentNode.removeChild(this._canvas); + map.off('move moveend zoomend resize', this._reset, this); + this._canvas = null; + this._mapInstance = null; + return this; + } + + setCells(cells: GridCell[]): void { + this._cells = cells; + this._reset(); + } + + private _reset(): void { + if (!this._mapInstance || !this._canvas) return; + + const size = this._mapInstance.getSize(); + const bounds = this._mapInstance.getBounds(); + const topLeft = this._mapInstance.latLngToLayerPoint(bounds.getNorthWest()); + + this._canvas.width = size.x; + this._canvas.height = size.y; + this._canvas.style.width = `${size.x}px`; + this._canvas.style.height = `${size.y}px`; + L.DomUtil.setPosition(this._canvas, topLeft); + + this._draw(); + } + + private _draw(): void { + if (!this._mapInstance || !this._canvas) return; + const ctx = this._canvas.getContext('2d'); + if (!ctx) return; + + ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); + if (this._cells.length === 0) return; + + const viewBounds = this._mapInstance.getBounds(); + + for (const cell of this._cells) { + if (!viewBounds.intersects(cell.bounds)) continue; + + const sw = this._mapInstance.latLngToContainerPoint(cell.bounds.getSouthWest()); + const ne = this._mapInstance.latLngToContainerPoint(cell.bounds.getNorthEast()); + + const x = sw.x; + const y = ne.y; + const w = ne.x - sw.x; + const h = sw.y - ne.y; + + ctx.globalAlpha = 0.7; + ctx.fillStyle = getColorForPayback(cell.paybackPeriod); + ctx.fillRect(x, y, w, h); + + if (w > 3 && h > 3) { + ctx.globalAlpha = 0.5; + ctx.strokeStyle = COLORS.strokeDark; + ctx.lineWidth = 0.5; + ctx.strokeRect(x, y, w, h); + } + } + + if (this._hoveredCell) { + const sw = this._mapInstance.latLngToContainerPoint(this._hoveredCell.bounds.getSouthWest()); + const ne = this._mapInstance.latLngToContainerPoint(this._hoveredCell.bounds.getNorthEast()); + ctx.globalAlpha = 1; + ctx.strokeStyle = COLORS.whiteFull; + ctx.lineWidth = 2; + ctx.strokeRect(sw.x, ne.y, ne.x - sw.x, sw.y - ne.y); + } + + ctx.globalAlpha = 1; + } + + private _onMouseMove(e: MouseEvent): void { + if (!this._mapInstance || !this._canvas) return; + + const rect = this._canvas.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + + let foundCell: GridCell | null = null; + + for (const cell of this._cells) { + const sw = this._mapInstance.latLngToContainerPoint(cell.bounds.getSouthWest()); + const ne = this._mapInstance.latLngToContainerPoint(cell.bounds.getNorthEast()); + + if (x >= sw.x && x <= ne.x && y >= ne.y && y <= sw.y) { + foundCell = cell; + break; + } + } + + if (foundCell !== this._hoveredCell) { + this._hoveredCell = foundCell; + this._draw(); + if (this._onHover) this._onHover(foundCell, e); + } + } + + private _onMouseOut(): void { + if (this._hoveredCell) { + this._hoveredCell = null; + this._draw(); + if (this._onHover) this._onHover(null, new MouseEvent('mouseout')); + } + } +} + +// Legend component +const PaybackLegend: React.FC = () => { + const legendItems = [ + { range: '1-2 years', color: COLORS.dataGreen, label: 'Excellent' }, + { range: '3-4 years', color: COLORS.dataLightGreen, label: 'Good' }, + { range: '5-6 years', color: COLORS.dataYellow, label: 'Moderate' }, + { range: '7-8 years', color: COLORS.dataOrange, label: 'Fair' }, + { range: '9-10 years', color: COLORS.dataRed, label: 'Poor' }, + ]; + + return ( + + + Payback Period Legend + + + {legendItems.map((item) => ( + + + + {item.range} + + + {item.label} + + + ))} + + + ); +}; + +// Stats panel component +interface StatsPanelProps { + cells: GridCell[]; +} + +const StatsPanel: React.FC = ({ cells }) => { + const avgPayback = cells.length > 0 + ? (cells.reduce((sum, c) => sum + c.paybackPeriod, 0) / cells.length).toFixed(1) + : '0'; + + return ( + + + + Analysis Summary + + + + + + Total Grid Cells + + + {cells.length.toLocaleString()} + + + + + + Avg. Payback Period + + + {avgPayback} years + + + + + ); +}; + +export default function PrescriptionMapViewer() { + const navigate = useNavigate(); + const { data: committedCoords, hasCoordinates, isLoading, clearCoordinateData, formSubmitted, setFormSubmitted } = useCoordinates(); + + const mapContainerRef = React.useRef(null); + const mapRef = React.useRef(null); + const gridLayerRef = React.useRef(null); + const boundaryLayerRef = React.useRef(null); + const tooltipRef = React.useRef(null); + + const [cells, setCells] = React.useState([]); + + // Extract boundary coordinates from context + const boundaryCoords = React.useMemo(() => { + // From context (GeoJSON FeatureCollection) + if (committedCoords && (committedCoords as any).type === 'FeatureCollection') { + const fc = committedCoords as any; + const polyFeature = fc.features?.find( + (f: any) => f.geometry?.type === 'Polygon' + ); + + if (polyFeature?.geometry?.coordinates?.[0]) { + return polyFeature.geometry.coordinates[0].map((coord: [number, number]) => ({ + lat: coord[1], + lng: coord[0], + })); + } + } + + return []; + }, [committedCoords]); + + // Initialize map + React.useEffect(() => { + if (!mapContainerRef.current || mapRef.current) return; + + const map = L.map(mapContainerRef.current, { + center: [46.7, -116.96], + zoom: 14, + zoomControl: false, + }); + + // Add zoom control to bottom right + L.control.zoom({ position: 'bottomright' }).addTo(map); + + mapRef.current = map; + + // Add ESRI satellite imagery + L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', { + attribution: 'Tiles © Esri', + maxZoom: 20, + }).addTo(map); + + // Add ESRI labels + L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/Reference/World_Boundaries_and_Places/MapServer/tile/{z}/{y}/{x}', { + maxZoom: 20, + }).addTo(map); + + // Create tooltip element + const tooltip = document.createElement('div'); + tooltip.style.cssText = ` + position: absolute; + background: rgba(0,0,0,0.85); + padding: 8px 12px; + border-radius: 6px; + font-size: 12px; + color: #fff; + pointer-events: none; + z-index: 1000; + box-shadow: 0 4px 12px rgba(0,0,0,0.4); + display: none; + border: 1px solid rgba(255,255,255,0.1); + `; + document.body.appendChild(tooltip); + tooltipRef.current = tooltip; + + return () => { + if (tooltipRef.current) { + document.body.removeChild(tooltipRef.current); + tooltipRef.current = null; + } + if (mapRef.current) { + mapRef.current.remove(); + mapRef.current = null; + } + }; + }, []); + + // Update boundary and grid when data changes + React.useEffect(() => { + const map = mapRef.current; + if (!map || boundaryCoords.length < 3 || !formSubmitted) return; + + // Remove old layers + if (boundaryLayerRef.current) { + map.removeLayer(boundaryLayerRef.current); + } + if (gridLayerRef.current) { + map.removeLayer(gridLayerRef.current); + } + + // Draw boundary line + const latLngs = boundaryCoords.map(c => L.latLng(c.lat, c.lng)); + latLngs.push(latLngs[0]); + + const boundaryLine = L.polyline(latLngs, { + color: COLORS.gold, + weight: 3, + opacity: 1, + }).addTo(map); + + boundaryLayerRef.current = boundaryLine; + + // Generate grid cells + const generatedCells = generateGridCells(boundaryCoords, 25); + setCells(generatedCells); + console.log(`[PrescriptionMapViewer] Generated ${generatedCells.length} grid cells`); + + // Hover handler for tooltip + const handleHover = (cell: GridCell | null, e: MouseEvent) => { + if (!tooltipRef.current) return; + + if (cell) { + tooltipRef.current.innerHTML = ` +
Cell Details
+
Payback: ${cell.paybackPeriod} years
+
Application Rate: ${cell.applicationRate} tons/acre
+ `; + tooltipRef.current.style.display = 'block'; + tooltipRef.current.style.left = `${e.clientX + 12}px`; + tooltipRef.current.style.top = `${e.clientY + 12}px`; + } else { + tooltipRef.current.style.display = 'none'; + } + }; + + // Add grid canvas layer + const gridLayer = new GridCanvasLayer(generatedCells, handleHover); + gridLayer.addTo(map); + gridLayerRef.current = gridLayer; + + // Fit bounds to boundary + const bounds = L.latLngBounds(latLngs); + map.fitBounds(bounds, { padding: [60, 60] }); + + }, [boundaryCoords, formSubmitted]); + + // Clear grid when form not submitted + React.useEffect(() => { + if (!formSubmitted && mapRef.current) { + if (boundaryLayerRef.current) { + mapRef.current.removeLayer(boundaryLayerRef.current); + boundaryLayerRef.current = null; + } + if (gridLayerRef.current) { + mapRef.current.removeLayer(gridLayerRef.current); + gridLayerRef.current = null; + } + setCells([]); + } + }, [formSubmitted]); + + const handleReset = () => { + clearCoordinateData(); + setFormSubmitted(false); + navigate('/'); + }; + + // Empty state + if (!isLoading && (!hasCoordinates || !formSubmitted)) { + return ( + + + + + + + + + No Prescription Data + + + To view prescription maps and biochar application recommendations, please submit your farm configuration with boundary coordinates. + + + + + + + ); + } + + return ( + + {/* Header */} + + + + Prescription Map + + + Biochar application recommendations based on your field analysis + + + + + + + + + {/* Main content */} + + {/* Map container */} + +
+ + {/* Info overlay */} + + + + Hover over cells to see details + + + + + {/* Sidebar */} + + + + + + + ); +} diff --git a/frontend/src/features/prescriptions/index.ts b/frontend/src/features/prescriptions/index.ts new file mode 100644 index 0000000..aa248ca --- /dev/null +++ b/frontend/src/features/prescriptions/index.ts @@ -0,0 +1 @@ +export { default as PrescriptionMapViewer } from './PrescriptionMapViewer'; diff --git a/frontend/src/index.css b/frontend/src/index.css index 1287ec1..7ace972 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -64,7 +64,6 @@ button:focus-visible { @media (prefers-color-scheme: light) { :root { color: #213547; - background-color: #ffffff; } a:hover { color: #747bff; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index ec8d40d..0a1f35e 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -5,13 +5,16 @@ import './index.css' import Header from './components/Header.tsx' import AppRoutes from './components/AppRoutes.tsx' import { AuthProvider } from './contexts/AuthContext' +import { CoordinateProvider } from './contexts/CoordinateContext' createRoot(document.getElementById('root')!).render( -
- + +
+ + , diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index 09f5624..313c6aa 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -1,49 +1,48 @@ -import { Box, Button, Typography, Container } from '@mui/material'; -import { useNavigate } from 'react-router'; +import { Box, Typography, Container } from '@mui/material'; import { useAuth } from '../contexts/AuthContext'; +import { FarmBiocharForm } from '../features/farm'; +import { COLORS } from '../styles/colors'; const HomePage = () => { - const navigate = useNavigate(); - const { user, logout } = useAuth(); - - const handleLogout = async () => { - try { - await logout(); - navigate('/login'); - } catch (error) { - console.error('Logout failed:', error); - } - }; + const { user } = useAuth(); return ( - + - - Welcome, {user?.first_name || user?.username}! - + {/* Header */} + + + Welcome, {user?.first_name || user?.username}! + + + Manage your farm biochar applications and data below. + + - - You are successfully logged in to CharAI. - + {/* Intro boilerplate */} + + + About this tool + + + This application helps you plan and manage biochar applications across your farm. Use the form below to define each field, specify crops and current selling prices, and upload geographic coordinate files that define your field boundaries. + + + After uploading your coordinate file (required) and any optional yield data, you can submit a request to estimate potential impacts and budget allocation for biochar application. + + - + {/* Farm configuration form placed below the intro */} + + + ); diff --git a/frontend/src/pages/LandingPage.tsx b/frontend/src/pages/LandingPage.tsx index c9cd32e..5aa3810 100644 --- a/frontend/src/pages/LandingPage.tsx +++ b/frontend/src/pages/LandingPage.tsx @@ -1,38 +1,36 @@ -import { useState } from "react"; -import { Box, Button, Typography } from "@mui/material"; +import { Box, Typography, Container } from "@mui/material"; +import { FarmBiocharForm } from "../features/farm"; +import { COLORS } from "../styles/colors"; +import { useAuth } from "../contexts/AuthContext"; const LandingPage = () => { - const [count, setCount] = useState(0); + const { isAuthenticated } = useAuth(); return ( - - - CharAI - - - - Frontend initialized with React, TypeScript, Vite, and Material UI - - - - + + + CharAI + + + Optimize your farm's biochar application with AI-powered recommendations + + + + {isAuthenticated && } + + ); } diff --git a/frontend/src/pages/LoginPage.tsx b/frontend/src/pages/LoginPage.tsx index 4f6549e..80ab256 100644 --- a/frontend/src/pages/LoginPage.tsx +++ b/frontend/src/pages/LoginPage.tsx @@ -43,7 +43,7 @@ const LoginPage = () => { sx={{ width: '100%', maxWidth: 400, - backgroundColor: '#1a1a1a', + backgroundColor: COLORS.bgCard, padding: 3, borderRadius: 2, boxShadow: `0 4px 12px ${COLORS.blackMedium}`, diff --git a/frontend/src/pages/PrescriptionsPage.tsx b/frontend/src/pages/PrescriptionsPage.tsx new file mode 100644 index 0000000..7198d93 --- /dev/null +++ b/frontend/src/pages/PrescriptionsPage.tsx @@ -0,0 +1,7 @@ +import { PrescriptionMapViewer } from "../features/prescriptions"; + +export default function PrescriptionsPage() { + return ( + + ) +} \ No newline at end of file diff --git a/frontend/src/pages/SignupPage.tsx b/frontend/src/pages/SignupPage.tsx index 285d39d..e1406d9 100644 --- a/frontend/src/pages/SignupPage.tsx +++ b/frontend/src/pages/SignupPage.tsx @@ -50,7 +50,7 @@ const SignupPage = () => { sx={{ width: '100%', maxWidth: 500, - backgroundColor: '#1a1a1a', + backgroundColor: COLORS.bgCard, padding: 3, borderRadius: 2, boxShadow: `0 4px 12px ${COLORS.blackMedium}`, diff --git a/frontend/src/services/coordinateService.ts b/frontend/src/services/coordinateService.ts new file mode 100644 index 0000000..9cff652 --- /dev/null +++ b/frontend/src/services/coordinateService.ts @@ -0,0 +1,25 @@ +// Temporary localStorage-based coordinate service +// TODO: Replace with backend API calls when ready + +import type { FeatureCollection } from 'geojson'; + +// Placeholder function for file parsing +// In the future, backend will handle file upload and return parsed GeoJSON +export function parseFileToGeoJSON(file: File): Promise { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = (e) => { + try { + const content = e.target?.result as string; + // For now, assume file is valid GeoJSON + // TODO: Implement proper file parsing or delegate to backend + const geojson = JSON.parse(content) as FeatureCollection; + resolve(geojson); + } catch (err) { + reject(new Error('Failed to parse file as GeoJSON')); + } + }; + reader.onerror = () => reject(new Error('Failed to read file')); + reader.readAsText(file); + }); +} diff --git a/frontend/src/services/fileUploadService.ts b/frontend/src/services/fileUploadService.ts new file mode 100644 index 0000000..4ce2753 --- /dev/null +++ b/frontend/src/services/fileUploadService.ts @@ -0,0 +1,44 @@ +const DEFAULT_API = 'http://localhost:8000/api'; +const API_URL = ((import.meta.env.VITE_API_URL || DEFAULT_API) + '/data').replace(/\/$/, ''); + +async function handleResponse(response: Response) { + const text = await response.text(); + let data: any = null; + if (text) { + try { + data = JSON.parse(text); + } catch (e) { + data = text; + } + } + + if (!response.ok) { + throw data || { detail: response.statusText }; + } + + return data; +} + +export const uploadCoordinateFile = async (data: any): Promise => { + const response = await fetch(`${API_URL}/coordinate-file-upload/`, { + method: 'POST', + credentials: 'include', + body: data, + }); + + const result = await handleResponse(response); + + return result as any; +} + +export const uploadYieldFile = async (data: any): Promise => { + const response = await fetch(`${API_URL}/yield-file-upload/`, { + method: 'POST', + credentials: 'include', + body: data, + }); + + const result = await handleResponse(response); + + return result as any; +} \ No newline at end of file diff --git a/frontend/src/styles/colors.ts b/frontend/src/styles/colors.ts index a9f080e..5a39698 100644 --- a/frontend/src/styles/colors.ts +++ b/frontend/src/styles/colors.ts @@ -4,11 +4,53 @@ export const COLORS = { whiteHigh: 'rgba(255, 255, 255, 0.87)', // for primary text whiteMedium: 'rgba(255, 255, 255, 0.5)', // for placeholders or secondary text whiteLow: 'rgba(255, 255, 255, 0.23)', // for borders or subtle accents + whiteVeryLow: 'rgba(255, 255, 255, 0.12)', // for subtle dividers + whiteHover: 'rgba(255, 255, 255, 0.08)', // for hover states on dark bg + whiteDisabled: 'rgba(255, 255, 255, 0.3)', // for disabled text // blacks + blackFull: '#000000', + blackHigh: 'rgba(0, 0, 0, 0.85)', // for dark overlays blackMedium: 'rgba(0, 0, 0, 0.5)', // for shadows or overlays blackLow: 'rgba(0, 0, 0, 0.3)', // for subtle shadows + blackOverlay: 'rgba(0, 0, 0, 0.7)', // for modal overlays - // primary accent (for now, styling will change heavily) - indigo: '#646cff', // for links or buttons + // grays / backgrounds + bgDark: '#0a0a0a', // very dark background + bgCard: '#1a1a1a', // card background + bgPage: '#242424', // page background + textDark: '#111', // dark text on light backgrounds + strokeDark: '#222', // dark stroke for grid cells + + // primary accent - indigo + indigo: '#646cff', // primary accent for links/buttons + indigoHover: '#7a81ff', // hover state for indigo + indigoLight: 'rgba(100, 108, 255, 0.1)', // light indigo background + indigoMedium: 'rgba(100, 108, 255, 0.15)', // medium indigo background + indigoVeryLight: 'rgba(100, 108, 255, 0.05)', // very light indigo background + indigoBorder: 'rgba(99, 102, 241, 0.3)', // indigo border + indigoText: '#a5b4fc', // light indigo for text + + // error / danger - red + error: '#ef4444', + errorHover: '#c62828', + errorBg: '#d32f2f', + errorLight: 'rgba(239, 68, 68, 0.1)', + errorBorder: 'rgba(239, 68, 68, 0.4)', + + // warning - amber/yellow + warning: '#fbbf24', + warningBorder: 'rgba(251, 191, 36, 0.5)', + warningLight: 'rgba(251, 191, 36, 0.1)', + + // accent - gold (for map highlights) + gold: '#FFD700', + + // data visualization - payback period scale + dataGreen: '#1a9641', // excellent (1-2 years) + dataLightGreen: '#a6d96a', // good (3-4 years) + dataYellow: '#f9d423', // moderate (5-6 years) + dataOrange: '#f58634', // fair (7-8 years) + dataRed: '#d7191c', // poor (9-10 years) + dataDefault: '#90caf9', // fallback/default }; diff --git a/frontend/src/types/fileUpload.ts b/frontend/src/types/fileUpload.ts new file mode 100644 index 0000000..ece1858 --- /dev/null +++ b/frontend/src/types/fileUpload.ts @@ -0,0 +1 @@ +// this will be filled out when backend defines apis. \ No newline at end of file diff --git a/frontend/src/types/maplibre/bounds.ts b/frontend/src/types/maplibre/bounds.ts new file mode 100644 index 0000000..8be349c --- /dev/null +++ b/frontend/src/types/maplibre/bounds.ts @@ -0,0 +1,51 @@ +import type { FeatureCollection, Geometry } from 'geojson'; + +// Compute LngLatBoundsLike ([[minLng, minLat], [maxLng, maxLat]]) from a GeoJSON FeatureCollection +export function computeBoundsFromGeoJSON(data: FeatureCollection): [[number, number], [number, number]] | null { + let minLng = Infinity; + let minLat = Infinity; + let maxLng = -Infinity; + let maxLat = -Infinity; + + const expand = (coords: any): void => { + if (!coords) return; + if (typeof coords[0] === 'number' && typeof coords[1] === 'number') { + const lng = coords[0]; + const lat = coords[1]; + if (lng < minLng) minLng = lng; + if (lat < minLat) minLat = lat; + if (lng > maxLng) maxLng = lng; + if (lat > maxLat) maxLat = lat; + } else if (Array.isArray(coords)) { + for (const c of coords) expand(c); + } + }; + + for (const feature of data.features) { + const geom: Geometry | null = feature.geometry as any; + if (!geom) continue; + switch (geom.type) { + case 'Point': + case 'MultiPoint': + case 'LineString': + case 'MultiLineString': + case 'Polygon': + case 'MultiPolygon': + expand((geom as any).coordinates); + break; + case 'GeometryCollection': + for (const g of (geom as any).geometries || []) { + expand((g as any).coordinates); + } + break; + default: + break; + } + } + + if (minLng === Infinity || minLat === Infinity || maxLng === -Infinity || maxLat === -Infinity) { + return null; + } + + return [[minLng, minLat], [maxLng, maxLat]]; +} diff --git a/frontend/src/types/maplibre/priorityStyle.ts b/frontend/src/types/maplibre/priorityStyle.ts new file mode 100644 index 0000000..cd12c5d --- /dev/null +++ b/frontend/src/types/maplibre/priorityStyle.ts @@ -0,0 +1,29 @@ +import { COLORS } from '../../styles/colors'; + +// MapLibre GL paint expression for fill-color. +// Primary: derive color by numeric ROI metric `paybackPeriod` (e.g., months). +// - Quickest payback -> red (urgent) +// - Slowest payback -> green (fine) +// Fallback: if `paybackPeriod` missing, use categorical priority/priorityRange mapping. +export const priorityFillColorExpression = [ + 'case', + ['has', 'paybackPeriod'], + // step(paybackPeriod, baseColor, stop1, color1, stop2, color2, ...) + ['step', ['get', 'paybackPeriod'], + COLORS.dataRed, + 6, COLORS.dataOrange, + 12, COLORS.dataYellow, + 18, COLORS.dataLightGreen, + 24, COLORS.dataGreen + ], + // Fallback to categorical priority mapping + ['match', + ['downcase', ['coalesce', ['to-string', ['get', 'priority']], ['to-string', ['get', 'priorityRange']], '' ]], + 'high', COLORS.dataRed, + 'medium-high', COLORS.dataOrange, + 'medium', COLORS.dataYellow, + 'medium-low', COLORS.dataLightGreen, + 'low', COLORS.dataGreen, + COLORS.dataDefault + ] +];