|
|
|
@ -12,14 +12,14 @@ class LineOfSightSample {
|
|
|
|
final double distanceMeters;
|
|
|
|
final double distanceMeters;
|
|
|
|
final double terrainMeters;
|
|
|
|
final double terrainMeters;
|
|
|
|
final double lineHeightMeters;
|
|
|
|
final double lineHeightMeters;
|
|
|
|
final double radioHorizonMeters;
|
|
|
|
final double refractedHeightMeters;
|
|
|
|
final double clearanceMeters;
|
|
|
|
final double clearanceMeters;
|
|
|
|
|
|
|
|
|
|
|
|
const LineOfSightSample({
|
|
|
|
const LineOfSightSample({
|
|
|
|
required this.distanceMeters,
|
|
|
|
required this.distanceMeters,
|
|
|
|
required this.terrainMeters,
|
|
|
|
required this.terrainMeters,
|
|
|
|
required this.lineHeightMeters,
|
|
|
|
required this.lineHeightMeters,
|
|
|
|
required this.radioHorizonMeters,
|
|
|
|
required this.refractedHeightMeters,
|
|
|
|
required this.clearanceMeters,
|
|
|
|
required this.clearanceMeters,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -32,6 +32,8 @@ class LineOfSightResult {
|
|
|
|
final double? firstObstructionDistanceMeters;
|
|
|
|
final double? firstObstructionDistanceMeters;
|
|
|
|
final List<LineOfSightSample> samples;
|
|
|
|
final List<LineOfSightSample> samples;
|
|
|
|
final String? errorMessage;
|
|
|
|
final String? errorMessage;
|
|
|
|
|
|
|
|
final double usedKFactor;
|
|
|
|
|
|
|
|
final double? frequencyMHz;
|
|
|
|
|
|
|
|
|
|
|
|
const LineOfSightResult({
|
|
|
|
const LineOfSightResult({
|
|
|
|
required this.hasData,
|
|
|
|
required this.hasData,
|
|
|
|
@ -40,6 +42,8 @@ class LineOfSightResult {
|
|
|
|
required this.maxObstructionMeters,
|
|
|
|
required this.maxObstructionMeters,
|
|
|
|
required this.firstObstructionDistanceMeters,
|
|
|
|
required this.firstObstructionDistanceMeters,
|
|
|
|
required this.samples,
|
|
|
|
required this.samples,
|
|
|
|
|
|
|
|
required this.usedKFactor,
|
|
|
|
|
|
|
|
this.frequencyMHz,
|
|
|
|
this.errorMessage,
|
|
|
|
this.errorMessage,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
@ -50,7 +54,9 @@ class LineOfSightResult {
|
|
|
|
isClear = false,
|
|
|
|
isClear = false,
|
|
|
|
maxObstructionMeters = 0,
|
|
|
|
maxObstructionMeters = 0,
|
|
|
|
firstObstructionDistanceMeters = null,
|
|
|
|
firstObstructionDistanceMeters = null,
|
|
|
|
samples = const [];
|
|
|
|
samples = const [],
|
|
|
|
|
|
|
|
usedKFactor = 4.0 / 3.0,
|
|
|
|
|
|
|
|
frequencyMHz = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class LineOfSightPathSegment {
|
|
|
|
class LineOfSightPathSegment {
|
|
|
|
@ -91,6 +97,11 @@ class LineOfSightService {
|
|
|
|
static const Duration _cacheTtl = Duration(hours: 24);
|
|
|
|
static const Duration _cacheTtl = Duration(hours: 24);
|
|
|
|
static const int _maxFetchAttempts = 4; // initial try + 3 retries
|
|
|
|
static const int _maxFetchAttempts = 4; // initial try + 3 retries
|
|
|
|
static const Duration _initialBackoff = Duration(milliseconds: 300);
|
|
|
|
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 http.Client _httpClient;
|
|
|
|
final bool _ownsHttpClient;
|
|
|
|
final bool _ownsHttpClient;
|
|
|
|
@ -108,7 +119,7 @@ class LineOfSightService {
|
|
|
|
List<LatLng> points, {
|
|
|
|
List<LatLng> points, {
|
|
|
|
double startAntennaHeightMeters = 1.5,
|
|
|
|
double startAntennaHeightMeters = 1.5,
|
|
|
|
double endAntennaHeightMeters = 1.5,
|
|
|
|
double endAntennaHeightMeters = 1.5,
|
|
|
|
double kFactor = 4.0 / 3.0,
|
|
|
|
double? frequencyMHz,
|
|
|
|
double obstructionToleranceMeters = 0.0,
|
|
|
|
double obstructionToleranceMeters = 0.0,
|
|
|
|
}) async {
|
|
|
|
}) async {
|
|
|
|
if (points.length < 2) {
|
|
|
|
if (points.length < 2) {
|
|
|
|
@ -125,6 +136,7 @@ class LineOfSightService {
|
|
|
|
var blockedSegments = 0;
|
|
|
|
var blockedSegments = 0;
|
|
|
|
var unknownSegments = 0;
|
|
|
|
var unknownSegments = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
final kFactor = _kFactorForFrequency(frequencyMHz);
|
|
|
|
for (int i = 0; i < points.length - 1; i++) {
|
|
|
|
for (int i = 0; i < points.length - 1; i++) {
|
|
|
|
final result = await analyzeLink(
|
|
|
|
final result = await analyzeLink(
|
|
|
|
points[i],
|
|
|
|
points[i],
|
|
|
|
@ -132,6 +144,7 @@ class LineOfSightService {
|
|
|
|
startAntennaHeightMeters: startAntennaHeightMeters,
|
|
|
|
startAntennaHeightMeters: startAntennaHeightMeters,
|
|
|
|
endAntennaHeightMeters: endAntennaHeightMeters,
|
|
|
|
endAntennaHeightMeters: endAntennaHeightMeters,
|
|
|
|
kFactor: kFactor,
|
|
|
|
kFactor: kFactor,
|
|
|
|
|
|
|
|
frequencyMHz: frequencyMHz,
|
|
|
|
obstructionToleranceMeters: obstructionToleranceMeters,
|
|
|
|
obstructionToleranceMeters: obstructionToleranceMeters,
|
|
|
|
);
|
|
|
|
);
|
|
|
|
segments.add(
|
|
|
|
segments.add(
|
|
|
|
@ -165,7 +178,8 @@ class LineOfSightService {
|
|
|
|
LatLng end, {
|
|
|
|
LatLng end, {
|
|
|
|
double startAntennaHeightMeters = 1.5,
|
|
|
|
double startAntennaHeightMeters = 1.5,
|
|
|
|
double endAntennaHeightMeters = 1.5,
|
|
|
|
double endAntennaHeightMeters = 1.5,
|
|
|
|
double kFactor = 4.0 / 3.0,
|
|
|
|
required double kFactor,
|
|
|
|
|
|
|
|
double? frequencyMHz,
|
|
|
|
double obstructionToleranceMeters = 0.0,
|
|
|
|
double obstructionToleranceMeters = 0.0,
|
|
|
|
}) async {
|
|
|
|
}) async {
|
|
|
|
final totalDistanceMeters = _distance.as(LengthUnit.Meter, start, end);
|
|
|
|
final totalDistanceMeters = _distance.as(LengthUnit.Meter, start, end);
|
|
|
|
@ -177,6 +191,8 @@ class LineOfSightService {
|
|
|
|
maxObstructionMeters: 0,
|
|
|
|
maxObstructionMeters: 0,
|
|
|
|
firstObstructionDistanceMeters: null,
|
|
|
|
firstObstructionDistanceMeters: null,
|
|
|
|
samples: const [],
|
|
|
|
samples: const [],
|
|
|
|
|
|
|
|
usedKFactor: kFactor,
|
|
|
|
|
|
|
|
frequencyMHz: frequencyMHz,
|
|
|
|
);
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -205,7 +221,8 @@ class LineOfSightService {
|
|
|
|
required List<double> elevations,
|
|
|
|
required List<double> elevations,
|
|
|
|
double startAntennaHeightMeters = 1.5,
|
|
|
|
double startAntennaHeightMeters = 1.5,
|
|
|
|
double endAntennaHeightMeters = 1.5,
|
|
|
|
double endAntennaHeightMeters = 1.5,
|
|
|
|
double kFactor = 4.0 / 3.0,
|
|
|
|
required double kFactor,
|
|
|
|
|
|
|
|
double? frequencyMHz,
|
|
|
|
double obstructionToleranceMeters = 0.0,
|
|
|
|
double obstructionToleranceMeters = 0.0,
|
|
|
|
}) {
|
|
|
|
}) {
|
|
|
|
if (points.length < 2 || elevations.length != points.length) {
|
|
|
|
if (points.length < 2 || elevations.length != points.length) {
|
|
|
|
@ -240,7 +257,10 @@ class LineOfSightService {
|
|
|
|
(2 * effectiveEarthRadius);
|
|
|
|
(2 * effectiveEarthRadius);
|
|
|
|
final terrainHeight = elevations[i] + earthBulge;
|
|
|
|
final terrainHeight = elevations[i] + earthBulge;
|
|
|
|
final clearance = lineHeight - terrainHeight;
|
|
|
|
final clearance = lineHeight - terrainHeight;
|
|
|
|
final radioHorizonHeight = lineHeight - earthBulge;
|
|
|
|
final unrefBulge =
|
|
|
|
|
|
|
|
(distanceFromStart * (totalDistanceMeters - distanceFromStart)) /
|
|
|
|
|
|
|
|
(2 * _earthRadiusMeters);
|
|
|
|
|
|
|
|
final refractedHeight = lineHeight + (unrefBulge - earthBulge);
|
|
|
|
|
|
|
|
|
|
|
|
if (clearance < -obstructionToleranceMeters) {
|
|
|
|
if (clearance < -obstructionToleranceMeters) {
|
|
|
|
isClear = false;
|
|
|
|
isClear = false;
|
|
|
|
@ -256,7 +276,7 @@ class LineOfSightService {
|
|
|
|
distanceMeters: distanceFromStart,
|
|
|
|
distanceMeters: distanceFromStart,
|
|
|
|
terrainMeters: terrainHeight,
|
|
|
|
terrainMeters: terrainHeight,
|
|
|
|
lineHeightMeters: lineHeight,
|
|
|
|
lineHeightMeters: lineHeight,
|
|
|
|
radioHorizonMeters: radioHorizonHeight,
|
|
|
|
refractedHeightMeters: refractedHeight,
|
|
|
|
clearanceMeters: clearance,
|
|
|
|
clearanceMeters: clearance,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
);
|
|
|
|
@ -269,9 +289,20 @@ class LineOfSightService {
|
|
|
|
maxObstructionMeters: maxObstructionMeters,
|
|
|
|
maxObstructionMeters: maxObstructionMeters,
|
|
|
|
firstObstructionDistanceMeters: firstObstructionDistanceMeters,
|
|
|
|
firstObstructionDistanceMeters: firstObstructionDistanceMeters,
|
|
|
|
samples: samples,
|
|
|
|
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(
|
|
|
|
List<LatLng> _buildSamplePoints(
|
|
|
|
LatLng start,
|
|
|
|
LatLng start,
|
|
|
|
LatLng end,
|
|
|
|
LatLng end,
|
|
|
|
|