chore: add build artifacts, map assets, and routing data

This commit is contained in:
Hamza-Ayed
2026-09-19 12:34:24 +03:00
parent aa2b9f131f
commit 43ad2e0ad4
327 changed files with 52421 additions and 1810 deletions
@@ -1,3 +1,4 @@
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart';
@@ -5,8 +6,12 @@ import 'package:intaleq_maps/intaleq_maps.dart';
import '../models/angle_unit.dart';
import '../models/navigation_state.dart';
import '../models/tactical_sheet_type.dart';
import '../services/military_grid_utils.dart';
import '../services/offline_los_engine.dart';
import '../services/local_network_tracker.dart';
import '../widgets/interactive_map_picker_hud.dart';
import 'artillery_controller.dart';
import 'hlz_controller.dart';
@@ -42,17 +47,52 @@ class TacticalMapController extends GetxController {
final ViewshedController viewshed = Get.find<ViewshedController>();
final ResectionController resection = Get.find<ResectionController>();
final NavigationController navigation = Get.find<NavigationController>();
final LocalNetworkTrackerService tracker = Get.find<LocalNetworkTrackerService>();
// ── Observables / الحالات التفاعلية ──────────────────────────────────────
final RxString currentTacticalMode = 'nav'.obs;
final Rx<AngleUnit> angleUnit = AngleUnit.dual.obs;
final RxBool showContours = true.obs;
final RxBool showContours = false.obs;
final Rx<LatLng?> currentGpsPosition = Rx<LatLng?>(null);
final Rx<LatLng> currentCameraCenter = const LatLng(31.9539, 35.9106).obs;
final Rx<MapPickerTarget?> activePickerTarget = Rx<MapPickerTarget?>(null);
final Rx<LatLng?> pickedRouteOrigin = Rx<LatLng?>(null);
final Rx<LatLng?> pickedRouteDestination = Rx<LatLng?>(null);
// ── Persistent Sheets / النوافذ التكتيكية السفلية المستمرة ────────────────
final Rx<ActiveTacticalSheet?> activeSheet = Rx<ActiveTacticalSheet?>(null);
final RxBool isSheetMinimized = false.obs;
void openSheet(ActiveTacticalSheet sheet) {
activeSheet.value = sheet;
isSheetMinimized.value = false;
}
void closeSheet() {
activeSheet.value = null;
isSheetMinimized.value = false;
}
IntaleqMapController? mapController;
/// Clear All Tactical Layers & Reset Map / مسح كافة الطبقات والحسابات التكتيكية وتصفير الخريطة
void clearAllTacticalLayers() {
artillery.reset();
hlz.reset();
minefield.reset();
isochrone.reset();
los.reset();
viewshed.reset();
resection.reset();
navigation.reset();
symbols.reset();
pickedRouteOrigin.value = null;
pickedRouteDestination.value = null;
activePickerTarget.value = null;
currentTacticalMode.value = 'nav';
}
@override
void onInit() {
super.onInit();
@@ -70,6 +110,7 @@ class TacticalMapController extends GetxController {
perm = await Geolocator.requestPermission();
}
if (perm == LocationPermission.whileInUse || perm == LocationPermission.always) {
// 1. Get initial position and jump camera
final pos = await Geolocator.getCurrentPosition(
locationSettings: const LocationSettings(
accuracy: LocationAccuracy.high,
@@ -78,9 +119,23 @@ class TacticalMapController extends GetxController {
);
currentGpsPosition.value = LatLng(pos.latitude, pos.longitude);
currentCameraCenter.value = currentGpsPosition.value!;
tracker.updateMyPosition(currentGpsPosition.value!, pos.heading);
mapController?.animateCamera(
CameraUpdate.newLatLngZoom(currentGpsPosition.value!, 14.0),
);
// 2. Listen to continuous GPS updates for Blue Force Tracking
Geolocator.getPositionStream(
locationSettings: const LocationSettings(
accuracy: LocationAccuracy.high,
distanceFilter: 2, // update every 2 meters
),
).listen((Position newPos) {
final latLng = LatLng(newPos.latitude, newPos.longitude);
currentGpsPosition.value = latLng;
tracker.updateMyPosition(latLng, newPos.heading);
});
}
} catch (e) {
debugPrint('GPS init error: $e');
@@ -206,10 +261,31 @@ class TacticalMapController extends GetxController {
case MapPickerTarget.isochroneCenter:
isochrone.setCenter(pos);
break;
case MapPickerTarget.resectionLandmark:
resection.setMapLandmark(pos.latitude, pos.longitude);
switchMode('resection_cam');
break;
case MapPickerTarget.routeOrigin:
pickedRouteOrigin.value = pos;
switchMode('routing');
break;
case MapPickerTarget.routeDestination:
pickedRouteDestination.value = pos;
switchMode('routing');
final origin = pickedRouteOrigin.value ?? currentGpsPosition.value ?? const LatLng(31.9539, 35.9106);
navigation.calculateRoute(
origin: origin,
destination: pos,
mapController: mapController,
).then((plan) {
if (plan != null && plan.polylinePoints.isNotEmpty) {
fitBounds(plan.polylinePoints);
}
});
break;
}
if (activeSheet.value != null) { isSheetMinimized.value = false; }
}
/// Cancel Picker / إلغاء وضع المؤشر
@@ -270,6 +346,8 @@ class TacticalMapController extends GetxController {
return 'منظومة الشفافات (IPB)';
case 'routing':
return 'توجيه القوافل التكتيكي';
case 'rangefinder':
return 'قياس المسافة البصري (بدون GPS)';
default:
return 'منظومة العمليات الميدانية (Off-Grid)';
}
@@ -294,7 +372,7 @@ class TacticalMapController extends GetxController {
);
}
// 2. Resection Fix Marker
// 2. Resection Fix Marker (موقع الراصد المحسوب - ماركر بارز باللون الأخضر التكتيكي)
if (resection.resectionResult.value != null && !navState.isNavigating) {
final res = resection.resectionResult.value!;
markers.add(
@@ -302,9 +380,9 @@ class TacticalMapController extends GetxController {
markerId: const MarkerId('observer_calculated_position'),
position: LatLng(res.lat, res.lng),
icon: InlqBitmap.defaultMarkerWithHue(InlqBitmap.hueGreen),
infoWindow: const InfoWindow(
title: 'موقع الراصد المحسوب (GPS-Denied Fix)',
snippet: 'تم استخراج الموقع بالتقاطع البصري العكسي',
infoWindow: InfoWindow(
title: '🎯 موقعك المحسوب (GPS-Denied Fix)',
snippet: 'خطأ الرصد: ±${res.estimatedAccuracyMeters.toStringAsFixed(1)}م • دقة تكتيكية عالية',
),
),
);
@@ -336,7 +414,7 @@ class TacticalMapController extends GetxController {
infoWindow: InfoWindow(
title: 'مركبة العمليات الميدانية',
snippet:
'${navState.currentSpeedKmH.round()} كم/س • سمت ${navState.currentHeadingDeg.round()}°',
'${navState.currentSpeedKmH.round()} كم/س • اتجاه ${navState.currentHeadingDeg.round()}°',
),
),
);
@@ -396,6 +474,22 @@ class TacticalMapController extends GetxController {
);
}
// --- Blue Force Tracking (Friendly Units) ---
for (final unit in tracker.friendlyUnits.values) {
markers.add(
Marker(
markerId: MarkerId('bft_${unit.deviceId}'),
position: unit.position,
icon: InlqBitmap.defaultMarkerWithHue(InlqBitmap.hueAzure), // Blue for friendly
infoWindow: InfoWindow(
title: '${unit.callsign} (${unit.role.name})',
snippet: 'آخر ظهور: منذ ${DateTime.now().difference(unit.lastSeen).inSeconds} ثوانٍ',
),
),
);
}
return markers;
}
@@ -496,6 +590,24 @@ class TacticalMapController extends GetxController {
);
}
// 6. Visual Resection Sightlines (خطوط الرؤية البصرية للمعالم المرصودة)
if (resection.resectionResult.value != null && resection.observations.isNotEmpty) {
final res = resection.resectionResult.value!;
final observerPos = LatLng(res.lat, res.lng);
for (int i = 0; i < resection.observations.length; i++) {
final obs = resection.observations[i];
final landmarkPos = LatLng(obs.landmark.lat, obs.landmark.lng);
polylines.add(
Polyline(
polylineId: PolylineId('resection_sightline_$i'),
points: [observerPos, landmarkPos],
color: const Color(0xFF00F0FF),
width: 3.5,
),
);
}
}
return polylines;
}
@@ -559,14 +671,36 @@ class TacticalMapController extends GetxController {
Polygon(
polygonId: const PolygonId('minefield_safe_breach_polygon'),
points: minefield.zoneResult.value!.breachLanePolygon,
fillColor: const Color(0x6610B981),
fillColor: const Color(0x3310B981),
strokeColor: const Color(0xFF10B981),
strokeWidth: 2,
),
);
}
// 4. Isochrone Response Time Rings
// 4. Resection Position Uncertainty Ring (دائرة دقة الموقع التكتيكي الأخضر)
if (resection.resectionResult.value != null) {
final res = resection.resectionResult.value!;
final radiusMeters = math.max(25.0, res.estimatedAccuracyMeters);
final ringPoints = <LatLng>[];
for (int a = 0; a <= 360; a += 15) {
final rad = a * (math.pi / 180.0);
final dLat = (radiusMeters / 6371000.0) * (180.0 / math.pi);
final dLng = (radiusMeters / 6371000.0) * (180.0 / math.pi) / math.cos(res.lat * math.pi / 180.0);
ringPoints.add(LatLng(res.lat + dLat * math.sin(rad), res.lng + dLng * math.cos(rad)));
}
polygons.add(
Polygon(
polygonId: const PolygonId('resection_accuracy_circle'),
points: ringPoints,
fillColor: const Color(0x3322C55E),
strokeColor: const Color(0xFF22C55E),
strokeWidth: 2.0,
),
);
}
// 5. Isochrone Response Time Rings
if (currentTacticalMode.value == 'isochrone' &&
isochrone.isochroneRings.isNotEmpty) {
for (int i = 0; i < isochrone.isochroneRings.length; i++) {
@@ -583,7 +717,7 @@ class TacticalMapController extends GetxController {
}
}
// 5. Tactical Overlays Polygons
// 6. Tactical Overlays Polygons
for (final layer in overlays.overlayLayers) {
if (layer.isVisible) {
for (int i = 0; i < layer.polygons.length; i++) {