diff --git a/frontend/src/app/lightning/node/node.component.html b/frontend/src/app/lightning/node/node.component.html
index 858aa9b48..b5d472d2c 100644
--- a/frontend/src/app/lightning/node/node.component.html
+++ b/frontend/src/app/lightning/node/node.component.html
@@ -52,6 +52,10 @@
Unknown
+
+ Avg channel distance |
+ {{ avgDistance | number : '1.0-0' }} km / {{ kmToMiles(avgDistance) | number : '1.0-0' }} mi |
+
diff --git a/frontend/src/app/lightning/node/node.component.scss b/frontend/src/app/lightning/node/node.component.scss
index d54b1851b..77b9cabb7 100644
--- a/frontend/src/app/lightning/node/node.component.scss
+++ b/frontend/src/app/lightning/node/node.component.scss
@@ -101,3 +101,7 @@ app-fiat {
font-family: "Courier New", Courier, monospace;
}
}
+
+.separator {
+ margin: 0 1em;
+}
diff --git a/frontend/src/app/lightning/node/node.component.ts b/frontend/src/app/lightning/node/node.component.ts
index ec2edd252..411246d2b 100644
--- a/frontend/src/app/lightning/node/node.component.ts
+++ b/frontend/src/app/lightning/node/node.component.ts
@@ -3,9 +3,11 @@ import { ActivatedRoute, ParamMap } from '@angular/router';
import { Observable } from 'rxjs';
import { catchError, map, switchMap, tap } from 'rxjs/operators';
import { SeoService } from '../../services/seo.service';
+import { ApiService } from '../../services/api.service';
import { LightningApiService } from '../lightning-api.service';
import { GeolocationData } from '../../shared/components/geolocation/geolocation.component';
import { ILiquidityAd, parseLiquidityAdHex } from './liquidity-ad';
+import { haversineDistance, kmToMiles } from 'src/app/shared/common.utils';
interface CustomRecord {
type: string;
@@ -34,8 +36,12 @@ export class NodeComponent implements OnInit {
showDetails = false;
liquidityAd: ILiquidityAd;
tlvRecords: CustomRecord[];
+ avgChannelDistance$: Observable;
+
+ kmToMiles = kmToMiles;
constructor(
+ private apiService: ApiService,
private lightningApiService: LightningApiService,
private activatedRoute: ActivatedRoute,
private seoService: SeoService,
@@ -119,6 +125,26 @@ export class NodeComponent implements OnInit {
}];
})
);
+
+ this.avgChannelDistance$ = this.activatedRoute.paramMap
+ .pipe(
+ switchMap((params: ParamMap) => {
+ return this.apiService.getChannelsGeo$(params.get('public_key'), 'nodepage');
+ }),
+ map((channelsGeo) => {
+ if (channelsGeo?.length) {
+ const totalDistance = channelsGeo.reduce((sum, chan) => {
+ return sum + haversineDistance(chan[3], chan[2], chan[7], chan[6]);
+ }, 0);
+ return totalDistance / channelsGeo.length;
+ } else {
+ return null;
+ }
+ }),
+ catchError(() => {
+ return null;
+ })
+ ) as Observable;
}
toggleShowDetails(): void {
diff --git a/frontend/src/app/shared/common.utils.ts b/frontend/src/app/shared/common.utils.ts
index d38583217..7d206f4b5 100644
--- a/frontend/src/app/shared/common.utils.ts
+++ b/frontend/src/app/shared/common.utils.ts
@@ -118,3 +118,21 @@ export function convertRegion(input, to: 'name' | 'abbreviated'): string {
}
}
}
+
+export function haversineDistance(lat1: number, lon1: number, lat2: number, lon2: number): number {
+ const rlat1 = lat1 * Math.PI / 180;
+ const rlon1 = lon1 * Math.PI / 180;
+ const rlat2 = lat2 * Math.PI / 180;
+ const rlon2 = lon2 * Math.PI / 180;
+
+ const dlat = Math.sin((rlat2 - rlat1) / 2);
+ const dlon = Math.sin((rlon2 - rlon1) / 2);
+ const a = Math.min(1, Math.max(0, (dlat * dlat) + (Math.cos(rlat1) * Math.cos(rlat2) * dlon * dlon)));
+ const d = 2 * 6371 * Math.asin(Math.sqrt(a));
+
+ return d;
+}
+
+export function kmToMiles(km: number): number {
+ return km * 0.62137119;
+}
\ No newline at end of file