diff --git a/client/package-lock.json b/client/package-lock.json index 8e1b9cfd..74e77a0e 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -19,6 +19,7 @@ "django-s3-file-field": "1.1.0", "geojs": "1.19.0", "lodash": "4.17.23", + "maplibre-gl": "5.17.0", "vue": "3.5.30", "vue-router": "5.0.4", "vuetify": "3.12.3" @@ -33,7 +34,7 @@ "eslint-plugin-vue": "10.8.0", "sass-embedded": "^1.98.0", "typescript": "5.9.3", - "vite": "8.0.2", + "vite": "8.0.3", "vite-plugin-vuetify": "2.1.3", "vue-tsc": "3.2.6" } @@ -1641,22 +1642,24 @@ } }, "node_modules/@emnapi/core": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.0.tgz", - "integrity": "sha512-0DQ98G9ZQZOxfUcQn1waV2yS8aWdZ6kJMbYCJB3oUBecjWYO1fqJ+a1DRfPF3O5JEkwqwP1A9QEN/9mYm2Yd0w==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz", + "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" } }, "node_modules/@emnapi/runtime": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.0.tgz", - "integrity": "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz", + "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.4.0" } @@ -1667,6 +1670,7 @@ "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.4.0" } @@ -1880,6 +1884,115 @@ "node": ">=v12.0.0" } }, + "node_modules/@mapbox/geojson-rewind": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.2.tgz", + "integrity": "sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA==", + "license": "ISC", + "dependencies": { + "get-stream": "^6.0.1", + "minimist": "^1.2.6" + }, + "bin": { + "geojson-rewind": "geojson-rewind" + } + }, + "node_modules/@mapbox/jsonlint-lines-primitives": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz", + "integrity": "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@mapbox/point-geometry": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-1.1.0.tgz", + "integrity": "sha512-YGcBz1cg4ATXDCM/71L9xveh4dynfGmcLDqufR+nQQy3fKwsAZsWd/x4621/6uJaeB9mwOHE6hPeDgXz9uViUQ==", + "license": "ISC" + }, + "node_modules/@mapbox/tiny-sdf": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.7.tgz", + "integrity": "sha512-25gQLQMcpivjOSA40g3gO6qgiFPDpWRoMfd+G/GoppPIeP6JDaMMkMrEJnMZhKyyS6iKwVt5YKu02vCUyJM3Ug==", + "license": "BSD-2-Clause" + }, + "node_modules/@mapbox/unitbezier": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz", + "integrity": "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==", + "license": "BSD-2-Clause" + }, + "node_modules/@mapbox/vector-tile": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-2.0.4.tgz", + "integrity": "sha512-AkOLcbgGTdXScosBWwmmD7cDlvOjkg/DetGva26pIRiZPdeJYjYKarIlb4uxVzi6bwHO6EWH82eZ5Nuv4T5DUg==", + "license": "BSD-3-Clause", + "dependencies": { + "@mapbox/point-geometry": "~1.1.0", + "@types/geojson": "^7946.0.16", + "pbf": "^4.0.1" + } + }, + "node_modules/@mapbox/whoots-js": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz", + "integrity": "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==", + "license": "ISC", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@maplibre/geojson-vt": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@maplibre/geojson-vt/-/geojson-vt-5.0.4.tgz", + "integrity": "sha512-KGg9sma45S+stfH9vPCJk1J0lSDLWZgCT9Y8u8qWZJyjFlP8MNP1WGTxIMYJZjDvVT3PDn05kN1C95Sut1HpgQ==", + "license": "ISC" + }, + "node_modules/@maplibre/maplibre-gl-style-spec": { + "version": "24.7.0", + "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-24.7.0.tgz", + "integrity": "sha512-Ed7rcKYU5iELfablg9Mj+TVCsXsPBgdMyXPRAxb2v7oWg9YJnpQdZ5msDs1LESu/mtXy3Z48Vdppv2t/x5kAhw==", + "license": "ISC", + "dependencies": { + "@mapbox/jsonlint-lines-primitives": "~2.0.2", + "@mapbox/unitbezier": "^0.0.1", + "json-stringify-pretty-compact": "^4.0.0", + "minimist": "^1.2.8", + "quickselect": "^3.0.0", + "rw": "^1.3.3", + "tinyqueue": "^3.0.0" + }, + "bin": { + "gl-style-format": "dist/gl-style-format.mjs", + "gl-style-migrate": "dist/gl-style-migrate.mjs", + "gl-style-validate": "dist/gl-style-validate.mjs" + } + }, + "node_modules/@maplibre/mlt": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@maplibre/mlt/-/mlt-1.1.8.tgz", + "integrity": "sha512-8vtfYGidr1rNkv5IwIoU2lfe3Oy+Wa8HluzQYcQi9cveU9K3pweAal/poQj4GJ0K/EW4bTQp2wVAs09g2yDRZg==", + "license": "(MIT OR Apache-2.0)", + "dependencies": { + "@mapbox/point-geometry": "^1.1.0" + } + }, + "node_modules/@maplibre/vt-pbf": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@maplibre/vt-pbf/-/vt-pbf-4.3.0.tgz", + "integrity": "sha512-jIvp8F5hQCcreqOOpEt42TJMUlsrEcpf/kI1T2v85YrQRV6PPXUcEXUg5karKtH6oh47XJZ4kHu56pUkOuqA7w==", + "license": "MIT", + "dependencies": { + "@mapbox/point-geometry": "^1.1.0", + "@mapbox/vector-tile": "^2.0.4", + "@maplibre/geojson-vt": "^5.0.4", + "@types/geojson": "^7946.0.16", + "@types/supercluster": "^7.1.3", + "pbf": "^4.0.1", + "supercluster": "^8.0.1" + } + }, "node_modules/@mdi/font": { "version": "7.4.47", "resolved": "https://registry.npmjs.org/@mdi/font/-/font-7.4.47.tgz", @@ -1903,6 +2016,24 @@ "node": ">= 10" } }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.2.tgz", + "integrity": "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2140,9 +2271,6 @@ "cpu": [ "arm" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2163,9 +2291,6 @@ "cpu": [ "arm" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2186,9 +2311,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2209,9 +2331,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2232,9 +2351,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2255,9 +2371,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2344,9 +2457,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.11.tgz", - "integrity": "sha512-SJ+/g+xNnOh6NqYxD0V3uVN4W3VfnrGsC9/hoglicgTNfABFG9JjISvkkU0dNY84MNHLWyOgxP9v9Y9pX4S7+A==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.12.tgz", + "integrity": "sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==", "cpu": [ "arm64" ], @@ -2360,9 +2473,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.11.tgz", - "integrity": "sha512-7WQgR8SfOPwmDZGFkThUvsmd/nwAWv91oCO4I5LS7RKrssPZmOt7jONN0cW17ydGC1n/+puol1IpoieKqQidmg==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.12.tgz", + "integrity": "sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==", "cpu": [ "arm64" ], @@ -2376,9 +2489,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.11.tgz", - "integrity": "sha512-39Ks6UvIHq4rEogIfQBoBRusj0Q0nPVWIvqmwBLaT6aqQGIakHdESBVOPRRLacy4WwUPIx4ZKzfZ9PMW+IeyUQ==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.12.tgz", + "integrity": "sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==", "cpu": [ "x64" ], @@ -2392,9 +2505,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.11.tgz", - "integrity": "sha512-jfsm0ZHfhiqrvWjJAmzsqiIFPz5e7mAoCOPBNTcNgkiid/LaFKiq92+0ojH+nmJmKYkre4t71BWXUZDNp7vsag==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.12.tgz", + "integrity": "sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==", "cpu": [ "x64" ], @@ -2408,9 +2521,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.11.tgz", - "integrity": "sha512-zjQaUtSyq1nVe3nxmlSCuR96T1LPlpvmJ0SZy0WJFEsV4kFbXcq2u68L4E6O0XeFj4aex9bEauqjW8UQBeAvfQ==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.12.tgz", + "integrity": "sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==", "cpu": [ "arm" ], @@ -2424,9 +2537,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.11.tgz", - "integrity": "sha512-WMW1yE6IOnehTcFE9eipFkm3XN63zypWlrJQ2iF7NrQ9b2LDRjumFoOGJE8RJJTJCTBAdmLMnJ8uVitACUUo1Q==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==", "cpu": [ "arm64" ], @@ -2440,9 +2553,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.11.tgz", - "integrity": "sha512-jfndI9tsfm4APzjNt6QdBkYwre5lRPUgHeDHoI7ydKUuJvz3lZeCfMsI56BZj+7BYqiKsJm7cfd/6KYV7ubrBg==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.12.tgz", + "integrity": "sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==", "cpu": [ "arm64" ], @@ -2456,9 +2569,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.11.tgz", - "integrity": "sha512-ZlFgw46NOAGMgcdvdYwAGu2Q+SLFA9LzbJLW+iyMOJyhj5wk6P3KEE9Gct4xWwSzFoPI7JCdYmYMzVtlgQ+zfw==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==", "cpu": [ "ppc64" ], @@ -2472,9 +2585,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.11.tgz", - "integrity": "sha512-hIOYmuT6ofM4K04XAZd3OzMySEO4K0/nc9+jmNcxNAxRi6c5UWpqfw3KMFV4MVFWL+jQsSh+bGw2VqmaPMTLyw==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==", "cpu": [ "s390x" ], @@ -2488,9 +2601,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.11.tgz", - "integrity": "sha512-qXBQQO9OvkjjQPLdUVr7Nr2t3QTZI7s4KZtfw7HzBgjbmAPSFwSv4rmET9lLSgq3rH/ndA3ngv3Qb8l2njoPNA==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==", "cpu": [ "x64" ], @@ -2504,9 +2617,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.11.tgz", - "integrity": "sha512-/tpFfoSTzUkH9LPY+cYbqZBDyyX62w5fICq9qzsHLL8uTI6BHip3Q9Uzft0wylk/i8OOwKik8OxW+QAhDmzwmg==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.12.tgz", + "integrity": "sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==", "cpu": [ "x64" ], @@ -2520,9 +2633,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.11.tgz", - "integrity": "sha512-mcp3Rio2w72IvdZG0oQ4bM2c2oumtwHfUfKncUM6zGgz0KgPz4YmDPQfnXEiY5t3+KD/i8HG2rOB/LxdmieK2g==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.12.tgz", + "integrity": "sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==", "cpu": [ "arm64" ], @@ -2536,9 +2649,9 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.11.tgz", - "integrity": "sha512-LXk5Hii1Ph9asuGRjBuz8TUxdc1lWzB7nyfdoRgI0WGPZKmCxvlKk8KfYysqtr4MfGElu/f/pEQRh8fcEgkrWw==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.12.tgz", + "integrity": "sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==", "cpu": [ "wasm32" ], @@ -2551,26 +2664,10 @@ "node": ">=14.0.0" } }, - "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", - "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1", - "@tybys/wasm-util": "^0.10.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - } - }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.11.tgz", - "integrity": "sha512-dDwf5otnx0XgRY1yqxOC4ITizcdzS/8cQ3goOWv3jFAo4F+xQYni+hnMuO6+LssHHdJW7+OCVL3CoU4ycnh35Q==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.12.tgz", + "integrity": "sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==", "cpu": [ "arm64" ], @@ -2584,9 +2681,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.11.tgz", - "integrity": "sha512-LN4/skhSggybX71ews7dAj6r2geaMJfm3kMbK2KhFMg9B10AZXnKoLCVVgzhMHL0S+aKtr4p8QbAW8k+w95bAA==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.12.tgz", + "integrity": "sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==", "cpu": [ "x64" ], @@ -3651,6 +3748,15 @@ "integrity": "sha512-TC0dmN0K8YcWEAEfiPi5gJP14eJe30TTGjkvek3iM/1NdHHsdCA/Td6GvNndMOo/iSnIsZ4HuuhrYPDAmbxzww==", "license": "MIT" }, + "node_modules/@types/supercluster": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/supercluster/-/supercluster-7.1.3.tgz", + "integrity": "sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/webxr": { "version": "0.5.24", "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz", @@ -5941,6 +6047,18 @@ "node": ">= 0.4" } }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -6330,6 +6448,12 @@ "dev": true, "license": "MIT" }, + "node_modules/json-stringify-pretty-compact": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-4.0.0.tgz", + "integrity": "sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==", + "license": "MIT" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -6703,6 +6827,49 @@ "url": "https://github.com/sponsors/sxzz" } }, + "node_modules/maplibre-gl": { + "version": "5.17.0", + "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-5.17.0.tgz", + "integrity": "sha512-gwS6NpXBfWD406dtT5YfEpl2hmpMm+wcPqf04UAez/TxY1OBjiMdK2ZoMGcNIlGHelKc4+Uet6zhDdDEnlJVHA==", + "license": "BSD-3-Clause", + "dependencies": { + "@mapbox/geojson-rewind": "^0.5.2", + "@mapbox/jsonlint-lines-primitives": "^2.0.2", + "@mapbox/point-geometry": "^1.1.0", + "@mapbox/tiny-sdf": "^2.0.7", + "@mapbox/unitbezier": "^0.0.1", + "@mapbox/vector-tile": "^2.0.4", + "@mapbox/whoots-js": "^3.1.0", + "@maplibre/geojson-vt": "^5.0.4", + "@maplibre/maplibre-gl-style-spec": "^24.4.1", + "@maplibre/mlt": "^1.1.2", + "@maplibre/vt-pbf": "^4.2.1", + "@types/geojson": "^7946.0.16", + "@types/supercluster": "^7.1.3", + "earcut": "^3.0.2", + "gl-matrix": "^3.4.4", + "kdbush": "^4.0.2", + "murmurhash-js": "^1.0.0", + "pbf": "^4.0.1", + "potpack": "^2.1.0", + "quickselect": "^3.0.0", + "supercluster": "^8.0.1", + "tinyqueue": "^3.0.0" + }, + "engines": { + "node": ">=16.14.0", + "npm": ">=8.1.0" + }, + "funding": { + "url": "https://github.com/maplibre/maplibre-gl-js?sponsor=1" + } + }, + "node_modules/maplibre-gl/node_modules/gl-matrix": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.4.tgz", + "integrity": "sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==", + "license": "MIT" + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -6877,6 +7044,12 @@ "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", "license": "MIT" }, + "node_modules/murmurhash-js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz", + "integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==", + "license": "MIT" + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -7141,6 +7314,18 @@ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "license": "MIT" }, + "node_modules/pbf": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pbf/-/pbf-4.0.1.tgz", + "integrity": "sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA==", + "license": "BSD-3-Clause", + "dependencies": { + "resolve-protobuf-schema": "^2.1.0" + }, + "bin": { + "pbf": "bin/pbf" + } + }, "node_modules/perfect-debounce": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", @@ -7154,9 +7339,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", "engines": { "node": ">=12" @@ -7226,6 +7411,12 @@ "optional": true, "peer": true }, + "node_modules/potpack": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/potpack/-/potpack-2.1.0.tgz", + "integrity": "sha512-pcaShQc1Shq0y+E7GqJqvZj8DTthWV1KeHGdi0Z6IAin2Oi3JnLCOfwnCo84qc+HAp52wT9nK9H7FAJp5a44GQ==", + "license": "ISC" + }, "node_modules/prebuild-install": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", @@ -7286,6 +7477,12 @@ "url": "https://github.com/sponsors/ahocevar" } }, + "node_modules/protocol-buffers-schema": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", + "integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==", + "license": "MIT" + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -7349,6 +7546,12 @@ ], "license": "MIT" }, + "node_modules/quickselect": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz", + "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==", + "license": "ISC" + }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -7497,6 +7700,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-protobuf-schema": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz", + "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==", + "license": "MIT", + "dependencies": { + "protocol-buffers-schema": "^3.3.1" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -7515,14 +7727,14 @@ "license": "Unlicense" }, "node_modules/rolldown": { - "version": "1.0.0-rc.11", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.11.tgz", - "integrity": "sha512-NRjoKMusSjfRbSYiH3VSumlkgFe7kYAa3pzVOsVYVFY3zb5d7nS+a3KGQ7hJKXuYWbzJKPVQ9Wxq2UvyK+ENpw==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.12.tgz", + "integrity": "sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==", "devOptional": true, "license": "MIT", "dependencies": { "@oxc-project/types": "=0.122.0", - "@rolldown/pluginutils": "1.0.0-rc.11" + "@rolldown/pluginutils": "1.0.0-rc.12" }, "bin": { "rolldown": "bin/cli.mjs" @@ -7531,27 +7743,27 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.11", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.11", - "@rolldown/binding-darwin-x64": "1.0.0-rc.11", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.11", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.11", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.11", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.11", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.11", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.11", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.11", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.11", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.11", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.11", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.11", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.11" + "@rolldown/binding-android-arm64": "1.0.0-rc.12", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.12", + "@rolldown/binding-darwin-x64": "1.0.0-rc.12", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.12", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.12", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.12", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.12", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.12", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.12", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.12", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.12" } }, "node_modules/rolldown/node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.11", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.11.tgz", - "integrity": "sha512-xQO9vbwBecJRv9EUcQ/y0dzSTJgA7Q6UVN7xp6B81+tBGSLVAK03yJ9NkJaUA7JFD91kbjxRSC/mDnmvXzbHoQ==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.12.tgz", + "integrity": "sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==", "devOptional": true, "license": "MIT" }, @@ -7849,7 +8061,6 @@ "cpu": [ "arm" ], - "libc": "glibc", "license": "MIT", "optional": true, "os": [ @@ -7866,7 +8077,6 @@ "cpu": [ "arm64" ], - "libc": "glibc", "license": "MIT", "optional": true, "os": [ @@ -7883,7 +8093,6 @@ "cpu": [ "arm" ], - "libc": "musl", "license": "MIT", "optional": true, "os": [ @@ -7900,7 +8109,6 @@ "cpu": [ "arm64" ], - "libc": "musl", "license": "MIT", "optional": true, "os": [ @@ -7917,7 +8125,6 @@ "cpu": [ "riscv64" ], - "libc": "musl", "license": "MIT", "optional": true, "os": [ @@ -7934,7 +8141,6 @@ "cpu": [ "x64" ], - "libc": "musl", "license": "MIT", "optional": true, "os": [ @@ -7951,7 +8157,6 @@ "cpu": [ "riscv64" ], - "libc": "glibc", "license": "MIT", "optional": true, "os": [ @@ -7968,7 +8173,6 @@ "cpu": [ "x64" ], - "libc": "glibc", "license": "MIT", "optional": true, "os": [ @@ -8220,6 +8424,15 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/supercluster": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz", + "integrity": "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==", + "license": "ISC", + "dependencies": { + "kdbush": "^4.0.2" + } + }, "node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -8316,6 +8529,12 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tinyqueue": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-3.0.0.tgz", + "integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==", + "license": "ISC" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -8585,16 +8804,16 @@ "license": "MIT" }, "node_modules/vite": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.2.tgz", - "integrity": "sha512-1gFhNi+bHhRE/qKZOJXACm6tX4bA3Isy9KuKF15AgSRuRazNBOJfdDemPBU16/mpMxApDPrWvZ08DcLPEoRnuA==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.3.tgz", + "integrity": "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==", "devOptional": true, "license": "MIT", "dependencies": { "lightningcss": "^1.32.0", - "picomatch": "^4.0.3", + "picomatch": "^4.0.4", "postcss": "^8.5.8", - "rolldown": "1.0.0-rc.11", + "rolldown": "1.0.0-rc.12", "tinyglobby": "^0.2.15" }, "bin": { diff --git a/client/package.json b/client/package.json index d3179d68..8512561b 100644 --- a/client/package.json +++ b/client/package.json @@ -24,6 +24,7 @@ "django-s3-file-field": "1.1.0", "geojs": "1.19.0", "lodash": "4.17.23", + "maplibre-gl": "5.17.0", "vue": "3.5.30", "vue-router": "5.0.4", "vuetify": "3.12.3" @@ -38,7 +39,7 @@ "eslint-plugin-vue": "10.8.0", "sass-embedded": "^1.98.0", "typescript": "5.9.3", - "vite": "8.0.2", + "vite": "8.0.3", "vite-plugin-vuetify": "2.1.3", "vue-tsc": "3.2.6" } diff --git a/client/src/api/api.ts b/client/src/api/api.ts index e646bbc7..b635c97d 100644 --- a/client/src/api/api.ts +++ b/client/src/api/api.ts @@ -1,6 +1,7 @@ import axios from "axios"; import { AxiosError } from "axios"; import { SpectroInfo } from "@components/geoJS/geoJSUtils"; +import type { FeatureCollection, Point } from "geojson"; export interface Recording { id: number; @@ -31,6 +32,13 @@ export interface Recording { tags_text?: string[]; } +export type RecordingLocationsFeatureProperties = { + recording_id: number; + filename: string; +}; + +export type RecordingLocationsGeoJson = FeatureCollection; + export interface Species { species_code: string; family: string; @@ -287,6 +295,8 @@ export interface RecordingListParams { sort_direction?: 'asc' | 'desc'; page?: number; limit?: number; + /** WGS84 [minLon, minLat, maxLon, maxLat]; recordings must intersect this box. */ + bbox?: [number, number, number, number]; } /** Paginated recording list response (v-data-table-server compatible). */ @@ -315,6 +325,13 @@ async function getRecordings(getPublic = false, params?: RecordingListParams) { if (params.sort_direction) query.set('sort_direction', params.sort_direction); if (params.page !== undefined) query.set('page', String(params.page)); if (params.limit !== undefined) query.set('limit', String(params.limit)); + if ( + params.bbox !== undefined && + params.bbox.length === 4 && + params.bbox.every((n) => Number.isFinite(n)) + ) { + query.set('bbox', params.bbox.join(',')); + } } if (!params?.page) query.set('page', '1'); if (!params?.limit) query.set('limit', '20'); @@ -329,6 +346,8 @@ export interface UnsubmittedNeighborsParams { sort_direction?: 'asc' | 'desc'; /** Comma-separated or array of tag texts; recording must have all listed tags. */ tags?: string | string[]; + /** Bounding box filter (lon/lat) as `[min_lon, min_lat, max_lon, max_lat]`. */ + bbox?: [number, number, number, number]; } export interface UnsubmittedNeighborsResponse { @@ -347,6 +366,13 @@ async function getUnsubmittedNeighbors( const tagStr = Array.isArray(params.tags) ? params.tags.join(',') : params.tags; if (tagStr) query.set('tags', tagStr); } + if ( + params?.bbox !== undefined && + params.bbox.length === 4 && + params.bbox.every((n) => Number.isFinite(n)) + ) { + query.set('bbox', params.bbox.join(',')); + } const response = await axiosInstance.get( `/recording/unsubmitted-neighbors/?${query.toString()}` ); @@ -455,6 +481,28 @@ async function getCellBbox(cellId: number) { return await axiosInstance.get(`/grts/${cellId}/bbox`); } +export interface RecordingLocationsParams { + exclude_submitted?: boolean; + /** Comma-separated or array of tag texts; recording must have all listed tags. */ + tags?: string | string[]; + /** Bounding box filter (lon/lat) as `[min_lon, min_lat, max_lon, max_lat]`. */ + bbox?: [number, number, number, number]; +} + +async function getRecordingLocations(params?: RecordingLocationsParams) { + const query = new URLSearchParams(); + if (params?.exclude_submitted !== undefined) query.set("exclude_submitted", String(params.exclude_submitted)); + if (params?.tags !== undefined) { + const tagStr = Array.isArray(params.tags) ? params.tags.join(",") : params.tags; + if (tagStr) query.set("tags", tagStr); + } + if (params?.bbox !== undefined && params.bbox.length === 4 && params.bbox.every((n) => Number.isFinite(n))) { + query.set("bbox", params.bbox.join(',')); + } + const qs = query.toString(); + return axiosInstance.get(`/recording-locations/${qs ? `?${qs}` : ""}`); +} + async function getFileAnnotations(recordingId: number) { return axiosInstance.get(`recording/${recordingId}/recording-annotations`); } @@ -706,6 +754,7 @@ export { getCellLocation, getCellBbox, getCellfromLocation, + getRecordingLocations, getGuanoMetadata, getFileAnnotations, putFileAnnotation, diff --git a/client/src/components/MapLocation.vue b/client/src/components/MapLocation.vue index 24714a54..6e512096 100644 --- a/client/src/components/MapLocation.vue +++ b/client/src/components/MapLocation.vue @@ -21,6 +21,10 @@ export default defineComponent({ type: Object as PropType<{ x?: number; y?: number } | undefined>, default: () => undefined, }, + bbox: { + type: Array as unknown as PropType<[number, number, number, number] | null>, + default: null, + }, grtsCellId: { type: Number, default: undefined, @@ -63,8 +67,35 @@ export default defineComponent({ position: { bottom: 10, left: 10}, }); + const drawBbox = (bounds: [number, number, number, number]) => { + const [west, south, east, north] = bounds; + const data = [ + { x: west, y: south }, + { x: east, y: south }, + { x: east, y: north }, + { x: west, y: north }, + { x: west, y: south }, + ]; + bboxFeature.value.data([data]).style({ + stroke: true, + strokeWidth: 2, + strokeColor: "black", + fill: true, + fillColor: "orange", + fillOpacity: 0.15, + }).draw(); + bboxLayer.value.draw(); + map.value.center({ x: (west + east) / 2, y: (south + north) / 2 }); + map.value.zoom(6); + }; - if (props.grtsCellId !== undefined) { + if ( + props.bbox + && props.bbox.length === 4 + && props.bbox.every((n) => Number.isFinite(n)) + ) { + drawBbox(props.bbox); + } else if (props.grtsCellId !== undefined) { const annotation = await getCellBbox(props.grtsCellId); const coordinates = annotation.data.geometry.coordinates; const data = coordinates.map((point: number[]) => ({ x: point[0], y: point[1] })); @@ -123,7 +154,36 @@ export default defineComponent({ } }); watch(() => props.updateMap, () => { - if (props.location?.x && props.location?.y) { + if ( + props.bbox + && props.bbox.length === 4 + && props.bbox.every((n) => Number.isFinite(n)) + && bboxFeature.value + && map.value + ) { + const [west, south, east, north] = props.bbox; + const data = [ + { x: west, y: south }, + { x: east, y: south }, + { x: east, y: north }, + { x: west, y: north }, + { x: west, y: south }, + ]; + bboxFeature.value + .data([data]) + .style({ + stroke: true, + strokeWidth: 2, + strokeColor: "black", + fill: true, + fillColor: "orange", + fillOpacity: 0.15, + }) + .draw(); + bboxLayer.value?.draw(); + map.value.center({ x: (west + east) / 2, y: (south + north) / 2 }); + map.value.zoom(6); + } else if (props.location?.x && props.location?.y) { markerLocation.value = { x: props.location?.x, y: props.location.y }; markerFeature.value .data([markerLocation.value]) diff --git a/client/src/components/RecordingLocationsMap.vue b/client/src/components/RecordingLocationsMap.vue new file mode 100644 index 00000000..30dd6a4c --- /dev/null +++ b/client/src/components/RecordingLocationsMap.vue @@ -0,0 +1,347 @@ + + + + + + diff --git a/client/src/components/RecordingTable.vue b/client/src/components/RecordingTable.vue index 56fa8e3d..8a03a6ff 100644 --- a/client/src/components/RecordingTable.vue +++ b/client/src/components/RecordingTable.vue @@ -36,6 +36,10 @@ export default defineComponent({ type: String as PropType<'my' | 'shared'>, required: true, }, + tags: { + type: Array as PropType, + default: undefined, + }, editRecording: { type: Function as PropType<(item: Recording) => void>, default: undefined, @@ -44,9 +48,14 @@ export default defineComponent({ type: Function as PropType<(recording: Recording) => void>, default: undefined, }, + /** When set, list requests include this WGS84 bbox [minLon, minLat, maxLon, maxLat]. */ + bboxFilter: { + type: Array as unknown as PropType<[number, number, number, number] | null>, + default: null, + }, }, - emits: [], - setup(props, { expose }) { + emits: ['update:tags'], + setup(props, { emit, expose }) { const { configuration, showSubmittedRecordings, @@ -66,13 +75,17 @@ export default defineComponent({ let intervalRef: number | null = null; const filterTagsModel = computed({ - get: () => (props.variant === 'my' ? filterTags.value : sharedFilterTags.value), + get: () => { + if (props.tags !== undefined) return props.tags; + return props.variant === 'my' ? filterTags.value : sharedFilterTags.value; + }, set: (v: string[]) => { - if (props.variant === 'my') { - filterTags.value = v; - } else { - sharedFilterTags.value = v; + if (props.tags !== undefined) { + emit('update:tags', v); + return; } + if (props.variant === 'my') filterTags.value = v; + else sharedFilterTags.value = v; }, }); @@ -231,7 +244,7 @@ export default defineComponent({ ? sortByFirst.key : 'created'; const sort_direction = sortByFirst?.order === 'asc' ? 'asc' : 'desc'; - const tags = props.variant === 'my' ? filterTags.value : sharedFilterTags.value; + const tags = filterTagsModel.value; const params: RecordingListParams = { page: options.page, limit: options.itemsPerPage, @@ -243,6 +256,10 @@ export default defineComponent({ if (props.variant === 'shared') { params.public = true; } + const bf = props.bboxFilter; + if (bf && bf.length === 4 && bf.every((n) => Number.isFinite(n))) { + params.bbox = bf; + } return params; } @@ -313,9 +330,8 @@ export default defineComponent({ } function addTagToFilter(tag: string) { - const arr = props.variant === 'my' ? filterTags : sharedFilterTags; - if (!arr.value.includes(tag)) { - arr.value = [...arr.value, tag]; + if (!filterTagsModel.value.includes(tag)) { + filterTagsModel.value = [...filterTagsModel.value, tag]; } } @@ -330,15 +346,6 @@ export default defineComponent({ const tableClass = computed(() => (props.variant === 'my' ? 'my-recordings' : 'shared-recordings')); - const listStyles = computed(() => { - const markEnabled = configuration.value.mark_annotations_completed_enabled; - let sectionHeight = markEnabled ? '35vh' : '40vh'; - if (props.variant === 'shared' && !configuration.value.is_admin && !configuration.value.non_admin_upload_enabled) { - sectionHeight = '75vh'; - } - return { height: sectionHeight, 'max-height': sectionHeight }; - }); - const showTotalCount = computed(() => ( totalCount.value > 0 && configuration.value.mark_annotations_completed_enabled @@ -354,6 +361,14 @@ export default defineComponent({ fetchRecordings(lastOptions.value); }); + watch( + () => props.bboxFilter, + () => { + fetchRecordings(lastOptions.value); + }, + { deep: true } + ); + onMounted(() => { fetchRecordings({ page: 1, itemsPerPage: 20 }); }); @@ -379,7 +394,6 @@ export default defineComponent({ onUpdateOptions, onUpdateSortBy, tableClass, - listStyles, showTotalCount, rawHeaders, isColumnVisible, @@ -402,7 +416,6 @@ export default defineComponent({ density="compact" class="elevation-1" :class="tableClass" - :style="listStyles" @update:options="onUpdateOptions" @update:sort-by="onUpdateSortBy" > diff --git a/client/src/main.ts b/client/src/main.ts index 719fc597..62dfdd04 100644 --- a/client/src/main.ts +++ b/client/src/main.ts @@ -1,4 +1,5 @@ import '@mdi/font/css/materialdesignicons.css'; +import 'maplibre-gl/dist/maplibre-gl.css'; import 'vuetify/styles'; import * as Sentry from '@sentry/vue'; import { createApp, } from 'vue'; diff --git a/client/src/use/useState.ts b/client/src/use/useState.ts index 1b03814e..a90e2b35 100644 --- a/client/src/use/useState.ts +++ b/client/src/use/useState.ts @@ -115,6 +115,7 @@ const transparencyThreshold = ref(0); // 0-100 percentage const FILTER_TAG_STORAGE_KEY = 'bataiFilterTags'; const SHARED_FILTER_TAG_STORAGE_KEY = 'bataiSharedFilterTags'; +const MAP_FILTER_BOUNDS_STORAGE_KEY = 'bataiMapFilterBounds'; const filterTags: Ref = ref([]); const sharedFilterTags: Ref = ref([]); @@ -225,6 +226,38 @@ export default function useState() { } + function saveMapFilterBounds(bounds: [number, number, number, number]) { + if ( + !bounds + || bounds.length !== 4 + || bounds.some((n) => !Number.isFinite(n)) + ) { + return; + } + localStorage.setItem(MAP_FILTER_BOUNDS_STORAGE_KEY, JSON.stringify(bounds)); + } + + function clearMapFilterBounds() { + localStorage.removeItem(MAP_FILTER_BOUNDS_STORAGE_KEY); + } + + function loadMapFilterBounds(): [number, number, number, number] | null { + try { + const raw = localStorage.getItem(MAP_FILTER_BOUNDS_STORAGE_KEY); + if (!raw) return null; + + const parsed: unknown = JSON.parse(raw); + if (!Array.isArray(parsed) || parsed.length !== 4) return null; + + const nums = parsed.map((v) => (typeof v === 'number' ? v : Number(v))); + if (nums.some((n) => !Number.isFinite(n))) return null; + + return nums as [number, number, number, number]; + } catch { + return null; + } + } + return { annotationState, creationType, @@ -290,6 +323,9 @@ export default function useState() { sharedFilterTags, saveFilterTags, loadFilterTags, + saveMapFilterBounds, + clearMapFilterBounds, + loadMapFilterBounds, spectrogramFilename, }; } diff --git a/client/src/views/Recordings.vue b/client/src/views/Recordings.vue index 3007cc48..df800812 100644 --- a/client/src/views/Recordings.vue +++ b/client/src/views/Recordings.vue @@ -1,19 +1,35 @@