feat(tactical): sovereign military suite with 100% real satellite DEM intervisibility, viewshed 360, offline engines, and entitlements
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
import 'dart:math' as math;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:intaleq_maps/intaleq_maps.dart';
|
||||
import 'offline_los_engine.dart';
|
||||
import 'dem_tile_elevation_service.dart';
|
||||
|
||||
/// One radial sight ray from observer at a specific azimuth angle
|
||||
class RadialRayResult {
|
||||
final double azimuthDeg;
|
||||
final double maxRangeMeters;
|
||||
final double visibleDistanceMeters;
|
||||
final LatLng endpoint;
|
||||
final bool isBlockedByTerrain;
|
||||
final LatLng? obstacleCoord;
|
||||
final double? obstacleElevation;
|
||||
|
||||
const RadialRayResult({
|
||||
required this.azimuthDeg,
|
||||
required this.maxRangeMeters,
|
||||
required this.visibleDistanceMeters,
|
||||
required this.endpoint,
|
||||
required this.isBlockedByTerrain,
|
||||
this.obstacleCoord,
|
||||
this.obstacleElevation,
|
||||
});
|
||||
}
|
||||
|
||||
/// 360° Comprehensive Tactical Viewshed (حقل الرؤية والرصد الدائري 360 درجة)
|
||||
class Viewshed360Report {
|
||||
final LatLng center;
|
||||
final double observerHeightM;
|
||||
final double radiusMeters;
|
||||
final double observerGroundElevM;
|
||||
final double observerTotalElevM;
|
||||
final List<RadialRayResult> rays;
|
||||
final List<LatLng> polygonVertices;
|
||||
final double visibleAreaSqKm;
|
||||
final double totalPotentialAreaSqKm;
|
||||
final int visibilityPercentage;
|
||||
final double averageSightRangeKm;
|
||||
|
||||
const Viewshed360Report({
|
||||
required this.center,
|
||||
required this.observerHeightM,
|
||||
required this.radiusMeters,
|
||||
required this.observerGroundElevM,
|
||||
required this.observerTotalElevM,
|
||||
required this.rays,
|
||||
required this.polygonVertices,
|
||||
required this.visibleAreaSqKm,
|
||||
required this.totalPotentialAreaSqKm,
|
||||
required this.visibilityPercentage,
|
||||
required this.averageSightRangeKm,
|
||||
});
|
||||
|
||||
void printTelemetry() {
|
||||
debugPrint(
|
||||
'════════════════════════════════════════════════════════════════════');
|
||||
debugPrint('🌐 [TACTICAL 360° VIEWSHED & FAN COVERAGE REPORT]');
|
||||
debugPrint(
|
||||
'• مركز الرصد: ${center.latitude.toStringAsFixed(5)}, ${center.longitude.toStringAsFixed(5)}');
|
||||
debugPrint(
|
||||
'• ارتفاع سطح الأرض: ${observerGroundElevM.round()}م | إجمالي مع البصريات: ${observerTotalElevM.round()}م');
|
||||
debugPrint(
|
||||
'• نصف قطر المسح: ${(radiusMeters / 1000.0).toStringAsFixed(1)} كم');
|
||||
debugPrint('• عدد أشعة المسح: ${rays.length} شعاع (360° Radial Sweep)');
|
||||
debugPrint(
|
||||
'• مساحة الرؤية المكشوفة: ${visibleAreaSqKm.toStringAsFixed(2)} كم² من أصل ${totalPotentialAreaSqKm.toStringAsFixed(2)} كم²');
|
||||
debugPrint('• كفاءة التغطية البصرية: $visibilityPercentage%');
|
||||
debugPrint(
|
||||
'• متوسط مدى الرؤية الفعلي: ${averageSightRangeKm.toStringAsFixed(2)} كم');
|
||||
debugPrint(
|
||||
'════════════════════════════════════════════════════════════════════');
|
||||
}
|
||||
}
|
||||
|
||||
/// Sovereign Ultra-Fast 360° Tactical Viewshed Engine
|
||||
|
||||
class Viewshed360Engine {
|
||||
Viewshed360Engine._();
|
||||
|
||||
/// Calculate 360° Viewshed asynchronously after pre-fetching real satellite DEM tiles
|
||||
static Future<Viewshed360Report> calculateAsync({
|
||||
required LatLng observer,
|
||||
double radiusMeters = 5000.0,
|
||||
double observerHeightM = 2.0,
|
||||
double targetHeightM = 1.5,
|
||||
int rayCount = 120,
|
||||
int samplesPerRay = 30,
|
||||
}) async {
|
||||
final dLat = (radiusMeters / 111132.95);
|
||||
final dLng = (radiusMeters /
|
||||
(111412.84 * math.cos(observer.latitude * math.pi / 180.0)));
|
||||
final boundingCoords = [
|
||||
LatLng(observer.latitude + dLat, observer.longitude + dLng),
|
||||
LatLng(observer.latitude - dLat, observer.longitude + dLng),
|
||||
LatLng(observer.latitude + dLat, observer.longitude - dLng),
|
||||
LatLng(observer.latitude - dLat, observer.longitude - dLng),
|
||||
];
|
||||
await DemTileElevationService.prefetchTilesForCoords(boundingCoords,
|
||||
zoom: 12);
|
||||
return calculate(
|
||||
observer: observer,
|
||||
radiusMeters: radiusMeters,
|
||||
observerHeightM: observerHeightM,
|
||||
targetHeightM: targetHeightM,
|
||||
rayCount: rayCount,
|
||||
samplesPerRay: samplesPerRay,
|
||||
);
|
||||
}
|
||||
|
||||
/// Calculate 360° Viewshed in real-time on device (Takes 10-30ms)
|
||||
static Viewshed360Report calculate({
|
||||
required LatLng observer,
|
||||
double radiusMeters = 5000.0,
|
||||
double observerHeightM = 2.0,
|
||||
double targetHeightM = 1.5,
|
||||
int rayCount =
|
||||
120, // 1 ray every 3 degrees (120 rays) or 360 rays for maximum density
|
||||
int samplesPerRay = 30,
|
||||
}) {
|
||||
final obsGround = DemTileElevationService.getElevationSync(
|
||||
observer.latitude, observer.longitude);
|
||||
final obsTotal = obsGround + observerHeightM;
|
||||
|
||||
final rays = <RadialRayResult>[];
|
||||
final polygon = <LatLng>[];
|
||||
double sumDist = 0.0;
|
||||
double visibleAreaAccumulator = 0.0;
|
||||
|
||||
final degStep = 360.0 / rayCount;
|
||||
final latRad = observer.latitude * (math.pi / 180.0);
|
||||
const earthMetersPerLatDeg = 111132.95;
|
||||
final earthMetersPerLngDeg = 111412.84 * math.cos(latRad);
|
||||
|
||||
const int samples = 30;
|
||||
|
||||
for (int i = 0; i < rayCount; i++) {
|
||||
final azDeg = i * degStep;
|
||||
final azRad = azDeg * (math.pi / 180.0);
|
||||
|
||||
final dLat = (radiusMeters * math.cos(azRad)) / earthMetersPerLatDeg;
|
||||
final dLng = (radiusMeters * math.sin(azRad)) / earthMetersPerLngDeg;
|
||||
|
||||
double maxAngleSoFar = -double.infinity;
|
||||
double visibleHorizonDist = radiusMeters;
|
||||
double visibleHorizonLat = observer.latitude + dLat;
|
||||
double visibleHorizonLng = observer.longitude + dLng;
|
||||
bool isBlocked = false;
|
||||
LatLng? blockCoord;
|
||||
double? blockElev;
|
||||
|
||||
for (int step = 1; step <= samples; step++) {
|
||||
final frac = step / samples;
|
||||
final sDist = radiusMeters * frac;
|
||||
final sLat = observer.latitude + dLat * frac;
|
||||
final sLng = observer.longitude + dLng * frac;
|
||||
|
||||
final sElev = DemTileElevationService.getElevationSync(sLat, sLng);
|
||||
final sagitta = JordanDemSurface.sagitta(sDist, radiusMeters - sDist);
|
||||
final apparentElev = sElev + targetHeightM - sagitta;
|
||||
final angle = (apparentElev - obsTotal) / sDist;
|
||||
|
||||
if (angle >= maxAngleSoFar) {
|
||||
maxAngleSoFar = angle;
|
||||
visibleHorizonDist = sDist;
|
||||
visibleHorizonLat = sLat;
|
||||
visibleHorizonLng = sLng;
|
||||
} else {
|
||||
if (!isBlocked) {
|
||||
isBlocked = true;
|
||||
blockCoord = LatLng(sLat, sLng);
|
||||
blockElev = sElev;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final endPoint = LatLng(visibleHorizonLat, visibleHorizonLng);
|
||||
|
||||
rays.add(RadialRayResult(
|
||||
azimuthDeg: azDeg,
|
||||
maxRangeMeters: radiusMeters,
|
||||
visibleDistanceMeters: visibleHorizonDist,
|
||||
endpoint: endPoint,
|
||||
isBlockedByTerrain: isBlocked,
|
||||
obstacleCoord: blockCoord,
|
||||
obstacleElevation: blockElev,
|
||||
));
|
||||
|
||||
polygon.add(endPoint);
|
||||
sumDist += visibleHorizonDist;
|
||||
|
||||
// Sector area accumulation = 0.5 * r^2 * dTheta
|
||||
final dThetaRad = degStep * (math.pi / 180.0);
|
||||
visibleAreaAccumulator += 0.5 *
|
||||
(visibleHorizonDist / 1000.0) *
|
||||
(visibleHorizonDist / 1000.0) *
|
||||
dThetaRad;
|
||||
}
|
||||
|
||||
if (polygon.isNotEmpty) {
|
||||
polygon.add(polygon.first); // Close the polygon ring
|
||||
}
|
||||
|
||||
final totalPotentialArea =
|
||||
math.pi * (radiusMeters / 1000.0) * (radiusMeters / 1000.0);
|
||||
final visPercent = totalPotentialArea > 0
|
||||
? ((visibleAreaAccumulator / totalPotentialArea) * 100)
|
||||
.round()
|
||||
.clamp(0, 100)
|
||||
: 0;
|
||||
final avgRange = rayCount > 0 ? (sumDist / rayCount) / 1000.0 : 0.0;
|
||||
|
||||
final report = Viewshed360Report(
|
||||
center: observer,
|
||||
observerHeightM: observerHeightM,
|
||||
radiusMeters: radiusMeters,
|
||||
observerGroundElevM: obsGround,
|
||||
observerTotalElevM: obsTotal,
|
||||
rays: rays,
|
||||
polygonVertices: polygon,
|
||||
visibleAreaSqKm: visibleAreaAccumulator,
|
||||
totalPotentialAreaSqKm: totalPotentialArea,
|
||||
visibilityPercentage: visPercent,
|
||||
averageSightRangeKm: avgRange,
|
||||
);
|
||||
|
||||
report.printTelemetry();
|
||||
return report;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user