Skip to content

Commit

Permalink
Merge pull request #32 from oceanprotocol/feat/map-nodes-fetch
Browse files Browse the repository at this point in the history
adds map data context & locations pins for all nodes
  • Loading branch information
bogdanfazakas authored Sep 17, 2024
2 parents f20ceeb + eb29cbd commit 448fc7e
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 51 deletions.
115 changes: 68 additions & 47 deletions src/components/Map/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet'
import MarkerIcon from '../../assets/marker_map_icon.png'
import L, { LatLngExpression } from 'leaflet'
import 'leaflet/dist/leaflet.css'
import { useDataContext } from '@/context/DataContext'
import { NodeData } from '@/shared/types/RowDataType'
import styles from './style.module.css'
import { useMapContext } from '../../context/MapContext'
import { LinearProgress } from '@mui/material'
import { LocationNode } from '../../shared/types/locationNodeType'

export default function Map() {
const [isClient, setIsClient] = useState(false)
const { data, loading, error } = useDataContext()
console.log('Table data: ', data)
const { data, loading, error } = useMapContext()

useEffect(() => {
setIsClient(true)
Expand All @@ -27,56 +27,77 @@ export default function Map() {
})

const getRandomOffset = (): number => {
const min = 0.0002;
const max = 0.0006;
const randomValue = Math.random() * (max - min) + min;
return Math.random() < 0.5 ? -randomValue : randomValue;
};
const min = 0.0002
const max = 0.0006
const randomValue = Math.random() * (max - min) + min
return Math.random() < 0.5 ? -randomValue : randomValue
}

const offsetCoordinates = (latitude: number, longitude: number, index: number, total: number): LatLngExpression => {

const latOffset = getRandomOffset();
const lngOffset = getRandomOffset();

return [
latitude + latOffset,
longitude + lngOffset
];
};
const groupedNodes = data.reduce((acc, node: NodeData) => {
if (node?.location?.lat && node?.location?.lon) {
const key = `${node.location.lat},${node.location.lon}`
if (!acc[key]) {
acc[key] = []
const offsetCoordinates = (latitude: number, longitude: number): LatLngExpression => {
const latOffset = getRandomOffset()
const lngOffset = getRandomOffset()
return [latitude + latOffset, longitude + lngOffset]
}

const groupedNodesByCity = data.reduce(
(
acc: Record<string, { lat: number; lon: number; country: string; count: number }>,
node: LocationNode
) => {
const { city, lat, lon, country } = node._source

if (city) {
if (!acc[city]) {
acc[city] = { lat, lon, country, count: 0 }
}

acc[city].count += 1
}
acc[key].push(node)
}
return acc
}, {} as Record<string, NodeData[]>)

return acc
},
{}
)

return (
isClient && (
<MapContainer center={center} zoom={2} style={{ height: '500px', width: '100%' }}>
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
{Object.entries(groupedNodes).map(([key, nodes]) => (
nodes.map((node, index) => (
<Marker
icon={customIcon}
position={offsetCoordinates(node.location.lat, node.location.lon, index, nodes.length)}
key={`${node.id}-${index}`}
>
<Popup className={styles.popup}>
<strong>Node ID:</strong> {node.id}
<br />
<strong>Network:</strong> {node.indexer?.[0]?.network}
<br />
<strong>Location:</strong> {node.location.country}
<br />
<strong>City:</strong> {node.location.city}
</Popup>
</Marker>
))
))}
{!loading &&
!error &&
Object.entries(groupedNodesByCity).map(
([city, { lat, lon, country, count }]) => {
if (
typeof lat !== 'number' ||
typeof lon !== 'number' ||
isNaN(lat) ||
isNaN(lon)
) {
console.warn(
`Invalid coordinates for city: ${city}, lat: ${lat}, lon: ${lon}`
)
return null
}

return (
<Marker
key={city}
icon={customIcon}
position={offsetCoordinates(lat, lon)}
>
<Popup className={styles.popup}>
<strong>City:</strong> {city}
<br />
<strong>Country:</strong> {country}
<br />
<strong>Total Nodes:</strong> {count}
<br />
</Popup>
</Marker>
)
}
)}
{loading && <LinearProgress />}
</MapContainer>
)
)
Expand Down
82 changes: 82 additions & 0 deletions src/context/MapContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React, {
createContext,
useState,
useEffect,
useContext,
ReactNode,
useMemo
} from 'react'
import axios from 'axios'
import { LocationNode } from '../shared/types/locationNodeType'

interface CountryNodesInfo {
country: string
nodeIds: string[]
totalNodes: number
}

interface MapContextType {
data: LocationNode[]
countryNodesInfo: CountryNodesInfo[]
loading: boolean
error: any
}

interface MapProviderProps {
children: ReactNode
}

export const MapContext = createContext<MapContextType | undefined>(undefined)

export const MapProvider: React.FC<MapProviderProps> = ({ children }) => {
const [data, setData] = useState<LocationNode[]>([])
const [countryNodesInfo, setCountryNodesInfo] = useState<CountryNodesInfo[]>([])
const [loading, setLoading] = useState<boolean>(true)
const [error, setError] = useState<any>(null)

const fetchUrl = useMemo(() => {
const baseUrl =
process.env.NEXT_PUBLIC_API_URL || 'https://incentive-backend.oceanprotocol.com'
return `${baseUrl}/locations`
}, [])

useEffect(() => {
const fetchData = async () => {
setLoading(true)
try {
const response = await axios.get(fetchUrl)
const nodes = response.data

setData(nodes)
} catch (err) {
console.log('error', err)
setError(err)
} finally {
setLoading(false)
}
}

fetchData()
}, [fetchUrl])

return (
<MapContext.Provider
value={{
data,
countryNodesInfo,
loading,
error
}}
>
{children}
</MapContext.Provider>
)
}

export const useMapContext = () => {
const context = useContext(MapContext)
if (context === undefined) {
throw new Error('useMapContext must be used within a MapProvider')
}
return context
}
11 changes: 7 additions & 4 deletions src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import RootLayout from '../components/Layout';
import { AdminProvider } from '../context/AdminProvider';
import { chains } from '../shared/utils/chains';
import { DataProvider } from '@/context/DataContext';
import { MapProvider } from '../context/MapContext'

export default function App({ Component, pageProps }: AppProps) {
const config = getDefaultConfig({
Expand All @@ -28,13 +29,15 @@ export default function App({ Component, pageProps }: AppProps) {
<RainbowKitProvider>
<AdminProvider>
<DataProvider>
<RootLayout>
<Component {...pageProps} />
</RootLayout>
<MapProvider>
<RootLayout>
<Component {...pageProps} />
</RootLayout>
</MapProvider>
</DataProvider>
</AdminProvider>
</RainbowKitProvider>
</QueryClientProvider>
</WagmiProvider>
);
)
}
13 changes: 13 additions & 0 deletions src/shared/types/locationNodeType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export interface LocationNode {
_index: string
_id: string
_score: number
_source: {
id: string
ip: string
country: string
city: string
lat: number
lon: number
}
}

0 comments on commit 448fc7e

Please sign in to comment.