chore: add build artifacts, map assets, and routing data
This commit is contained in:
@@ -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++) {
|
||||
|
||||
Reference in New Issue
Block a user