feat: add map with peers to info page (#398)
* feat: fetch and process map data DB * feat: add map * feat: nodes-db should be sorted alphabetically by key
This commit is contained in:
Generated
+110
@@ -23,6 +23,7 @@
|
||||
"buffer": "^6.0.3",
|
||||
"crypto": "npm:crypto-browserify",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"dotted-map": "^2.2.3",
|
||||
"ethers": "^5.6.4",
|
||||
"file-saver": "^2.0.5",
|
||||
"formik": "2.2.9",
|
||||
@@ -4702,6 +4703,37 @@
|
||||
"integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@turf/boolean-point-in-polygon": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@turf/boolean-point-in-polygon/-/boolean-point-in-polygon-6.5.0.tgz",
|
||||
"integrity": "sha512-DtSuVFB26SI+hj0SjrvXowGTUCHlgevPAIsukssW6BG5MlNSBQAo70wpICBNJL6RjukXg8d2eXaAWuD/CqL00A==",
|
||||
"dependencies": {
|
||||
"@turf/helpers": "^6.5.0",
|
||||
"@turf/invariant": "^6.5.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/turf"
|
||||
}
|
||||
},
|
||||
"node_modules/@turf/helpers": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-6.5.0.tgz",
|
||||
"integrity": "sha512-VbI1dV5bLFzohYYdgqwikdMVpe7pJ9X3E+dlr425wa2/sMJqYDhTO++ec38/pcPvPE6oD9WEEeU3Xu3gza+VPw==",
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/turf"
|
||||
}
|
||||
},
|
||||
"node_modules/@turf/invariant": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@turf/invariant/-/invariant-6.5.0.tgz",
|
||||
"integrity": "sha512-Wv8PRNCtPD31UVbdJE/KVAWKe7l6US+lJItRR/HOEW3eh+U/JwRCSUl/KZ7bmjM/C+zLNoreM2TU6OoLACs4eg==",
|
||||
"dependencies": {
|
||||
"@turf/helpers": "^6.5.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/turf"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/aria-query": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz",
|
||||
@@ -8635,6 +8667,15 @@
|
||||
"integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/dotted-map": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/dotted-map/-/dotted-map-2.2.3.tgz",
|
||||
"integrity": "sha512-8hyOOHHLLVCcCisM3yb9hqp+3bJ7TSMcr1SfrUw8Wxp5UMqih35jIvUyagweCooJbz/EH1nC9GGuPysh7+YlAg==",
|
||||
"dependencies": {
|
||||
"@turf/boolean-point-in-polygon": "^6.0.1",
|
||||
"proj4": "^2.6.1"
|
||||
}
|
||||
},
|
||||
"node_modules/duplexer": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
|
||||
@@ -13913,6 +13954,11 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mgrs": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mgrs/-/mgrs-1.0.0.tgz",
|
||||
"integrity": "sha512-awNbTOqCxK1DBGjalK3xqWIstBZgN6fxsMSiXLs9/spqWkF2pAhb2rrYCFSsr1/tT7PhcDGjZndG8SWYn0byYA=="
|
||||
},
|
||||
"node_modules/micromatch": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
|
||||
@@ -16307,6 +16353,15 @@
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
|
||||
},
|
||||
"node_modules/proj4": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/proj4/-/proj4-2.8.0.tgz",
|
||||
"integrity": "sha512-baC+YcD4xsSqJ+CpCZljj2gcQDhlKb+J+Zjv/2KSBwWNjk4M0pafgQsE+mWurd84tflMIsP+7j7mtIpFDHzQfQ==",
|
||||
"dependencies": {
|
||||
"mgrs": "1.0.0",
|
||||
"wkt-parser": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/promise": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/promise/-/promise-8.1.0.tgz",
|
||||
@@ -19965,6 +20020,11 @@
|
||||
"integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/wkt-parser": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/wkt-parser/-/wkt-parser-1.3.2.tgz",
|
||||
"integrity": "sha512-A26BOOo7sHAagyxG7iuRhnKMO7Q3mEOiOT4oGUmohtN/Li5wameeU4S6f8vWw6NADTVKljBs8bzA8JPQgSEMVQ=="
|
||||
},
|
||||
"node_modules/word-wrap": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
|
||||
@@ -23491,6 +23551,28 @@
|
||||
"integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@turf/boolean-point-in-polygon": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@turf/boolean-point-in-polygon/-/boolean-point-in-polygon-6.5.0.tgz",
|
||||
"integrity": "sha512-DtSuVFB26SI+hj0SjrvXowGTUCHlgevPAIsukssW6BG5MlNSBQAo70wpICBNJL6RjukXg8d2eXaAWuD/CqL00A==",
|
||||
"requires": {
|
||||
"@turf/helpers": "^6.5.0",
|
||||
"@turf/invariant": "^6.5.0"
|
||||
}
|
||||
},
|
||||
"@turf/helpers": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-6.5.0.tgz",
|
||||
"integrity": "sha512-VbI1dV5bLFzohYYdgqwikdMVpe7pJ9X3E+dlr425wa2/sMJqYDhTO++ec38/pcPvPE6oD9WEEeU3Xu3gza+VPw=="
|
||||
},
|
||||
"@turf/invariant": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@turf/invariant/-/invariant-6.5.0.tgz",
|
||||
"integrity": "sha512-Wv8PRNCtPD31UVbdJE/KVAWKe7l6US+lJItRR/HOEW3eh+U/JwRCSUl/KZ7bmjM/C+zLNoreM2TU6OoLACs4eg==",
|
||||
"requires": {
|
||||
"@turf/helpers": "^6.5.0"
|
||||
}
|
||||
},
|
||||
"@types/aria-query": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz",
|
||||
@@ -26622,6 +26704,15 @@
|
||||
"integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==",
|
||||
"dev": true
|
||||
},
|
||||
"dotted-map": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/dotted-map/-/dotted-map-2.2.3.tgz",
|
||||
"integrity": "sha512-8hyOOHHLLVCcCisM3yb9hqp+3bJ7TSMcr1SfrUw8Wxp5UMqih35jIvUyagweCooJbz/EH1nC9GGuPysh7+YlAg==",
|
||||
"requires": {
|
||||
"@turf/boolean-point-in-polygon": "^6.0.1",
|
||||
"proj4": "^2.6.1"
|
||||
}
|
||||
},
|
||||
"duplexer": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
|
||||
@@ -30564,6 +30655,11 @@
|
||||
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=",
|
||||
"dev": true
|
||||
},
|
||||
"mgrs": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mgrs/-/mgrs-1.0.0.tgz",
|
||||
"integrity": "sha512-awNbTOqCxK1DBGjalK3xqWIstBZgN6fxsMSiXLs9/spqWkF2pAhb2rrYCFSsr1/tT7PhcDGjZndG8SWYn0byYA=="
|
||||
},
|
||||
"micromatch": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
|
||||
@@ -32170,6 +32266,15 @@
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
|
||||
},
|
||||
"proj4": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/proj4/-/proj4-2.8.0.tgz",
|
||||
"integrity": "sha512-baC+YcD4xsSqJ+CpCZljj2gcQDhlKb+J+Zjv/2KSBwWNjk4M0pafgQsE+mWurd84tflMIsP+7j7mtIpFDHzQfQ==",
|
||||
"requires": {
|
||||
"mgrs": "1.0.0",
|
||||
"wkt-parser": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"promise": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/promise/-/promise-8.1.0.tgz",
|
||||
@@ -34945,6 +35050,11 @@
|
||||
"integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==",
|
||||
"dev": true
|
||||
},
|
||||
"wkt-parser": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/wkt-parser/-/wkt-parser-1.3.2.tgz",
|
||||
"integrity": "sha512-A26BOOo7sHAagyxG7iuRhnKMO7Q3mEOiOT4oGUmohtN/Li5wameeU4S6f8vWw6NADTVKljBs8bzA8JPQgSEMVQ=="
|
||||
},
|
||||
"word-wrap": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
|
||||
|
||||
+3
-1
@@ -40,6 +40,7 @@
|
||||
"buffer": "^6.0.3",
|
||||
"crypto": "npm:crypto-browserify",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"dotted-map": "^2.2.3",
|
||||
"ethers": "^5.6.4",
|
||||
"file-saver": "^2.0.5",
|
||||
"formik": "2.2.9",
|
||||
@@ -132,7 +133,8 @@
|
||||
"depcheck": "depcheck .",
|
||||
"lint": "eslint --fix \"src/**/*.ts\" \"src/**/*.tsx\" && prettier --write \"src/**/*.ts\" \"src/**/*.tsx\"",
|
||||
"lint:check": "eslint \"src/**/*.ts\" \"src/**/*.tsx\" && prettier --check \"src/**/*.ts\" \"src/**/*.tsx\"",
|
||||
"check:types": "tsc --project tsconfig.lib.json"
|
||||
"check:types": "tsc --project tsconfig.lib.json",
|
||||
"update-map-data": "node ./utils/update-map-data.js"
|
||||
},
|
||||
"files": [
|
||||
"lib",
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
+80
-3
@@ -1,9 +1,86 @@
|
||||
import { ReactElement, CSSProperties } from 'react'
|
||||
import { ReactElement, CSSProperties, useContext, useState, useEffect } from 'react'
|
||||
import type { Peer } from '@ethersphere/bee-js'
|
||||
import DottedMap, { DottedMapWithoutCountriesLib } from 'dotted-map/without-countries'
|
||||
import nodesDb from '../assets/data/nodes-db.json'
|
||||
import { Context } from '../providers/Bee'
|
||||
import mapData from '../assets/data/map-data.json'
|
||||
|
||||
interface Props {
|
||||
style?: CSSProperties
|
||||
}
|
||||
|
||||
export default function Card({ style }: Props): ReactElement {
|
||||
return <div style={Object.assign({}, style, { width: '100%', height: '380px', backgroundColor: '#f3f3f3' })}></div>
|
||||
interface MapRecord {
|
||||
lat: number
|
||||
lng: number
|
||||
}
|
||||
|
||||
type MapDB = Record<string, MapRecord>
|
||||
|
||||
const fullMapDb = nodesDb as unknown as MapDB
|
||||
const deduplicatedRecords = deduplicate(fullMapDb)
|
||||
|
||||
function deduplicate(db: MapDB): MapRecord[] {
|
||||
const noDuplicates: Record<string, MapRecord> = {}
|
||||
|
||||
Object.entries(fullMapDb).forEach(([key, record]) => {
|
||||
noDuplicates[`${record.lat} ${record.lng}`] = record
|
||||
})
|
||||
|
||||
return Object.values(noDuplicates)
|
||||
}
|
||||
|
||||
function findIntersection(db: MapDB, peers: Peer[]): MapRecord[] {
|
||||
const noDuplicates: Record<string, MapRecord> = {}
|
||||
peers.forEach(({ address }) => {
|
||||
const record = db[address]
|
||||
|
||||
if (record) noDuplicates[`${record.lat} ${record.lng}`] = record
|
||||
})
|
||||
|
||||
return Object.values(noDuplicates)
|
||||
}
|
||||
|
||||
function addPins(map: DottedMap, pins: MapRecord[], color: string) {
|
||||
pins.forEach(({ lat, lng }) => {
|
||||
map.addPin({ lat, lng, svgOptions: { color } })
|
||||
})
|
||||
}
|
||||
|
||||
const mapPrecomputed = new DottedMap({ map: JSON.parse(mapData) })
|
||||
addPins(mapPrecomputed, deduplicatedRecords, '#dd7200')
|
||||
|
||||
const mapSvgOptions: DottedMapWithoutCountriesLib.SvgSettings = { shape: 'hexagon', radius: 0.21, color: '#dadada' }
|
||||
|
||||
export default function Card({ style }: Props): ReactElement {
|
||||
const { peers } = useContext(Context)
|
||||
const [map, setMap] = useState<string>(mapPrecomputed.getSVG(mapSvgOptions))
|
||||
|
||||
useEffect(() => {
|
||||
if (!peers) return
|
||||
|
||||
const points = findIntersection(fullMapDb, peers)
|
||||
const mapNew = Object.create(mapPrecomputed)
|
||||
addPins(mapNew, points, '#67BE68')
|
||||
setMap(mapNew.getSVG(mapSvgOptions))
|
||||
}, [peers])
|
||||
|
||||
return (
|
||||
<div
|
||||
style={Object.assign({}, style, {
|
||||
width: '100%',
|
||||
height: '380px',
|
||||
backgroundColor: '#f3f3f3',
|
||||
overflow: 'hidden',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
})}
|
||||
>
|
||||
<img
|
||||
alt="world map"
|
||||
src={`data:image/svg+xml;utf8,${encodeURIComponent(map)}`}
|
||||
style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain' }}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Executable
+54
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const axios = require('axios')
|
||||
const fs = require('fs')
|
||||
const getMapJSON = require('dotted-map').getMapJSON
|
||||
|
||||
const DATA_SOURCE = 'https://swarmscan-api.resenje.org/v1/network/dump'
|
||||
const DATA_DESTINATION = './src/assets/data/nodes-db.json'
|
||||
const MAP_HEIGHT = 50
|
||||
const MAP_DESTINATION = './src/assets/data/map-data.json'
|
||||
|
||||
async function getData(url) {
|
||||
const res = await axios.get(url)
|
||||
|
||||
return res.data
|
||||
}
|
||||
|
||||
function processData(data) {
|
||||
const db = new Map()
|
||||
data.nodes.forEach(node => {
|
||||
db.set(node.overlay, { lat: node.location.latitude, lng: node.location.longitude })
|
||||
})
|
||||
|
||||
return Object.fromEntries([...db.entries()].sort())
|
||||
}
|
||||
|
||||
function saveFile(db, path) {
|
||||
return fs.writeFileSync(path, JSON.stringify(db, null, 2))
|
||||
}
|
||||
|
||||
function preComputeMap() {
|
||||
return getMapJSON({ height: MAP_HEIGHT, grid: 'diagonal' })
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('Fetching DB data')
|
||||
const dataDump = await getData(DATA_SOURCE)
|
||||
|
||||
console.log('Processing DB data')
|
||||
const db = processData(dataDump)
|
||||
|
||||
console.log('Saving DB data')
|
||||
saveFile(db, DATA_DESTINATION)
|
||||
|
||||
console.log('Pre-computing the word map')
|
||||
const map = preComputeMap()
|
||||
|
||||
console.log('Saving map data')
|
||||
saveFile(map, MAP_DESTINATION)
|
||||
|
||||
console.log('Done')
|
||||
}
|
||||
|
||||
main()
|
||||
Reference in New Issue
Block a user