Document LOS frequency and k-factor math

Show the connector frequency right next to the Frequency label, display the derived k value, and keep the info dialog tied to the exact
This commit is contained in:
just_stuff_tm
2026-02-23 02:42:58 -05:00
parent 2bdd9d35cc
commit fc55fb98ce
3 changed files with 166 additions and 35 deletions
+39 -8
View File
@@ -12,14 +12,14 @@ class LineOfSightSample {
final double distanceMeters;
final double terrainMeters;
final double lineHeightMeters;
final double radioHorizonMeters;
final double refractedHeightMeters;
final double clearanceMeters;
const LineOfSightSample({
required this.distanceMeters,
required this.terrainMeters,
required this.lineHeightMeters,
required this.radioHorizonMeters,
required this.refractedHeightMeters,
required this.clearanceMeters,
});
}
@@ -32,6 +32,8 @@ class LineOfSightResult {
final double? firstObstructionDistanceMeters;
final List<LineOfSightSample> samples;
final String? errorMessage;
final double usedKFactor;
final double? frequencyMHz;
const LineOfSightResult({
required this.hasData,
@@ -40,6 +42,8 @@ class LineOfSightResult {
required this.maxObstructionMeters,
required this.firstObstructionDistanceMeters,
required this.samples,
required this.usedKFactor,
this.frequencyMHz,
this.errorMessage,
});
@@ -50,7 +54,9 @@ class LineOfSightResult {
isClear = false,
maxObstructionMeters = 0,
firstObstructionDistanceMeters = null,
samples = const [];
samples = const [],
usedKFactor = 4.0 / 3.0,
frequencyMHz = null;
}
class LineOfSightPathSegment {
@@ -91,6 +97,11 @@ class LineOfSightService {
static const Duration _cacheTtl = Duration(hours: 24);
static const int _maxFetchAttempts = 4; // initial try + 3 retries
static const Duration _initialBackoff = Duration(milliseconds: 300);
static const double _baselineFrequencyMHz = 915.0;
static const double _baselineKFactor = 4.0 / 3.0;
static double get baselineFrequencyMHz => _baselineFrequencyMHz;
static double get baselineKFactor => _baselineKFactor;
final http.Client _httpClient;
final bool _ownsHttpClient;
@@ -108,7 +119,7 @@ class LineOfSightService {
List<LatLng> points, {
double startAntennaHeightMeters = 1.5,
double endAntennaHeightMeters = 1.5,
double kFactor = 4.0 / 3.0,
double? frequencyMHz,
double obstructionToleranceMeters = 0.0,
}) async {
if (points.length < 2) {
@@ -125,6 +136,7 @@ class LineOfSightService {
var blockedSegments = 0;
var unknownSegments = 0;
final kFactor = _kFactorForFrequency(frequencyMHz);
for (int i = 0; i < points.length - 1; i++) {
final result = await analyzeLink(
points[i],
@@ -132,6 +144,7 @@ class LineOfSightService {
startAntennaHeightMeters: startAntennaHeightMeters,
endAntennaHeightMeters: endAntennaHeightMeters,
kFactor: kFactor,
frequencyMHz: frequencyMHz,
obstructionToleranceMeters: obstructionToleranceMeters,
);
segments.add(
@@ -165,7 +178,8 @@ class LineOfSightService {
LatLng end, {
double startAntennaHeightMeters = 1.5,
double endAntennaHeightMeters = 1.5,
double kFactor = 4.0 / 3.0,
required double kFactor,
double? frequencyMHz,
double obstructionToleranceMeters = 0.0,
}) async {
final totalDistanceMeters = _distance.as(LengthUnit.Meter, start, end);
@@ -177,6 +191,8 @@ class LineOfSightService {
maxObstructionMeters: 0,
firstObstructionDistanceMeters: null,
samples: const [],
usedKFactor: kFactor,
frequencyMHz: frequencyMHz,
);
}
@@ -205,7 +221,8 @@ class LineOfSightService {
required List<double> elevations,
double startAntennaHeightMeters = 1.5,
double endAntennaHeightMeters = 1.5,
double kFactor = 4.0 / 3.0,
required double kFactor,
double? frequencyMHz,
double obstructionToleranceMeters = 0.0,
}) {
if (points.length < 2 || elevations.length != points.length) {
@@ -240,7 +257,10 @@ class LineOfSightService {
(2 * effectiveEarthRadius);
final terrainHeight = elevations[i] + earthBulge;
final clearance = lineHeight - terrainHeight;
final radioHorizonHeight = lineHeight - earthBulge;
final unrefBulge =
(distanceFromStart * (totalDistanceMeters - distanceFromStart)) /
(2 * _earthRadiusMeters);
final refractedHeight = lineHeight + (unrefBulge - earthBulge);
if (clearance < -obstructionToleranceMeters) {
isClear = false;
@@ -256,7 +276,7 @@ class LineOfSightService {
distanceMeters: distanceFromStart,
terrainMeters: terrainHeight,
lineHeightMeters: lineHeight,
radioHorizonMeters: radioHorizonHeight,
refractedHeightMeters: refractedHeight,
clearanceMeters: clearance,
),
);
@@ -269,9 +289,20 @@ class LineOfSightService {
maxObstructionMeters: maxObstructionMeters,
firstObstructionDistanceMeters: firstObstructionDistanceMeters,
samples: samples,
usedKFactor: kFactor,
frequencyMHz: frequencyMHz,
);
}
static double _kFactorForFrequency(double? frequencyMHz) {
if (frequencyMHz == null) return _baselineKFactor;
final delta =
(frequencyMHz - _baselineFrequencyMHz) / _baselineFrequencyMHz;
final adjustment = delta * 0.15;
final scaled = _baselineKFactor * (1 + adjustment);
return scaled.clamp(1.1, 1.6).toDouble();
}
List<LatLng> _buildSamplePoints(
LatLng start,
LatLng end,