@@ -12,14 +12,35 @@ import { type JSX, useCallback, useEffect, useMemo, useState } from "react";
12
12
import {
13
13
AttributionControl ,
14
14
GeolocateControl ,
15
+ Layer ,
15
16
Marker ,
16
17
NavigationControl ,
17
18
Popup ,
18
19
ScaleControl ,
20
+ Source ,
19
21
useMap ,
20
22
} from "react-map-gl" ;
21
23
import MapGl from "react-map-gl/maplibre" ;
22
24
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
+
23
44
type NodePosition = {
24
45
latitude : number ;
25
46
longitude : number ;
@@ -33,8 +54,72 @@ const convertToLatLng = (position: {
33
54
longitude : ( position . longitudeI ?? 0 ) / 1e7 ,
34
55
} ) ;
35
56
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
+ } ;
36
121
const MapPage = ( ) : JSX . Element => {
37
- const { nodes, waypoints } = useDevice ( ) ;
122
+ const { nodes, waypoints, neighborInfo } = useDevice ( ) ;
38
123
const currentTheme = useTheme ( ) ;
39
124
const { default : map } = useMap ( ) ;
40
125
@@ -131,6 +216,19 @@ const MapPage = (): JSX.Element => {
131
216
[ validNodes , handleMarkerClick ] ,
132
217
) ;
133
218
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
+ ) ;
134
232
useEffect ( ( ) => {
135
233
map ?. on ( "load" , ( ) => {
136
234
getMapBounds ( ) ;
@@ -185,6 +283,26 @@ const MapPage = (): JSX.Element => {
185
283
</ Marker >
186
284
) ) }
187
285
{ 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 >
188
306
{ selectedNode ? (
189
307
< Popup
190
308
anchor = "top"
0 commit comments