Files
maps-saas/packages/tactical_app/lib/controllers/navigation_controller.dart
T

142 lines
5.6 KiB
Dart

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import '../services/offline_road_graph_engine.dart';
import '../services/offline_routing_engine.dart';
import '../services/offline_routing_package_service.dart';
import '../services/tactical_api_service.dart';
import '../services/turn_by_turn_navigation_engine.dart';
import '../services/valhalla_offline_engine.dart';
/// ============================================================================
/// [NavigationController] - وحدة التحكم بالملاحة وتوجيه القوافل التكتيكية
/// ============================================================================
class NavigationController extends GetxController {
// ── Observables / الحالات التفاعلية ──────────────────────────────────────
final Rx<OfflineRoutePlan?> activeRoute = Rx<OfflineRoutePlan?>(null);
final Rx<TacticalVehicleProfile> vehicleProfile =
Rx<TacticalVehicleProfile>(TacticalVehicleProfile.convoy);
final RxBool isCalculating = false.obs;
/// هل التوجيه المحلي الحقيقي (شبكة الطرق الكاملة) جاهز على الجهاز؟
final RxBool isRealRoadEngineReady = false.obs;
NavigationController() {
_refreshEngineStatus();
}
Future<void> _refreshEngineStatus() async {
final pkgInstalled = await OfflineRoutingPackageService.isInstalled();
final dbReady = await OfflineRoadGraphEngine.isDatabaseAvailable();
isRealRoadEngineReady.value = pkgInstalled || dbReady;
}
/// Calculate Hybrid Tactical Route / احتساب مسار تكتيكي هجين
Future<OfflineRoutePlan?> calculateRoute({
required LatLng origin,
required LatLng destination,
TacticalVehicleProfile? profile,
bool autoStart = false,
IntaleqMapController? mapController,
}) async {
final activeProfile = profile ?? vehicleProfile.value;
vehicleProfile.value = activeProfile;
isCalculating.value = true;
OfflineRoutePlan? plan;
// 1. Try Online NestJS Server Routing API
try {
final serverRoute = await TacticalApiService.calculateTacticalRoute(
origin: origin,
destination: destination,
profile: activeProfile.name,
);
if (serverRoute != null && serverRoute.points.isNotEmpty) {
plan = OfflineRoutePlan(
polylinePoints: serverRoute.points,
totalDistanceKm: serverRoute.distanceKm,
estimatedDurationMinutes: serverRoute.durationMinutes,
profile: activeProfile,
tacticalWaypoints: const ['موقع الانطلاق', 'الهدف التكتيكي المحدد'],
isOffline: false,
usesRealRoadNetwork: true,
);
}
} catch (e) {
debugPrint('Online route fallback: $e');
}
// ── Offline chain preparation ──
// تأكد من وجود حزمة التوجيه محلياً؛ إن غابت وحُاول الحساب أثناء توفر
// الشبكة تُنزَّل مرة واحدة تلقائياً (يفشل بسرعة عند انقطاع الإنترنت).
final routingPackageReady = await OfflineRoutingPackageService.ensureInstalled();
if (!routingPackageReady) {
debugPrint('Navigation: routing package unavailable — offline engines will degrade gracefully');
}
// 2. On-Device SQLite road graph engine (667K real edges, Arabic street names)
// الأولوية الأعلى أوفلاين: يحتوي على كامل شبكة طرق الأردن الحقيقية
// (1.99 مليون عقدة، 667 ألف حافة) مع أسماء شوارع عربية وانحناءات واقعية.
// يعمل بسرعة < 150ms حتى للمسافات الطويلة (عمان ← العقبة).
plan ??= await OfflineRoadGraphEngine.calculateRoute(
start: origin,
destination: destination,
profile: activeProfile,
);
// 3. On-Device Valhalla engine (native bridge — may not be available on all platforms)
// يحترم الاتجاه الممنوع وقيود الدوران + ارتفاعات SRTM.
plan ??= await ValhallaOfflineEngine.calculateOfflineRoute(
start: origin,
destination: destination,
profile: activeProfile,
regionDir: await OfflineRoutingPackageService.installDirPath(),
);
if (plan != null) {
debugPrint('Navigation: using real road network route '
'(${plan.polylinePoints.length} pts, ${plan.totalDistanceKm.toStringAsFixed(1)} km, '
'${plan.maneuvers.length} maneuvers, offline=${plan.isOffline})');
}
// 4. Last resort: legacy built-in synthetic graph (36 strategic nodes only)
plan ??= OfflineRoutingEngine.calculateOnDeviceRoute(
start: origin,
destination: destination,
profile: activeProfile,
);
activeRoute.value = plan;
isCalculating.value = false;
if (autoStart) {
startNavigation(mapController: mapController);
}
return plan;
}
/// Start Active Turn-by-Turn Guidance / بدء الإرشاد الملاحي الحي
void startNavigation({IntaleqMapController? mapController}) {
if (activeRoute.value == null) return;
TurnByTurnNavigationEngine.startNavigation(
plan: activeRoute.value!,
simulate: false,
controller: mapController,
);
}
/// Stop Navigation / إنهاء الملاحة
void stopNavigation() {
TurnByTurnNavigationEngine.stopNavigation();
}
/// Reset Route State / إعادة ضبط المسار
void reset() {
stopNavigation();
activeRoute.value = null;
}
}