diff --git a/angular.json b/angular.json index 43a8ed72..093c5597 100644 --- a/angular.json +++ b/angular.json @@ -54,7 +54,9 @@ "node_modules/@ali-hm/angular-tree-component/css/angular-tree-component.css", "node_modules/katex/dist/katex.min.css", "node_modules/prismjs/themes/prism-okaidia.css", - "src/scss/angular2-multiselect-dropdown.scss" + "src/scss/angular2-multiselect-dropdown.scss", + "src/app/components/data-view/data-map/leaflet.css", + "node_modules/leaflet-draw/dist/leaflet.draw.css" ], "stylePreprocessorOptions": { "includePaths": [ diff --git a/package-lock.json b/package-lock.json index 7a881354..34e4a4b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,8 @@ "@coreui/icons-angular": "^4.7.14", "@coreui/utils": "2.0.2", "@ngtools/webpack": "^17.2.2", + "@terraformer/wkt": "^2.2.1", + "@turf/turf": "^7.1.0", "ace-builds": "^1.31.1", "angular-ng-autocomplete": "^2.0.12", "angular2-multiselect-dropdown": "^9.0.0", @@ -37,10 +39,17 @@ "flag-icons": "^6.10.0", "flatpickr": "^4.6.3", "font-awesome": "^4.7.0", + "geojson": "^0.5.0", + "graphlib-dot": "^0.6.4", + "hammer": "0.0.5", + "hammerjs": "^2.0.8", + "highlight.js": "^11.8.0", "jquery": "3.7.1", "jquery-ui": "^1.13.0", "json-pointer": "^0.6.2", "katex": "0.16.21", + "leaflet": "^1.9.4", + "leaflet-draw": "^1.0.4", "lodash": "^4.17.20", "marked": "^9.1.6", "moment": "^2.30.1", @@ -81,9 +90,13 @@ "@types/jasmine": "~3.6.0", "@types/jasminewd2": "^2.0.8", "@types/json-pointer": "^1.0.34", + "@types/leaflet": "^1.9.14", + "@types/leaflet-draw": "^1.0.11", "@types/lodash": "^4.17.13", "@types/marked": "^4.3.0", "@types/node": "^12.11.1", + "@types/terraformer__wkt": "^2.0.3", + "@types/uuid": "^10.0.0", "codelyzer": "^6.0.2", "fstream": "^1.0.12", "html-webpack-plugin": "^5.5.3", @@ -2412,17 +2425,6 @@ "webpack": "^5.54.0" } }, - "node_modules/@angular-builders/custom-webpack/node_modules/@types/node": { - "version": "24.3.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz", - "integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "undici-types": "~7.10.0" - } - }, "node_modules/@angular-builders/custom-webpack/node_modules/@vitejs/plugin-basic-ssl": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.1.0.tgz", @@ -2914,14 +2916,6 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", "dev": true }, - "node_modules/@angular-builders/custom-webpack/node_modules/undici-types": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", - "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/@angular-builders/custom-webpack/node_modules/vite": { "version": "5.4.19", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", @@ -4015,17 +4009,6 @@ "node": ">=12" } }, - "node_modules/@angular-devkit/build-angular/node_modules/@types/node": { - "version": "20.11.22", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.22.tgz", - "integrity": "sha512-/G+IxWxma6V3E+pqK1tSl2Fo1kl41pK1yeCyDsgkF9WlVAme4j5ISYM2zR11bgLFJGLN5sVK40T4RJNuiZbEjA==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "undici-types": "~5.26.4" - } - }, "node_modules/@angular-devkit/build-angular/node_modules/@vitejs/plugin-basic-ssl": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.1.0.tgz", @@ -8404,86 +8387,2752 @@ "@sigstore/core": "^1.0.0", "@sigstore/protobuf-specs": "^0.3.0" }, - "engines": { - "node": "^16.14.0 || >=18.0.0" + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", + "dev": true + }, + "node_modules/@terraformer/wkt": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@terraformer/wkt/-/wkt-2.2.1.tgz", + "integrity": "sha512-XDUsW/lvbMzFi7GIuRD9+UqR4QyP+5C+TugeJLMDczKIRbaHoE9J3N8zLSdyOGmnJL9B6xTS3YMMlBnMU0Ar5A==", + "license": "MIT" + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", + "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", + "dev": true, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@tufjs/models": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-2.0.0.tgz", + "integrity": "sha512-c8nj8BaOExmZKO2DXhDfegyhSGcG9E/mPN3U13L+/PsoWm1uaGiHHjxqSHQiasDBQwDA3aHuw9+9spYAP1qvvg==", + "dev": true, + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^9.0.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@tufjs/models/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@tufjs/models/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@turf/along": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/along/-/along-7.2.0.tgz", + "integrity": "sha512-Cf+d2LozABdb0TJoIcJwFKB+qisJY4nMUW9z6PAuZ9UCH7AR//hy2Z06vwYCKFZKP4a7DRPkOMBadQABCyoYuw==", + "license": "MIT", + "dependencies": { + "@turf/bearing": "^7.2.0", + "@turf/destination": "^7.2.0", + "@turf/distance": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/along/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/angle": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/angle/-/angle-7.2.0.tgz", + "integrity": "sha512-b28rs1NO8Dt/MXadFhnpqH7GnEWRsl+xF5JeFtg9+eM/+l/zGrdliPYMZtAj12xn33w22J1X4TRprAI0rruvVQ==", + "license": "MIT", + "dependencies": { + "@turf/bearing": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@turf/rhumb-bearing": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/angle/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/area": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/area/-/area-7.2.0.tgz", + "integrity": "sha512-zuTTdQ4eoTI9nSSjerIy4QwgvxqwJVciQJ8tOPuMHbXJ9N/dNjI7bU8tasjhxas/Cx3NE9NxVHtNpYHL0FSzoA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/area/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/bbox": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/bbox/-/bbox-7.2.0.tgz", + "integrity": "sha512-wzHEjCXlYZiDludDbXkpBSmv8Zu6tPGLmJ1sXQ6qDwpLE1Ew3mcWqt8AaxfTP5QwDNQa3sf2vvgTEzNbPQkCiA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/bbox-clip": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/bbox-clip/-/bbox-clip-7.2.0.tgz", + "integrity": "sha512-q6RXTpqeUQAYLAieUL1n3J6ukRGsNVDOqcYtfzaJbPW+0VsAf+1cI16sN700t0sekbeU1DH/RRVAHhpf8+36wA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/bbox-clip/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/bbox-polygon": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/bbox-polygon/-/bbox-polygon-7.2.0.tgz", + "integrity": "sha512-Aj4G1GAAy26fmOqMjUk0Z+Lcax5VQ9g1xYDbHLQWXvfTsaueBT+RzdH6XPnZ/seEEnZkio2IxE8V5af/osupgA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/bbox-polygon/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/bbox/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/bearing": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/bearing/-/bearing-7.2.0.tgz", + "integrity": "sha512-Jm0Xt3GgHjRrWvBtAGvgfnADLm+4exud2pRlmCYx8zfiKuNXQFkrcTZcOiJOgTfG20Agq28iSh15uta47jSIbg==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/bearing/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/bezier-spline": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/bezier-spline/-/bezier-spline-7.2.0.tgz", + "integrity": "sha512-7BPkc3ufYB9KLvcaTpTsnpXzh9DZoENxCS0Ms9XUwuRXw45TpevwUpOsa3atO76iKQ5puHntqFO4zs8IUxBaaA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/bezier-spline/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/boolean-clockwise": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-clockwise/-/boolean-clockwise-7.2.0.tgz", + "integrity": "sha512-0fJeFSARxy6ealGBM4Gmgpa1o8msQF87p2Dx5V6uSqzT8VPDegX1NSWl4b7QgXczYa9qv7IAABttdWP0K7Q7eQ==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-clockwise/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/boolean-concave": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-concave/-/boolean-concave-7.2.0.tgz", + "integrity": "sha512-v3dTN04dfO6VqctQj1a+pjDHb6+/Ev90oAR2QjJuAntY4ubhhr7vKeJdk/w+tWNSMKULnYwfe65Du3EOu3/TeA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-concave/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/boolean-contains": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-contains/-/boolean-contains-7.2.0.tgz", + "integrity": "sha512-dgRQm4uVO5XuLee4PLVH7CFQZKdefUBMIXTPITm2oRIDmPLJKHDOFKQTNkGJ73mDKKBR2lmt6eVH3br6OYrEYg==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "^7.2.0", + "@turf/boolean-point-in-polygon": "^7.2.0", + "@turf/boolean-point-on-line": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-contains/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/boolean-crosses": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-crosses/-/boolean-crosses-7.2.0.tgz", + "integrity": "sha512-9GyM4UUWFKQOoNhHVSfJBf5XbPy8Fxfz9djjJNAnm/IOl8NmFUSwFPAjKlpiMcr6yuaAoc9R/1KokS9/eLqPvA==", + "license": "MIT", + "dependencies": { + "@turf/boolean-point-in-polygon": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@turf/line-intersect": "^7.2.0", + "@turf/polygon-to-line": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-crosses/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/boolean-disjoint": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-disjoint/-/boolean-disjoint-7.2.0.tgz", + "integrity": "sha512-xdz+pYKkLMuqkNeJ6EF/3OdAiJdiHhcHCV0ykX33NIuALKIEpKik0+NdxxNsZsivOW6keKwr61SI+gcVtHYcnQ==", + "license": "MIT", + "dependencies": { + "@turf/boolean-point-in-polygon": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/line-intersect": "^7.2.0", + "@turf/meta": "^7.2.0", + "@turf/polygon-to-line": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-disjoint/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/boolean-equal": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-equal/-/boolean-equal-7.2.0.tgz", + "integrity": "sha512-TmjKYLsxXqEmdDtFq3QgX4aSogiISp3/doeEtDOs3NNSR8susOtBEZkmvwO6DLW+g/rgoQJIBR6iVoWiRqkBxw==", + "license": "MIT", + "dependencies": { + "@turf/clean-coords": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@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-equal/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/boolean-intersects": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-intersects/-/boolean-intersects-7.2.0.tgz", + "integrity": "sha512-GLRyLQgK3F14drkK5Qi9Mv7Z9VT1bgQUd9a3DB3DACTZWDSwfh8YZUFn/HBwRkK8dDdgNEXaavggQHcPi1k9ow==", + "license": "MIT", + "dependencies": { + "@turf/boolean-disjoint": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-intersects/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/boolean-overlap": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-overlap/-/boolean-overlap-7.2.0.tgz", + "integrity": "sha512-ieM5qIE4anO+gUHIOvEN7CjyowF+kQ6v20/oNYJCp63TVS6eGMkwgd+I4uMzBXfVW66nVHIXjODdUelU+Xyctw==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@turf/line-intersect": "^7.2.0", + "@turf/line-overlap": "^7.2.0", + "@turf/meta": "^7.2.0", + "@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-overlap/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/boolean-parallel": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-parallel/-/boolean-parallel-7.2.0.tgz", + "integrity": "sha512-iOtuzzff8nmwv05ROkSvyeGLMrfdGkIi+3hyQ+DH4IVyV37vQbqR5oOJ0Nt3Qq1Tjrq9fvF8G3OMdAv3W2kY9w==", + "license": "MIT", + "dependencies": { + "@turf/clean-coords": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/line-segment": "^7.2.0", + "@turf/rhumb-bearing": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-parallel/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/boolean-point-in-polygon": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-point-in-polygon/-/boolean-point-in-polygon-7.2.0.tgz", + "integrity": "sha512-lvEOjxeXIp+wPXgl9kJA97dqzMfNexjqHou+XHVcfxQgolctoJiRYmcVCWGpiZ9CBf/CJha1KmD1qQoRIsjLaA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@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-in-polygon/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/boolean-point-on-line": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-point-on-line/-/boolean-point-on-line-7.2.0.tgz", + "integrity": "sha512-H/bXX8+2VYeSyH8JWrOsu8OGmeA9KVZfM7M6U5/fSqGsRHXo9MyYJ94k39A9kcKSwI0aWiMXVD2UFmiWy8423Q==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-point-on-line/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/boolean-touches": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-touches/-/boolean-touches-7.2.0.tgz", + "integrity": "sha512-8qb1CO+cwFATGRGFgTRjzL9aibfsbI91pdiRl7KIEkVdeN/H9k8FDrUA1neY7Yq48IaciuwqjbbojQ16FD9b0w==", + "license": "MIT", + "dependencies": { + "@turf/boolean-point-in-polygon": "^7.2.0", + "@turf/boolean-point-on-line": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-touches/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/boolean-valid": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-valid/-/boolean-valid-7.2.0.tgz", + "integrity": "sha512-xb7gdHN8VV6ivPJh6rPpgxmAEGReiRxqY+QZoEZVGpW2dXcmU1BdY6FA6G/cwvggXAXxJBREoANtEDgp/0ySbA==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "^7.2.0", + "@turf/boolean-crosses": "^7.2.0", + "@turf/boolean-disjoint": "^7.2.0", + "@turf/boolean-overlap": "^7.2.0", + "@turf/boolean-point-in-polygon": "^7.2.0", + "@turf/boolean-point-on-line": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@turf/line-intersect": "^7.2.0", + "@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-valid/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/boolean-within": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-within/-/boolean-within-7.2.0.tgz", + "integrity": "sha512-zB3AiF59zQZ27Dp1iyhp9mVAKOFHat8RDH45TZhLY8EaqdEPdmLGvwMFCKfLryQcUDQvmzP8xWbtUR82QM5C4g==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "^7.2.0", + "@turf/boolean-point-in-polygon": "^7.2.0", + "@turf/boolean-point-on-line": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-within/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/buffer": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/buffer/-/buffer-7.2.0.tgz", + "integrity": "sha512-QH1FTr5Mk4z1kpQNztMD8XBOZfpOXPOtlsxaSAj2kDIf5+LquA6HtJjZrjUngnGtzG5+XwcfyRL4ImvLnFjm5Q==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "^7.2.0", + "@turf/center": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/jsts": "^2.7.1", + "@turf/meta": "^7.2.0", + "@turf/projection": "^7.2.0", + "@types/geojson": "^7946.0.10", + "d3-geo": "1.7.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/buffer/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/@turf/center": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/center/-/center-7.2.0.tgz", + "integrity": "sha512-UTNp9abQ2kuyRg5gCIGDNwwEQeF3NbpYsd1Q0KW9lwWuzbLVNn0sOwbxjpNF4J2HtMOs5YVOcqNvYyuoa2XrXw==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/center-mean": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/center-mean/-/center-mean-7.2.0.tgz", + "integrity": "sha512-NaW6IowAooTJ35O198Jw3U4diZ6UZCCeJY+4E+WMLpks3FCxMDSHEfO2QjyOXQMGWZnVxVelqI5x9DdniDbQ+A==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/center-mean/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/center-median": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/center-median/-/center-median-7.2.0.tgz", + "integrity": "sha512-/CgVyHNG4zAoZpvkl7qBCe4w7giWNVtLyTU5PoIfg1vWM4VpYw+N7kcBBH46bbzvVBn0vhmZr586r543EwdC/A==", + "license": "MIT", + "dependencies": { + "@turf/center-mean": "^7.2.0", + "@turf/centroid": "^7.2.0", + "@turf/distance": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/center-median/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/center-of-mass": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/center-of-mass/-/center-of-mass-7.2.0.tgz", + "integrity": "sha512-ij3pmG61WQPHGTQvOziPOdIgwTMegkYTwIc71Gl7xn4C0vWH6KLDSshCphds9xdWSXt2GbHpUs3tr4XGntHkEQ==", + "license": "MIT", + "dependencies": { + "@turf/centroid": "^7.2.0", + "@turf/convex": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/center-of-mass/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/center/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/centroid": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/centroid/-/centroid-7.2.0.tgz", + "integrity": "sha512-yJqDSw25T7P48au5KjvYqbDVZ7qVnipziVfZ9aSo7P2/jTE7d4BP21w0/XLi3T/9bry/t9PR1GDDDQljN4KfDw==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/centroid/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/circle": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/circle/-/circle-7.2.0.tgz", + "integrity": "sha512-1AbqBYtXhstrHmnW6jhLwsv7TtmT0mW58Hvl1uZXEDM1NCVXIR50yDipIeQPjrCuJ/Zdg/91gU8+4GuDCAxBGA==", + "license": "MIT", + "dependencies": { + "@turf/destination": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/circle/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/clean-coords": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/clean-coords/-/clean-coords-7.2.0.tgz", + "integrity": "sha512-+5+J1+D7wW7O/RDXn46IfCHuX1gIV1pIAQNSA7lcDbr3HQITZj334C4mOGZLEcGbsiXtlHWZiBtm785Vg8i+QQ==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/clean-coords/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/clone": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/clone/-/clone-7.2.0.tgz", + "integrity": "sha512-JlGUT+/5qoU5jqZmf6NMFIoLDY3O7jKd53Up+zbpJ2vzUp6QdwdNzwrsCeONhynWM13F0MVtPXH4AtdkrgFk4g==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/clone/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/clusters": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/clusters/-/clusters-7.2.0.tgz", + "integrity": "sha512-sKOrIKHHtXAuTKNm2USnEct+6/MrgyzMW42deZ2YG2RRKWGaaxHMFU2Yw71Yk4DqStOqTIBQpIOdrRuSOwbuQw==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/clusters-dbscan": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/clusters-dbscan/-/clusters-dbscan-7.2.0.tgz", + "integrity": "sha512-VWVUuDreev56g3/BMlnq/81yzczqaz+NVTypN5CigGgP67e+u/CnijphiuhKjtjDd/MzGjXgEWBJc26Y6LYKAw==", + "license": "MIT", + "dependencies": { + "@turf/clone": "^7.2.0", + "@turf/distance": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "rbush": "^3.0.1", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/clusters-dbscan/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/clusters-kmeans": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/clusters-kmeans/-/clusters-kmeans-7.2.0.tgz", + "integrity": "sha512-BxQdK8jc8Mwm9yoClCYkktm4W004uiQGqb/i/6Y7a8xqgJITWDgTu/cy//wOxAWPk4xfe6MThjnqkszWW8JdyQ==", + "license": "MIT", + "dependencies": { + "@turf/clone": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "skmeans": "0.9.7", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/clusters-kmeans/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/clusters/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/collect": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/collect/-/collect-7.2.0.tgz", + "integrity": "sha512-zRVGDlYS8Bx/Zz4vnEUyRg4dmqHhkDbW/nIUIJh657YqaMj1SFi4Iv2i9NbcurlUBDJFkpuOhCvvEvAdskJ8UA==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "^7.2.0", + "@turf/boolean-point-in-polygon": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@types/geojson": "^7946.0.10", + "rbush": "^3.0.1", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/collect/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/combine": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/combine/-/combine-7.2.0.tgz", + "integrity": "sha512-VEjm3IvnbMt3IgeRIhCDhhQDbLqCU1/5uN1+j1u6fyA095pCizPThGp4f/COSzC3t1s/iiV+fHuDsB6DihHffQ==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/combine/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/concave": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/concave/-/concave-7.2.0.tgz", + "integrity": "sha512-cpaDDlumK762kdadexw5ZAB6g/h2pJdihZ+e65lbQVe3WukJHAANnIEeKsdFCuIyNKrwTz2gWu5ws+OpjP48Yw==", + "license": "MIT", + "dependencies": { + "@turf/clone": "^7.2.0", + "@turf/distance": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@turf/meta": "^7.2.0", + "@turf/tin": "^7.2.0", + "@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/concave/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/convex": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/convex/-/convex-7.2.0.tgz", + "integrity": "sha512-HsgHm+zHRE8yPCE/jBUtWFyaaBmpXcSlyHd5/xsMhSZRImFzRzBibaONWQo7xbKZMISC3Nc6BtUjDi/jEVbqyA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "concaveman": "^1.2.1", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/convex/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/destination": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/destination/-/destination-7.2.0.tgz", + "integrity": "sha512-8DUxtOO0Fvrh1xclIUj3d9C5WS20D21F5E+j+X9Q+ju6fcM4huOqTg5ckV1DN2Pg8caABEc5HEZJnGch/5YnYQ==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/destination/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/difference": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/difference/-/difference-7.2.0.tgz", + "integrity": "sha512-NHKD1v3s8RX+9lOpvHJg6xRuJOKiY3qxHhz5/FmE0VgGqnCkE7OObqWZ5SsXG+Ckh0aafs5qKhmDdDV/gGi6JA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "polyclip-ts": "^0.16.8", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/difference/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/dissolve": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/dissolve/-/dissolve-7.2.0.tgz", + "integrity": "sha512-gPG5TE3mAYuZqBut8tPYCKwi4hhx5Cq0ALoQMB9X0hrVtFIKrihrsj98XQM/5pL/UIpAxQfwisQvy6XaOFaoPA==", + "license": "MIT", + "dependencies": { + "@turf/flatten": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "polyclip-ts": "^0.16.8", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/dissolve/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/distance": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/distance/-/distance-7.2.0.tgz", + "integrity": "sha512-HBjjXIgEcD/wJYjv7/6OZj5yoky2oUvTtVeIAqO3lL80XRvoYmVg6vkOIu6NswkerwLDDNT9kl7+BFLJoHbh6Q==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/distance-weight": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/distance-weight/-/distance-weight-7.2.0.tgz", + "integrity": "sha512-NeoyV0fXDH+7nIoNtLjAoH9XL0AS1pmTIyDxEE6LryoDTsqjnuR0YQxIkLCCWDqECoqaOmmBqpeWONjX5BwWCg==", + "license": "MIT", + "dependencies": { + "@turf/centroid": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/distance-weight/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/distance/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/ellipse": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/ellipse/-/ellipse-7.2.0.tgz", + "integrity": "sha512-/Y75S5hE2+xjnTw4dXpQ5r/Y2HPM4xrwkPRCCQRpuuboKdEvm42azYmh7isPnMnBTVcmGb9UmGKj0HHAbiwt1g==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@turf/rhumb-destination": "^7.2.0", + "@turf/transform-rotate": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/ellipse/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/envelope": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/envelope/-/envelope-7.2.0.tgz", + "integrity": "sha512-xOMtDeNKHwUuDfzQeoSNmdabsP0/IgVDeyzitDe/8j9wTeW+MrKzVbGz7627PT3h6gsO+2nUv5asfKtUbmTyHA==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "^7.2.0", + "@turf/bbox-polygon": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/envelope/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/explode": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/explode/-/explode-7.2.0.tgz", + "integrity": "sha512-jyMXg93J1OI7/65SsLE1k9dfQD3JbcPNMi4/O3QR2Qb3BAs2039oFaSjtW+YqhMqVC4V3ZeKebMcJ8h9sK1n+A==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/explode/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/flatten": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/flatten/-/flatten-7.2.0.tgz", + "integrity": "sha512-q38Qsqr4l7mxp780zSdn0gp/WLBX+sa+gV6qIbDQ1HKCrrPK8QQJmNx7gk1xxEXVot6tq/WyAPysCQdX+kLmMA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/flatten/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/flip": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/flip/-/flip-7.2.0.tgz", + "integrity": "sha512-X0TQ0U/UYh4tyXdLO5itP1sO2HOvfrZC0fYSWmTfLDM14jEPkEK8PblofznfBygL+pIFtOS2is8FuVcp5XxYpQ==", + "license": "MIT", + "dependencies": { + "@turf/clone": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/flip/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/geojson-rbush": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/geojson-rbush/-/geojson-rbush-7.2.0.tgz", + "integrity": "sha512-ST8fLv+EwxVkDgsmhHggM0sPk2SfOHTZJkdgMXVFT7gB9o4lF8qk4y4lwvCCGIfFQAp2yv/PN5EaGMEKutk6xw==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "rbush": "^3.0.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/great-circle": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/great-circle/-/great-circle-7.2.0.tgz", + "integrity": "sha512-n30OiADyOKHhor0aXNgYfXQYXO3UtsOKmhQsY1D89/Oh1nCIXG/1ZPlLL9ZoaRXXBTUBjh99a+K8029NQbGDhw==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@types/geojson": "^7946.0.10" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/helpers": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-7.2.0.tgz", + "integrity": "sha512-cXo7bKNZoa7aC7ydLmUR02oB3IgDe7MxiPuRz3cCtYQHn+BJ6h1tihmamYDWWUlPHgSNF0i3ATc4WmDECZafKw==", + "license": "MIT", + "dependencies": { + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/helpers/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/hex-grid": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/hex-grid/-/hex-grid-7.2.0.tgz", + "integrity": "sha512-Yo2yUGxrTCQfmcVsSjDt0G3Veg8YD26WRd7etVPD9eirNNgXrIyZkbYA7zVV/qLeRWVmYIKRXg1USWl7ORQOGA==", + "license": "MIT", + "dependencies": { + "@turf/distance": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/intersect": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/hex-grid/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/interpolate": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/interpolate/-/interpolate-7.2.0.tgz", + "integrity": "sha512-Ifgjm1SEo6XujuSAU6lpRMvoJ1SYTreil1Rf5WsaXj16BQJCedht/4FtWCTNhSWTwEz2motQ1WNrjTCuPG94xA==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "^7.2.0", + "@turf/centroid": "^7.2.0", + "@turf/clone": "^7.2.0", + "@turf/distance": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/hex-grid": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@turf/meta": "^7.2.0", + "@turf/point-grid": "^7.2.0", + "@turf/square-grid": "^7.2.0", + "@turf/triangle-grid": "^7.2.0", + "@types/geojson": "^7946.0.10" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/intersect": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/intersect/-/intersect-7.2.0.tgz", + "integrity": "sha512-81GMzKS9pKqLPa61qSlFxLFeAC8XbwyCQ9Qv4z6o5skWk1qmMUbEHeMqaGUTEzk+q2XyhZ0sju1FV4iLevQ/aw==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "polyclip-ts": "^0.16.8", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/intersect/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/invariant": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/invariant/-/invariant-7.2.0.tgz", + "integrity": "sha512-kV4u8e7Gkpq+kPbAKNC21CmyrXzlbBgFjO1PhrHPgEdNqXqDawoZ3i6ivE3ULJj2rSesCjduUaC/wyvH/sNr2Q==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/invariant/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/isobands": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/isobands/-/isobands-7.2.0.tgz", + "integrity": "sha512-lYoHeRieFzpBp29Jh19QcDIb0E+dzo/K5uwZuNga4wxr6heNU0AfkD4ByAHYIXHtvmp4m/JpSKq/2N6h/zvBkg==", + "license": "MIT", + "dependencies": { + "@turf/area": "^7.2.0", + "@turf/bbox": "^7.2.0", + "@turf/boolean-point-in-polygon": "^7.2.0", + "@turf/explode": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "marchingsquares": "^1.3.3", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/isobands/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/isolines": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/isolines/-/isolines-7.2.0.tgz", + "integrity": "sha512-4ZXKxvA/JKkxAXixXhN3UVza5FABsdYgOWXyYm3L5ryTPJVOYTVSSd9A+CAVlv9dZc3YdlsqMqLTXNOOre/kwg==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "marchingsquares": "^1.3.3", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/isolines/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "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.2.0", + "resolved": "https://registry.npmjs.org/@turf/kinks/-/kinks-7.2.0.tgz", + "integrity": "sha512-BtxDxGewJR0Q5WR9HKBSxZhirFX+GEH1rD7/EvgDsHS8e1Y5/vNQQUmXdURjdPa4StzaUBsWRU5T3A356gLbPA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/kinks/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/length": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/length/-/length-7.2.0.tgz", + "integrity": "sha512-LBmYN+iCgVtWNLsckVnpQIJENqIIPO63mogazMp23lrDGfWXu07zZQ9ZinJVO5xYurXNhc/QI2xxoqt2Xw90Ig==", + "license": "MIT", + "dependencies": { + "@turf/distance": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/length/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/line-arc": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/line-arc/-/line-arc-7.2.0.tgz", + "integrity": "sha512-kfWzA5oYrTpslTg5fN50G04zSypiYQzjZv3FLjbZkk6kta5fo4JkERKjTeA8x4XNojb+pfmjMBB0yIh2w2dDRw==", + "license": "MIT", + "dependencies": { + "@turf/circle": "^7.2.0", + "@turf/destination": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-arc/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/line-chunk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/line-chunk/-/line-chunk-7.2.0.tgz", + "integrity": "sha512-1ODyL5gETtWSL85MPI0lgp/78vl95M39gpeBxePXyDIqx8geDP9kXfAzctuKdxBoR4JmOVM3NT7Fz7h+IEkC+g==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/length": "^7.2.0", + "@turf/line-slice-along": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-intersect": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/line-intersect/-/line-intersect-7.2.0.tgz", + "integrity": "sha512-GhCJVEkc8EmggNi85EuVLoXF5T5jNVxmhIetwppiVyJzMrwkYAkZSYB3IBFYGUUB9qiNFnTwungVSsBV/S8ZiA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@types/geojson": "^7946.0.10", + "sweepline-intersections": "^1.5.0", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-intersect/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/line-offset": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/line-offset/-/line-offset-7.2.0.tgz", + "integrity": "sha512-1+OkYueDCbnEWzbfBh3taVr+3SyM2bal5jfnSEuDiLA6jnlScgr8tn3INo+zwrUkPFZPPAejL1swVyO5TjUahw==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-overlap": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/line-overlap/-/line-overlap-7.2.0.tgz", + "integrity": "sha512-NNn7/jg53+N10q2Kyt66bEDqN3101iW/1zA5FW7J6UbKApDFkByh+18YZq1of71kS6oUYplP86WkDp16LFpqqw==", + "license": "MIT", + "dependencies": { + "@turf/boolean-point-on-line": "^7.2.0", + "@turf/geojson-rbush": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@turf/line-segment": "^7.2.0", + "@turf/meta": "^7.2.0", + "@turf/nearest-point-on-line": "^7.2.0", + "@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-overlap/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/line-segment": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/line-segment/-/line-segment-7.2.0.tgz", + "integrity": "sha512-E162rmTF9XjVN4rINJCd15AdQGCBlNqeWN3V0YI1vOUpZFNT2ii4SqEMCcH2d+5EheHLL8BWVwZoOsvHZbvaWA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-segment/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/line-slice": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/line-slice/-/line-slice-7.2.0.tgz", + "integrity": "sha512-bHotzZIaU1GPV3RMwttYpDrmcvb3X2i1g/WUttPZWtKrEo2VVAkoYdeZ2aFwtogERYS4quFdJ/TDzAtquBC8WQ==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@turf/nearest-point-on-line": "^7.2.0", + "@types/geojson": "^7946.0.10" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-slice-along": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/line-slice-along/-/line-slice-along-7.2.0.tgz", + "integrity": "sha512-4/gPgP0j5Rp+1prbhXqn7kIH/uZTmSgiubUnn67F8nb9zE+MhbRglhSlRYEZxAVkB7VrGwjyolCwvrROhjHp2A==", + "license": "MIT", + "dependencies": { + "@turf/bearing": "^7.2.0", + "@turf/destination": "^7.2.0", + "@turf/distance": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@types/geojson": "^7946.0.10" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-split": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/line-split/-/line-split-7.2.0.tgz", + "integrity": "sha512-yJTZR+c8CwoKqdW/aIs+iLbuFwAa3Yan+EOADFQuXXIUGps3bJUXx/38rmowNoZbHyP1np1+OtrotyHu5uBsfQ==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "^7.2.0", + "@turf/geojson-rbush": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@turf/line-intersect": "^7.2.0", + "@turf/line-segment": "^7.2.0", + "@turf/meta": "^7.2.0", + "@turf/nearest-point-on-line": "^7.2.0", + "@turf/square": "^7.2.0", + "@turf/truncate": "^7.2.0", + "@types/geojson": "^7946.0.10" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-to-polygon": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/line-to-polygon/-/line-to-polygon-7.2.0.tgz", + "integrity": "sha512-iKpJqc7EYc5NvlD4KaqrKKO6mXR7YWO/YwtW60E2FnsF/blnsy9OfAOcilYHgH3S/V/TT0VedC7DW7Kgjy2EIA==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "^7.2.0", + "@turf/clone": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-to-polygon/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/mask": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/mask/-/mask-7.2.0.tgz", + "integrity": "sha512-ulJ6dQqXC0wrjIoqFViXuMUdIPX5Q6GPViZ3kGfeVijvlLM7kTFBsZiPQwALSr5nTQg4Ppf3FD0Jmg8IErPrgA==", + "license": "MIT", + "dependencies": { + "@turf/clone": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@types/geojson": "^7946.0.10", + "polyclip-ts": "^0.16.8", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/mask/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/meta": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/meta/-/meta-7.2.0.tgz", + "integrity": "sha512-igzTdHsQc8TV1RhPuOLVo74Px/hyPrVgVOTgjWQZzt3J9BVseCdpfY/0cJBdlSRI4S/yTmmHl7gAqjhpYH5Yaw==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@types/geojson": "^7946.0.10" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/midpoint": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/midpoint/-/midpoint-7.2.0.tgz", + "integrity": "sha512-AMn5S9aSrbXdE+Q4Rj+T5nLdpfpn+mfzqIaEKkYI021HC0vb22HyhQHsQbSeX+AWcS4CjD1hFsYVcgKI+5qCfw==", + "license": "MIT", + "dependencies": { + "@turf/bearing": "^7.2.0", + "@turf/destination": "^7.2.0", + "@turf/distance": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/midpoint/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/moran-index": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/moran-index/-/moran-index-7.2.0.tgz", + "integrity": "sha512-Aexh1EmXVPJhApr9grrd120vbalIthcIsQ3OAN2Tqwf+eExHXArJEJqGBo9IZiQbIpFJeftt/OvUvlI8BeO1bA==", + "license": "MIT", + "dependencies": { + "@turf/distance-weight": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/moran-index/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/nearest-neighbor-analysis": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/nearest-neighbor-analysis/-/nearest-neighbor-analysis-7.2.0.tgz", + "integrity": "sha512-LmP/crXb7gilgsL0wL9hsygqc537W/a1W5r9XBKJT4SKdqjoXX5APJatJfd3nwXbRIqwDH0cDA9/YyFjBPlKnA==", + "license": "MIT", + "dependencies": { + "@turf/area": "^7.2.0", + "@turf/bbox": "^7.2.0", + "@turf/bbox-polygon": "^7.2.0", + "@turf/centroid": "^7.2.0", + "@turf/distance": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/meta": "^7.2.0", + "@turf/nearest-point": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/nearest-neighbor-analysis/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/nearest-point": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/nearest-point/-/nearest-point-7.2.0.tgz", + "integrity": "sha512-0wmsqXZ8CGw4QKeZmS+NdjYTqCMC+HXZvM3XAQIU6k6laNLqjad2oS4nDrtcRs/nWDvcj1CR+Io7OiQ6sbpn5Q==", + "license": "MIT", + "dependencies": { + "@turf/clone": "^7.2.0", + "@turf/distance": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/nearest-point-on-line": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/nearest-point-on-line/-/nearest-point-on-line-7.2.0.tgz", + "integrity": "sha512-UOhAeoDPVewBQV+PWg1YTMQcYpJsIqfW5+EuZ5vJl60XwUa0+kqB/eVfSLNXmHENjKKIlEt9Oy9HIDF4VeWmXA==", + "license": "MIT", + "dependencies": { + "@turf/distance": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/nearest-point-on-line/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/nearest-point-to-line": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/nearest-point-to-line/-/nearest-point-to-line-7.2.0.tgz", + "integrity": "sha512-EorU7Qj30A7nAjh++KF/eTPDlzwuuV4neBz7tmSTB21HKuXZAR0upJsx6M2X1CSyGEgNsbFB0ivNKIvymRTKBw==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@turf/meta": "^7.2.0", + "@turf/point-to-line-distance": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/nearest-point-to-line/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/nearest-point/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/planepoint": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/planepoint/-/planepoint-7.2.0.tgz", + "integrity": "sha512-8Vno01tvi5gThUEKBQ46CmlEKDAwVpkl7stOPFvJYlA1oywjAL4PsmgwjXgleZuFtXQUPBNgv5a42Pf438XP4g==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/planepoint/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/point-grid": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/point-grid/-/point-grid-7.2.0.tgz", + "integrity": "sha512-ai7lwBV2FREPW3XiUNohT4opC1hd6+F56qZe20xYhCTkTD9diWjXHiNudQPSmVAUjgMzQGasblQQqvOdL+bJ3Q==", + "license": "MIT", + "dependencies": { + "@turf/boolean-within": "^7.2.0", + "@turf/distance": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/point-grid/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/point-on-feature": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/point-on-feature/-/point-on-feature-7.2.0.tgz", + "integrity": "sha512-ksoYoLO9WtJ/qI8VI9ltF+2ZjLWrAjZNsCsu8F7nyGeCh4I8opjf4qVLytFG44XA2qI5yc6iXDpyv0sshvP82Q==", + "license": "MIT", + "dependencies": { + "@turf/boolean-point-in-polygon": "^7.2.0", + "@turf/center": "^7.2.0", + "@turf/explode": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/nearest-point": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/point-on-feature/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/point-to-line-distance": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/point-to-line-distance/-/point-to-line-distance-7.2.0.tgz", + "integrity": "sha512-fB9Rdnb5w5+t76Gho2dYDkGe20eRrFk8CXi4v1+l1PC8YyLXO+x+l3TrtT8HzL/dVaZeepO6WUIsIw3ditTOPg==", + "license": "MIT", + "dependencies": { + "@turf/bearing": "^7.2.0", + "@turf/distance": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@turf/meta": "^7.2.0", + "@turf/nearest-point-on-line": "^7.2.0", + "@turf/projection": "^7.2.0", + "@turf/rhumb-bearing": "^7.2.0", + "@turf/rhumb-distance": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/point-to-line-distance/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/point-to-polygon-distance": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/point-to-polygon-distance/-/point-to-polygon-distance-7.2.0.tgz", + "integrity": "sha512-w+WYuINgTiFjoZemQwOaQSje/8Kq+uqJOynvx7+gleQPHyWQ3VtTodtV4LwzVzXz8Sf7Mngx1Jcp2SNai5CJYA==", + "license": "MIT", + "dependencies": { + "@turf/boolean-point-in-polygon": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@turf/meta": "^7.2.0", + "@turf/point-to-line-distance": "^7.2.0", + "@turf/polygon-to-line": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/point-to-polygon-distance/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/points-within-polygon": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/points-within-polygon/-/points-within-polygon-7.2.0.tgz", + "integrity": "sha512-jRKp8/mWNMzA+hKlQhxci97H5nOio9tp14R2SzpvkOt+cswxl+NqTEi1hDd2XetA7tjU0TSoNjEgVY8FfA0S6w==", + "license": "MIT", + "dependencies": { + "@turf/boolean-point-in-polygon": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/points-within-polygon/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/polygon-smooth": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/polygon-smooth/-/polygon-smooth-7.2.0.tgz", + "integrity": "sha512-KCp9wF2IEynvGXVhySR8oQ2razKP0zwg99K+fuClP21pSKCFjAPaihPEYq6e8uI/1J7ibjL5++6EMl+LrUTrLg==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/polygon-smooth/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/polygon-tangents": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/polygon-tangents/-/polygon-tangents-7.2.0.tgz", + "integrity": "sha512-AHUUPmOjiQDrtP/ODXukHBlUG0C/9I1je7zz50OTfl2ZDOdEqFJQC3RyNELwq07grTXZvg5TS5wYx/Y7nsm47g==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "^7.2.0", + "@turf/boolean-within": "^7.2.0", + "@turf/explode": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@turf/nearest-point": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/polygon-tangents/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/polygon-to-line": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/polygon-to-line/-/polygon-to-line-7.2.0.tgz", + "integrity": "sha512-9jeTN3LiJ933I5sd4K0kwkcivOYXXm1emk0dHorwXeSFSHF+nlYesEW3Hd889wb9lZd7/SVLMUeX/h39mX+vCA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/polygon-to-line/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/polygonize": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/polygonize/-/polygonize-7.2.0.tgz", + "integrity": "sha512-U9v+lBhUPDv+nsg/VcScdiqCB59afO6CHDGrwIl2+5i6Ve+/KQKjpTV/R+NqoC1iMXAEq3brY6HY8Ukp/pUWng==", + "license": "MIT", + "dependencies": { + "@turf/boolean-point-in-polygon": "^7.2.0", + "@turf/envelope": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/polygonize/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/projection": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/projection/-/projection-7.2.0.tgz", + "integrity": "sha512-/qke5vJScv8Mu7a+fU3RSChBRijE6EVuFHU3RYihMuYm04Vw8dBMIs0enEpoq0ke/IjSbleIrGQNZIMRX9EwZQ==", + "license": "MIT", + "dependencies": { + "@turf/clone": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/projection/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/quadrat-analysis": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/quadrat-analysis/-/quadrat-analysis-7.2.0.tgz", + "integrity": "sha512-fDQh3+ldYNxUqS6QYlvJ7GZLlCeDZR6tD3ikdYtOsSemwW1n/4gm2xcgWJqy3Y0uszBwxc13IGGY7NGEjHA+0w==", + "license": "MIT", + "dependencies": { + "@turf/area": "^7.2.0", + "@turf/bbox": "^7.2.0", + "@turf/bbox-polygon": "^7.2.0", + "@turf/centroid": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@turf/point-grid": "^7.2.0", + "@turf/random": "^7.2.0", + "@turf/square-grid": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/quadrat-analysis/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/random": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/random/-/random-7.2.0.tgz", + "integrity": "sha512-fNXs5mOeXsrirliw84S8UCNkpm4RMNbefPNsuCTfZEXhcr1MuHMzq4JWKb4FweMdN1Yx2l/xcytkO0s71cJ50w==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/random/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/rectangle-grid": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/rectangle-grid/-/rectangle-grid-7.2.0.tgz", + "integrity": "sha512-f0o5ifvy0Ml/nHDJzMNcuSk4h11aa3BfvQNnYQhLpuTQu03j/ICZNlzKTLxwjcUqvxADUifty7Z9CX5W6zky4A==", + "license": "MIT", + "dependencies": { + "@turf/boolean-intersects": "^7.2.0", + "@turf/distance": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/rectangle-grid/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/rewind": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/rewind/-/rewind-7.2.0.tgz", + "integrity": "sha512-SZpRAZiZsE22+HVz6pEID+ST25vOdpAMGk5NO1JeqzhpMALIkIGnkG+xnun2CfYHz7wv8/Z0ADiAvei9rkcQYA==", + "license": "MIT", + "dependencies": { + "@turf/boolean-clockwise": "^7.2.0", + "@turf/clone": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/rewind/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/rhumb-bearing": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/rhumb-bearing/-/rhumb-bearing-7.2.0.tgz", + "integrity": "sha512-jbdexlrR8X2ZauUciHx3tRwG+BXoMXke4B8p8/IgDlAfIrVdzAxSQN89FMzIKnjJ/kdLjo9bFGvb92bu31Etug==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/rhumb-bearing/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/rhumb-destination": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/rhumb-destination/-/rhumb-destination-7.2.0.tgz", + "integrity": "sha512-U9OLgLAHlH4Wfx3fBZf3jvnkDjdTcfRan5eI7VPV1+fQWkOteATpzkiRjCvSYK575GljVwWBjkKca8LziGWitQ==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/rhumb-destination/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/rhumb-distance": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/rhumb-distance/-/rhumb-distance-7.2.0.tgz", + "integrity": "sha512-NsijTPON1yOc9tirRPEQQuJ5aQi7pREsqchQquaYKbHNWsexZjcDi4wnw2kM3Si4XjmgynT+2f7aXH7FHarHzw==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/rhumb-distance/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/sample": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/sample/-/sample-7.2.0.tgz", + "integrity": "sha512-f+ZbcbQJ9glQ/F26re8LadxO0ORafy298EJZe6XtbctRTJrNus6UNAsl8+GYXFqMnXM22tbTAznnJX3ZiWNorA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/sample/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/sector": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/sector/-/sector-7.2.0.tgz", + "integrity": "sha512-zL06MjbbMG4DdpiNz+Q9Ax8jsCekt3R76uxeWShulAGkyDB5smdBOUDoRwxn05UX7l4kKv4Ucq2imQXhxKFd1w==", + "license": "MIT", + "dependencies": { + "@turf/circle": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@turf/line-arc": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/sector/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/shortest-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/shortest-path/-/shortest-path-7.2.0.tgz", + "integrity": "sha512-6fpx8feZ2jMSaeRaFdqFShGWkNb+veUOeyLFSHA/aRD9n/e9F2pWZoRbQWKbKTpcKFJ2FnDEqCZnh/GrcAsqWA==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "^7.2.0", + "@turf/bbox-polygon": "^7.2.0", + "@turf/boolean-point-in-polygon": "^7.2.0", + "@turf/clean-coords": "^7.2.0", + "@turf/distance": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@turf/meta": "^7.2.0", + "@turf/transform-scale": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/shortest-path/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/simplify": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/simplify/-/simplify-7.2.0.tgz", + "integrity": "sha512-9YHIfSc8BXQfi5IvEMbCeQYqNch0UawIGwbboJaoV8rodhtk6kKV2wrpXdGqk/6Thg6/RWvChJFKVVTjVrULyQ==", + "license": "MIT", + "dependencies": { + "@turf/clean-coords": "^7.2.0", + "@turf/clone": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/simplify/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/square": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/square/-/square-7.2.0.tgz", + "integrity": "sha512-9pMoAGFvqzCDOlO9IRSSBCGXKbl8EwMx6xRRBMKdZgpS0mZgfm9xiptMmx/t1m4qqHIlb/N+3MUF7iMBx6upcA==", + "license": "MIT", + "dependencies": { + "@turf/distance": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/square-grid": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/square-grid/-/square-grid-7.2.0.tgz", + "integrity": "sha512-EmzGXa90hz+tiCOs9wX+Lak6pH0Vghb7QuX6KZej+pmWi3Yz7vdvQLmy/wuN048+wSkD5c8WUo/kTeNDe7GnmA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/rectangle-grid": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/square-grid/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/square/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/standard-deviational-ellipse": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/standard-deviational-ellipse/-/standard-deviational-ellipse-7.2.0.tgz", + "integrity": "sha512-+uC0pR2nRjm90JvMXe/2xOCZsYV2II1ZZ2zmWcBWv6bcFXBspcxk2QfCC3k0bj6jDapELzoQgnn3cG5lbdQV2w==", + "license": "MIT", + "dependencies": { + "@turf/center-mean": "^7.2.0", + "@turf/ellipse": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@turf/meta": "^7.2.0", + "@turf/points-within-polygon": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/standard-deviational-ellipse/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/tag": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/tag/-/tag-7.2.0.tgz", + "integrity": "sha512-TAFvsbp5TCBqXue8ui+CtcLsPZ6NPC88L8Ad6Hb/R6VAi21qe0U42WJHQYXzWmtThoTNwxi+oKSeFbRDsr0FIA==", + "license": "MIT", + "dependencies": { + "@turf/boolean-point-in-polygon": "^7.2.0", + "@turf/clone": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/tag/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/tesselate": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/tesselate/-/tesselate-7.2.0.tgz", + "integrity": "sha512-zHGcG85aOJJu1seCm+CYTJ3UempX4Xtyt669vFG6Hbr/Hc7ii6STQ2ysFr7lJwFtU9uyYhphVrrgwIqwglvI/Q==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@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/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/tin": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/tin/-/tin-7.2.0.tgz", + "integrity": "sha512-y24Vt3oeE6ZXvyLJamP0Ke02rPlDGE9gF7OFADnR0mT+2uectb0UTIBC3kKzON80TEAlA3GXpKFkCW5Fo/O/Kg==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/tin/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/transform-rotate": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/transform-rotate/-/transform-rotate-7.2.0.tgz", + "integrity": "sha512-EMCj0Zqy3cF9d3mGRqDlYnX2ZBXe3LgT+piDR0EuF5c5sjuKErcFcaBIsn/lg1gp4xCNZFinkZ3dsFfgGHf6fw==", + "license": "MIT", + "dependencies": { + "@turf/centroid": "^7.2.0", + "@turf/clone": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@turf/meta": "^7.2.0", + "@turf/rhumb-bearing": "^7.2.0", + "@turf/rhumb-destination": "^7.2.0", + "@turf/rhumb-distance": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/transform-rotate/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/transform-scale": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/transform-scale/-/transform-scale-7.2.0.tgz", + "integrity": "sha512-HYB+pw938eeI8s1/zSWFy6hq+t38fuUaBb0jJsZB1K9zQ1WjEYpPvKF/0//80zNPlyxLv3cOkeBucso3hzI07A==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "^7.2.0", + "@turf/center": "^7.2.0", + "@turf/centroid": "^7.2.0", + "@turf/clone": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@turf/meta": "^7.2.0", + "@turf/rhumb-bearing": "^7.2.0", + "@turf/rhumb-destination": "^7.2.0", + "@turf/rhumb-distance": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/transform-scale/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/transform-translate": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/transform-translate/-/transform-translate-7.2.0.tgz", + "integrity": "sha512-zAglR8MKCqkzDTjGMIQgbg/f+Q3XcKVzr9cELw5l9CrS1a0VTSDtBZLDm0kWx0ankwtam7ZmI2jXyuQWT8Gbug==", + "license": "MIT", + "dependencies": { + "@turf/clone": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@turf/meta": "^7.2.0", + "@turf/rhumb-destination": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/transform-translate/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/triangle-grid": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/triangle-grid/-/triangle-grid-7.2.0.tgz", + "integrity": "sha512-4gcAqWKh9hg6PC5nNSb9VWyLgl821cwf9yR9yEzQhEFfwYL/pZONBWCO1cwVF23vSYMSMm+/TwqxH4emxaArfw==", + "license": "MIT", + "dependencies": { + "@turf/distance": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/intersect": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" } }, - "node_modules/@socket.io/component-emitter": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", - "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", - "dev": true - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "dev": true - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true + "node_modules/@turf/triangle-grid/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true + "node_modules/@turf/truncate": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/truncate/-/truncate-7.2.0.tgz", + "integrity": "sha512-jyFzxYbPugK4XjV5V/k6Xr3taBjjvo210IbPHJXw0Zh7Y6sF+hGxeRVtSuZ9VP/6oRyqAOHKUrze+OOkPqBgUg==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true + "node_modules/@turf/truncate/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" }, - "node_modules/@tufjs/canonical-json": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", - "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", - "dev": true, - "engines": { - "node": "^16.14.0 || >=18.0.0" + "node_modules/@turf/turf": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/turf/-/turf-7.2.0.tgz", + "integrity": "sha512-G1kKBu4hYgoNoRJgnpJohNuS7bLnoWHZ2G/4wUMym5xOSiYah6carzdTEsMoTsauyi7ilByWHx5UHwbjjCVcBw==", + "license": "MIT", + "dependencies": { + "@turf/along": "^7.2.0", + "@turf/angle": "^7.2.0", + "@turf/area": "^7.2.0", + "@turf/bbox": "^7.2.0", + "@turf/bbox-clip": "^7.2.0", + "@turf/bbox-polygon": "^7.2.0", + "@turf/bearing": "^7.2.0", + "@turf/bezier-spline": "^7.2.0", + "@turf/boolean-clockwise": "^7.2.0", + "@turf/boolean-concave": "^7.2.0", + "@turf/boolean-contains": "^7.2.0", + "@turf/boolean-crosses": "^7.2.0", + "@turf/boolean-disjoint": "^7.2.0", + "@turf/boolean-equal": "^7.2.0", + "@turf/boolean-intersects": "^7.2.0", + "@turf/boolean-overlap": "^7.2.0", + "@turf/boolean-parallel": "^7.2.0", + "@turf/boolean-point-in-polygon": "^7.2.0", + "@turf/boolean-point-on-line": "^7.2.0", + "@turf/boolean-touches": "^7.2.0", + "@turf/boolean-valid": "^7.2.0", + "@turf/boolean-within": "^7.2.0", + "@turf/buffer": "^7.2.0", + "@turf/center": "^7.2.0", + "@turf/center-mean": "^7.2.0", + "@turf/center-median": "^7.2.0", + "@turf/center-of-mass": "^7.2.0", + "@turf/centroid": "^7.2.0", + "@turf/circle": "^7.2.0", + "@turf/clean-coords": "^7.2.0", + "@turf/clone": "^7.2.0", + "@turf/clusters": "^7.2.0", + "@turf/clusters-dbscan": "^7.2.0", + "@turf/clusters-kmeans": "^7.2.0", + "@turf/collect": "^7.2.0", + "@turf/combine": "^7.2.0", + "@turf/concave": "^7.2.0", + "@turf/convex": "^7.2.0", + "@turf/destination": "^7.2.0", + "@turf/difference": "^7.2.0", + "@turf/dissolve": "^7.2.0", + "@turf/distance": "^7.2.0", + "@turf/distance-weight": "^7.2.0", + "@turf/ellipse": "^7.2.0", + "@turf/envelope": "^7.2.0", + "@turf/explode": "^7.2.0", + "@turf/flatten": "^7.2.0", + "@turf/flip": "^7.2.0", + "@turf/geojson-rbush": "^7.2.0", + "@turf/great-circle": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/hex-grid": "^7.2.0", + "@turf/interpolate": "^7.2.0", + "@turf/intersect": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@turf/isobands": "^7.2.0", + "@turf/isolines": "^7.2.0", + "@turf/kinks": "^7.2.0", + "@turf/length": "^7.2.0", + "@turf/line-arc": "^7.2.0", + "@turf/line-chunk": "^7.2.0", + "@turf/line-intersect": "^7.2.0", + "@turf/line-offset": "^7.2.0", + "@turf/line-overlap": "^7.2.0", + "@turf/line-segment": "^7.2.0", + "@turf/line-slice": "^7.2.0", + "@turf/line-slice-along": "^7.2.0", + "@turf/line-split": "^7.2.0", + "@turf/line-to-polygon": "^7.2.0", + "@turf/mask": "^7.2.0", + "@turf/meta": "^7.2.0", + "@turf/midpoint": "^7.2.0", + "@turf/moran-index": "^7.2.0", + "@turf/nearest-neighbor-analysis": "^7.2.0", + "@turf/nearest-point": "^7.2.0", + "@turf/nearest-point-on-line": "^7.2.0", + "@turf/nearest-point-to-line": "^7.2.0", + "@turf/planepoint": "^7.2.0", + "@turf/point-grid": "^7.2.0", + "@turf/point-on-feature": "^7.2.0", + "@turf/point-to-line-distance": "^7.2.0", + "@turf/point-to-polygon-distance": "^7.2.0", + "@turf/points-within-polygon": "^7.2.0", + "@turf/polygon-smooth": "^7.2.0", + "@turf/polygon-tangents": "^7.2.0", + "@turf/polygon-to-line": "^7.2.0", + "@turf/polygonize": "^7.2.0", + "@turf/projection": "^7.2.0", + "@turf/quadrat-analysis": "^7.2.0", + "@turf/random": "^7.2.0", + "@turf/rectangle-grid": "^7.2.0", + "@turf/rewind": "^7.2.0", + "@turf/rhumb-bearing": "^7.2.0", + "@turf/rhumb-destination": "^7.2.0", + "@turf/rhumb-distance": "^7.2.0", + "@turf/sample": "^7.2.0", + "@turf/sector": "^7.2.0", + "@turf/shortest-path": "^7.2.0", + "@turf/simplify": "^7.2.0", + "@turf/square": "^7.2.0", + "@turf/square-grid": "^7.2.0", + "@turf/standard-deviational-ellipse": "^7.2.0", + "@turf/tag": "^7.2.0", + "@turf/tesselate": "^7.2.0", + "@turf/tin": "^7.2.0", + "@turf/transform-rotate": "^7.2.0", + "@turf/transform-scale": "^7.2.0", + "@turf/transform-translate": "^7.2.0", + "@turf/triangle-grid": "^7.2.0", + "@turf/truncate": "^7.2.0", + "@turf/union": "^7.2.0", + "@turf/unkink-polygon": "^7.2.0", + "@turf/voronoi": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" } }, - "node_modules/@tufjs/models": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-2.0.0.tgz", - "integrity": "sha512-c8nj8BaOExmZKO2DXhDfegyhSGcG9E/mPN3U13L+/PsoWm1uaGiHHjxqSHQiasDBQwDA3aHuw9+9spYAP1qvvg==", - "dev": true, + "node_modules/@turf/turf/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/union": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/union/-/union-7.2.0.tgz", + "integrity": "sha512-Xex/cfKSmH0RZRWSJl4RLlhSmEALVewywiEXcu0aIxNbuZGTcpNoI0h4oLFrE/fUd0iBGFg/EGLXRL3zTfpg6g==", + "license": "MIT", "dependencies": { - "@tufjs/canonical-json": "2.0.0", - "minimatch": "^9.0.3" + "@turf/helpers": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "polyclip-ts": "^0.16.8", + "tslib": "^2.8.1" }, - "engines": { - "node": "^16.14.0 || >=18.0.0" + "funding": { + "url": "https://opencollective.com/turf" } }, - "node_modules/@tufjs/models/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, + "node_modules/@turf/union/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/unkink-polygon": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/unkink-polygon/-/unkink-polygon-7.2.0.tgz", + "integrity": "sha512-dFPfzlIgkEr15z6oXVxTSWshWi51HeITGVFtl1GAKGMtiXJx1uMqnfRsvljqEjaQu/4AzG1QAp3b+EkSklQSiQ==", + "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "@turf/area": "^7.2.0", + "@turf/boolean-point-in-polygon": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "rbush": "^3.0.1", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" } }, - "node_modules/@tufjs/models/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, + "node_modules/@turf/unkink-polygon/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/voronoi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/voronoi/-/voronoi-7.2.0.tgz", + "integrity": "sha512-3K6N0LtJsWTXxPb/5N2qD9e8f4q8+tjTbGV3lE3v8x06iCnNlnuJnqM5NZNPpvgvCatecBkhClO3/3RndE61Fw==", + "license": "MIT", "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" + "@turf/clone": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@types/d3-voronoi": "^1.1.12", + "@types/geojson": "^7946.0.10", + "d3-voronoi": "1.1.2", + "tslib": "^2.8.1" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://opencollective.com/turf" } }, + "node_modules/@turf/voronoi/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/@tweenjs/tween.js": { "version": "17.6.0", "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-17.6.0.tgz", @@ -8574,6 +11223,12 @@ "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==", "optional": true }, + "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/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -8633,6 +11288,12 @@ "@types/send": "*" } }, + "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/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -8680,6 +11341,26 @@ "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", "dev": true }, + "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/leaflet-draw": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@types/leaflet-draw/-/leaflet-draw-1.0.13.tgz", + "integrity": "sha512-YU82kilOaU+wPNbqKCCDfHH3hqepN6XilrBwG/mSeZ/z4ewumaRCOah44s3FMxSu/Aa0SVa3PPJvhIZDUA09mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/leaflet": "^1.9" + } + }, "node_modules/@types/lodash": { "version": "4.17.13", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.13.tgz", @@ -8783,12 +11464,29 @@ "@types/node": "*" } }, + "node_modules/@types/terraformer__wkt": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/terraformer__wkt/-/terraformer__wkt-2.0.3.tgz", + "integrity": "sha512-60CGvi30kMIKl2QERrE6LD5iPm4lutZ1M/mqBY4wrn6H/QlZQa/5CN1e6trZ6ZtSRHLbHLwG+egt/nAIDbPG0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/unist": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==", "optional": true }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/ws": { "version": "8.5.10", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", @@ -9467,6 +12165,15 @@ "node": "*" } }, + "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/binary-extensions": { "version": "2.0.0", "license": "MIT", @@ -10217,7 +12924,6 @@ }, "node_modules/commander": { "version": "2.20.0", - "dev": true, "license": "MIT" }, "node_modules/common-path-prefix": { @@ -10290,6 +12996,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/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/connect": { "version": "3.7.0", "dev": true, @@ -10853,8 +13577,7 @@ }, "node_modules/d3-array": { "version": "1.2.4", - "license": "BSD-3-Clause", - "optional": true + "license": "BSD-3-Clause" }, "node_modules/d3-axis": { "version": "3.0.0", @@ -11246,6 +13969,12 @@ "d3-selection": "2 - 3" } }, + "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/d3-zoom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", @@ -11589,6 +14318,12 @@ "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", "dev": true }, + "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/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -12172,7 +14907,6 @@ }, "node_modules/fast-deep-equal": { "version": "3.1.3", - "dev": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -12523,6 +15257,48 @@ "node": ">=6.9.0" } }, + "node_modules/geojson": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/geojson/-/geojson-0.5.0.tgz", + "integrity": "sha512-/Bx5lEn+qRF4TfQ5aLu6NH+UKtvIv7Lhc487y/c8BdludrCTpiWf9wyI0RTyqg49MFefIAvFDuEi5Dfd/zgNxQ==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "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.1", + "resolved": "https://registry.npmjs.org/geojson-polygon-self-intersections/-/geojson-polygon-self-intersections-1.2.1.tgz", + "integrity": "sha512-/QM1b5u2d172qQVO//9CGRa49jEmclKEsYOQmWP9ooEjj63tBM51m2805xsbxkzlEELQ2REgTf700gUhhlegxA==", + "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/get-caller-file": { "version": "2.0.5", "dev": true, @@ -12681,6 +15457,25 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, + "node_modules/graphlib": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz", + "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.15" + } + }, + "node_modules/graphlib-dot": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/graphlib-dot/-/graphlib-dot-0.6.4.tgz", + "integrity": "sha512-rdhDTu0mBlloTpFMfkQq+e3y4yL22OqP5MhQbkw6QUURqa+4YLgv3XZy2fA64wdEcJNZ+waI76URemVgdFtzng==", + "license": "MIT", + "dependencies": { + "graphlib": "^2.1.8", + "lodash": "^4.17.15" + } + }, "node_modules/gzip-size": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", @@ -12696,6 +15491,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/hammer": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/hammer/-/hammer-0.0.5.tgz", + "integrity": "sha512-mdGMKdrHiO1QVY7A+514ZtoaujioWjd8LT1DQpKMsgGke1JmmSvbywgbOWFRRmSfGupcSGra1N9fvFXNtwXb+Q==", + "bin": { + "hammer": "bin/hammer" + }, + "engines": { + "node": "*" + } + }, + "node_modules/hammerjs": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz", + "integrity": "sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -12761,6 +15576,15 @@ "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==", "optional": true }, + "node_modules/highlight.js": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/hosted-git-info": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.1.tgz", @@ -13791,6 +16615,15 @@ "node >= 0.2.0" ] }, + "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/karma": { "version": "6.4.2", "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.2.tgz", @@ -14085,6 +16918,18 @@ "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", "optional": true }, + "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/leaflet-draw": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/leaflet-draw/-/leaflet-draw-1.0.4.tgz", + "integrity": "sha512-rsQ6saQO5ST5Aj6XRFylr5zvarWgzWnrg46zQ1MEOEIHsppdC/8hnN8qMoFvACsPvTioAuysya/TVtog15tyAQ==", + "license": "MIT" + }, "node_modules/less": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/less/-/less-4.2.0.tgz", @@ -14455,6 +17300,12 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/marchingsquares": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/marchingsquares/-/marchingsquares-1.3.3.tgz", + "integrity": "sha512-gz6nNQoVK7Lkh2pZulrT4qd4347S/toG9RXH2pyzhLgkL5mLkBoqgv4EvAGXcV0ikDW72n/OQb3Xe8bGagQZCg==", + "license": "AGPL-3.0" + }, "node_modules/marked": { "version": "9.1.6", "resolved": "https://registry.npmjs.org/marked/-/marked-9.1.6.tgz", @@ -16756,6 +19607,31 @@ "url-polyfill": "^1.1.12" } }, + "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/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/popper.js": { "version": "1.16.1", "dev": true, @@ -17101,6 +19977,12 @@ } ] }, + "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/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -17139,6 +20021,15 @@ "node": ">= 0.8" } }, + "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/read-package-json": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-7.0.0.tgz", @@ -18301,6 +21192,12 @@ "node": ">= 10" } }, + "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/slash": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", @@ -18670,6 +21567,12 @@ } } }, + "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/sprintf-js": { "version": "1.0.3", "dev": true, @@ -18857,6 +21760,15 @@ "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/symbol-observable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", @@ -19043,6 +21955,12 @@ "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==", "optional": true }, + "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/tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -19075,6 +21993,32 @@ "node": ">=0.6" } }, + "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/totalist": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", @@ -19299,14 +22243,6 @@ "node": ">=18.0" } }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", diff --git a/package.json b/package.json index 16876299..e93f5d1e 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,8 @@ "@coreui/icons-angular": "^4.7.14", "@coreui/utils": "2.0.2", "@ngtools/webpack": "^17.2.2", + "@terraformer/wkt": "^2.2.1", + "@turf/turf": "^7.1.0", "ace-builds": "^1.31.1", "angular-ng-autocomplete": "^2.0.12", "angular2-multiselect-dropdown": "^9.0.0", @@ -40,8 +42,15 @@ "flag-icons": "^6.10.0", "flatpickr": "^4.6.3", "font-awesome": "^4.7.0", + "geojson": "^0.5.0", + "graphlib-dot": "^0.6.4", + "hammer": "0.0.5", + "hammerjs": "^2.0.8", + "highlight.js": "^11.8.0", "jquery": "3.7.1", "jquery-ui": "^1.13.0", + "leaflet": "^1.9.4", + "leaflet-draw": "^1.0.4", "json-pointer": "^0.6.2", "katex": "0.16.21", "lodash": "^4.17.20", @@ -85,8 +94,12 @@ "@types/jasminewd2": "^2.0.8", "@types/json-pointer": "^1.0.34", "@types/lodash": "^4.17.13", + "@types/leaflet": "^1.9.14", + "@types/leaflet-draw": "^1.0.11", "@types/marked": "^4.3.0", "@types/node": "^12.11.1", + "@types/terraformer__wkt": "^2.0.3", + "@types/uuid": "^10.0.0", "codelyzer": "^6.0.2", "fstream": "^1.0.12", "html-webpack-plugin": "^5.5.3", diff --git a/src/app/components/components.module.ts b/src/app/components/components.module.ts index 93f005b0..3412342b 100644 --- a/src/app/components/components.module.ts +++ b/src/app/components/components.module.ts @@ -134,6 +134,8 @@ import {AlgMetadataComponent} from './polyalg/algnode/alg-metadata/alg-metadata. import {DoubleArgComponent} from './polyalg/controls/double-arg/double-arg.component'; import {WindowArgComponent} from './polyalg/controls/window-arg/window-arg.component'; import {AutocompleteComponent} from './autocomplete/autocomplete.component'; +import {DataMapComponent} from './data-view/data-map/data-map.component'; +import {MapValuesPipe} from '../pipes/pipes'; @NgModule({ @@ -239,6 +241,7 @@ import {AutocompleteComponent} from './autocomplete/autocomplete.component'; DataCardComponent, DataTableComponent, DataGraphComponent, + DataMapComponent, MediaComponent, DeleteConfirmComponent, ExpandableTextComponent, @@ -272,7 +275,8 @@ import {AutocompleteComponent} from './autocomplete/autocomplete.component'; AlgMetadataComponent, DoubleArgComponent, WindowArgComponent, - AutocompleteComponent + AutocompleteComponent, + MapValuesPipe ], exports: [ BreadcrumbComponent, @@ -297,8 +301,10 @@ import {AutocompleteComponent} from './autocomplete/autocomplete.component'; ReloadButtonComponent, DockerInstanceComponent, AlgViewerComponent, + DataMapComponent, JsonTextComponent, - AutocompleteComponent + AutocompleteComponent, + MapValuesPipe ] }) export class ComponentsModule { diff --git a/src/app/components/data-view/data-map/data-map.component.html b/src/app/components/data-view/data-map/data-map.component.html new file mode 100644 index 00000000..5326c1e5 --- /dev/null +++ b/src/app/components/data-view/data-map/data-map.component.html @@ -0,0 +1,36 @@ +
+
+
+
+
+ + {{ this.isLoadingMessage }} + Loading... + + {{ this.isLoadingMessage }} + Loading... +
+
+
+ + +
+
+
+ + + + + + + diff --git a/src/app/components/data-view/data-map/data-map.component.scss b/src/app/components/data-view/data-map/data-map.component.scss new file mode 100644 index 00000000..7b1dd75c --- /dev/null +++ b/src/app/components/data-view/data-map/data-map.component.scss @@ -0,0 +1,62 @@ +.map-container { + position: relative; + height: 100%; + border: 1px solid lightgray; +} + +// Only for query mode, the results view already has a margin +.map-margin { + margin: 1rem 0; + height: calc(100% - 2rem); +} + +.inside-results { + min-height: 600px; + padding: 0; +} + +#map { + position: absolute; + inset: 0; + z-index: 1; +} + +.map-overlay { + position: absolute; + inset: 0; + z-index: 2; + pointer-events: none; + display: grid; + background: rgba(0, 0, 0, 0.1); + place-items: center; +} + +.spinner-container { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.5rem; + background: rgba(0, 0, 0, 0.25); + padding: 2rem; +} + +.map-overlay-buttons { + display: flex; + flex-direction: row; + gap: 1rem; + position: absolute; + z-index: 3; + bottom: 2rem; + right: 2rem; +} + +.spinner-container > span { + font-size: 1.5rem; + font-weight: 400; + color: white; +} + + +.leaflet-overlay-pane > svg { + transition: opacity 50ms ease-in-out; +} \ No newline at end of file diff --git a/src/app/components/data-view/data-map/data-map.component.ts b/src/app/components/data-view/data-map/data-map.component.ts new file mode 100644 index 00000000..12491f7d --- /dev/null +++ b/src/app/components/data-view/data-map/data-map.component.ts @@ -0,0 +1,561 @@ +import {AfterViewInit, Component, effect, Input, OnDestroy} from '@angular/core'; +import {DataTemplateComponent} from '../data-template/data-template.component'; +import * as d3 from 'd3'; +import {GeoPath, GeoPermissibleObjects} from 'd3'; +import * as d3Geo from 'd3-geo'; +import * as L from 'leaflet'; +import 'leaflet-draw'; +import {LayerSettingsService} from '../../../views/querying/gis/services/layersettings.service'; +import {MapLayer} from '../../../views/querying/gis/models/MapLayer.model'; +import {MapGeometryWithData} from '../../../views/querying/gis/models/MapGeometryWithData.model'; +import {CombinedResult} from '../data-view.model'; +import {LatLng} from 'leaflet'; +import {Polygon, Position} from 'geojson'; +import {cibYarn} from '@coreui/icons'; + +// tslint:disable:no-non-null-assertion + +@Component({ + selector: 'app-data-map', + templateUrl: './data-map.component.html', + styleUrls: ['./data-map.component.scss'] +}) +export class DataMapComponent extends DataTemplateComponent implements AfterViewInit, OnDestroy { + // If the map is shown inside the results section, different styling needs to be applied. + @Input() isInsideResults = false; + + currentBaseLayer: L.TileLayer | undefined; + layers: MapLayer[] = []; + isLoading = false; + isLoadingMessage = 'TODO isLoadingMessage'; + canRerenderLayers = false; + previewResult: CombinedResult = null; + + // leaflet-draw + leafletDrawControl = null; + layerIdToPoylgon = new Map(); + currentDrawingLayer: MapLayer = null; + // polygonTool = null; + isInitialRender = true; + + readonly MIN_ZOOM = 0; + readonly MAX_ZOOM = 19; + readonly INITIAL_ZOOM = 6; + private map!: L.DrawMap; + private svg: + | d3.Selection + | undefined; + private g: d3.Selection | undefined; + private pathGenerator!: GeoPath; + private tooltip!: d3.Selection; + private renderedGeometries: d3.Selection; + + constructor(protected layerSettings: LayerSettingsService) { + super(); + + effect(() => { + if (!this.isInsideResults) { + return; + } + + // Display the results. + const result = this.$result(); + if (!result) { + return; + } + + // The CombinedResult will be given to the LayerSettings, if the user clicks on the button to show the + // results in the full GIS query mode. + this.previewResult = result; + + // Always update layers, because even if the current query did not result in any geometries that we can + // show on the map, we have to at least remove the old results. + const layers = []; + const resultsLayer = MapLayer.from(result); + if (resultsLayer.data.length > 0){ + layers.push(resultsLayer); + } + this.layerSettings.setLayers(layers); + }); + } + + ngOnInit() { + this.subscriptions.add(this.layerSettings.selectedBaseLayer$.subscribe((item) => { + if (!item) { + return; + } + + if (this.currentBaseLayer) { + this.map.removeLayer(this.currentBaseLayer); + } + + if (item !== 'EMPTY') { + this.currentBaseLayer = L.tileLayer(item, { + maxZoom: this.MAX_ZOOM, + attribution: + '© OpenStreetMap contributors', + }).addTo(this.map); + } + })); + + this.subscriptions.add(this.layerSettings.layers$.subscribe((layers) => { + this.layers = layers; + this.renderLayersWithD3(); + })); + + this.subscriptions.add(this.layerSettings.canRerenderLayers$.subscribe((canRerenderLayers) => { + this.canRerenderLayers = canRerenderLayers; + })); + + this.subscriptions.add(this.layerSettings.toggleLayerVisibility$.subscribe((layer) => { + this.toggleLayerVisibility(layer); + })); + + this.subscriptions.add(this.layerSettings.fitLayerToMap$.subscribe((layer) => { + if (layer) { + this.fitLayerToMap(layer); + } + })); + + + } + + fitLayerToMap(layer: MapLayer) { + const bounds = layer.getBounds(); + if (bounds.length > 0) { + const latLngBounds = L.latLngBounds(bounds); + if (latLngBounds.isValid()) { + this.map.fitBounds(latLngBounds); + } + } + } + + ngOnDestroy() { + // TODO: This breaks the navigation from results to map query view + this.subscriptions.unsubscribe(); + } + + ngAfterViewInit(): void { + const leafletMap = L.map('map').setView([52, 10], this.INITIAL_ZOOM); + this.map = leafletMap; + + // Leaflet.Draw edit toolbar + const drawnItems = new L.FeatureGroup(); + leafletMap.addLayer(drawnItems); + this.leafletDrawControl = new L.Control.Draw({ + edit: { + featureGroup: drawnItems + }, + draw: { + circle: false, + marker: false, + polyline: false, + rectangle: false, + circlemarker: false + } + }); + + leafletMap.on(L.Draw.Event.CREATED, (event) => { + if (this.currentDrawingLayer === null) { + throw new Error('We are drawing, but there is no layer set?'); + } + const polygon: L.Polygon = event.layer; + this.layerIdToPoylgon.set(this.currentDrawingLayer.uuid, polygon); + console.log('Polygon added for layer ', this.currentDrawingLayer.uuid, polygon); + leafletMap.removeControl(this.leafletDrawControl); + // this.polygonTool.disable(); + drawnItems.addLayer(polygon); + + // Send coordinates back to map-layers + const coordinates = polygon.getLatLngs() as LatLng[][]; + const positions: Position[][] = coordinates.map((ring) => + ring.map((c) => [c.lng, c.lat]) + ); + // Close the linear ring + positions[0].push(positions[0][0]); + const geoJsonPolygon: Polygon = { + type: 'Polygon', + coordinates: positions, + }; + this.layerSettings.addPolygonToLayer(this.currentDrawingLayer, geoJsonPolygon); + }); + + this.subscriptions.add(this.layerSettings.layerEnableDrawingMode$.subscribe((mapLayer) => { + if (mapLayer === null) { + return; + } + this.currentDrawingLayer = mapLayer; + leafletMap.addControl(this.leafletDrawControl); + })); + + this.subscriptions.add(this.layerSettings.layerDisableDrawingMode$.subscribe((mapLayer) => { + if (mapLayer === null) { + return; + } + + // This is important if the users disables the drawing mode. + leafletMap.removeControl(this.leafletDrawControl); + + // Remove shape from the map if it exists. + if (!this.layerIdToPoylgon.has(mapLayer.uuid)) { + return; + } + const polygon = this.layerIdToPoylgon.get(mapLayer.uuid); + drawnItems.removeLayer(polygon); + })); + + this.svg = d3.select(this.map.getPanes().overlayPane).append('svg'); + this.g = this.svg.append('g').attr('class', 'leaflet-zoom-hide'); + this.tooltip = d3 + .select('body') + .append('div') + .style('position', 'absolute') + .style('font-size', '0.75rem') + .style('font-family', 'monospace') + .style('background', 'white') + .style('border', '1px solid #ccc') + .style('padding', '5px') + .style('display', 'none') + .style('z-index', '9999'); + + function projectPoint(this: any, x: number, y: number) { + const point = leafletMap.latLngToLayerPoint(new L.LatLng(y, x)); + this.stream.point(point.x, point.y); + } + + const transform = d3Geo.geoTransform({point: projectPoint}); + this.pathGenerator = d3Geo.geoPath().projection(transform); + + this.map.on('zoomstart', () => { + if (this.g) { + this.g.style('visibility', 'hidden'); + } + }); + this.map.on('zoomend', () => { + this.updateSvgPosition(); + }); + this.map.on('moveend', () => { + if (!this.svg || !this.g) { + return; + } + const bounds = this.map.getBounds(); + const topLeft = this.map.latLngToLayerPoint(bounds.getNorthWest()); + this.svg + .style('width', '999999px') + .style('height', '999999px') + .style('left', topLeft.x + 'px') + .style('top', topLeft.y + 'px'); + this.g.attr('transform', `translate(${-topLeft.x}, ${-topLeft.y})`); + }); + + this.currentBaseLayer = L.tileLayer( + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + { + maxZoom: this.MAX_ZOOM, + attribution: + '© OpenStreetMap contributors', + }, + ).addTo(this.map); + } + + showLoadingSpinner(message: string) { + this.isLoading = true; + this.isLoadingMessage = message; + } + + updateSvgPosition() { + this.showLoadingSpinner('Reposition shapes on map'); + + setTimeout(() => { + try { + if (!this.g || !this.svg) { + return; + } + + // GeoJSON paths + this.renderedGeometries + .filter((d) => d.type === 'path') + .attr('d', (d) => this.pathGenerator(d.geometry)); + // Circles + this.renderedGeometries + .filter((d) => d.type === 'point' && d.layer!.pointShapeVisualization.selectedMode === 'Circle') + .each((d) => { + const layerPoint = this.map.latLngToLayerPoint([ + d.getPoint().coordinates[1], + d.getPoint().coordinates[0], + ]); + d.cache['x'] = layerPoint.x; + d.cache['y'] = layerPoint.y; + }) + .attr('cx', (d) => d.cache['x']) + .attr('cy', (d) => d.cache['y']); + // Manually created Paths: Squares, Triangles, Stars + const createTrianglePath = this.createTrianglePath; + const createStarPath = this.createStarPath; + const createSquarePath = this.createSquarePath; + const createCrossPath = this.createCrossPath; + + this.renderedGeometries + .filter((d) => d.type === 'point' && d.layer!.pointShapeVisualization.selectedMode !== 'Circle') + .each((d) => { + const layerPoint = this.map.latLngToLayerPoint([ + d.getPoint().coordinates[1], + d.getPoint().coordinates[0], + ]); + d.cache['x'] = layerPoint.x; + d.cache['y'] = layerPoint.y; + }) + .attr('d', (d) => { + switch (d.layer!.pointShapeVisualization.selectedMode) { + case 'Triangle': + return createTrianglePath(d.cache['x'], d.cache['y'], d.cache['size']); + case 'Star': + return createStarPath(d.cache['x'], d.cache['y'], d.cache['size']); + case 'Square': + return createSquarePath(d.cache['x'], d.cache['y'], d.cache['size']); + case 'Cross': + return createCrossPath(d.cache['x'], d.cache['y'], d.cache['size']); + default: + throw new Error(`Update SVG-Repositioning function to include point shape [${d.layer!.pointShapeVisualization.selectedMode}]`); + } + }); + + const bounds = this.map.getBounds(); + const topLeft = this.map.latLngToLayerPoint( + bounds.getNorthWest(), + ); + this.svg + .style('width', '999999px') + .style('height', '999999px') + .style('left', topLeft.x + 'px') + .style('top', topLeft.y + 'px'); + this.g.attr( + 'transform', + `translate(${-topLeft.x}, ${-topLeft.y})`, + ); + this.g.style('visibility', 'visible'); + } finally { + this.isLoading = false; + } + }, 0); + } + + renderLayersWithD3() { + console.log('Map: Render layers...', this.layers); + this.showLoadingSpinner('Rendering layers'); + + setTimeout(() => { + try { + if (!this.svg || !this.g) { + return; + } + + // Remove all previously added elements + this.g.selectAll('*').remove(); + const geometries: MapGeometryWithData[] = []; + for (const layer of this.layers.slice().reverse()) { + // Initialize all configs + layer.pointShapeVisualization.init(layer.data); + layer.areaShapeVisualization.init(layer.data); + layer.colorVisualization.init(layer.data); + geometries.push(...layer.data); + } + + console.log('Render geometries: ', geometries); + this.renderedGeometries = this.renderGeometries(geometries); + + // Set SVG position correctly + this.updateSvgPosition(); + + // Only for the first layer we add: Adjust map to points from layer. + // Otherwise: Maybe the user is already looking at the data they are interested in -> do not change + // map position, because it could be unwanted. + // Only do this for the + if (this.isInitialRender && this.layers.length === 1) { + this.fitLayerToMap(this.layers[0]); + } + + } finally { + this.isLoading = false; + this.isInitialRender = false; + } + }, 0); + } + + renderGeometries(geometries: (MapGeometryWithData)[]) { + if (!this.g) { + return; + } + + const tt = this.tooltip; + const pathGenerator = this.pathGenerator; + const map = this.map; + const createTrianglePath = this.createTrianglePath; + const createStarPath = this.createStarPath; + const createSquarePath = this.createSquarePath; + const createCrossPath = this.createCrossPath; + + return this.g + .selectAll('.geometry') + .data(geometries) + .enter() + .append(function (d) { + if (d.type === 'point' && d.layer!.pointShapeVisualization.selectedMode === 'Circle') { + return document.createElementNS('http://www.w3.org/2000/svg', 'circle'); + } + + // All other more complex shapes need to be created by using paths. + return document.createElementNS('http://www.w3.org/2000/svg', 'path'); + }) + .attr('class', 'geometry') + .attr('layer-id', (d) => d.layer!.uuid) + .attr('layer-index', (d) => d.layer!.index.toString()) + .each(function (d) { + const element = d3.select(this); + const fill = d.layer!.colorVisualization.getValueForAttribute('fill', d) as string; + + switch (d.type) { + case 'point': + const layerPoint = map.latLngToLayerPoint([ + d.getPoint().coordinates[1], + d.getPoint().coordinates[0], + ]); + const x = layerPoint.x; + const y = layerPoint.y; + const size = d.layer!.pointShapeVisualization.getValueForAttribute('r', d) as number; + d.cache['x'] = x; + d.cache['y'] = y; + d.cache['size'] = size; + + switch (d.layer!.pointShapeVisualization.selectedMode) { + case 'Circle': + element + .attr('cx', x) + .attr('cy', y) + .attr('r', size) + .attr('fill', fill); + break; + case 'Square': + element + .attr('d', (d) => createSquarePath(x, y, size)) + .attr('fill', fill); + break; + case 'Triangle': + element + .attr('d', () => createTrianglePath(x, y, size)) + .attr('fill', fill); + break; + case 'Star': + element + .attr('d', (d) => createStarPath(x, y, size)) + .attr('fill', fill); + break; + case 'Cross': + element + .attr('d', (d) => createCrossPath(x, y, size)) + .attr('stroke', fill); + break; + default: + throw new Error(`Update render function to include shape [${d.type}]`); + } + break; + case 'path': + element + .attr('d', pathGenerator(d.geometry)) + .attr('stroke-width', d.layer!.areaShapeVisualization.getValueForAttribute('stroke-width', d)) + .attr('stroke', d.layer!.colorVisualization.getValueForAttribute('stroke', d)) + .attr('fill', fill) + .attr('fill-opacity', d.layer!.colorVisualization.getValueForAttribute('fill-opacity', d)); + break; + default: + throw new Error(`Update render function to include shape [${d.type}]`); + } + }) + .style('pointer-events', 'auto') + .style('cursor', 'pointer') + .on('mouseover', function (event, d) { + tt.style('display', 'block').html(JSON.stringify(d.data, null, 2)); + }) + .on('mousemove', function (event) { + tt.style('top', event.pageY + 10 + 'px').style( + 'left', + event.pageX + 10 + 'px', + ); + }) + .on('mouseout', function () { + tt.style('display', 'none'); + }); + } + + createTrianglePath(x, y, size) { + const side = size * 3; + const height = (Math.sqrt(3) / 2) * side; + return `M ${x} ${y - height / 2} + L ${x - side / 2} ${y + height / 2} + L ${x + side / 2} ${y + height / 2} Z`; + } + + createStarPath(x, y, size) { + const outerRadius = size * 2; + const innerRadius = size; + const spikes = 5; + + let path = ''; + const step = Math.PI / spikes; + for (let i = 0; i < 2 * spikes; i++) { + const radius = i % 2 === 0 ? outerRadius : innerRadius; + const xStar = x + Math.cos(i * step) * radius; + const yStar = y - Math.sin(i * step) * radius; + path += i === 0 ? `M ${xStar} ${yStar}` : `L ${xStar} ${yStar}`; + } + return `${path} Z`; + } + + createSquarePath(x, y, size) { + // No idea why this is necessary, but on the second render the size is suddenly no longer a number + // and gets concatenated instead of added, which leads to the square looking wrong on the map. + size = Number(size); + return `M ${x - size} ${y - size} + L ${x + size} ${y - size} + L ${x + size} ${y + size} + L ${x - size} ${y + size} Z`; + } + + createCrossPath(x, y, size) { + const half = Number(size); + return `M ${x - half} ${y - half} L ${x + half} ${y + half} + M ${x - half} ${y + half} L ${x + half} ${y - half}`; + } + + toggleLayerVisibility(layer: MapLayer) { + if (!this.g?.node()) { + throw new Error('SVG g does not exist.'); + } + + const layerElements = this.g + .node()! + .querySelectorAll( + `[layer-id='${layer.uuid}']`, + ); + + if (!layerElements.length) { + // Nothing to do + return; + } + + layerElements.forEach((elem) => { + if (layer.isActive) { + elem.classList.remove('map-layer-hidden'); + } else { + elem.classList.add('map-layer-hidden'); + } + }); + } + + navigateToMapQueryMode() { + // Give CombinedResult to map-layers component, so that the full query can be run and added from there. + this.layerSettings.setResultsQuery(this.previewResult); + this._router.navigate(['/views/querying/gis']); + } +} + diff --git a/src/app/components/data-view/data-map/leaflet.css b/src/app/components/data-view/data-map/leaflet.css new file mode 100644 index 00000000..cbe009ce --- /dev/null +++ b/src/app/components/data-view/data-map/leaflet.css @@ -0,0 +1,775 @@ +/* required styles */ + +.leaflet-pane, +.leaflet-tile, +.leaflet-marker-icon, +.leaflet-marker-shadow, +.leaflet-tile-container, +.leaflet-pane > svg, +.leaflet-pane > canvas, +.leaflet-zoom-box, +.leaflet-image-layer, +.leaflet-layer { + position: absolute; + left: 0; + top: 0; +} + +.leaflet-container { + overflow: hidden; +} + +.leaflet-tile, +.leaflet-marker-icon, +.leaflet-marker-shadow { + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + -webkit-user-drag: none; +} + +/* Prevents IE11 from highlighting tiles in blue */ +.leaflet-tile::selection { + background: transparent; +} + +/* Safari renders non-retina tile on retina better with this, but Chrome is worse */ +.leaflet-safari .leaflet-tile { + image-rendering: -webkit-optimize-contrast; +} + +/* hack that prevents hw layers "stretching" when loading new tiles */ +.leaflet-safari .leaflet-tile-container { + width: 1600px; + height: 1600px; + -webkit-transform-origin: 0 0; +} + +.leaflet-marker-icon, +.leaflet-marker-shadow { + display: block; +} + +/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */ +/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */ +.leaflet-container .leaflet-overlay-pane svg { + max-width: none !important; + max-height: none !important; +} + +.leaflet-container .leaflet-marker-pane img, +.leaflet-container .leaflet-shadow-pane img, +.leaflet-container .leaflet-tile-pane img, +.leaflet-container img.leaflet-image-layer, +.leaflet-container .leaflet-tile { + max-width: none !important; + max-height: none !important; + width: auto; + padding: 0; +} + +.leaflet-container img.leaflet-tile { + /* See: https://bugs.chromium.org/p/chromium/issues/detail?id=600120 */ + mix-blend-mode: plus-lighter; +} + +.leaflet-container.leaflet-touch-zoom { + -ms-touch-action: pan-x pan-y; + touch-action: pan-x pan-y; +} + +.leaflet-container.leaflet-touch-drag { + -ms-touch-action: pinch-zoom; + /* Fallback for FF which doesn't support pinch-zoom */ + touch-action: none; + touch-action: pinch-zoom; +} + +.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom { + -ms-touch-action: none; + touch-action: none; +} + +.leaflet-container { + -webkit-tap-highlight-color: transparent; +} + +.leaflet-container a { + -webkit-tap-highlight-color: rgba(51, 181, 229, 0.4); +} + +.leaflet-tile { + filter: inherit; + visibility: hidden; +} + +.leaflet-tile-loaded { + visibility: inherit; +} + +.leaflet-zoom-box { + width: 0; + height: 0; + -moz-box-sizing: border-box; + box-sizing: border-box; + z-index: 800; +} + +/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */ +.leaflet-overlay-pane svg { + -moz-user-select: none; +} + +.leaflet-pane { + z-index: 400; +} + +.leaflet-tile-pane { + z-index: 200; +} + +.leaflet-overlay-pane { + z-index: 400; +} + +.leaflet-shadow-pane { + z-index: 500; +} + +.leaflet-marker-pane { + z-index: 600; +} + +.leaflet-tooltip-pane { + z-index: 650; +} + +.leaflet-popup-pane { + z-index: 700; +} + +.leaflet-map-pane canvas { + z-index: 100; +} + +.leaflet-map-pane svg { + z-index: 200; +} + +.leaflet-vml-shape { + width: 1px; + height: 1px; +} + +.lvml { + behavior: url(#default#VML); + display: inline-block; + position: absolute; +} + + +/* control positioning */ + +.leaflet-control { + position: relative; + z-index: 800; + pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ + pointer-events: auto; +} + +.leaflet-top, +.leaflet-bottom { + position: absolute; + z-index: 1000; + pointer-events: none; +} + +.leaflet-top { + top: 0; +} + +.leaflet-right { + right: 0; +} + +.leaflet-bottom { + bottom: 0; +} + +.leaflet-left { + left: 0; +} + +.leaflet-control { + float: left; + clear: both; +} + +.leaflet-right .leaflet-control { + float: right; +} + +.leaflet-top .leaflet-control { + margin-top: 10px; +} + +.leaflet-bottom .leaflet-control { + margin-bottom: 10px; +} + +.leaflet-left .leaflet-control { + margin-left: 10px; +} + +.leaflet-right .leaflet-control { + margin-right: 10px; +} + + +/* zoom and fade animations */ + +.leaflet-fade-anim .leaflet-popup { + opacity: 0; + -webkit-transition: opacity 0.2s linear; + -moz-transition: opacity 0.2s linear; + transition: opacity 0.2s linear; +} + +.leaflet-fade-anim .leaflet-map-pane .leaflet-popup { + opacity: 1; +} + +.leaflet-zoom-animated { + -webkit-transform-origin: 0 0; + -ms-transform-origin: 0 0; + transform-origin: 0 0; +} + +svg.leaflet-zoom-animated { + will-change: transform; +} + +.leaflet-zoom-anim .leaflet-zoom-animated { + -webkit-transition: -webkit-transform 0.25s cubic-bezier(0, 0, 0.25, 1); + -moz-transition: -moz-transform 0.25s cubic-bezier(0, 0, 0.25, 1); + transition: transform 0.25s cubic-bezier(0, 0, 0.25, 1); +} + +.leaflet-zoom-anim .leaflet-tile, +.leaflet-pan-anim .leaflet-tile { + -webkit-transition: none; + -moz-transition: none; + transition: none; +} + +.leaflet-zoom-anim .leaflet-zoom-hide { + visibility: hidden; +} + + +/* cursors */ + +.leaflet-interactive { + cursor: pointer; +} + +.leaflet-grab { + cursor: -webkit-grab; + cursor: -moz-grab; + cursor: grab; +} + +.leaflet-crosshair, +.leaflet-crosshair .leaflet-interactive { + cursor: crosshair; +} + +.leaflet-popup-pane, +.leaflet-control { + cursor: auto; +} + +.leaflet-dragging .leaflet-grab, +.leaflet-dragging .leaflet-grab .leaflet-interactive, +.leaflet-dragging .leaflet-marker-draggable { + cursor: move; + cursor: -webkit-grabbing; + cursor: -moz-grabbing; + cursor: grabbing; +} + +/* marker & overlays interactivity */ +.leaflet-marker-icon, +.leaflet-marker-shadow, +.leaflet-image-layer, +.leaflet-pane > svg path, +.leaflet-tile-container { + pointer-events: none; +} + +.leaflet-marker-icon.leaflet-interactive, +.leaflet-image-layer.leaflet-interactive, +.leaflet-pane > svg path.leaflet-interactive, +svg.leaflet-image-layer.leaflet-interactive path { + pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ + pointer-events: auto; +} + +/* visual tweaks */ + +.leaflet-container { + background: #ddd; + outline-offset: 1px; +} + +.leaflet-container a { + color: #0078A8; +} + +.leaflet-zoom-box { + border: 2px dotted #38f; + background: rgba(255, 255, 255, 0.5); +} + + +/* general typography */ +.leaflet-container { + font-family: "Helvetica Neue", Arial, Helvetica, sans-serif; + font-size: 12px; + font-size: 0.75rem; + line-height: 1.5; +} + + +/* general toolbar styles */ + +.leaflet-bar { + box-shadow: 0 1px 5px rgba(0, 0, 0, 0.65); + border-radius: 4px; +} + +.leaflet-bar a { + background-color: #fff; + border-bottom: 1px solid #ccc; + width: 26px; + height: 26px; + line-height: 26px; + display: block; + text-align: center; + text-decoration: none; + color: black; +} + +.leaflet-bar a, +.leaflet-control-layers-toggle { + background-position: 50% 50%; + background-repeat: no-repeat; + display: block; +} + +.leaflet-bar a:hover, +.leaflet-bar a:focus { + background-color: #f4f4f4; +} + +.leaflet-bar a:first-child { + border-top-left-radius: 4px; + border-top-right-radius: 4px; +} + +.leaflet-bar a:last-child { + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-bottom: none; +} + +.leaflet-bar a.leaflet-disabled { + cursor: default; + background-color: #f4f4f4; + color: #bbb; +} + +.leaflet-touch .leaflet-bar a { + width: 30px; + height: 30px; + line-height: 30px; +} + +.leaflet-touch .leaflet-bar a:first-child { + border-top-left-radius: 2px; + border-top-right-radius: 2px; +} + +.leaflet-touch .leaflet-bar a:last-child { + border-bottom-left-radius: 2px; + border-bottom-right-radius: 2px; +} + +/* zoom control */ + +.leaflet-control-zoom-in, +.leaflet-control-zoom-out { + font: bold 18px 'Lucida Console', Monaco, monospace; + text-indent: 1px; +} + +.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out { + font-size: 22px; +} + + +/* layers control */ + +.leaflet-control-layers { + box-shadow: 0 1px 5px rgba(0, 0, 0, 0.4); + background: #fff; + border-radius: 5px; +} + +.leaflet-control-layers-toggle { + /*Note: commented out, because we don't need them, and I don't want to change the WabPack config*/ + /*background-image: url(images/layers.png);*/ + width: 36px; + height: 36px; +} + +.leaflet-retina .leaflet-control-layers-toggle { + /*Note: commented out, because we don't need them, and I don't want to change the WabPack config*/ + /*background-image: url(images/layers-2x.png);*/ + background-size: 26px 26px; +} + +.leaflet-touch .leaflet-control-layers-toggle { + width: 44px; + height: 44px; +} + +.leaflet-control-layers .leaflet-control-layers-list, +.leaflet-control-layers-expanded .leaflet-control-layers-toggle { + display: none; +} + +.leaflet-control-layers-expanded .leaflet-control-layers-list { + display: block; + position: relative; +} + +.leaflet-control-layers-expanded { + padding: 6px 10px 6px 6px; + color: #333; + background: #fff; +} + +.leaflet-control-layers-scrollbar { + overflow-y: scroll; + overflow-x: hidden; + padding-right: 5px; +} + +.leaflet-control-layers-selector { + margin-top: 2px; + position: relative; + top: 1px; +} + +.leaflet-control-layers label { + display: block; + font-size: 13px; + font-size: 1.08333em; +} + +.leaflet-control-layers-separator { + height: 0; + border-top: 1px solid #ddd; + margin: 5px -10px 5px -6px; +} + +/* Default icon URLs */ +.leaflet-default-icon-path { /* used only in path-guessing heuristic, see L.Icon.Default */ + /*Note: commented out, because we don't need them, and I don't want to change the WabPack config*/ + /*background-image: url(images/marker-icon.png);*/ +} + + +/* attribution and scale controls */ + +.leaflet-container .leaflet-control-attribution { + background: #fff; + background: rgba(255, 255, 255, 0.8); + margin: 0; +} + +.leaflet-control-attribution, +.leaflet-control-scale-line { + padding: 0 5px; + color: #333; + line-height: 1.4; +} + +.leaflet-control-attribution a { + text-decoration: none; +} + +.leaflet-control-attribution a:hover, +.leaflet-control-attribution a:focus { + text-decoration: underline; +} + +.leaflet-attribution-flag { + display: inline !important; + vertical-align: baseline !important; + width: 1em; + height: 0.6669em; +} + +.leaflet-left .leaflet-control-scale { + margin-left: 5px; +} + +.leaflet-bottom .leaflet-control-scale { + margin-bottom: 5px; +} + +.leaflet-control-scale-line { + border: 2px solid #777; + border-top: none; + line-height: 1.1; + padding: 2px 5px 1px; + white-space: nowrap; + -moz-box-sizing: border-box; + box-sizing: border-box; + background: rgba(255, 255, 255, 0.8); + text-shadow: 1px 1px #fff; +} + +.leaflet-control-scale-line:not(:first-child) { + border-top: 2px solid #777; + border-bottom: none; + margin-top: -2px; +} + +.leaflet-control-scale-line:not(:first-child):not(:last-child) { + border-bottom: 2px solid #777; +} + +.leaflet-touch .leaflet-control-attribution, +.leaflet-touch .leaflet-control-layers, +.leaflet-touch .leaflet-bar { + box-shadow: none; +} + +.leaflet-touch .leaflet-control-layers, +.leaflet-touch .leaflet-bar { + border: 2px solid rgba(0, 0, 0, 0.2); + background-clip: padding-box; +} + + +/* popup */ + +.leaflet-popup { + position: absolute; + text-align: center; + margin-bottom: 20px; +} + +.leaflet-popup-content-wrapper { + padding: 1px; + text-align: left; + border-radius: 12px; +} + +.leaflet-popup-content { + margin: 13px 24px 13px 20px; + line-height: 1.3; + font-size: 13px; + font-size: 1.08333em; + min-height: 1px; +} + +.leaflet-popup-content p { + margin: 17px 0; + margin: 1.3em 0; +} + +.leaflet-popup-tip-container { + width: 40px; + height: 20px; + position: absolute; + left: 50%; + margin-top: -1px; + margin-left: -20px; + overflow: hidden; + pointer-events: none; +} + +.leaflet-popup-tip { + width: 17px; + height: 17px; + padding: 1px; + + margin: -10px auto 0; + pointer-events: auto; + + -webkit-transform: rotate(45deg); + -moz-transform: rotate(45deg); + -ms-transform: rotate(45deg); + transform: rotate(45deg); +} + +.leaflet-popup-content-wrapper, +.leaflet-popup-tip { + background: white; + color: #333; + box-shadow: 0 3px 14px rgba(0, 0, 0, 0.4); +} + +.leaflet-container a.leaflet-popup-close-button { + position: absolute; + top: 0; + right: 0; + border: none; + text-align: center; + width: 24px; + height: 24px; + font: 16px/24px Tahoma, Verdana, sans-serif; + color: #757575; + text-decoration: none; + background: transparent; +} + +.leaflet-container a.leaflet-popup-close-button:hover, +.leaflet-container a.leaflet-popup-close-button:focus { + color: #585858; +} + +.leaflet-popup-scrolled { + overflow: auto; +} + +.leaflet-oldie .leaflet-popup-content-wrapper { + -ms-zoom: 1; +} + +.leaflet-oldie .leaflet-popup-tip { + width: 24px; + margin: 0 auto; + + -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)"; + filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678); +} + +.leaflet-oldie .leaflet-control-zoom, +.leaflet-oldie .leaflet-control-layers, +.leaflet-oldie .leaflet-popup-content-wrapper, +.leaflet-oldie .leaflet-popup-tip { + border: 1px solid #999; +} + + +/* div icon */ + +.leaflet-div-icon { + background: #fff; + border: 1px solid #666; +} + + +/* Tooltip */ +/* Base styles for the element that has a tooltip */ +.leaflet-tooltip { + position: absolute; + padding: 6px; + background-color: #fff; + border: 1px solid #fff; + border-radius: 3px; + color: #222; + white-space: nowrap; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + pointer-events: none; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4); +} + +.leaflet-tooltip.leaflet-interactive { + cursor: pointer; + pointer-events: auto; +} + +.leaflet-tooltip-top:before, +.leaflet-tooltip-bottom:before, +.leaflet-tooltip-left:before, +.leaflet-tooltip-right:before { + position: absolute; + pointer-events: none; + border: 6px solid transparent; + background: transparent; + content: ""; +} + +/* Directions */ + +.leaflet-tooltip-bottom { + margin-top: 6px; +} + +.leaflet-tooltip-top { + margin-top: -6px; +} + +.leaflet-tooltip-bottom:before, +.leaflet-tooltip-top:before { + left: 50%; + margin-left: -6px; +} + +.leaflet-tooltip-top:before { + bottom: 0; + margin-bottom: -12px; + border-top-color: #fff; +} + +.leaflet-tooltip-bottom:before { + top: 0; + margin-top: -12px; + margin-left: -6px; + border-bottom-color: #fff; +} + +.leaflet-tooltip-left { + margin-left: -6px; +} + +.leaflet-tooltip-right { + margin-left: 6px; +} + +.leaflet-tooltip-left:before, +.leaflet-tooltip-right:before { + top: 50%; + margin-top: -6px; +} + +.leaflet-tooltip-left:before { + right: 0; + margin-right: -12px; + border-left-color: #fff; +} + +.leaflet-tooltip-right:before { + left: 0; + margin-left: -12px; + border-right-color: #fff; +} + +/* Printing */ + +@media print { + /* Prevent printers from removing background-images of controls. */ + .leaflet-control { + -webkit-print-color-adjust: exact; + print-color-adjust: exact; + } +} diff --git a/src/app/components/data-view/data-view.component.html b/src/app/components/data-view/data-view.component.html index 2adca469..ba6179e8 100644 --- a/src/app/components/data-view/data-view.component.html +++ b/src/app/components/data-view/data-view.component.html @@ -17,6 +17,11 @@ (click)="$presentationType.set(presentationTypes.GRAPH)" [class.active]="$presentationType() === presentationTypes.GRAPH" tooltip="graph" placement="top" delay="200"> +
+ +
+ +
+ +
+ \ No newline at end of file diff --git a/src/app/views/querying/console/components/code-editor/query-editor.component.scss b/src/app/views/querying/console/components/code-editor/query-editor.component.scss new file mode 100644 index 00000000..1a17f128 --- /dev/null +++ b/src/app/views/querying/console/components/code-editor/query-editor.component.scss @@ -0,0 +1,32 @@ +.document-db-info { + text-align: right; +} + +.advanced-console-db-info { + display: inline-block; + + option { + font-size: 10px; + font-style: italic; + } +} + +.grey-bg { + background-color: #f0f3f5; + border: none; + border-bottom: 1px white solid; +} + +.grey-border { + border: 1px rgba(0, 0, 0, .125) solid; +} + +.advanced-console { + height: 185.617px; + margin: 1px; +} + +.simple-console { + height: 240px; + margin-bottom: 0; +} \ No newline at end of file diff --git a/src/app/views/querying/console/components/code-editor/query-editor.component.ts b/src/app/views/querying/console/components/code-editor/query-editor.component.ts new file mode 100644 index 00000000..c6960f4e --- /dev/null +++ b/src/app/views/querying/console/components/code-editor/query-editor.component.ts @@ -0,0 +1,134 @@ +import { + Component, + effect, + EventEmitter, + inject, + Input, + OnInit, + Output, + signal, untracked, + ViewChild, + WritableSignal +} from '@angular/core'; +import {ToasterService} from "../../../../../components/toast-exposer/toaster.service"; +import {NamespaceModel} from "../../../../../models/catalog.model"; +import {QueryHistory} from "../../query-history.model"; +import {CatalogService} from "../../../../../services/catalog.service"; +import {usesAdvancedConsole} from "../console-helper"; +import {EditorComponent} from "../../../../../components/editor/editor.component"; + + +@Component({ + selector: 'app-query-editor', + templateUrl: './query-editor.component.html', + styleUrls: ['query-editor.component.scss'] +}) +export class QueryEditor implements OnInit { + public readonly _toast = inject(ToasterService); + public readonly _catalog = inject(CatalogService); + + readonly namespaces: WritableSignal = signal([]); + private readonly LOCAL_STORAGE_NAMESPACE_KEY = 'polypheny-namespace'; + showNamespaceConfig: boolean; + + @Input() language: WritableSignal = signal('sql'); + @Input() activeNamespace: WritableSignal = signal(""); + @ViewChild('editor', {static: false}) codeEditor: EditorComponent; + + constructor() { + effect(() => { + const namespace = this._catalog.namespaces(); + untracked(() => { + this.namespaces.set(Array.from(namespace.values())); + }); + }); + } + + ngOnInit() { + this.loadAndSetNamespaceDB(); + } + + setCode(code: string): void { + this.codeEditor.setCode(code); + } + + getCode(): string { + return this.codeEditor.getCode(); + } + + private loadAndSetNamespaceDB() { + let namespaceName = localStorage.getItem(this.LOCAL_STORAGE_NAMESPACE_KEY); + + if (namespaceName === null || (this.namespaces && this.namespaces.length > 0 && (this.namespaces().filter(n => n.name === namespaceName).length === 0))) { + if (this.namespaces() && this.namespaces().length > 0) { + namespaceName = this.namespaces()[0].name; + } else { + namespaceName = 'public'; + } + } + if (!namespaceName) { + return; + } + + this.activeNamespace.set(namespaceName); + this.storeNamespace(namespaceName); + } + + storeNamespace(name: string) { + localStorage.setItem(this.LOCAL_STORAGE_NAMESPACE_KEY, name); + } + + clearConsole() { + this.codeEditor.setCode(''); + } + + parse(code: string) { + const formatted = JSON.stringify(JSON.parse('[' + code + ']'), null, 4); + return formatted.substring(1, formatted.length - 1); + } + + formatQuery() { + let code = this.codeEditor.getCode(); + if (!code) { + return; + } + let before = ''; + const after = ')'; + + // here we replace the Json incompatible types with placeholders + const temp = code.match(/NumberDecimal\([^)]*\)/g); + + if (temp !== null) { + for (let i = 0; i < temp.length; i++) { + code = code.replace(temp[i], '"___' + i + '"'); + } + } + + const splits = code.split('('); + before = splits.shift() + '('; + + try { + let json = this.parse(splits.join('(').slice(0, -1)); + // we have to translate them back + if (temp !== null) { + for (let i = 0; i < temp.length; i++) { + json = json.replace('"___' + i + '"', temp[i]); + } + } + + this.codeEditor.setCode(before + json + after); + } catch (e) { + this._toast.warn(e); + } + } + + toggleNamespaceField() { + this.showNamespaceConfig = !this.showNamespaceConfig; + } + + changedDefaultDB(n) { + this.activeNamespace.set(n); + } + + protected readonly usesAdvancedConsole = usesAdvancedConsole; +} diff --git a/src/app/views/querying/console/components/console-helper.ts b/src/app/views/querying/console/components/console-helper.ts new file mode 100644 index 00000000..70a377a1 --- /dev/null +++ b/src/app/views/querying/console/components/console-helper.ts @@ -0,0 +1,7 @@ +export function usesAdvancedConsole(lang: string) { + return lang === 'mql' || lang === 'cypher'; +} + +export const QUERY_LANGUAGES = Array.from( + ['SQL', 'MQL', 'CYPHER', 'CQL', 'PIG'] +); diff --git a/src/app/views/querying/console/components/submit-query-button/submit-query-button.component.html b/src/app/views/querying/console/components/submit-query-button/submit-query-button.component.html new file mode 100644 index 00000000..df643749 --- /dev/null +++ b/src/app/views/querying/console/components/submit-query-button/submit-query-button.component.html @@ -0,0 +1,9 @@ + + + + + diff --git a/src/app/views/querying/console/components/submit-query-button/submit-query-button.component.scss b/src/app/views/querying/console/components/submit-query-button/submit-query-button.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/views/querying/console/components/submit-query-button/submit-query-button.component.ts b/src/app/views/querying/console/components/submit-query-button/submit-query-button.component.ts new file mode 100644 index 00000000..3e10f923 --- /dev/null +++ b/src/app/views/querying/console/components/submit-query-button/submit-query-button.component.ts @@ -0,0 +1,26 @@ +import {Component, EventEmitter, Input, Output} from '@angular/core'; +import {QUERY_LANGUAGES} from '../console-helper'; + + +@Component({ + selector: 'app-submit-execute-button', + templateUrl: './submit-query-button.component.html', + styleUrls: ['submit-query-button.component.scss'] +}) +export class SubmitQueryButtonComponent { + @Input() loading = false; + @Input() language = 'sql'; + + @Output() languageChange = new EventEmitter(); + @Output() submitQuery = new EventEmitter(); + + protected readonly QUERY_LANGUAGES = QUERY_LANGUAGES; + + onLanguageChange(newLanguage: string): void { + this.languageChange.emit(newLanguage); + } + + onExecute(): void { + this.submitQuery.emit(); + } +} diff --git a/src/app/views/querying/console/console.component.ts b/src/app/views/querying/console/console.component.ts index 5825c774..3ef7965a 100644 --- a/src/app/views/querying/console/console.component.ts +++ b/src/app/views/querying/console/console.component.ts @@ -25,13 +25,11 @@ import {DataResultItem, InfoResultItem, SeparatorResultItem} from './result-item styleUrls: ['./console.component.scss'] }) export class ConsoleComponent implements OnInit, OnDestroy { - private readonly _crud = inject(CrudService); private readonly _leftSidebar = inject(LeftSidebarService); private readonly _breadcrumb = inject(BreadcrumbService); private readonly _settings = inject(WebuiSettingsService); public readonly _util = inject(UtilService); - public readonly _toast = inject(ToasterService); public readonly _catalog = inject(CatalogService); private readonly _sidebar = inject(LeftSidebarService); @@ -123,7 +121,6 @@ export class ConsoleComponent implements OnInit, OnDestroy { window.onkeydown = null; } - submitQuery() { this.delayedNamespace = null; const code = this.codeEditor.getCode(); diff --git a/src/app/views/querying/gis/components/config-section/config-section.component.html b/src/app/views/querying/gis/components/config-section/config-section.component.html new file mode 100644 index 00000000..bc9476e4 --- /dev/null +++ b/src/app/views/querying/gis/components/config-section/config-section.component.html @@ -0,0 +1,21 @@ +
+ +
+ + +
+
diff --git a/src/app/views/querying/gis/components/config-section/config-section.component.scss b/src/app/views/querying/gis/components/config-section/config-section.component.scss new file mode 100644 index 00000000..aa0c1ebd --- /dev/null +++ b/src/app/views/querying/gis/components/config-section/config-section.component.scss @@ -0,0 +1,31 @@ +.section-header { + all: unset; + box-sizing: border-box; + width: 100%; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid #D3D3D3; + padding: 0.25rem 0; + transition: all 100ms ease-in-out; + + &:hover { + color: #5856D5; + + & > svg { + fill: #5856D5; + } + } + + & > svg { + width: 20px; + height: 20px; + fill: #BBBBBB; + } +} + +.section-body { + flex: 1; + margin: 1rem 0; +} diff --git a/src/app/views/querying/gis/components/config-section/config-section.component.ts b/src/app/views/querying/gis/components/config-section/config-section.component.ts new file mode 100644 index 00000000..7bc364c8 --- /dev/null +++ b/src/app/views/querying/gis/components/config-section/config-section.component.ts @@ -0,0 +1,39 @@ +import {Component, Injector, Input, OnInit} from '@angular/core'; +import { Visualization } from '../../models/visualization.interface'; +import {MapLayer} from '../../models/MapLayer.model'; +import {MapLayerConfiguration} from '../../models/MapLayerConfiguration.interface'; + +@Component({ + selector: 'app-config-section', + templateUrl: './config-section.component.html', + styleUrl: './config-section.component.scss', +}) +export class ConfigSectionComponent implements OnInit { + + constructor() { + } + @Input() config?: MapLayerConfiguration; + @Input() layer?: MapLayer; + @Input() title = ''; + + injector?: Injector; + + isSectionBodyVisible = false; + + ngOnInit(): void { + this.injector = Injector.create({ + providers: [{ provide: 'config', useValue: this.config }], + }); + } + + // Needed? + updateConfigInjector(): void { + this.injector = Injector.create({ + providers: [{ provide: 'config', useValue: this.config }], + }); + } + + toggleSectionBodyVisibility(): void { + this.isSectionBodyVisible = !this.isSectionBodyVisible; + } +} diff --git a/src/app/views/querying/gis/components/configuration/DataPreview.ts b/src/app/views/querying/gis/components/configuration/DataPreview.ts new file mode 100644 index 00000000..7b1baef4 --- /dev/null +++ b/src/app/views/querying/gis/components/configuration/DataPreview.ts @@ -0,0 +1,17 @@ +import {MapLayerConfiguration} from '../../models/MapLayerConfiguration.interface'; +import {DataPreviewComponent} from './data-preview/data-preview.component'; +import {MapLayer} from '../../models/MapLayer.model'; + +export class DataPreview implements MapLayerConfiguration { + configurationComponentType = DataPreviewComponent; + layer: MapLayer; + + constructor(layer: MapLayer) { + this.layer = layer; + } + + copy(): MapLayerConfiguration { + throw new Error('Layer should not need to be copied, there is no configuration that can change.'); + } + +} diff --git a/src/app/views/querying/gis/components/configuration/FilterConfig.ts b/src/app/views/querying/gis/components/configuration/FilterConfig.ts new file mode 100644 index 00000000..1b4ee6c9 --- /dev/null +++ b/src/app/views/querying/gis/components/configuration/FilterConfig.ts @@ -0,0 +1,19 @@ +import {MapLayerConfiguration} from '../../models/MapLayerConfiguration.interface'; +import {Polygon} from 'geojson'; +import {FilterComponent} from './filter/filter.component'; +import {MapLayer} from '../../models/MapLayer.model'; + +export class FilterConfig implements MapLayerConfiguration { + configurationComponentType = FilterComponent; + layer: MapLayer; + isDrawingModeActive = false; + polygon: Polygon; + + constructor(layer: MapLayer) { + this.layer = layer; + } + + copy(): MapLayerConfiguration { + return new FilterConfig(this.layer); + } +} diff --git a/src/app/views/querying/gis/components/configuration/data-preview/data-preview.component.css b/src/app/views/querying/gis/components/configuration/data-preview/data-preview.component.css new file mode 100644 index 00000000..e07ef394 --- /dev/null +++ b/src/app/views/querying/gis/components/configuration/data-preview/data-preview.component.css @@ -0,0 +1,12 @@ +div { + display: flex; + flex-direction: column; + gap: 1rem; +} + +ngx-json-viewer { + background: #f9f9f9; + max-height: 500px; + overflow: auto; +} + diff --git a/src/app/views/querying/gis/components/configuration/data-preview/data-preview.component.html b/src/app/views/querying/gis/components/configuration/data-preview/data-preview.component.html new file mode 100644 index 00000000..c91320f2 --- /dev/null +++ b/src/app/views/querying/gis/components/configuration/data-preview/data-preview.component.html @@ -0,0 +1,4 @@ +
+ +
diff --git a/src/app/views/querying/gis/components/configuration/data-preview/data-preview.component.ts b/src/app/views/querying/gis/components/configuration/data-preview/data-preview.component.ts new file mode 100644 index 00000000..554c33f5 --- /dev/null +++ b/src/app/views/querying/gis/components/configuration/data-preview/data-preview.component.ts @@ -0,0 +1,25 @@ +import { Component, Inject } from '@angular/core'; +import { MapLayerConfigurationComponent } from '../../../models/visualization-configuration.interface'; +import { LayerSettingsService } from '../../../services/layersettings.service'; +import {DataPreview} from '../DataPreview'; +import {MapLayer} from '../../../models/MapLayer.model'; + +@Component({ + selector: 'app-data-preview', + templateUrl: './data-preview.component.html', + styleUrl: './data-preview.component.css', +}) +export class DataPreviewComponent implements MapLayerConfigurationComponent { + layer: MapLayer; + previewObject: Object; + + constructor( + @Inject('config') protected config: DataPreview, + private layerSettings: LayerSettingsService, + ) { + this.layer = config.layer; + this.previewObject = config.layer.data[0].data; + } + + protected readonly Object = Object; +} diff --git a/src/app/views/querying/gis/components/configuration/filter/filter.component.css b/src/app/views/querying/gis/components/configuration/filter/filter.component.css new file mode 100644 index 00000000..af3d568f --- /dev/null +++ b/src/app/views/querying/gis/components/configuration/filter/filter.component.css @@ -0,0 +1,21 @@ +.flex { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.spatial-filter-section { + display: flex; + flex-direction: column; + gap: 0.25rem; + + & > button { + /* So that the button width does not change when the labels are changed. */ + width: 210px; + } +} + +.info { + display: block; + color: #a5a5a5; +} \ No newline at end of file diff --git a/src/app/views/querying/gis/components/configuration/filter/filter.component.html b/src/app/views/querying/gis/components/configuration/filter/filter.component.html new file mode 100644 index 00000000..35e90711 --- /dev/null +++ b/src/app/views/querying/gis/components/configuration/filter/filter.component.html @@ -0,0 +1,22 @@ +
+
+ + + Info: Once the drawing mode is enabled, + you can use the Polygon Tool on the Map to draw a polygon. + When done, the filter is automatically applied to the query.. + +
+ +
+ +
+ +
diff --git a/src/app/views/querying/gis/components/configuration/filter/filter.component.ts b/src/app/views/querying/gis/components/configuration/filter/filter.component.ts new file mode 100644 index 00000000..aa073aa8 --- /dev/null +++ b/src/app/views/querying/gis/components/configuration/filter/filter.component.ts @@ -0,0 +1,32 @@ +import {Component, Inject} from '@angular/core'; +import {MapLayerConfigurationComponent} from '../../../models/visualization-configuration.interface'; +import {LayerSettingsService} from '../../../services/layersettings.service'; +import {DataPreview} from '../DataPreview'; +import {MapLayer} from '../../../models/MapLayer.model'; +import {FilterConfig} from '../FilterConfig'; + +@Component({ + selector: 'app-data-preview', + templateUrl: './filter.component.html', + styleUrl: './filter.component.css', +}) +export class FilterComponent implements MapLayerConfigurationComponent { + + constructor( + @Inject('config') protected config: FilterConfig, + private layerSettings: LayerSettingsService, + ) { + } + + enableDrawingMode() { + this.layerSettings.enableDrawingModeForLayer(this.config.layer); + } + + disableDrawingMode() { + this.layerSettings.disableDrawingModeForLayer(this.config.layer); + } + + editQuery() { + this.layerSettings.editQuery(this.config.layer); + } +} diff --git a/src/app/views/querying/gis/components/layers/map-layers.component.html b/src/app/views/querying/gis/components/layers/map-layers.component.html new file mode 100644 index 00000000..8457709e --- /dev/null +++ b/src/app/views/querying/gis/components/layers/map-layers.component.html @@ -0,0 +1,206 @@ +
+
+ + + +
+
+ + + {{ layer.index }} +
+ +
+
+ + + + +
+
+ +
+
+ {{ layer.query === null ? 'FILE' : 'QUERY' }} +
+
+ {{ layer.isQueryLayer ? layer.query : layer.name }} +
+
+
+ + + + + + +
+
+
+
+ +
+ + + + 0 + + +
+ Base Layer +
+
+ + + + +
+
+
+ + + + + +
{{ editQueryForMapLayer ? 'Edit Query' : 'Add Layer' }}
+ +
+ +
+
+ + + + + +
+ +
+
+ + +
+ + + + +
+ +
+ + +
+ + + Error
+ {{ addLayerDialogErrorMessage }} +
+
+ +
+ + + + + + +
+ +
+ +
+ +
diff --git a/src/app/views/querying/gis/components/layers/map-layers.component.scss b/src/app/views/querying/gis/components/layers/map-layers.component.scss new file mode 100644 index 00000000..e1c27a1d --- /dev/null +++ b/src/app/views/querying/gis/components/layers/map-layers.component.scss @@ -0,0 +1,253 @@ +.layers-container { + position: relative; + height: 100%; + display: flex; + flex-direction: column; + margin: 1rem; + grid-gap: 1rem; + + max-height: 100%; + overflow-y: auto; + + & > .spacer-top { + flex: 1; + } + + & > .spacer-bottom { + margin-bottom: 2rem; + } +} + +.drag-drop-layers { + display: flex; + flex-direction: column; + grid-gap: 1rem; +} + +.top-bottom-label { + text-align: center; + color: lightgray; + margin: 0 6rem; + letter-spacing: 0.5px; + user-select: none; +} + +.top-label { + border-bottom: 1px solid lightgray; +} + +.bottom-label { + border-top: 1px solid lightgray; +} + +.base-layers { + display: flex; + justify-content: space-evenly; + padding: 0.5rem 0.25rem; +} + +.base-layers > div { + display: grid; + place-items: center; + height: 50px; + width: 50px; + border-radius: 0.2rem; + transition: all 100ms ease-in-out; +} + +.base-layers > div:hover { + background: #f1f1f1; + cursor: pointer; +} + +.base-layers > div > svg { + width: 30px; + fill: #bfdab3; +} + +.add-layer-button { + all: unset; + border-radius: 100%; + border: 1px solid #1a186c; + background: #4b49b6; + position: absolute; + bottom: -1.75rem; + left: 50%; + transform: translateX(-50%); + padding: 1.5rem; + display: grid; + place-items: center; + cursor: pointer; +} + +.add-layer-button > svg { + width: 1rem; + transform: scale(1.2); + fill: #f3f4f7; +} + +.add-layer-query { + display: flex; + flex-direction: column; + gap: 1rem; + + & > .query-controls { + align-content: flex-end; + } +} + +.layer-card { + display: flex; + border: 1px solid lightgray; + flex-direction: column; + background: white; + + & > .card-header { + display: grid; + place-items: center; + grid-gap: 0.5rem; + grid-template-columns: 1fr 1fr 1fr; + color: lightgray; + + & > span { + width: 100%; + user-select: none; + } + + & > .resize-grip { + display: grid; + place-items: center; + width: 2rem; + height: 15px; + grid-template-columns: repeat(3, 1fr); + grid-template-rows: repeat(2, 1fr); + cursor: grab; + + & > span { + height: 3px; + width: 3px; + background-color: #bbb; + border-radius: 50%; + display: inline-block; + } + } + + & > .icons { + display: flex; + gap: 0.35rem; + width: 100%; + justify-content: flex-end; + + & > .eye, & > .remove, & > .zoom, & > .rerun-query { + all: unset; + + & > svg { + width: 1rem; + fill: lightgray; + transition: all 100ms ease-in-out; + cursor: pointer; + + &:hover { + fill: #4b49b6; + } + } + } + } + } + + & .edit-query-button { + gap: 0.5rem; + display: none; + align-items: center; + & > svg { + fill: white; + width: 16px; + } + } + + &:hover { + & .edit-query-button { + display: flex; + + & > svg { + fill: currentColor; + } + } + } + + & > .layer-card-body { + display: flex; + flex-direction: column; + gap: 1rem; + } + + & .title { + word-break: break-all; + + & .layer-length { + font-size: 0.85rem; + margin-bottom: 0.2rem; + } + + & .layer-name { + text-transform: uppercase; + letter-spacing: 0.5px; + font-weight: bold; + } + } + + & .config-sections { + display: flex; + flex-direction: column; + gap: 0.5rem; + } + + & .config-container { + flex: 1; + } +} + +.monospace { + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'Source Code Pro', 'source-code-pro', monospace; +} + +.layer-card-placeholder { + border: 2px dashed #007bff; + background-color: #e9f5ff; + height: 50px; +} + +.json-viewer-popover-body { + max-height: 40vh; + overflow-y: auto; +} + +.add-layer-modal-body { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.share-section { + display: flex; + align-items: center; + gap: 1rem; + + & > span { + color: #a5a5a5; + } +} + +.share-button { + & > svg { + fill: #27aae1; + width: 16px; + margin-right: 0.25rem; + } + + &:hover { + & > svg { + fill: currentColor; + } + } +} diff --git a/src/app/views/querying/gis/components/layers/map-layers.component.ts b/src/app/views/querying/gis/components/layers/map-layers.component.ts new file mode 100644 index 00000000..d7b4566c --- /dev/null +++ b/src/app/views/querying/gis/components/layers/map-layers.component.ts @@ -0,0 +1,617 @@ +import { + AfterViewInit, + Component, + effect, + ElementRef, + HostListener, + inject, + OnDestroy, + OnInit, + Renderer2, + signal, + ViewChild, + WritableSignal +} from '@angular/core'; +import {LayerContext} from '../../models/LayerContext.model'; +import {LayerSettingsService} from '../../services/layersettings.service'; +import {MapGeometryWithData} from '../../models/MapGeometryWithData.model'; +import * as GeoJSON from 'geojson'; +import {FeatureCollection} from 'geojson'; +import {MapLayer} from '../../models/MapLayer.model'; +import isEqual from 'lodash/isEqual'; +import {CdkDragDrop, moveItemInArray,} from '@angular/cdk/drag-drop'; +// noinspection ES6UnusedImports +import {getSampleMapLayers} from '../../models/get-sample-maplayers'; +import {CrudService} from '../../../../../services/crud.service'; +import {WebSocket} from '../../../../../services/webSocket'; +import {Result} from '../../../../../components/data-view/models/result-set.model'; +import {WebuiSettingsService} from '../../../../../services/webui-settings.service'; +import {Subscription} from 'rxjs'; +import {DataModel, PolyAlgRequest, QueryRequest} from '../../../../../models/ui-request.model'; +import {CombinedResult} from '../../../../../components/data-view/data-view.model'; +import {CatalogService} from '../../../../../services/catalog.service'; +import {QueryEditor} from '../../../console/components/code-editor/query-editor.component'; +import {AlgValidatorService, trimLines} from '../../../../../components/polyalg/polyalg-viewer/alg-validator.service'; +import {SidebarNode} from '../../../../../models/sidebar-node.model'; +import {InformationGroup, InformationPage} from '../../../../../models/information-page.model'; +import {AlgViewerComponent} from '../../../../../components/polyalg/polyalg-viewer/alg-viewer.component'; +import {geojsonToWKT} from '@terraformer/wkt'; + + +interface BaseLayer { + name: string; + value: string; +} + +@Component({ + selector: 'app-map-layers', + templateUrl: './map-layers.component.html', + styleUrl: './map-layers.component.scss', +}) +export class MapLayersComponent implements OnInit, AfterViewInit, OnDestroy { + protected editQueryForMapLayer: MapLayer; + + constructor( + protected layerSettings: LayerSettingsService, + private el: ElementRef, + private renderer: Renderer2, + ) { + this.websocket = new WebSocket(); + this.initWebsocket(); + + // Starting a new GIS query session: Remove all layers which were previously added and not cleaned up. + this.updateLayers([]); + + effect(() => { + const res = this.results(); + if (res.length > 0) { + const combinedResult = CombinedResult.from(res[0]); + + if (combinedResult.error) { + this.addLayerDialogErrorMessage = `There was an error executing the query. Error: ${combinedResult.error}`; + } else { + const queryLayer = MapLayer.from(combinedResult); + console.log('queryLayer.query', queryLayer.query); + + if (this.lastQueryAnalyzerId && this.lastQueryAnalyzerPage) { + this._crud.getAnalyzerPage(this.lastQueryAnalyzerId, this.lastQueryAnalyzerPage).subscribe({ + next: res => { + const informationPage = res as InformationPage; + const groups = new Map(Object.entries(informationPage.groups)); + for (const group of groups.values()) { + const informationObjects = Object.values(group.informationObjects); + for (const informationObject of informationObjects) { + if (informationObject.type === 'InformationPolyAlg') { + queryLayer.planNode = JSON.parse(informationObject.jsonPolyAlg); + } + } + } + }, error: err => { + console.log(err); + } + }); + } + + console.log('this.addDataToExistingLayer', this.addDataToExistingLayer); + if (this.addDataToExistingLayer) { + this.addDataToExistingLayer.addData(queryLayer.data, true); + // This timestamp will trigger the change detection. + this.addDataToExistingLayer.lastUpdated = queryLayer.lastUpdated; + + // Reset references to layer, so that the user can start the next query. + if (this.applyFilterToLayer) { + // Restores the state in the filter configuration section + this.applyFilterToLayer.filterConfig.polygon = null; + // Restores the state on the map component + this.layerSettings.disableDrawingModeForLayer(this.applyFilterToLayer); + // Restores the state here. + this.applyFilterToLayer = null; + // Remove the PolyPlan from the AlgViewer, so that when setting the same plan for another + // filter operation, the changed event is fired again. + this.algViewerComponent.setPolyAlgPlan(null, 'LOGICAL'); + } else { + // Only update the query if it was written in a query language the query console + // understands. + console.log('Update query'); + this.addDataToExistingLayer.query = queryLayer.query; + } + + this.addDataToExistingLayer = null; + + // Instantly trigger rerender. + this.updateLayers(this.layers); + } else { + this.addLayerInternal(queryLayer); + } + this.isAddLayerModalVisible = false; + } + } + + }, { + // Necessary to set the polyPlan of the AlgViewer to null after + // the result from the query arrived. + allowSignalWrites: true + }); + } + + // DI + private readonly _crud = inject(CrudService); + private readonly _settings = inject(WebuiSettingsService); + public readonly _catalog = inject(CatalogService); + public readonly _validator = inject(AlgValidatorService); + + @ViewChild(QueryEditor) queryEditor!: QueryEditor; + @ViewChild(AlgViewerComponent) algViewerComponent!: AlgViewerComponent; + + // Querying + websocket: WebSocket; + results: WritableSignal[]> = signal([]); + readonly language: WritableSignal = signal('sql'); + private subscriptions = new Subscription(); + private addDataToExistingLayer: MapLayer = null; + private lastQueryAnalyzerId = null; + private lastQueryAnalyzerPage = null; + private applyFilterToLayer: MapLayer = null; + + protected baseLayers: BaseLayer[] = [ + { + name: 'OSM', + value: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + }, + { + name: 'OSM Hot', + value: 'https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png', + }, + { + name: 'Grey Background', + value: 'EMPTY', + }, + ]; + protected selectedBaseLayer: BaseLayer = this.baseLayers[0]; + protected layers: MapLayer[] = []; + protected renderedLayers: MapLayer[] = []; + + // Add Layer + protected isAddLayerModalVisible = false; + protected addLayerDialogErrorMessage = ''; + protected loadedGeoJsonFile?: GeoJSON.FeatureCollection = undefined; + protected loadedGeoJsonFileName = ''; + protected anyLayersVisible = false; + protected addLayerModes: LayerContext[] = [ + // LayerContext.Results, + LayerContext.Query, + // LayerContext.DB, + LayerContext.External, + ]; + protected addLayerMode: LayerContext = LayerContext.Query; + + // Correctly set height. + private pollingTimer: any; + private lastHeight = ''; + protected readonly Object = Object; + protected readonly LayerContext = LayerContext; + protected readonly queryLanguages = ['cypher', 'sql', 'mql']; + readonly activeNamespace: WritableSignal = signal(null); + + runPolyPlan(layer: MapLayer) { + if (this.applyFilterToLayer || this.addDataToExistingLayer) { + console.log('Another query is already in progress. Wait for it to finish.', this.applyFilterToLayer, this.addDataToExistingLayer); + return; + } + + this.applyFilterToLayer = layer; + this.addDataToExistingLayer = layer; + console.log('runPolyPlan layer.planNode=', layer.planNode); + this.algViewerComponent.setPolyAlgPlan(layer.planNode, 'LOGICAL'); + } + + onPolyPlanChanged(polyPlan: string) { + console.log('onPolyPlanChanged', polyPlan); + if (!this.applyFilterToLayer || !polyPlan) { + return; + } + console.log('onPolyPlanChanged this.applyFilterToLayer', this.applyFilterToLayer); + + let plan = ''; + + if (this.applyFilterToLayer.language === 'mongo') { + const wkt = geojsonToWKT(this.applyFilterToLayer.filterConfig.polygon); + + // TODO: Get SRID from layer. + // TODO: class const + // Do not use distance for MQL_GEO_WITHIN + const distance = -1; + plan = `DOC_FILTER[MQL_GEO_WITHIN(${this.applyFilterToLayer.geometryField}, 'SRID=4326;${wkt}':DOCUMENT, ${distance}:FLOAT)](${polyPlan})`; + plan = trimLines(plan); + } + + console.log('Run plan:', plan); + const request = new PolyAlgRequest(plan, DataModel.DOCUMENT, 'LOGICAL'); + request.noLimit = true; + this.websocket.sendMessage(request); + } + + ngOnDestroy(): void { + // This component is only destroyed once we navigate away from the Map-Based Query Mode. In this case, + // we want to remove everything that belongs to this session. + // Note: Don't do the same thing for the map: Because when we are navigating from the results to the full + // query mode, we want to keep the session going, and transfer the last-run query over. + this.layerSettings.reset(); + this.subscriptions.unsubscribe(); + clearInterval(this.pollingTimer); + } + + private initWebsocket() { + const sub = this.websocket.onMessage().subscribe({ + next: msg => { + if (Array.isArray(msg) && msg[0].hasOwnProperty('routerLink')) { + const sidebarNodesTemp: SidebarNode[] = msg; + const logicalQueryPlanNode = sidebarNodesTemp.filter(n => n.name === 'Logical Query Plan')[0] || null; + if (logicalQueryPlanNode !== null) { + const split = logicalQueryPlanNode.routerLink.split('/'); + this.lastQueryAnalyzerId = split[0]; + this.lastQueryAnalyzerPage = split[1]; + } + } else if (Array.isArray(msg) && ((msg[0].hasOwnProperty('data') || msg[0].hasOwnProperty('affectedTuples') || msg[0].hasOwnProperty('error')))) { // array of ResultSet + this.results.set([]>msg); + } else if (msg.hasOwnProperty('data') || msg.hasOwnProperty('affectedTuples') || msg.hasOwnProperty('error')) { + // PolyRequest + this.results.set([msg]); + } + }, + error: err => { + //this._leftSidebar.setError('Lost connection with the server.'); + setTimeout(() => { + this.initWebsocket(); + }, +this._settings.getSetting('reconnection.timeout')); + } + }); + this.subscriptions.add(sub); + } + + ngAfterViewInit(): void { + this.startPollingHeight(); + } + + ngOnInit(): void { + this.subscriptions.add(this.layerSettings.layers$.subscribe((layers) => { + this.layers = layers; + this.renderedLayers = this.deepCopyLayers(layers, false); + this.layerSettings.setCanRerenderLayers(false); + this.updateLayerUi(); + })); + + this.subscriptions.add(this.layerSettings.modifiedConfig$.subscribe((config) => { + if (!config) { + return; + } + this.checkCanRerender(); + })); + + this.subscriptions.add(this.layerSettings.rerenderButtonClicked$.subscribe(() => { + this.updateLayers(this.layers); + })); + + this.subscriptions.add(this.layerSettings.queryFromConsoleResults$.subscribe((query) => { + if (query) { + console.log('Run full query from results', query); + this.submitQuery(query.query, query.language.toString(), query.namespace); + // Remove it, so that if we navigate away and back again, we won't run the query twice. + this.layerSettings.setResultsQuery(null); + } + })); + + this.subscriptions.add(this.layerSettings.layerPolygonFilter$.subscribe((layerAndPolygon) => { + if (layerAndPolygon === null) { + return; + } + const [layer, polygon] = layerAndPolygon; + layer.filterConfig.polygon = polygon; + this.runPolyPlan(layer); + })); + + this.subscriptions.add(this.layerSettings.layerEnableDrawingMode$.subscribe((mapLayer) => { + if (mapLayer === null) { + return; + } + + // Currently, the drawing mode is exclusive to a single layer. Disable for all other layers. + for (const layer of this.layers) { + if (layer.uuid === mapLayer.uuid) { + continue; + } + layer.filterConfig.isDrawingModeActive = false; + } + })); + + this.subscriptions.add(this.layerSettings.editQueryForMapLayer$.subscribe((mapLayer) => { + if (mapLayer === null) { + return; + } + if (!mapLayer.isQueryLayer) { + return; + } + + this.editQueryForMapLayer = mapLayer; + + // Show the Add Layer Modal, that we also use to edit the query. + if (!this.isAddLayerModalVisible) { + console.log('EDIT MODE', this.editQueryForMapLayer); + this.queryEditor.setCode(mapLayer.query); + const language = mapLayer.language === 'mongo' ? 'mql' : mapLayer.language; + this.language.set(language); + this.activeNamespace.set(mapLayer.namespace); + this.addLayerMode = LayerContext.Query; + this.isAddLayerModalVisible = true; + } + })); + + // this.updateLayers(getSampleMapLayers()); + } + + checkCanRerender() { + const canRerenderLayers = !isEqual( + this.deepCopyLayers(this.layers, false), + this.renderedLayers, + ); + this.layerSettings.setCanRerenderLayers(canRerenderLayers); + } + + trackByLayerUuid(index: number, layer: MapLayer): string { + return layer.uuid; + } + + @HostListener('window:resize') + onResize(): void { + this.setMapHeight(this.getMapHeight()); + } + + private startPollingHeight(): void { + // A bit dirty, but it works for now. For some reason, a second after the map is created, the size changes. + // We just poll for the first few seconds after the component is created, and update the size if it changes. + const endTime = Date.now() + 3000; + this.pollingTimer = setInterval(() => { + const currentHeight = this.getMapHeight(); + + if (currentHeight === undefined) { + return; + } + + if (currentHeight !== this.lastHeight) { + this.setMapHeight(currentHeight); + } + // TODO: Somehow, sometimes, the onResize event does not capture all changes in the viewport, e.g. + // when interacting with windows snapping or doing other fast stuff. For now, just always poll while + // the component is active. + // else { + // if (Date.now() > endTime) { + // clearInterval(this.pollingTimer); + // } + // } + }, 500); + } + + private getMapHeight(): string | undefined { + const elem = (document.querySelector('#map') as HTMLElement); + if (elem === null) { + return undefined; + } else { + return `${elem.offsetHeight}px`; + } + } + + private setMapHeight(mapHeight: string): void { + this.lastHeight = mapHeight; + this.renderer.setStyle(this.el.nativeElement, 'height', mapHeight); + } + + deepCopyLayers(layers: MapLayer[], includeData = true) { + // We only case if the configuration has changed. Copying the data over every time something changes would + // cause big performance problems. + return layers.map((layer) => layer.copy(includeData)); + } + + async loadGeoJsonFile($event: Event) { + if (!event) { + return; + } + try { + const input = event.target as HTMLInputElement; + if (input.files && input.files.length) { + const file = input.files[0]; + this.loadedGeoJsonFileName = file.name; + this.loadedGeoJsonFile = JSON.parse(await file.text()); + console.log('Loaded GeoJSON file!'); + } + } catch (error) { + if (error instanceof Error) { + alert(`Failed to load file: ${error.message}`); + } + this.loadedGeoJsonFile = undefined; + } + } + + rerunQuery(layer: MapLayer) { + if (this.applyFilterToLayer || this.addDataToExistingLayer) { + console.log('Another query is already in progress. Do not update this.addDataToExistingLayer.'); + return; + } + + // Data will be overwritten once the results are in + this.addDataToExistingLayer = layer; + this.submitQuery(layer.query, layer.language, layer.namespace); + } + + fitLayerToMap(layer: MapLayer) { + this.layerSettings.setFitLayerToMap(layer); + } + + removeLayer(layerToRemove: MapLayer) { + const newLayers = this.layers.filter(layer => layer !== layerToRemove); + this.updateLayers(newLayers); + } + + onBaseLayerChange(selectedLayer: BaseLayer): void { + this.layerSettings.setBaseLayer(selectedLayer.value); + } + + addLayerInternal(layer: MapLayer) { + const newLayers = [ + layer, + ...this.layers, + ].map((v, i) => { + v.index = i + 1; + return v; + }); + + this.updateLayers(newLayers); + } + + async fetchGeoJsonFile(url: string): Promise { + const response = await window.fetch(url, { + method: 'GET', + headers: { + 'content-type': 'application/json;charset=UTF-8', + }, + }); + const geojson: GeoJSON.FeatureCollection = await response.json(); + return geojson; + } + + submitQuery(query: string, language: string, namespace: string): boolean { + const request = new QueryRequest(query, true, false, language, namespace); + request.noLimit = true; + return this._crud.anyQuery(this.websocket, request); + } + + async addLayer() { + this.addLayerDialogErrorMessage = ''; + + switch (this.addLayerMode) { + case LayerContext.Query: + if (!this.submitQuery(this.queryEditor.getCode(), this.language(), this.activeNamespace())) { + this.addLayerDialogErrorMessage = 'There was an error executing this query.'; + } + // Dialog will be hidden when result has arrived in constructor.effect, because it is possible to + // get the error in the result, and not directly here. + break; + case LayerContext.Results: + case LayerContext.DB: + alert('TODO'); + break; + case LayerContext.External: + if (this.loadedGeoJsonFile) { + const layer = new MapLayer( + this.loadedGeoJsonFileName, + ).addData( + this.loadedGeoJsonFile.features.map( + (f, i) => + new MapGeometryWithData( + i, + f.geometry, + f.properties ? f.properties : {}, + ), + ), + ); + console.log('Added GeoJSON layer: ', layer); + this.isAddLayerModalVisible = false; + this.addLayerInternal(layer); + } else { + this.addLayerDialogErrorMessage = 'No file selected / File could not be loaded.'; + } + break; + } + } + + dropLayer(event: CdkDragDrop) { + // TODO: After dropping a layer, the UI is very buggy + moveItemInArray(this.layers, event.previousIndex, event.currentIndex); + this.updateLayers(this.layers); + } + + updateLayers(newLayers: MapLayer[]) { + this.layerSettings.setLayers(newLayers); + this.updateLayerUi(); + } + + toggleLayerVisibility(layer: MapLayer) { + layer.isActive = !layer.isActive; + this.layerSettings.toggleLayerVisibility(layer); + } + + toggleAddLayerModalVisibility() { + if (!this.isAddLayerModalVisible){ + // Open modal in new layer mode + this.editQueryForMapLayer = null; + this.isAddLayerModalVisible = true; + } else { + // Close the modal + this.isAddLayerModalVisible = false; + } + } + + addLayerModalVisibilityChanged(event: any) { + this.isAddLayerModalVisible = event; + } + + updateLayerUi() { + // Layers which are first in the array are rendered first, and will be drawn over by other layers. + // Layers: BOTTOM -> TOP + for (let i = 0; i < this.layers.length; i++) { + this.layers[this.layers.length - 1 - i].index = i + 1; + } + this.anyLayersVisible = this.layers.length > 0; + } + + export() { + if (this.layers.length === 0) { + return; + } + + // Create and download a GeoJSON file. + const geoJSON = { + type: 'FeatureCollection', + features: this.layers.flatMap(layer => + layer.data.map(item => ({ + type: 'Feature', + geometry: item.geometry, + properties: { + ...item.data, + layerName: layer.name, + layerUUID: layer.uuid, + }, + })) + ), + }; + + const jsonString = JSON.stringify(geoJSON, null, 2); + const blob = new Blob([jsonString], {type: 'application/json'}); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + const now = new Date(); + // Timestamp looks like 01-12-2024-15-30 + const timestamp = [ + now.getDate().toString().padStart(2, '0'), + (now.getMonth() + 1).toString().padStart(2, '0'), + now.getFullYear(), + now.getHours().toString().padStart(2, '0'), + now.getMinutes().toString().padStart(2, '0') + ].join('-'); + a.download = `${timestamp}_polypheny_map_layers_export.geojson`; + a.click(); + URL.revokeObjectURL(url); + } + + editQuery() { + if (!this.editQueryForMapLayer) { + return; + } + this.editQueryForMapLayer.query = this.queryEditor.getCode(); + this.editQueryForMapLayer.language = this.language(); + this.editQueryForMapLayer.namespace = this.activeNamespace(); + this.isAddLayerModalVisible = false; + this.rerunQuery(this.editQueryForMapLayer); + this.editQueryForMapLayer = null; + } +} diff --git a/src/app/views/querying/gis/components/visualization/area-shape-visualization.model.ts b/src/app/views/querying/gis/components/visualization/area-shape-visualization.model.ts new file mode 100644 index 00000000..6b5e634c --- /dev/null +++ b/src/app/views/querying/gis/components/visualization/area-shape-visualization.model.ts @@ -0,0 +1,39 @@ +import { Visualization } from '../../models/visualization.interface'; +import { MapGeometryWithData } from '../../models/MapGeometryWithData.model'; +import {AreaShapeComponent} from './area-shape/area-shape.component'; +import {MapLayerConfiguration} from '../../models/MapLayerConfiguration.interface'; + +export class AreaShapeVisualization implements Visualization, MapLayerConfiguration { + name = 'Area Shape'; + // TODO: Change to own component + configurationComponentType = AreaShapeComponent; + + outlineThickness: number; + modes: string[] = ['Solid']; + selectedMode: string = this.modes[0]; + + constructor(outlineThickness: number) { + this.outlineThickness = outlineThickness; + } + + init(data: MapGeometryWithData[]): void { + // + } + + copy(): Visualization { + const copy = new AreaShapeVisualization(this.outlineThickness); + copy.selectedMode = this.selectedMode; + return copy; + } + + getValueForAttribute(attr: string, data: MapGeometryWithData): string | number { + switch (attr) { + case 'stroke-width': + return this.outlineThickness; + } + + throw new Error(`Visualization does not support attribute [${attr}]`); + } + + apply(): void {} +} diff --git a/src/app/views/querying/gis/components/visualization/area-shape/area-shape.component.css b/src/app/views/querying/gis/components/visualization/area-shape/area-shape.component.css new file mode 100644 index 00000000..7c613be9 --- /dev/null +++ b/src/app/views/querying/gis/components/visualization/area-shape/area-shape.component.css @@ -0,0 +1,5 @@ +div { + display: flex; + flex-direction: column; + gap: 1rem; +} diff --git a/src/app/views/querying/gis/components/visualization/area-shape/area-shape.component.html b/src/app/views/querying/gis/components/visualization/area-shape/area-shape.component.html new file mode 100644 index 00000000..901b40b7 --- /dev/null +++ b/src/app/views/querying/gis/components/visualization/area-shape/area-shape.component.html @@ -0,0 +1,27 @@ +
+ + Outline Thickness + + + + + + + + +
diff --git a/src/app/views/querying/gis/components/visualization/area-shape/area-shape.component.ts b/src/app/views/querying/gis/components/visualization/area-shape/area-shape.component.ts new file mode 100644 index 00000000..919ecf8f --- /dev/null +++ b/src/app/views/querying/gis/components/visualization/area-shape/area-shape.component.ts @@ -0,0 +1,33 @@ +import { Component, Inject } from '@angular/core'; +import { MapLayerConfigurationComponent } from '../../../models/visualization-configuration.interface'; +import { FormsModule } from '@angular/forms'; +import { LayerSettingsService } from '../../../services/layersettings.service'; +import { + FormCheckComponent, + FormCheckInputDirective, + FormCheckLabelDirective, + FormControlDirective, + FormSelectDirective, + InputGroupComponent, + InputGroupTextDirective, +} from '@coreui/angular'; +import { NgForOf, NgIf } from '@angular/common'; +import {AreaShapeVisualization} from '../area-shape-visualization.model'; + +@Component({ + selector: 'app-area-shape', + templateUrl: './area-shape.component.html', + styleUrl: './area-shape.component.css', +}) +export class AreaShapeComponent implements MapLayerConfigurationComponent { + constructor( + @Inject('config') protected config: AreaShapeVisualization, + private layerSettings: LayerSettingsService, + ) { + // + } + + configChanged() { + this.layerSettings.visualizationConfigurationChanged(this.config); + } +} diff --git a/src/app/views/querying/gis/components/visualization/color-visualization-model.ts b/src/app/views/querying/gis/components/visualization/color-visualization-model.ts new file mode 100644 index 00000000..4f29abb2 --- /dev/null +++ b/src/app/views/querying/gis/components/visualization/color-visualization-model.ts @@ -0,0 +1,103 @@ +import { Visualization } from '../../models/visualization.interface'; +import { MapGeometryWithData } from '../../models/MapGeometryWithData.model'; +import * as d3 from 'd3'; +import * as turf from '@turf/turf'; +import { ColorComponent } from './color/color.component'; +import {MapLayerConfiguration} from '../../models/MapLayerConfiguration.interface'; +import {MapLayer} from "../../models/MapLayer.model"; + +// tslint:disable:no-non-null-assertion + +export class ColorVisualization implements Visualization, MapLayerConfiguration { + name = 'Color'; + configurationComponentType = ColorComponent; + layer: MapLayer; + + modes: string[] = ['Static', 'Gradient']; + selectedMode: string = this.modes[0]; + + // Static + color: string; + fillOpacity = 0.25; + + // Gradient + fieldName = ''; + normalizeByArea = false; + colorScale?: d3.ScaleSequential; + + constructor(color: string, layer: MapLayer) { + this.color = color; + this.layer = layer; + } + + init(data: MapGeometryWithData[]): void { + if (this.selectedMode === this.modes[0]) { + // Nothing to do + return; + } + + const values = this.normalizeByArea + ? data + .map((d) => [ + d.getNumberValueFromField(this.fieldName), + turf.area(d.geometry), + ]) + .filter( + (v): v is [number, number] => + !isNaN(v[0]) && !isNaN(v[1]), + ) + .map((v) => (v[1] > 0 ? v[0] / v[1] : 0)) + : data + .map((d) => d.getNumberValueFromField(this.fieldName)) + .filter((v): v is number => !isNaN(v)); + + console.log('values', values); + + const minValue = d3.min(values) ?? 0; + const maxValue = d3.max(values) ?? 1; + console.log('minValue', minValue); + console.log('maxValue', maxValue); + + this.colorScale = d3 + .scaleSequential(d3.interpolateHsl('lightblue', 'darkblue')) + .domain([minValue, maxValue]); + console.log('this.colorScale', this.colorScale); + } + + copy(): Visualization { + const copy = new ColorVisualization(this.color, this.layer); + copy.selectedMode = this.selectedMode; + copy.fillOpacity = this.fillOpacity; + copy.fieldName = this.fieldName; + copy.normalizeByArea = this.normalizeByArea; + return copy; + } + + getValueForAttribute(attr: string, data: MapGeometryWithData): string | number { + switch (attr) { + case 'stroke': + switch (this.selectedMode) { + case 'Static': + return this.color; + case 'Gradient': + return 'black'; + } + return this.color; + case 'fill-opacity': + return this.fillOpacity; + case 'fill': + switch (this.selectedMode) { + case 'Static': + return this.color; + case 'Gradient': + return this.colorScale!( + data.getNumberValueFromField(this.fieldName), + ); + } + } + + throw new Error(`Visualization does not support attribute [${attr}]`); + } + + apply(): void {} +} diff --git a/src/app/views/querying/gis/components/visualization/color/color.component.css b/src/app/views/querying/gis/components/visualization/color/color.component.css new file mode 100644 index 00000000..7c613be9 --- /dev/null +++ b/src/app/views/querying/gis/components/visualization/color/color.component.css @@ -0,0 +1,5 @@ +div { + display: flex; + flex-direction: column; + gap: 1rem; +} diff --git a/src/app/views/querying/gis/components/visualization/color/color.component.html b/src/app/views/querying/gis/components/visualization/color/color.component.html new file mode 100644 index 00000000..087735c9 --- /dev/null +++ b/src/app/views/querying/gis/components/visualization/color/color.component.html @@ -0,0 +1,49 @@ +
+ + + + + + + Color + + + + + Opacity (Area) + + + + + Variable + + + +
diff --git a/src/app/views/querying/gis/components/visualization/color/color.component.ts b/src/app/views/querying/gis/components/visualization/color/color.component.ts new file mode 100644 index 00000000..3b140c32 --- /dev/null +++ b/src/app/views/querying/gis/components/visualization/color/color.component.ts @@ -0,0 +1,33 @@ +import { Component, Inject } from '@angular/core'; +import { MapLayerConfigurationComponent } from '../../../models/visualization-configuration.interface'; +import { FormsModule } from '@angular/forms'; +import { LayerSettingsService } from '../../../services/layersettings.service'; +import { + FormCheckComponent, + FormCheckInputDirective, + FormCheckLabelDirective, + FormControlDirective, + FormSelectDirective, + InputGroupComponent, + InputGroupTextDirective, +} from '@coreui/angular'; +import { ColorVisualization } from '../color-visualization-model'; +import { NgForOf, NgIf } from '@angular/common'; + +@Component({ + selector: 'app-color', + templateUrl: './color.component.html', + styleUrl: './color.component.css', +}) +export class ColorComponent implements MapLayerConfigurationComponent { + constructor( + @Inject('config') protected config: ColorVisualization, + private layerSettings: LayerSettingsService, + ) { + // + } + + configChanged() { + this.layerSettings.visualizationConfigurationChanged(this.config); + } +} diff --git a/src/app/views/querying/gis/components/visualization/empty-visualization.model.ts b/src/app/views/querying/gis/components/visualization/empty-visualization.model.ts new file mode 100644 index 00000000..211546a1 --- /dev/null +++ b/src/app/views/querying/gis/components/visualization/empty-visualization.model.ts @@ -0,0 +1,24 @@ +import { Type } from '@angular/core'; +import { MapGeometryWithData } from '../../models/MapGeometryWithData.model'; +import { MapLayerConfigurationComponent } from '../../models/visualization-configuration.interface'; +import { Visualization } from '../../models/visualization.interface'; +import {EmptyComponent} from './empty/empty.component'; +import {MapLayerConfiguration} from '../../models/MapLayerConfiguration.interface'; + +export class EmptyVisualization implements Visualization, MapLayerConfiguration { + name = 'Empty'; + configurationComponentType: Type = EmptyComponent; + + apply(): void { + throw new Error('Method not implemented.'); + } + copy(): Visualization { + throw new Error('Method not implemented.'); + } + init(data: MapGeometryWithData[]): void { + throw new Error('Method not implemented.'); + } + getValueForAttribute(attr: string, data: MapGeometryWithData): string | number { + throw new Error('Method not implemented.'); + } +} diff --git a/src/app/views/querying/gis/components/visualization/empty/empty.component.html b/src/app/views/querying/gis/components/visualization/empty/empty.component.html new file mode 100644 index 00000000..8811d3d6 --- /dev/null +++ b/src/app/views/querying/gis/components/visualization/empty/empty.component.html @@ -0,0 +1 @@ +Empty diff --git a/src/app/views/querying/gis/components/visualization/empty/empty.component.ts b/src/app/views/querying/gis/components/visualization/empty/empty.component.ts new file mode 100644 index 00000000..55df5705 --- /dev/null +++ b/src/app/views/querying/gis/components/visualization/empty/empty.component.ts @@ -0,0 +1,15 @@ +import {Component, Inject} from '@angular/core'; +import {MapLayerConfigurationComponent} from '../../../models/visualization-configuration.interface'; +import {ColorVisualization} from '../color-visualization-model'; +import {LayerSettingsService} from '../../../services/layersettings.service'; + +@Component({ + selector: 'app-empty', + templateUrl: './empty.component.html', +}) +export class EmptyComponent implements MapLayerConfigurationComponent { + constructor( + @Inject('config') protected config: ColorVisualization, + private layerSettings: LayerSettingsService, + ) {} +} diff --git a/src/app/views/querying/gis/components/visualization/label-visualization-model.ts b/src/app/views/querying/gis/components/visualization/label-visualization-model.ts new file mode 100644 index 00000000..ba240aff --- /dev/null +++ b/src/app/views/querying/gis/components/visualization/label-visualization-model.ts @@ -0,0 +1,30 @@ +import { Visualization } from '../../models/visualization.interface'; +import { MapGeometryWithData } from '../../models/MapGeometryWithData.model'; +import {LabelComponent} from './label/label.component'; +import {MapLayerConfiguration} from '../../models/MapLayerConfiguration.interface'; + +export class LabelVisualization implements Visualization, MapLayerConfiguration { + name = 'Label'; + configurationComponentType = LabelComponent; + + fieldName = ''; + + constructor() {} + + init(data: MapGeometryWithData[]): void { + // + } + + copy(): Visualization { + const copy = new LabelVisualization(); + copy.fieldName = this.fieldName; + return copy; + } + + getValueForAttribute(attr: string, data: MapGeometryWithData): string | number { + return 'TODO'; + // throw new Error(`Visualization does not support attribute [${attr}]`); + } + + apply(): void {} +} diff --git a/src/app/views/querying/gis/components/visualization/label/label.component.css b/src/app/views/querying/gis/components/visualization/label/label.component.css new file mode 100644 index 00000000..7c613be9 --- /dev/null +++ b/src/app/views/querying/gis/components/visualization/label/label.component.css @@ -0,0 +1,5 @@ +div { + display: flex; + flex-direction: column; + gap: 1rem; +} diff --git a/src/app/views/querying/gis/components/visualization/label/label.component.html b/src/app/views/querying/gis/components/visualization/label/label.component.html new file mode 100644 index 00000000..14ba5d14 --- /dev/null +++ b/src/app/views/querying/gis/components/visualization/label/label.component.html @@ -0,0 +1,11 @@ +
+ + Text + + +
diff --git a/src/app/views/querying/gis/components/visualization/label/label.component.ts b/src/app/views/querying/gis/components/visualization/label/label.component.ts new file mode 100644 index 00000000..2ba832c2 --- /dev/null +++ b/src/app/views/querying/gis/components/visualization/label/label.component.ts @@ -0,0 +1,34 @@ +import { Component, Inject } from '@angular/core'; +import { MapLayerConfigurationComponent } from '../../../models/visualization-configuration.interface'; +import { FormsModule } from '@angular/forms'; +import { LayerSettingsService } from '../../../services/layersettings.service'; +import { + FormCheckComponent, + FormCheckInputDirective, + FormCheckLabelDirective, + FormControlDirective, + FormSelectDirective, + InputGroupComponent, + InputGroupTextDirective, +} from '@coreui/angular'; +import { NgForOf, NgIf } from '@angular/common'; +import {AreaShapeVisualization} from '../area-shape-visualization.model'; +import {LabelVisualization} from '../label-visualization-model'; + +@Component({ + selector: 'app-area-shape', + templateUrl: './label.component.html', + styleUrl: './label.component.css', +}) +export class LabelComponent implements MapLayerConfigurationComponent { + constructor( + @Inject('config') protected config: LabelVisualization, + private layerSettings: LayerSettingsService, + ) { + // + } + + configChanged() { + this.layerSettings.visualizationConfigurationChanged(this.config); + } +} diff --git a/src/app/views/querying/gis/components/visualization/point-shape-visualization.model.ts b/src/app/views/querying/gis/components/visualization/point-shape-visualization.model.ts new file mode 100644 index 00000000..7ae561f5 --- /dev/null +++ b/src/app/views/querying/gis/components/visualization/point-shape-visualization.model.ts @@ -0,0 +1,45 @@ +import { Visualization } from '../../models/visualization.interface'; +import { MapGeometryWithData } from '../../models/MapGeometryWithData.model'; +import { PointShapeComponent } from './point-shape/point-shape.component'; +import {MapLayerConfiguration} from '../../models/MapLayerConfiguration.interface'; + +const modes = ['Circle', 'Square', 'Triangle', 'Star', 'Cross'] as const; // readonly tuple +type Mode = typeof modes[number]; // Union type: 'Circle' | 'Square' | 'Triangle' | 'Star' | 'Cross' + +export class PointShapeVisualization implements Visualization, MapLayerConfiguration { + name = 'Point Shape'; + configurationComponentType = PointShapeComponent; + + modes = modes; + selectedMode: Mode; + size: number; + + constructor(size: number) { + this.size = size; + this.selectedMode = this.modes[0]; + } + + init(data: MapGeometryWithData[]): void { + // + } + + copy(): PointShapeVisualization { + const copy = new PointShapeVisualization(this.size); + copy.selectedMode = this.selectedMode; + // copy.fieldName = this.fieldName; + return copy; + } + + getValueForAttribute(attr: string, data: MapGeometryWithData): string | number { + if (data.isPoint()) { + switch (attr) { + case 'r': + return this.size; + } + } + + throw new Error(`Visualization does not support attribute [${attr}]`); + } + + apply(): void {} +} diff --git a/src/app/views/querying/gis/components/visualization/point-shape/point-shape.component.css b/src/app/views/querying/gis/components/visualization/point-shape/point-shape.component.css new file mode 100644 index 00000000..7c613be9 --- /dev/null +++ b/src/app/views/querying/gis/components/visualization/point-shape/point-shape.component.css @@ -0,0 +1,5 @@ +div { + display: flex; + flex-direction: column; + gap: 1rem; +} diff --git a/src/app/views/querying/gis/components/visualization/point-shape/point-shape.component.html b/src/app/views/querying/gis/components/visualization/point-shape/point-shape.component.html new file mode 100644 index 00000000..4b80e577 --- /dev/null +++ b/src/app/views/querying/gis/components/visualization/point-shape/point-shape.component.html @@ -0,0 +1,37 @@ +
+ + Size + + + + + + + + + + + + + + + + + + +
diff --git a/src/app/views/querying/gis/components/visualization/point-shape/point-shape.component.ts b/src/app/views/querying/gis/components/visualization/point-shape/point-shape.component.ts new file mode 100644 index 00000000..e252c88d --- /dev/null +++ b/src/app/views/querying/gis/components/visualization/point-shape/point-shape.component.ts @@ -0,0 +1,34 @@ +import { Component, Inject } from '@angular/core'; +import { MapLayerConfigurationComponent } from '../../../models/visualization-configuration.interface'; +import { FormsModule } from '@angular/forms'; +import { LayerSettingsService } from '../../../services/layersettings.service'; +import { + FormCheckComponent, + FormCheckInputDirective, + FormCheckLabelDirective, + FormControlDirective, + FormSelectDirective, + InputGroupComponent, + InputGroupTextDirective, +} from '@coreui/angular'; +import { ColorVisualization } from '../color-visualization-model'; +import { NgForOf, NgIf } from '@angular/common'; +import {PointShapeVisualization} from '../point-shape-visualization.model'; + +@Component({ + selector: 'app-point-shape', + templateUrl: './point-shape.component.html', + styleUrl: './point-shape.component.css', +}) +export class PointShapeComponent implements MapLayerConfigurationComponent { + constructor( + @Inject('config') protected config: PointShapeVisualization, + private layerSettings: LayerSettingsService, + ) { + // + } + + configChanged() { + this.layerSettings.visualizationConfigurationChanged(this.config); + } +} diff --git a/src/app/views/querying/gis/gis.component.html b/src/app/views/querying/gis/gis.component.html new file mode 100644 index 00000000..84536ac3 --- /dev/null +++ b/src/app/views/querying/gis/gis.component.html @@ -0,0 +1,5 @@ + + + + + diff --git a/src/app/views/querying/gis/gis.component.scss b/src/app/views/querying/gis/gis.component.scss new file mode 100644 index 00000000..1c91e928 --- /dev/null +++ b/src/app/views/querying/gis/gis.component.scss @@ -0,0 +1,7 @@ +.master-detail { + display: grid; + grid-template-columns: auto 350px; + height: 100%; + max-height: 100%; + overflow: hidden; +} \ No newline at end of file diff --git a/src/app/views/querying/gis/gis.component.ts b/src/app/views/querying/gis/gis.component.ts new file mode 100644 index 00000000..88243ace --- /dev/null +++ b/src/app/views/querying/gis/gis.component.ts @@ -0,0 +1,37 @@ +import { + Component, + inject, + OnDestroy, + OnInit +} from '@angular/core'; +import {CrudService} from '../../../services/crud.service'; +import {LeftSidebarService} from '../../../components/left-sidebar/left-sidebar.service'; +import {BreadcrumbService} from '../../../components/breadcrumb/breadcrumb.service'; +import {WebuiSettingsService} from '../../../services/webui-settings.service'; +import {UtilService} from '../../../services/util.service'; +import {ToasterService} from '../../../components/toast-exposer/toaster.service'; +import {CatalogService} from '../../../services/catalog.service'; + +@Component({ + selector: 'app-gis', + templateUrl: './gis.component.html', + styleUrls: ['./gis.component.scss'] +}) +export class GisComponent implements OnInit, OnDestroy { + private readonly _crud = inject(CrudService); + private readonly _leftSidebar = inject(LeftSidebarService); + private readonly _breadcrumb = inject(BreadcrumbService); + private readonly _settings = inject(WebuiSettingsService); + public readonly _util = inject(UtilService); + public readonly _toast = inject(ToasterService); + public readonly _catalog = inject(CatalogService); + private readonly _sidebar = inject(LeftSidebarService); + + ngOnDestroy(): void { + + } + + ngOnInit(): void { + this._sidebar.hide(); + } +} diff --git a/src/app/views/querying/gis/models/LayerContext.model.ts b/src/app/views/querying/gis/models/LayerContext.model.ts new file mode 100644 index 00000000..a2964eb6 --- /dev/null +++ b/src/app/views/querying/gis/models/LayerContext.model.ts @@ -0,0 +1,6 @@ +export enum LayerContext { + Results = 'Results', + Query = 'Query', + DB = 'Polypheny', + External = 'External file (GeoJSON)' +} diff --git a/src/app/views/querying/gis/models/MapGeometryWithData.model.ts b/src/app/views/querying/gis/models/MapGeometryWithData.model.ts new file mode 100644 index 00000000..3450cd81 --- /dev/null +++ b/src/app/views/querying/gis/models/MapGeometryWithData.model.ts @@ -0,0 +1,65 @@ +import {Geometry, Point} from 'geojson'; +import {MapLayer} from './MapLayer.model'; + +/** + * Represents one row in the results returned by Polypheny. + */ +export class MapGeometryWithData { + /** + * Used for styling if the results are ordered + */ + index: number; + geometry: Geometry; + data: Record = {}; + cache: Record = {}; + layer?: MapLayer = undefined; + type: 'point' | 'path'; + + constructor( + index: number, + geometry: Geometry, + data: Record | undefined = undefined, + ) { + this.index = index; + this.geometry = geometry; + this.type = this.isPoint() ? 'point' : 'path'; + + if (data) { + this.data = data; + } + } + + isPoint(){ + return this.geometry.type === 'Point'; + } + + getPoint() { + if (this.isPoint()){ + return this.geometry as Point; + } + throw new Error('Can only call getPoint() if geometry is actually of type Point!'); + } + + copy(){ + // We leave out cache on purpose, because we will use the copy to compare both if the layer has changed. + return new MapGeometryWithData(this.index, this.geometry, this.data); + } + + getNumberValueFromField(fieldName: string): number { + let finalValue: any = this.data; + + for (const key of fieldName.split('.')) { + if (finalValue && typeof finalValue === 'object') { + finalValue = finalValue[key]; + } else { + return NaN; + } + } + + if (typeof finalValue === 'number') { + return finalValue; + } + + return NaN; + } +} diff --git a/src/app/views/querying/gis/models/MapLayer.model.ts b/src/app/views/querying/gis/models/MapLayer.model.ts new file mode 100644 index 00000000..519992b0 --- /dev/null +++ b/src/app/views/querying/gis/models/MapLayer.model.ts @@ -0,0 +1,327 @@ +import {Visualization} from './visualization.interface'; +import {MapGeometryWithData} from './MapGeometryWithData.model'; +import {ColorVisualization} from '../components/visualization/color-visualization-model'; +import {AreaShapeVisualization} from '../components/visualization/area-shape-visualization.model'; +import {LabelVisualization} from '../components/visualization/label-visualization-model'; +import {PointShapeVisualization} from '../components/visualization/point-shape-visualization.model'; +import {CombinedResult} from '../../../../components/data-view/data-view.model'; +import {DataModel} from '../../../../models/ui-request.model'; +import {Geometry, Polygon} from 'geojson'; +import {v4} from 'uuid'; +import * as L from 'leaflet'; +import {MapLayerConfiguration} from './MapLayerConfiguration.interface'; +import {DataPreview} from '../components/configuration/DataPreview'; +import {FilterConfig} from '../components/configuration/FilterConfig'; +import {AlgValidatorService} from '../../../../components/polyalg/polyalg-viewer/alg-validator.service'; +import {PlanNode} from '../../../../components/polyalg/models/polyalg-plan.model'; + +export class MapLayer { + + constructor(name: string) { + this.name = name; + this.uuid = v4(); + this.lastUpdated = new Date().toISOString(); + } + + uuid: string; + name: string; + data: MapGeometryWithData[] = []; + containsPoints = false; + containsAreas = false; + containsData = false; + + // Query + isQueryLayer = false; + query = ''; + geometryField = ''; + language = ''; + namespace = ''; + lastUpdated = ''; + + // Query Filter + planNode: PlanNode = null; + + dataPreview: MapLayerConfiguration = new DataPreview(this); + filterConfig: FilterConfig = new FilterConfig(this); + pointShapeVisualization: PointShapeVisualization = new PointShapeVisualization(3); + areaShapeVisualization: Visualization = new AreaShapeVisualization(1); + colorVisualization: Visualization = new ColorVisualization('red', this); + labelVisualization: Visualization = new LabelVisualization(); + + // Computed (Not used in copy) + isActive = true; + index = -1; + + static from(result: CombinedResult): MapLayer { + console.log('MapLayer from result: ', result); + const layer = new MapLayer(result.query); + layer.query = result.query; + layer.language = result.language; + layer.namespace = result.namespace; + layer.isQueryLayer = true; + const mapData = []; + let geometryField = undefined; + + switch (result.dataModel) { + case DataModel.DOCUMENT: + for (let rowIndex = 0; rowIndex < result.data.length; rowIndex++) { + const json = result.data[rowIndex][0]; + const jsonObject = Object.fromEntries( + Object.entries(JSON.parse(json)).map(([key, value]) => [key.toLowerCase(), value]) + ); + const [geometry, key] = this.getGeometryFromData(jsonObject); + if (geometry) { + geometryField = key; + const geometryWithData = new MapGeometryWithData(rowIndex, geometry, jsonObject); + mapData.push(geometryWithData); + } + } + break; + + case DataModel.RELATIONAL: + for (let rowIndex = 0; rowIndex < result.data.length; rowIndex++) { + const obj: Record = {}; + + for (let headerIndex = 0; headerIndex < result.header.length; headerIndex++) { + const header = result.header[headerIndex]; + const key = header.name.toLowerCase(); + const value = result.data[rowIndex][headerIndex]; + + if (header.dataType.startsWith('GEOMETRY')) { + obj[key] = JSON.parse(value); + } else if (header.dataType.startsWith('INTEGER')) { + obj[key] = parseInt(value, 10); + } else if (header.dataType.startsWith('DECIMAL')) { + obj[key] = parseFloat(value); + } else if (header.dataType.startsWith('DOUBLE')) { + obj[key] = parseFloat(value); + } + else { + obj[key] = value; + } + } + + const [geometry, key] = this.getGeometryFromData(obj); + if (geometry) { + geometryField = key; + const geometryWithData = new MapGeometryWithData(rowIndex, geometry, obj); + mapData.push(geometryWithData); + } + } + break; + + case DataModel.GRAPH: + for (let rowIndex = 0; rowIndex < result.data.length; rowIndex++) { + const obj: Record = {}; + + for (let headerIndex = 0; headerIndex < result.header.length; headerIndex++) { + const header = result.header[headerIndex]; + const key = header.name.toLowerCase(); + const datatype = header.dataType; + const value = result.data[rowIndex][headerIndex]; + + if (datatype.startsWith('NODE')) { + const json = JSON.parse(value); + const properties = json['properties']; + // Node stored as JSON + obj[key] = Object.fromEntries( + Object.entries(properties).map(([key, value]) => [key.toLowerCase(), value]) + ); + } else { + // Other value + obj[key] = value; + } + } + + const [geometry, key] = this.getGeometryFromData(obj); + if (geometry) { + geometryField = key; + const geometryWithData = new MapGeometryWithData(rowIndex, geometry, obj); + mapData.push(geometryWithData); + } + } + break; + + default: + throw Error(`Cannot convert CombinedResult to MapLayer. Unknown document model: ${result.dataModel}`); + } + + if (mapData.length > 0){ + layer.addData(mapData); + layer.geometryField = geometryField; + } + + console.log(`Created layer with ${layer.data.length}: data points:`, layer); + return layer; + } + + + static getGeometryFromData(data: Record): [Geometry, string] | undefined { + // Detect GeoJSON objects + for (const key in data) { + if (data.hasOwnProperty(key) && this.isGeoJSON(data[key])) { + return [data[key], key]; + } + } + + // TODO: If we do this, we cannot filter the layer, because we do not have a single field + // that we can use in the logical plan to reference the coordinates. + // Detect 2 columns that store latitude / longitude coordinates + // const latLong = [ + // ['lat', 'lon'], + // ['latitude', 'longitude'], + // ['lati', 'long'], + // ]; + // const isNumber = (value: any): boolean => { + // return typeof value === 'number' && !isNaN(value); + // }; + // + // for (const ll of latLong) { + // const lat = ll[0]; + // const lon = ll[1]; + // + // if ( + // data.hasOwnProperty(lat) && + // data.hasOwnProperty(lon) && + // isNumber(data[lat]) && + // isNumber(data[lon]) + // ) { + // return { + // type: 'Point', + // coordinates: [data[lon], data[lat]], + // }; + // } + // } + + // TODO: Detect heuristic, so that we can automatically detect the most common geometry types + // - string in WKT format + + return [undefined, undefined]; + } + + + static isGeoJSON(obj: any): boolean { + if (!obj || typeof obj !== 'object') { + return false; + } + + const validTypes: string[] = [ + 'Feature', + 'FeatureCollection', + 'GeometryCollection', + 'Point', + 'MultiPoint', + 'LineString', + 'MultiLineString', + 'Polygon', + 'MultiPolygon', + ]; + + if (!obj.type || !validTypes.includes(obj.type)) { + return false; + } + + switch (obj.type) { + case 'Feature': + return obj.hasOwnProperty('geometry') && obj.hasOwnProperty('properties'); + case 'FeatureCollection': + return Array.isArray(obj.features); + case 'GeometryCollection': + return Array.isArray(obj.geometries); + case 'Point': + case 'MultiPoint': + case 'LineString': + case 'MultiLineString': + case 'Polygon': + case 'MultiPolygon': + return obj.hasOwnProperty('coordinates'); + default: + return false; + } + } + + getBounds(): L.LatLng[] { + const bounds: L.LatLng[] = []; + + for (const data of this.data) { + switch (data.geometry.type) { + case 'Point': + bounds.push(L.latLng(data.geometry.coordinates[1], data.geometry.coordinates[0])); + break; + case 'LineString': + case 'MultiPoint': + data.geometry.coordinates.forEach((coord: number[]) => { + bounds.push(L.latLng(coord[1], coord[0])); + }); + break; + case 'Polygon': + case 'MultiLineString': + data.geometry.coordinates.forEach((ring: number[][]) => { + ring.forEach((coord: number[]) => { + bounds.push(L.latLng(coord[1], coord[0])); + }); + }); + break; + case 'MultiPolygon': + data.geometry.coordinates.forEach((polygon: number[][][]) => { + polygon.forEach((ring: number[][]) => { + ring.forEach((coord: number[]) => { + bounds.push(L.latLng(coord[1], coord[0])); + }); + }); + }); + break; + } + } + + return bounds; + } + + copy(includeData = true) { + // Do not copy isActive and isRemoved, because we use the copy to check if + // anything changes so we need to rerender, but in these cases we do not need + // to rerender. + + const copy = new MapLayer(this.name); + copy.lastUpdated = this.lastUpdated; + if (includeData) { + copy.addData( + this.data.map((d) => d.copy()), + ); + } + copy.uuid = this.uuid; + copy.pointShapeVisualization = this.pointShapeVisualization.copy(); + copy.areaShapeVisualization = this.areaShapeVisualization.copy(); + copy.colorVisualization = this.colorVisualization.copy(); + copy.labelVisualization = this.labelVisualization.copy(); + copy.filterConfig = this.filterConfig.copy() as FilterConfig; + return copy; + } + + addData(data: MapGeometryWithData[], overwrite=false) { + if (overwrite){ + this.data = []; + } + + this.containsPoints = false; + this.containsAreas = false; + if (data.length > 0){ + this.containsData = Object.keys(data[0].data).length !== 0; + } + + // Remove everything that does not have a geometry. + data = data.filter((d) => d.geometry !== null); + + data.forEach((d) => { + if (d.isPoint()) { + this.containsPoints = true; + } else { + this.containsAreas = true; + } + d.layer = this; + return; + }); + this.data.push(...data); + return this; + } +} diff --git a/src/app/views/querying/gis/models/MapLayerConfiguration.interface.ts b/src/app/views/querying/gis/models/MapLayerConfiguration.interface.ts new file mode 100644 index 00000000..c86170e6 --- /dev/null +++ b/src/app/views/querying/gis/models/MapLayerConfiguration.interface.ts @@ -0,0 +1,8 @@ +import {Type} from '@angular/core'; +import {MapLayerConfigurationComponent} from './visualization-configuration.interface'; + +export interface MapLayerConfiguration { + configurationComponentType: Type; + + copy(): MapLayerConfiguration; +} diff --git a/src/app/views/querying/gis/models/get-sample-maplayers.ts b/src/app/views/querying/gis/models/get-sample-maplayers.ts new file mode 100644 index 00000000..5cdb711c --- /dev/null +++ b/src/app/views/querying/gis/models/get-sample-maplayers.ts @@ -0,0 +1,305 @@ +import { MapLayer } from './MapLayer.model'; +import * as GeoJSON from 'geojson'; +import { MapGeometryWithData } from './MapGeometryWithData.model'; + +export function getSampleMapLayers(): MapLayer[] { + const data = ` + { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 13.554898, + 52.333956 + ] + }, + "properties": { + "PLAC": "Kiekebusch,,Dahme-Spreewald,BRANDENBURG,DEUTSCHLAND," + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 13.983899, + 52.027206 + ] + }, + "properties": { + "PLAC": "Krugau,,Dahme-Spreewald,BRANDENBURG,DEUTSCHLAND," + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 13.958323, + 52.059514 + ] + }, + "properties": { + "PLAC": "Kuschkow,,Dahme-Spreewald,BRANDENBURG,DEUTSCHLAND," + } + } + ] +}`; + const data2 = ` + { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 11.82575, + 52.56049 + ] + }, + "properties": { + "PLAC": "Dahlen,,,SACHSEN-ANHALT,DEUTSCHLAND," + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 11.61667, + 51.63333 + ] + }, + "properties": { + "PLAC": "Gerbstedt,,,SACHSEN-ANHALT,DEUTSCHLAND," + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 11.67008, + 52.077432 + ] + }, + "properties": { + "PLAC": "Salbke,39122,Magdeburg,SACHSEN-ANHALT,DEUTSCHLAND," + } + } + ] +}`; + const data3 = ` +{ + "type": "FeatureCollection", + "crs": { + "type": "name", + "properties": { + "name": "urn:ogc:def:crs:OGC:1.3:CRS84" + } + }, + "source": "© GeoBasis-DE / BKG 2013 (Daten verändert)", + "features": [ + { + "type": "Feature", + "properties": { + "ADE": 4, + "GF": 4, + "BSG": 1, + "RS": "01001", + "AGS": "01001", + "SDV_RS": "010010000000", + "GEN": "Flensburg", + "BEZ": "Kreisfreie Stadt", + "IBZ": 40, + "BEM": "--", + "NBD": "ja", + "SN_L": "01", + "SN_R": "0", + "SN_K": "01", + "SN_V1": "00", + "SN_V2": "00", + "SN_G": "000", + "FK_S3": "R", + "NUTS": "DEF01", + "RS_0": "010010000000", + "AGS_0": "01001000", + "WSK": "2008/01/01", + "DEBKG_ID": "DEBKGDL20000002R", + "destatis": { + "population": 89504, + "population_m": 44599, + "population_w": 44905 + } + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 9.412664108896104, + 54.8226409083269 + ], + [ + 9.42293404920774, + 54.82322297871825 + ], + [ + 9.437558196380342, + 54.80891340493068 + ], + [ + 9.428511475527905, + 54.800088026152174 + ], + [ + 9.436697178516075, + 54.78874409302239 + ], + [ + 9.438340826643119, + 54.803003371483264 + ], + [ + 9.442017460283664, + 54.8045726244278 + ], + [ + 9.440602675507414, + 54.800542052052265 + ], + [ + 9.44377911558914, + 54.805138125360195 + ], + [ + 9.45272991922579, + 54.80754852411532 + ], + [ + 9.457996005091418, + 54.81748018625207 + ], + [ + 9.467334502006006, + 54.82363231794173 + ], + [ + 9.490818773428426, + 54.82360740385943 + ], + [ + 9.492051905584956, + 54.81484953235985 + ], + [ + 9.505431081139028, + 54.81020748745684 + ], + [ + 9.503832434139722, + 54.804173364192415 + ], + [ + 9.499697545164823, + 54.803620434251044 + ], + [ + 9.505415377819059, + 54.799599946181225 + ], + [ + 9.49897574907422, + 54.79865203655195 + ], + [ + 9.506569452474432, + 54.79209752788728 + ], + [ + 9.502180299595505, + 54.77927472276625 + ], + [ + 9.505817873713303, + 54.77311082549625 + ], + [ + 9.49333489486901, + 54.76925229907075 + ], + [ + 9.474710042387382, + 54.7723792229719 + ], + [ + 9.47224934519076, + 54.76692973048646 + ], + [ + 9.460273736377953, + 54.76028982884087 + ], + [ + 9.460965252316052, + 54.75421956913382 + ], + [ + 9.453087744581662, + 54.75211765297414 + ], + [ + 9.379014517860757, + 54.75320021546097 + ], + [ + 9.35722059154765, + 54.77948194407854 + ], + [ + 9.405606646774057, + 54.79555897805081 + ], + [ + 9.402263643524694, + 54.808318620169885 + ], + [ + 9.411513078436418, + 54.816008174158625 + ], + [ + 9.404694993761636, + 54.82248286917383 + ], + [ + 9.412664108896104, + 54.8226409083269 + ] + ] + ] + } + } + ] +} +`; + const geoJson: GeoJSON.FeatureCollection = JSON.parse(data); + const geoJson2: GeoJSON.FeatureCollection = JSON.parse(data2); + const geoJson3: GeoJSON.FeatureCollection = JSON.parse(data3); + + return [ + new MapLayer('a').addData( + geoJson.features.map((f, i) => new MapGeometryWithData(i, f.geometry)), + ), + new MapLayer('b').addData( + geoJson2.features.map((f, i) => new MapGeometryWithData(i, f.geometry)), + ), + new MapLayer('Landkreise').addData( + geoJson3.features.map((f, i) => new MapGeometryWithData(i, f.geometry)), + ), + ]; +} diff --git a/src/app/views/querying/gis/models/visualization-configuration.interface.ts b/src/app/views/querying/gis/models/visualization-configuration.interface.ts new file mode 100644 index 00000000..d833ad4e --- /dev/null +++ b/src/app/views/querying/gis/models/visualization-configuration.interface.ts @@ -0,0 +1,3 @@ +export interface MapLayerConfigurationComponent { + +} diff --git a/src/app/views/querying/gis/models/visualization.interface.ts b/src/app/views/querying/gis/models/visualization.interface.ts new file mode 100644 index 00000000..fcb48469 --- /dev/null +++ b/src/app/views/querying/gis/models/visualization.interface.ts @@ -0,0 +1,16 @@ +import { MapLayerConfigurationComponent } from './visualization-configuration.interface'; +import { Type } from '@angular/core'; +import {MapGeometryWithData} from './MapGeometryWithData.model'; +import {MapLayerConfiguration} from './MapLayerConfiguration.interface'; + +export interface Visualization extends MapLayerConfiguration { + name: string; + + apply(): void; + + copy(): Visualization; + + init(data: MapGeometryWithData[]): void; + + getValueForAttribute(attr: string, data: MapGeometryWithData): string | number; +} diff --git a/src/app/views/querying/gis/services/layersettings.service.ts b/src/app/views/querying/gis/services/layersettings.service.ts new file mode 100644 index 00000000..fbae3d50 --- /dev/null +++ b/src/app/views/querying/gis/services/layersettings.service.ts @@ -0,0 +1,124 @@ +import {Injectable} from '@angular/core'; +import {BehaviorSubject, Observable, Subject} from 'rxjs'; +import {MapLayer} from '../models/MapLayer.model'; +import {Visualization} from '../models/visualization.interface'; +import {CombinedResult} from '../../../../components/data-view/data-view.model'; +import {MapLayerConfiguration} from '../models/MapLayerConfiguration.interface'; +import {Polygon} from 'geojson'; + + +@Injectable({ + providedIn: 'root', +}) +export class LayerSettingsService { + // Filter + private layerEnableDrawingMode: BehaviorSubject; + layerEnableDrawingMode$: Observable; + private layerDisableDrawingMode: BehaviorSubject; + layerDisableDrawingMode$: Observable; + private layerPolygonFilter: BehaviorSubject<[MapLayer, Polygon]>; + layerPolygonFilter$: Observable<[MapLayer, Polygon]>; + private fitLayerToMap: BehaviorSubject; + fitLayerToMap$: Observable; + private queryFromConsoleResults: BehaviorSubject; + queryFromConsoleResults$: Observable; + private selectedBaseLayer: BehaviorSubject; + selectedBaseLayer$: Observable; + private layers: BehaviorSubject; + layers$: Observable; + private modifiedConfig: BehaviorSubject; + modifiedConfig$: Observable; + private canRerenderLayers: BehaviorSubject; + canRerenderLayers$: Observable; + private rerenderButtonClickedSubject: Subject; + rerenderButtonClicked$: Observable; + private toggleLayerVisibilitySubject: Subject; + toggleLayerVisibility$: Observable; + + private editQueryForMapLayer: Subject; + editQueryForMapLayer$: Observable; + + constructor() { + this.reset(); + } + + reset() { + // To make sure that information from one session does not spill over the next session, we recreate all + // reactive variables, so that they don't hold any old values. Otherwise, the next time we subscribe, we will + // receive the most recent value, which could be from an old session. + this.layerEnableDrawingMode = new BehaviorSubject(null); + this.layerEnableDrawingMode$ = this.layerEnableDrawingMode.asObservable(); + this.layerDisableDrawingMode = new BehaviorSubject(null); + this.layerDisableDrawingMode$ = this.layerDisableDrawingMode.asObservable(); + this.layerPolygonFilter = new BehaviorSubject<[MapLayer, Polygon]>(null); + this.layerPolygonFilter$ = this.layerPolygonFilter.asObservable(); + this.fitLayerToMap = new BehaviorSubject(null); + this.fitLayerToMap$ = this.fitLayerToMap.asObservable(); + this.queryFromConsoleResults = new BehaviorSubject(null); + this.queryFromConsoleResults$ = this.queryFromConsoleResults.asObservable(); + this.selectedBaseLayer = new BehaviorSubject('EMPTY'); + this.selectedBaseLayer$ = this.selectedBaseLayer.asObservable(); + this.layers = new BehaviorSubject([]); + this.layers$ = this.layers.asObservable(); + this.modifiedConfig = new BehaviorSubject(null); + this.modifiedConfig$ = this.modifiedConfig.asObservable(); + this.canRerenderLayers = new BehaviorSubject(false); + this.canRerenderLayers$ = this.canRerenderLayers.asObservable(); + this.rerenderButtonClickedSubject = new Subject(); + this.rerenderButtonClicked$ = this.rerenderButtonClickedSubject.asObservable(); + this.toggleLayerVisibilitySubject = new Subject(); + this.toggleLayerVisibility$ = this.toggleLayerVisibilitySubject.asObservable(); + this.editQueryForMapLayer = new Subject(); + this.editQueryForMapLayer$ = this.editQueryForMapLayer.asObservable(); + } + + editQuery(layer: MapLayer) { + this.editQueryForMapLayer.next(layer); + } + + disableDrawingModeForLayer(layer: MapLayer) { + layer.filterConfig.isDrawingModeActive = false; + this.layerDisableDrawingMode.next(layer); + } + + enableDrawingModeForLayer(layer: MapLayer) { + layer.filterConfig.isDrawingModeActive = true; + this.layerEnableDrawingMode.next(layer); + } + + addPolygonToLayer(layer: MapLayer, polygon: Polygon) { + this.layerPolygonFilter.next([layer, polygon]); + } + + setFitLayerToMap(layer: MapLayer) { + this.fitLayerToMap.next(layer); + } + + setResultsQuery(result: CombinedResult) { + this.queryFromConsoleResults.next(result); + } + + setBaseLayer(item: string) { + this.selectedBaseLayer.next(item); + } + + setLayers(layers: MapLayer[]) { + this.layers.next(layers); + } + + visualizationConfigurationChanged(config: MapLayerConfiguration): void { + this.modifiedConfig.next(config); + } + + setCanRerenderLayers(canRerenderMap: boolean) { + this.canRerenderLayers.next(canRerenderMap); + } + + rerenderButtonClicked() { + this.rerenderButtonClickedSubject.next(); + } + + toggleLayerVisibility(layer: MapLayer) { + this.toggleLayerVisibilitySubject.next(layer); + } +} diff --git a/src/app/views/querying/querying.component.html b/src/app/views/querying/querying.component.html index fbd01b1c..b357962d 100644 --- a/src/app/views/querying/querying.component.html +++ b/src/app/views/querying/querying.component.html @@ -2,5 +2,6 @@ + diff --git a/src/app/views/views.module.ts b/src/app/views/views.module.ts index 98b3f480..52429630 100644 --- a/src/app/views/views.module.ts +++ b/src/app/views/views.module.ts @@ -63,7 +63,7 @@ import { FormCheckLabelDirective, FormControlDirective, FormDirective, - FormFeedbackComponent, + FormFeedbackComponent, FormLabelDirective, FormSelectDirective, FormTextDirective, GutterDirective, @@ -78,7 +78,7 @@ import { ModalHeaderComponent, ModalTitleDirective, ModalToggleDirective, - PlaceholderDirective, + PlaceholderDirective, PopoverDirective, ProgressBarComponent, ProgressComponent, RowComponent, @@ -91,6 +91,21 @@ import {EditEntityComponent} from './schema-editing/edit-entity/edit-entity.comp import {TreeModule} from '@ali-hm/angular-tree-component'; import {PolyalgComponent, ScrollToDirective} from './querying/polyalg/polyalg.component'; import {NgChartsModule} from 'ng2-charts'; +import {GisComponent} from './querying/gis/gis.component'; +import {MapLayersComponent} from './querying/gis/components/layers/map-layers.component'; +import { + SubmitQueryButtonComponent +} from './querying/console/components/submit-query-button/submit-query-button.component'; +import {QueryEditor} from './querying/console/components/code-editor/query-editor.component'; +import {ConfigSectionComponent} from './querying/gis/components/config-section/config-section.component'; +import {AreaShapeComponent} from './querying/gis/components/visualization/area-shape/area-shape.component'; +import {ColorComponent} from './querying/gis/components/visualization/color/color.component'; +import {EmptyComponent} from './querying/gis/components/visualization/empty/empty.component'; +import {LabelComponent} from './querying/gis/components/visualization/label/label.component'; +import {PointShapeComponent} from './querying/gis/components/visualization/point-shape/point-shape.component'; +import {NgxJsonViewerModule} from 'ngx-json-viewer'; +import {DataPreviewComponent} from './querying/gis/components/configuration/data-preview/data-preview.component'; +import {FilterComponent} from './querying/gis/components/configuration/filter/filter.component'; @NgModule({ @@ -162,13 +177,17 @@ import {NgChartsModule} from 'ng2-charts'; CollapseDirective, ButtonToolbarComponent, AlertComponent, - NgChartsModule + NgChartsModule, + NgxJsonViewerModule ], declarations: [ EditColumnsComponent, FormGeneratorComponent, GraphicalQueryingComponent, + GisComponent, ConsoleComponent, + SubmitQueryButtonComponent, + QueryEditor, TableViewComponent, UmlComponent, SchemaEditingComponent, @@ -187,12 +206,21 @@ import {NgChartsModule} from 'ng2-charts'; StatisticsColumnComponent, ValuePipe, SearchFilterPipe, - MapValuesPipe, FileUploaderComponent, DockerconfigComponent, EditEntityComponent, PolyalgComponent, - ScrollToDirective + ScrollToDirective, + // GIS + MapLayersComponent, + ConfigSectionComponent, + AreaShapeComponent, + ColorComponent, + EmptyComponent, + LabelComponent, + PointShapeComponent, + DataPreviewComponent, + FilterComponent, ], exports: [] }) diff --git a/src/scss/_custom.scss b/src/scss/_custom.scss index 2475c180..703d4072 100644 --- a/src/scss/_custom.scss +++ b/src/scss/_custom.scss @@ -46,10 +46,14 @@ audio, video { background-color: $footer-bg; } - .add-btn { width: 50px; height: 50px; margin-top: 90px; flex: 0; } + +.map-layer-hidden { + /* This classes is added through JavaScript, and needs to be included in the global style to work. */ + visibility: hidden; +}