diff --git a/crawler/frontend/index.html b/crawler/frontend/index.html index e4b78ea..eb84ccd 100644 --- a/crawler/frontend/index.html +++ b/crawler/frontend/index.html @@ -1,13 +1,18 @@ - - - - - Vite + React + TS - - -
- - + + + + + + Vite + React + TS + + + + + +
+ + + diff --git a/crawler/frontend/package-lock.json b/crawler/frontend/package-lock.json index 14177a5..eee8cb1 100644 --- a/crawler/frontend/package-lock.json +++ b/crawler/frontend/package-lock.json @@ -13,10 +13,13 @@ "@types/d3": "^7.4.3", "@types/mapbox-gl": "^3.4.1", "@types/turf": "^3.5.32", + "d3": "^7.9.0", + "mapbox-gl": "^3.12.0", "oidc-client-ts": "^3.2.1", "react": "^19.1.0", "react-dom": "^19.1.0", "react-oidc-context": "^3.3.0", + "rivets": "^0.9.6", "tailwindcss": "^4.1.10" }, "devDependencies": { @@ -24,6 +27,7 @@ "@types/node": "^24.0.1", "@types/react": "^19.1.2", "@types/react-dom": "^19.1.2", + "@types/rivets": "^0.9.5", "@vitejs/plugin-react-swc": "^3.9.0", "eslint": "^9.25.0", "eslint-plugin-react-hooks": "^5.2.0", @@ -740,6 +744,56 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mapbox/jsonlint-lines-primitives": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz", + "integrity": "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@mapbox/mapbox-gl-supported": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-3.0.0.tgz", + "integrity": "sha512-2XghOwu16ZwPJLOFVuIOaLbN0iKMn867evzXFyf0P22dqugezfJwLmdanAgU25ITvz1TvOfVP4jsDImlDJzcWg==", + "license": "BSD-3-Clause" + }, + "node_modules/@mapbox/point-geometry": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz", + "integrity": "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==", + "license": "ISC" + }, + "node_modules/@mapbox/tiny-sdf": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.6.tgz", + "integrity": "sha512-qMqa27TLw+ZQz5Jk+RcwZGH7BQf5G/TrutJhspsca/3SHwmgKQ1iq+d3Jxz5oysPVYTGP6aXxCo5Lk9Er6YBAA==", + "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": "1.3.1", + "resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-1.3.1.tgz", + "integrity": "sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw==", + "license": "BSD-3-Clause", + "dependencies": { + "@mapbox/point-geometry": "~0.1.0" + } + }, + "node_modules/@mapbox/whoots-js": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz", + "integrity": "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==", + "license": "ISC", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1805,6 +1859,25 @@ "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", "license": "MIT" }, + "node_modules/@types/geojson-vt": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@types/geojson-vt/-/geojson-vt-3.2.5.tgz", + "integrity": "sha512-qDO7wqtprzlpe8FfQ//ClPV9xiuoh2nkIgiouIptON9w5jvD/fA4szvP9GBlDVdJ5dldAl0kX/sy3URbWwLx0g==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/jquery": { + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.32.tgz", + "integrity": "sha512-b9Xbf4CkMqS02YH8zACqN1xzdxc3cO735Qe5AbSUFmyOiaWAbcpqh9Wna+Uk0vgACvoQHpWDg2rGdHkYPLmCiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/sizzle": "*" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -1812,6 +1885,23 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/mapbox__point-geometry": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.4.tgz", + "integrity": "sha512-mUWlSxAmYLfwnRBmgYV86tgYmMIICX4kza8YnE/eIlywGe2XoOxlpVnXWwir92xRLjwyarqwpu2EJKD2pk0IUA==", + "license": "MIT" + }, + "node_modules/@types/mapbox__vector-tile": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@types/mapbox__vector-tile/-/mapbox__vector-tile-1.3.4.tgz", + "integrity": "sha512-bpd8dRn9pr6xKvuEBQup8pwQfD4VUyqO/2deGjfpe6AwC8YRlyEipvefyRJUSiCJTZuCb8Pl1ciVV5ekqJ96Bg==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*", + "@types/mapbox__point-geometry": "*", + "@types/pbf": "*" + } + }, "node_modules/@types/mapbox-gl": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/@types/mapbox-gl/-/mapbox-gl-3.4.1.tgz", @@ -1831,6 +1921,12 @@ "undici-types": "~7.8.0" } }, + "node_modules/@types/pbf": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/pbf/-/pbf-3.0.5.tgz", + "integrity": "sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==", + "license": "MIT" + }, "node_modules/@types/react": { "version": "19.1.8", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz", @@ -1851,6 +1947,32 @@ "@types/react": "^19.0.0" } }, + "node_modules/@types/rivets": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/@types/rivets/-/rivets-0.9.5.tgz", + "integrity": "sha512-spCtZoSOrS8kNTJNOXamCCQurqOdF1Piak8bUQVqHQNRoTLoID6O6xVX41P5W2vvlxc9UpSG75zl4CRra0l3Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/jquery": "*" + } + }, + "node_modules/@types/sizzle": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.9.tgz", + "integrity": "sha512-xzLEyKB50yqCUPUJkIsrVvoWNfFUbIZI+RspLWt8u+tIW/BetMBZtgV2LY/2o+tYH8dRvQ+eoPf3NdhQCcLE2w==", + "dev": true, + "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/turf": { "version": "3.5.32", "resolved": "https://registry.npmjs.org/@types/turf/-/turf-3.5.32.tgz", @@ -2253,6 +2375,12 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/cheap-ruler": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cheap-ruler/-/cheap-ruler-4.0.0.tgz", + "integrity": "sha512-0BJa8f4t141BYKQyn9NSQt1PguFQXMXwZiA5shfoaBYHAb2fFk2RAX+tiWMoQU+Agtzt3mdt0JtuyshAXqZ+Vw==", + "license": "ISC" + }, "node_modules/chownr": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", @@ -2282,6 +2410,15 @@ "dev": true, "license": "MIT" }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2304,6 +2441,12 @@ "node": ">= 8" } }, + "node_modules/csscolorparser": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/csscolorparser/-/csscolorparser-1.0.3.tgz", + "integrity": "sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w==", + "license": "MIT" + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -2311,6 +2454,407 @@ "dev": true, "license": "MIT" }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", @@ -2336,6 +2880,15 @@ "dev": true, "license": "MIT" }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, "node_modules/detect-libc": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", @@ -2345,6 +2898,12 @@ "node": ">=8" } }, + "node_modules/earcut": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.1.tgz", + "integrity": "sha512-0l1/0gOjESMeQyYaK5IDiPNvFeu93Z/cO0TjZh9eZ1vyCtZnA7KMZ8rQggpsJHIbGSdrqYq9OhuveadOVHCshw==", + "license": "ISC" + }, "node_modules/enhanced-resolve": { "version": "5.18.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", @@ -2728,6 +3287,18 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/geojson-vt": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-4.0.2.tgz", + "integrity": "sha512-AV9ROqlNqoZEIJGfm1ncNjEXfkz2hdFlZf0qkVfmkwdKa8vj7H16YUOT81rJw1rdFhyEDlN2Tds91p/glzbl5A==", + "license": "ISC" + }, + "node_modules/gl-matrix": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz", + "integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==", + "license": "MIT" + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -2767,6 +3338,12 @@ "dev": true, "license": "MIT" }, + "node_modules/grid-index": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/grid-index/-/grid-index-1.1.0.tgz", + "integrity": "sha512-HZRwumpOGUrHyxO5bqKZL0B0GlUpwtCAzZ42sgxUPniu33R1LSFH5yrIcBCHjkctCAh3mtWKcKd9J4vDDdeVHA==", + "license": "ISC" + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2777,6 +3354,38 @@ "node": ">=8" } }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -2814,6 +3423,15 @@ "node": ">=0.8.19" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2906,6 +3524,12 @@ "node": ">=18" } }, + "node_modules/kdbush": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz", + "integrity": "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==", + "license": "ISC" + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -3190,6 +3814,70 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/mapbox-gl": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-3.12.0.tgz", + "integrity": "sha512-DV6TRr+xoPrLSKuGiUcbyLVkoLdNaNNpn6O7+ZC27yQH7BOOIF7l6JKbTCMhfMJuZBVJfL8YRJjlMJ6MZCTggA==", + "license": "SEE LICENSE IN LICENSE.txt", + "workspaces": [ + "src/style-spec", + "test/build/typings" + ], + "dependencies": { + "@mapbox/jsonlint-lines-primitives": "^2.0.2", + "@mapbox/mapbox-gl-supported": "^3.0.0", + "@mapbox/point-geometry": "^0.1.0", + "@mapbox/tiny-sdf": "^2.0.6", + "@mapbox/unitbezier": "^0.0.1", + "@mapbox/vector-tile": "^1.3.1", + "@mapbox/whoots-js": "^3.1.0", + "@types/geojson": "^7946.0.16", + "@types/geojson-vt": "^3.2.5", + "@types/mapbox__point-geometry": "^0.1.4", + "@types/mapbox__vector-tile": "^1.3.4", + "@types/pbf": "^3.0.5", + "@types/supercluster": "^7.1.3", + "cheap-ruler": "^4.0.0", + "csscolorparser": "~1.0.3", + "earcut": "^3.0.1", + "geojson-vt": "^4.0.2", + "gl-matrix": "^3.4.3", + "grid-index": "^1.1.0", + "kdbush": "^4.0.2", + "martinez-polygon-clipping": "^0.7.4", + "murmurhash-js": "^1.0.0", + "pbf": "^3.2.1", + "potpack": "^2.0.0", + "quickselect": "^3.0.0", + "serialize-to-js": "^3.1.2", + "supercluster": "^8.0.1", + "tinyqueue": "^3.0.0", + "vt-pbf": "^3.1.3" + } + }, + "node_modules/martinez-polygon-clipping": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/martinez-polygon-clipping/-/martinez-polygon-clipping-0.7.4.tgz", + "integrity": "sha512-jBEwrKtA0jTagUZj2bnmb4Yg2s4KnJGRePStgI7bAVjtcipKiF39R4LZ2V/UT61jMYWrTcBhPazexeqd6JAVtw==", + "license": "MIT", + "dependencies": { + "robust-predicates": "^2.0.4", + "splaytree": "^0.1.4", + "tinyqueue": "^1.2.0" + } + }, + "node_modules/martinez-polygon-clipping/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/martinez-polygon-clipping/node_modules/tinyqueue": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-1.2.3.tgz", + "integrity": "sha512-Qz9RgWuO9l8lT+Y9xvbzhPT2efIUIFd69N7eF7tJ9lnQl0iLj1M7peK7IoUGZL9DJHw9XftqLreccfxcQgYLxA==", + "license": "ISC" + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -3270,6 +3958,12 @@ "dev": true, "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", @@ -3390,6 +4084,19 @@ "node": ">=8" } }, + "node_modules/pbf": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.3.0.tgz", + "integrity": "sha512-XDF38WCH3z5OV/OVa8GKUNtLAyneuzbCisx7QUCF8Q6Nutx0WnJrQe5O+kOtBlLfRNUws98Y58Lblp+NJG5T4Q==", + "license": "BSD-3-Clause", + "dependencies": { + "ieee754": "^1.1.12", + "resolve-protobuf-schema": "^2.1.0" + }, + "bin": { + "pbf": "bin/pbf" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -3437,6 +4144,12 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/potpack": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/potpack/-/potpack-2.0.0.tgz", + "integrity": "sha512-Q+/tYsFU9r7xoOJ+y/ZTtdVQwTWfzjbiXBDMM/JKUux3+QPP02iUuIoeBQ+Ot6oEDlC+/PGjB/5A3K7KKb7hcw==", + "license": "ISC" + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -3447,6 +4160,12 @@ "node": ">= 0.8.0" } }, + "node_modules/protocol-buffers-schema": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", + "integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -3478,6 +4197,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/react": { "version": "19.1.0", "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", @@ -3522,6 +4247,15 @@ "node": ">=4" } }, + "node_modules/resolve-protobuf-schema": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz", + "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==", + "license": "MIT", + "dependencies": { + "protocol-buffers-schema": "^3.3.1" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -3533,6 +4267,20 @@ "node": ">=0.10.0" } }, + "node_modules/rivets": { + "version": "0.9.6", + "resolved": "https://registry.npmjs.org/rivets/-/rivets-0.9.6.tgz", + "integrity": "sha512-KfdMjLRWw4+38ej9bRXegKZVfYo0jEacwadA5z6NTKya+YohwGemwdbxvJ52WCXODkTnR4Q8UmUC6HVxsdzkxA==", + "dependencies": { + "sightglass": "~0.2.4" + } + }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "license": "Unlicense" + }, "node_modules/rollup": { "version": "4.43.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.43.0.tgz", @@ -3602,6 +4350,18 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, "node_modules/scheduler": { "version": "0.26.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", @@ -3621,6 +4381,15 @@ "node": ">=10" } }, + "node_modules/serialize-to-js": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/serialize-to-js/-/serialize-to-js-3.1.2.tgz", + "integrity": "sha512-owllqNuDDEimQat7EPG0tH7JjO090xKNzUtYz6X+Sk2BXDnOCilDdNLwjWeFywG9xkJul1ULvtUQa9O4pUaY0w==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -3644,6 +4413,11 @@ "node": ">=8" } }, + "node_modules/sightglass": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/sightglass/-/sightglass-0.2.6.tgz", + "integrity": "sha512-t1fgbuhURcWc8VgZk8kJQ3QmmZk3kghDcf0wpsN8I8RaV05IUkc2b195KpGqgocKT/q8+vKk6EcB2c7N2lAd6A==" + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -3653,6 +4427,12 @@ "node": ">=0.10.0" } }, + "node_modules/splaytree": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/splaytree/-/splaytree-0.1.4.tgz", + "integrity": "sha512-D50hKrjZgBzqD3FT2Ek53f2dcDLAQT8SSGrzj3vidNH5ISRgceeGVJ2dQIthKOuayqFXfFjXheHNo4bbt9LhRQ==", + "license": "MIT" + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -3666,6 +4446,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/supercluster": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz", + "integrity": "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==", + "license": "ISC", + "dependencies": { + "kdbush": "^4.0.2" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -3753,6 +4542,12 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/tinyqueue": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-3.0.0.tgz", + "integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==", + "license": "ISC" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -3946,6 +4741,17 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/vt-pbf": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz", + "integrity": "sha512-2LzDFzt0mZKZ9IpVF2r69G9bXaP2Q2sArJCmcCgvfTdCCZzSyz4aCLoQyUilu37Ll56tCblIZrXFIjNUpGIlmA==", + "license": "MIT", + "dependencies": { + "@mapbox/point-geometry": "0.1.0", + "@mapbox/vector-tile": "^1.3.1", + "pbf": "^3.2.1" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/crawler/frontend/package.json b/crawler/frontend/package.json index dc8fa68..9620e77 100644 --- a/crawler/frontend/package.json +++ b/crawler/frontend/package.json @@ -15,10 +15,13 @@ "@types/d3": "^7.4.3", "@types/mapbox-gl": "^3.4.1", "@types/turf": "^3.5.32", + "d3": "^7.9.0", + "mapbox-gl": "^3.12.0", "oidc-client-ts": "^3.2.1", "react": "^19.1.0", "react-dom": "^19.1.0", "react-oidc-context": "^3.3.0", + "rivets": "^0.9.6", "tailwindcss": "^4.1.10" }, "devDependencies": { @@ -26,6 +29,7 @@ "@types/node": "^24.0.1", "@types/react": "^19.1.2", "@types/react-dom": "^19.1.2", + "@types/rivets": "^0.9.5", "@vitejs/plugin-react-swc": "^3.9.0", "eslint": "^9.25.0", "eslint-plugin-react-hooks": "^5.2.0", diff --git a/crawler/frontend/public/script.js b/crawler/frontend/public/script.js deleted file mode 100644 index bd00bd8..0000000 --- a/crawler/frontend/public/script.js +++ /dev/null @@ -1,261 +0,0 @@ - -// rivet -var filter = { city: 'London', country: null, mode: 'qmprice' }; -filter['countries'] = Array.from(new Set(data.features.map(function (d) { return d['properties']['country'] }))); -rivets.bind(document.getElementById('overlay'), { filter: filter }); -console.log('kekeke') - - -function clone(d) { - return JSON.parse(JSON.stringify(d)); -} - -function percentile(arr, p) { - if (arr.length === 0) return 0; - if (typeof p !== 'number') throw new TypeError('p must be a number'); - if (p <= 0) return arr[0]; - if (p >= 1) return arr[arr.length - 1]; - - var index = arr.length * p, - lower = Math.floor(index), - upper = lower + 1, - weight = index % 1; - - if (upper >= arr.length) return arr[lower]; - return arr[lower] * (1 - weight) + arr[upper] * weight; -} - -function update() { - // close overlay - var modal = document.getElementById('modal_overlay'); - modal.classList.toggle('modal-open'); - - // init heatmap - heatmap = new HexgridHeatmap(map, "hexgrid-heatmap", "waterway-label"); - heatmap.setIntensity(9); // dunno yet - heatmap.setSpread(0.05); // dunno yet - heatmap.setCellDensity(0.5); // small value == bigger hexagons - heatmap.setPropertyName(filter.mode); - - if (filter.mode === 'qmprice') { - // if we visualize sqm based data, remove properties where we have no data - qmDim.filter(function (d) { return d > 0; }); - } - - - // set filter - if (filter.city) { - cityDim.filterExact(filter.city); - } else if (filter.country) { - countryDim.filterExact(filter.country); - } else { - alert('nothing loadable'); - } - filter.count = cityDim.top(Infinity).length; - - var subset = { "type": "FeatureCollection", "features": [] }; - indexDim.top(Infinity).forEach(function (i) { - subset.features.push(data.features[i.index]); - }); - - loadData(subset); -} - -function loadData(subset) { - heatmap.setData(subset); - var values = subset.features.map(function (d) { return d['properties'][filter.mode] }); - values = values.sort(function (a, b) { return a - b; }); - - // setting the color stops, min is at 5th percentile, max at 95percentile - var min = values[Math.round(values.length * 0.05)]; - var max = values[Math.round(values.length * 0.95)]; - var colorStopsPerc = [ - [0, "rgba(0,185,243,0)"], - [25, "rgba(0,185,243,0.24)"], - [60, "rgba(255,223,0,0.3)"], - [100, "rgba(255,105,0,0.3)"], - ]; - makeLegend(colorStopsPerc, min, max); - var colorStopsValue = colorStopsPerc.map(function (d) { - return [min + d[0] * (max - min) / 100, d[1]]; - }); - heatmap.setColorStops(colorStopsValue); - heatmap.update(); - - //get bounding box and zoom to that area - // we use a 1% percentile since some data can be corrupt - var longitudes = subset.features.map(function (d) { return d.geometry.coordinates[0]; }).sort(function (a, b) { return a - b; }); - var latitudes = subset.features.map(function (d) { return d.geometry.coordinates[1]; }).sort(function (a, b) { return a - b; }); - var minlng = percentile(longitudes, 0.01); - var maxlng = percentile(longitudes, 0.99); - var minlat = percentile(latitudes, 0.01); - var maxlat = percentile(latitudes, 0.99); - map.fitBounds([ - [minlng, minlat], - [maxlng, maxlat] - ]); -} - -function makeLegend(colorstops, minValue, maxValue) { - /** - * colorstops: [[0, 'green'], [100, 'red']] - * @type {number} - */ - var svg_height = 300, svg_width = 70; - var svg = d3.select('#svg'); - var defs = svg - .attr('height', svg_height) - .attr('width', svg_width); - - var linearGradient = svg.append("defs") - .append("linearGradient") - .attr("id", "linear-gradient"); - - linearGradient - .attr("x1", "0%") - .attr("y1", "100%") - .attr("x2", "0%") - .attr("y2", "0%"); - - svg.append("rect") - .attr("width", svg_width * 0.4) - .attr("height", svg_height) - .attr('rx', 4) - .style("fill", "url(#linear-gradient)"); - - colorstops.forEach(function (d) { - linearGradient.append("stop") - .attr("offset", d[0] + "%") - .attr("stop-color", d[1]); - }); - - - var xScale = d3.scaleLinear().range([svg_height - 20, 0]).domain([minValue, maxValue]); - var xAxis = d3.axisRight(xScale).ticks(5); - - svg.append("g") - .attr("class", "axis") - .attr("transform", "translate(" + svg_width / 2 + "," + (10) + ")") - .call(xAxis); -} - -// ORIGINAL BLOCK - -mapboxgl.accessToken = 'pk.eyJ1IjoiZGktdG8iLCJhIjoiY2o0bnBoYXcxMW1mNzJ3bDhmc2xiNWttaiJ9.ZccatVk_4shzoAsEUXXecA'; -var map = new mapboxgl.Map({ - container: 'map', - style: 'mapbox://styles/mapbox/light-v9', - center: [13.38032, 49.994210], - zoom: 5 -}); - - -console.log('ekekek'); -map.on("load", function () { - var crossData = data.features.map(function (d, i) { - //clone properties - var props = clone(d['properties']); - props['index'] = i; - return props; - }); - cf = crossfilter(crossData); - qmDim = cf.dimension(function (d) { return d.qm; }); - cityDim = cf.dimension(function (d) { return d.city; }); - countryDim = cf.dimension(function (d) { return d.country; }); - rentDim = cf.dimension(function (d) { return d.total_price; }); - roomsDim = cf.dimension(function (d) { return d.rooms; }); - urlDim = cf.dimension(function (d) { return d.url; }); - indexDim = cf.dimension(function (d) { return d.index; }); -}); -map.on('click', (e) => { - // { - // lngLat: { - // lng: 40.203, - // lat: -74.451 - // }, - // originalEvent: {...}, - // point: { - // x: 266, - // y: 464 - // }, - // target: {...}, - // type: "click" - // } - openListingsDialog(e.lngLat.lng, e.lngLat.lat); -}); -function openListingsDialog(longtitude, latitude) { - const searchBuffer = 0.001 // ~100m - const properties = heatmap._tree.search({ - minX: longtitude - searchBuffer, - maxX: longtitude + searchBuffer, - minY: latitude - searchBuffer, - maxY: latitude + searchBuffer - }) - const html = getListingDialogHTML(properties); - if (properties.length > 0) { - new mapboxgl.Popup() - .setLngLat([longtitude, latitude]) - .setHTML(html) - .setMaxWidth("500px") - .addTo(map); - } -} - -function getListingDialogHTML(properties) { - let listinHTMLs = []; - for (let property of properties) { - listinHTMLs.push(getPropertyHTML(property)); - } - // separate them with a line - const result = listinHTMLs.join('
'); - const styledResult = ` -
- ${result} -
- ` - - return styledResult; -} -function getPropertyHTML(property) { - const priceHistoryHTMLs = property.properties.price_history.map((d) => { - return `
  • ${d.last_seen.split('T')[0]}: £${d.price}
  • `; - }); - - let priceHistoryHTML = ''; - if (priceHistoryHTMLs.length > 1) { - priceHistoryHTML = ` - Price history: - -
    - ` - } - const lastSeenStr = property.properties.last_seen.split('T')[0]; - const lastSeenDays = Math.round((new Date() - new Date(lastSeenStr)) / (1000 * 60 * 60 * 24)); - - return ` -
    - -

    - Available from: ${property.properties.available_from} -
    - Price: £${property.properties.total_price} -
    - ${priceHistoryHTML} - Rooms: ${property.properties.rooms} -
    - Area: ${property.properties.qm} m² -
    - Price per area: £${property.properties.qmprice}/m² -
    - Last seen: ${lastSeenDays} days ago -
    - Agency: ${property.properties.agency} -
    - View Listing -

    -
    - `; - -} diff --git a/crawler/frontend/src/App.tsx b/crawler/frontend/src/App.tsx index bde04f5..1ee83e6 100644 --- a/crawler/frontend/src/App.tsx +++ b/crawler/frontend/src/App.tsx @@ -6,16 +6,7 @@ import { Map } from './components/Map'; import { Parameters } from './components/Parameters'; function App() { - useEffect(() => { - const script = document.createElement('script'); - script.src = './script.js'; - script.async = true; - document.body.appendChild(script); - - return () => { - document.body.removeChild(script); // Cleanup - }; - }, []); + const [listingData, setListingData] = useState({}); const [user, setUser] = useState(null); useEffect(() => { @@ -65,8 +56,15 @@ function App() {

    Click on the Vite and React logos to learn more

    */} - - + + {/* */} + + {Object.keys(listingData).length > 0 && + +
    + +
    + } ) } diff --git a/crawler/frontend/src/components/Map.tsx b/crawler/frontend/src/components/Map.tsx index eafe103..2afb519 100644 --- a/crawler/frontend/src/components/Map.tsx +++ b/crawler/frontend/src/components/Map.tsx @@ -1,6 +1,269 @@ -export function Map() { +import crossfilter from "crossfilter2"; +import * as d3 from "d3"; +import mapboxgl from "mapbox-gl"; +// import 'mapbox-gl/dist/mapbox-gl.css'; +import { useEffect, useRef } from "react"; + +export function Map( + props: { + listingData: any; + } +) { + const data = props.listingData; + var crossData = data.features.map(function (d, i) { + //clone properties + var props = clone(d['properties']); + props['index'] = i; + return props; + }); + const cf = crossfilter(crossData); + const qmDim = cf.dimension(function (d) { return d.qm; }); + const cityDim = cf.dimension(function (d) { return d.city; }); + const countryDim = cf.dimension(function (d) { return d.country; }); + const rentDim = cf.dimension(function (d) { return d.total_price; }); + const roomsDim = cf.dimension(function (d) { return d.rooms; }); + const urlDim = cf.dimension(function (d) { return d.url; }); + const indexDim = cf.dimension(function (d) { return d.index; }); + let heatmap = null; + + // rivet + var filter = { city: 'London', country: null, mode: 'qmprice' }; + // filter['countries'] = Array.from(new Set(data.features.map(function (d) { return d['properties']['country'] }))); + // rivets.bind(document.getElementById('overlay'), { filter: filter }); + const mapRef = useRef(mapboxgl.Map) + const mapContainerRef = useRef('map') + useEffect(() => { + mapboxgl.accessToken = 'pk.eyJ1IjoiZGktdG8iLCJhIjoiY2o0bnBoYXcxMW1mNzJ3bDhmc2xiNWttaiJ9.ZccatVk_4shzoAsEUXXecA'; + mapRef.current = new mapboxgl.Map({ + container: mapContainerRef.current, + style: 'mapbox://styles/mapbox/light-v9', + center: [13.38032, 49.994210], + zoom: 5 + }); + mapRef.current.on('load', function () { + update() + }) + mapRef.current.on('click', function (e) { + openListingsDialog(e.lngLat.lng, e.lngLat.lat); + }) + return () => { + mapRef.current.remove() + } + }, []) + + + function clone(d) { + return JSON.parse(JSON.stringify(d)); + } + + function percentile(arr, p) { + if (arr.length === 0) return 0; + if (typeof p !== 'number') throw new TypeError('p must be a number'); + if (p <= 0) return arr[0]; + if (p >= 1) return arr[arr.length - 1]; + + var index = arr.length * p, + lower = Math.floor(index), + upper = lower + 1, + weight = index % 1; + + if (upper >= arr.length) return arr[lower]; + return arr[lower] * (1 - weight) + arr[upper] * weight; + } + + function update() { + // close overlay + var modal = document.getElementById('modal_overlay'); + modal.classList.toggle('modal-open'); + + // init heatmap + heatmap = new HexgridHeatmap(mapRef.current, "hexgrid-heatmap", "waterway-label"); + heatmap.setIntensity(9); // dunno yet + heatmap.setSpread(0.05); // dunno yet + heatmap.setCellDensity(0.5); // small value == bigger hexagons + heatmap.setPropertyName(filter.mode); + + if (filter.mode === 'qmprice') { + // if we visualize sqm based data, remove properties where we have no data + qmDim.filter(function (d) { return d > 0; }); + } + + + // set filter + if (filter.city) { + cityDim.filterExact(filter.city); + } else if (filter.country) { + countryDim.filterExact(filter.country); + } else { + alert('nothing loadable'); + } + filter.count = cityDim.top(Infinity).length; + + var subset = { "type": "FeatureCollection", "features": [] }; + indexDim.top(Infinity).forEach(function (i) { + subset.features.push(data.features[i.index]); + }); + + loadData(heatmap, subset); + } + + function loadData(heatmap, subset) { + heatmap.setData(subset); + var values = subset.features.map(function (d) { return d['properties'][filter.mode] }); + values = values.sort(function (a, b) { return a - b; }); + + // setting the color stops, min is at 5th percentile, max at 95percentile + var min = values[Math.round(values.length * 0.05)]; + var max = values[Math.round(values.length * 0.95)]; + var colorStopsPerc = [ + [0, "rgba(0,185,243,0)"], + [25, "rgba(0,185,243,0.24)"], + [60, "rgba(255,223,0,0.3)"], + [100, "rgba(255,105,0,0.3)"], + ]; + makeLegend(colorStopsPerc, min, max); + var colorStopsValue = colorStopsPerc.map(function (d) { + return [min + d[0] * (max - min) / 100, d[1]]; + }); + heatmap.setColorStops(colorStopsValue); + heatmap.update(); + + //get bounding box and zoom to that area + // we use a 1% percentile since some data can be corrupt + var longitudes = subset.features.map(function (d) { return d.geometry.coordinates[0]; }).sort(function (a, b) { return a - b; }); + var latitudes = subset.features.map(function (d) { return d.geometry.coordinates[1]; }).sort(function (a, b) { return a - b; }); + var minlng = percentile(longitudes, 0.01); + var maxlng = percentile(longitudes, 0.99); + var minlat = percentile(latitudes, 0.01); + var maxlat = percentile(latitudes, 0.99); + mapRef.current.fitBounds([ + [minlng, minlat], + [maxlng, maxlat] + ]); + } + + function makeLegend(colorstops, minValue, maxValue) { + /** + * colorstops: [[0, 'green'], [100, 'red']] + * @type {number} + */ + var svg_height = 300, svg_width = 70; + var svg = d3.select('#svg'); + var defs = svg + .attr('height', svg_height) + .attr('width', svg_width); + + var linearGradient = svg.append("defs") + .append("linearGradient") + .attr("id", "linear-gradient"); + + linearGradient + .attr("x1", "0%") + .attr("y1", "100%") + .attr("x2", "0%") + .attr("y2", "0%"); + + svg.append("rect") + .attr("width", svg_width * 0.4) + .attr("height", svg_height) + .attr('rx', 4) + .style("fill", "url(#linear-gradient)"); + + colorstops.forEach(function (d) { + linearGradient.append("stop") + .attr("offset", d[0] + "%") + .attr("stop-color", d[1]); + }); + + + var xScale = d3.scaleLinear().range([svg_height - 20, 0]).domain([minValue, maxValue]); + var xAxis = d3.axisRight(xScale).ticks(5); + + svg.append("g") + .attr("class", "axis") + .attr("transform", "translate(" + svg_width / 2 + "," + (10) + ")") + .call(xAxis); + } + + function openListingsDialog(longtitude, latitude) { + const searchBuffer = 0.001 // ~100m + const properties = heatmap._tree.search({ + minX: longtitude - searchBuffer, + maxX: longtitude + searchBuffer, + minY: latitude - searchBuffer, + maxY: latitude + searchBuffer + }) + const html = getListingDialogHTML(properties); + if (properties.length > 0) { + new mapboxgl.Popup() + .setLngLat([longtitude, latitude]) + .setHTML(html) + .setMaxWidth("500px") + .addTo(mapRef.current); + } + } + + function getListingDialogHTML(properties) { + let listinHTMLs = []; + for (let property of properties) { + listinHTMLs.push(getPropertyHTML(property)); + } + // separate them with a line + const result = listinHTMLs.join('
    '); + const styledResult = ` +
    + ${result} +
    + ` + + return styledResult; + } + function getPropertyHTML(property) { + const priceHistoryHTMLs = property.properties.price_history.map((d) => { + return `
  • ${d.last_seen.split('T')[0]}: £${d.price}
  • `; + }); + + let priceHistoryHTML = ''; + if (priceHistoryHTMLs.length > 1) { + priceHistoryHTML = ` + Price history: +
      + ${priceHistoryHTMLs.join('')} +
    +
    + ` + } + const lastSeenStr = property.properties.last_seen.split('T')[0]; + const lastSeenDays = Math.round((new Date() - new Date(lastSeenStr)) / (1000 * 60 * 60 * 24)); + + return ` +
    + +

    + Available from: ${property.properties.available_from} +
    + Price: £${property.properties.total_price} +
    + ${priceHistoryHTML} + Rooms: ${property.properties.rooms} +
    + Area: ${property.properties.qm} m² +
    + Price per area: £${property.properties.qmprice}/m² +
    + Last seen: ${lastSeenDays} days ago +
    + Agency: ${property.properties.agency} +
    + View Listing +

    +
    + `; + + } + return <> -
    +
    diff --git a/crawler/frontend/src/components/Parameters.tsx b/crawler/frontend/src/components/Parameters.tsx index 4d3427e..7f86a79 100644 --- a/crawler/frontend/src/components/Parameters.tsx +++ b/crawler/frontend/src/components/Parameters.tsx @@ -1,8 +1,11 @@ import { useState } from "react"; import { useAuth } from "react-oidc-context"; -export function Parameters() { - const [data, setData] = useState({}); +export function Parameters( + props: { + setListingData: (data: any) => void, + } +) { const [error, setError] = useState('') const [isLoading, setIsLoading] = useState(false) const { user } = useAuth(); // Get user data (includes token) @@ -11,7 +14,7 @@ export function Parameters() { try { const accessToken = user?.access_token; - const response = await fetch('/api/listing', + const response = await fetch('/api/listing_geojson', { method: 'GET', headers: { @@ -22,14 +25,14 @@ export function Parameters() { ); if (!response.ok) throw new Error('Error: ' + response.json()); const data: Response = await response.json(); - setData(data); + props.setListingData(data); } catch (err) { setError('Failed to fetch data: ' + err); + alert(error) } finally { setIsLoading(false); } }; - console.log(data) return <>