142 lines
5.6 KiB
Dart
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;
|
|
}
|
|
}
|