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:
-
- ${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
-
-
- `;
-
-}
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 <>
-
+