@@ -12,14 +12,35 @@ import { type JSX, useCallback, useEffect, useMemo, useState } from "react";
1212import  { 
1313  AttributionControl , 
1414  GeolocateControl , 
15+   Layer , 
1516  Marker , 
1617  NavigationControl , 
1718  Popup , 
1819  ScaleControl , 
20+   Source , 
1921  useMap , 
2022}  from  "react-map-gl" ; 
2123import  MapGl  from  "react-map-gl/maplibre" ; 
2224
25+ // taken from android client these probably should be moved into a shared file 
26+ const  SNR_GOOD_THRESHOLD  =  - 7 ; 
27+ const  SNR_FAIR_THRESHOLD  =  - 15 ; 
28+ const  RSSI_GOOD_THRESHOLD  =  - 115 ; 
29+ const  RSSI_FAIR_THRESHOLD  =  - 126 ; 
30+ const  LINE_GOOD_COLOR  =  "#00ff00" ; 
31+ const  LINE_FAIR_COLOR  =  "#ffe600" ; 
32+ const  LINE_BAD_COLOR  =  "#f7931a" ; 
33+ 
34+ const  getSignalColor  =  ( snr : number ,  rssi ?: number )  =>  { 
35+   if  ( snr  >  SNR_GOOD_THRESHOLD  &&  ( rssi  ==  null  ||  rssi  >  RSSI_GOOD_THRESHOLD ) ) 
36+     return  LINE_GOOD_COLOR ; 
37+   if  ( snr  >  SNR_FAIR_THRESHOLD  &&  ( rssi  ==  null  ||  rssi  >  RSSI_FAIR_THRESHOLD ) ) 
38+     return  LINE_FAIR_COLOR ; 
39+   return  LINE_BAD_COLOR ; 
40+ } ; 
41+ 
42+ const  DIRECT_NODE_TIMEOUT  =  60  *  20 ;  // 60 seconds * ? minutes 
43+ 
2344type  NodePosition  =  { 
2445  latitude : number ; 
2546  longitude : number ; 
@@ -33,8 +54,72 @@ const convertToLatLng = (position: {
3354  longitude : ( position . longitudeI  ??  0 )  /  1e7 , 
3455} ) ; 
3556
57+ const  generateNeighborLines  =  ( 
58+   nodes : { 
59+     node : Protobuf . Mesh . NodeInfo ; 
60+     neighborInfo : Protobuf . Mesh . NeighborInfo ; 
61+   } [ ] , 
62+ )  =>  { 
63+   const  features  =  [ ] ; 
64+   for  ( const  {  node,  neighborInfo }  of  nodes )  { 
65+     const  start  =  convertToLatLng ( node . position ) ; 
66+     if  ( ! neighborInfo )  continue ; 
67+     for  ( const  neighbor  of  neighborInfo . neighbors )  { 
68+       const  toNode  =  nodes . find ( ( n )  =>  n . node . num  ===  neighbor . nodeId ) ?. node ; 
69+       if  ( ! toNode )  continue ; 
70+       const  end  =  convertToLatLng ( toNode . position ) ; 
71+       features . push ( { 
72+         type : "Feature" , 
73+         geometry : { 
74+           type : "LineString" , 
75+           coordinates : [ 
76+             [ start . longitude ,  start . latitude ] , 
77+             [ end . longitude ,  end . latitude ] , 
78+           ] , 
79+         } , 
80+         properties : { 
81+           color : getSignalColor ( neighbor . snr ) , 
82+         } , 
83+       } ) ; 
84+     } 
85+   } 
86+ 
87+   return  { 
88+     type : "FeatureCollection" , 
89+     features, 
90+   } ; 
91+ } ; 
92+ const  generateDirectLines  =  ( nodes : Protobuf . Mesh . NodeInfo [ ] )  =>  { 
93+   const  features  =  [ ] ; 
94+   for  ( const  node  of  nodes )  { 
95+     if  ( ! node . position )  continue ; 
96+     if  ( node . hopsAway  >  0 )  continue ; 
97+     if  ( Date . now ( )  /  1000  -  node . lastHeard  >  DIRECT_NODE_TIMEOUT )  continue ; 
98+     const  start  =  convertToLatLng ( node . position ) ; 
99+     const  selfNode  =  nodes . find ( ( n )  =>  n . isFavorite ) ; 
100+     const  end  =  convertToLatLng ( selfNode . position ) ; 
101+     features . push ( { 
102+       type : "Feature" , 
103+       geometry : { 
104+         type : "LineString" , 
105+         coordinates : [ 
106+           [ start . longitude ,  start . latitude ] , 
107+           [ end . longitude ,  end . latitude ] , 
108+         ] , 
109+       } , 
110+       properties : { 
111+         color : getSignalColor ( node . snr ) , 
112+       } , 
113+     } ) ; 
114+   } 
115+ 
116+   return  { 
117+     type : "FeatureCollection" , 
118+     features, 
119+   } ; 
120+ } ; 
36121const  MapPage  =  ( ) : JSX . Element  =>  { 
37-   const  {  nodes,  waypoints }  =  useDevice ( ) ; 
122+   const  {  nodes,  waypoints,  neighborInfo  }  =  useDevice ( ) ; 
38123  const  currentTheme  =  useTheme ( ) ; 
39124  const  {  default : map  }  =  useMap ( ) ; 
40125
@@ -131,6 +216,19 @@ const MapPage = (): JSX.Element => {
131216    [ validNodes ,  handleMarkerClick ] , 
132217  ) ; 
133218
219+   const  neighborLines  =  useMemo ( ( )  =>  { 
220+     return  generateNeighborLines ( 
221+       validNodes . map ( ( vn )  =>  ( { 
222+         node : vn , 
223+         neighborInfo : neighborInfo . get ( vn . num ) , 
224+       } ) ) , 
225+     ) ; 
226+   } ,  [ validNodes ,  neighborInfo ] ) ; 
227+ 
228+   const  directLines  =  useMemo ( 
229+     ( )  =>  generateDirectLines ( validNodes ) , 
230+     [ validNodes ] , 
231+   ) ; 
134232  useEffect ( ( )  =>  { 
135233    map ?. on ( "load" ,  ( )  =>  { 
136234      getMapBounds ( ) ; 
@@ -185,6 +283,26 @@ const MapPage = (): JSX.Element => {
185283            </ Marker > 
186284          ) ) } 
187285          { markers } 
286+           < Source  id = "neighbor-lines"  type = "geojson"  data = { neighborLines } > 
287+             < Layer 
288+               id = "neighborLineLayer" 
289+               type = "line" 
290+               paint = { { 
291+                 "line-color" : [ "get" ,  "color" ] , 
292+                 "line-width" : 2 , 
293+               } } 
294+             /> 
295+           </ Source > 
296+           < Source  id = "direct-lines"  type = "geojson"  data = { directLines } > 
297+             < Layer 
298+               id = "directLineLayer" 
299+               type = "line" 
300+               paint = { { 
301+                 "line-color" : [ "get" ,  "color" ] , 
302+                 "line-width" : 4 , 
303+               } } 
304+             /> 
305+           </ Source > 
188306          { selectedNode  ? ( 
189307            < Popup 
190308              anchor = "top" 
0 commit comments