Update: 2026-06-19 14:01:15
This commit is contained in:
@@ -177,12 +177,68 @@ _cameraFollowTimer = Timer.periodic(const Duration(seconds: 8), (timer) { ... })
|
|||||||
## 4. نظام الملاحة التفاعلي ورسم المسارات (Interactive Navigation & Mapping)
|
## 4. نظام الملاحة التفاعلي ورسم المسارات (Interactive Navigation & Mapping)
|
||||||
|
|
||||||
<div dir="rtl" align="right">
|
<div dir="rtl" align="right">
|
||||||
يعتمد تطبيق السائق على خرائط انطلق المبنية على محرك مابليبرا (MapLibre)، ويتم استدعاء مسار الملاحة من سيرفر SaaS عبر الدالة
|
يعتمد تطبيق السائق على خرائط انطلق المبنية على محرك مابليبرا (MapLibre)، ويتم استدعاء ورسم مسارات الملاحة التفاعلية في تطبيق السائق بدقة وتفصيل عالية عبر الفئات والأساليب التالية:
|
||||||
|
</div>
|
||||||
|
|
||||||
|
### أ. رسم المسارات المزدوجة ونوافذ المعلومات في شاشة طلب الرحلة (Dual-Route & Info Windows in Order Request)
|
||||||
|
<div dir="rtl" align="right">
|
||||||
|
في شاشة استقبال الطلب
|
||||||
|
</div>
|
||||||
|
|
||||||
|
[OrderRequestController](file:///Users/hamzaaleghwairyeen/development/App/Siro/siro_driver/lib/controller/home/captin/order_request_controller.dart)
|
||||||
|
|
||||||
|
<div dir="rtl" align="right">
|
||||||
|
يقوم التطبيق بالاستعلام ورسم مسارين جغرافيين في نفس الوقت عبر الدالة
|
||||||
|
</div>
|
||||||
|
|
||||||
|
[_calculateFullJourney](file:///Users/hamzaaleghwairyeen/development/App/Siro/siro_driver/lib/controller/home/captin/order_request_controller.dart#L226)
|
||||||
|
|
||||||
|
<div dir="rtl" align="right">
|
||||||
|
حيث يستدعي:
|
||||||
|
1. مسار الانطلاق (Pickup Route): من موقع السائق الحالي إلى موقع الراكب (يرسم باللون الأصفر/الذهبي).
|
||||||
|
2. مسار الرحلة الرئيسي (Trip Route): من موقع الراكب إلى الوجهة النهائية (يرسم باللون الأسود/الأزرق).
|
||||||
|
|
||||||
|
ولعرض تفاصيل المسافة والوقت كصندوق معلومات عائم (Info Window) مباشرة فوق الخريطة، يتم استدعاء الدالة
|
||||||
|
</div>
|
||||||
|
|
||||||
|
[_updateMarkers](file:///Users/hamzaaleghwairyeen/development/App/Siro/siro_driver/lib/controller/home/captin/order_request_controller.dart#L468)
|
||||||
|
|
||||||
|
<div dir="rtl" align="right">
|
||||||
|
والتي تقوم بطلب مولد الماركرز
|
||||||
|
</div>
|
||||||
|
|
||||||
|
[MarkerGenerator.createCustomMarkerBitmap](file:///Users/hamzaaleghwairyeen/development/App/Siro/siro_driver/lib/views/home/Captin/orderCaptin/marker_generator.dart)
|
||||||
|
|
||||||
|
<div dir="rtl" align="right">
|
||||||
|
لتوليد صور ماركر مخصصة ديناميكياً تحتوي على الوقت والمسافة كصندوق معلومات يعلو الخريطة فوق نقطة الركوب (أقرب مسافة وزمن وصول للسائق) ونقطة الوصول (المسافة والزمن المقدرين للرحلة الكلية للراكب).
|
||||||
|
</div>
|
||||||
|
|
||||||
|
### ب. تسلسل رسم وحذف المسارات أثناء دورة حياة الرحلة (Lifecycle Route Transitions)
|
||||||
|
<div dir="rtl" align="right">
|
||||||
|
تخضع مسارات الخريطة لعملية تحديث وحذف دورية أثناء الرحلة في الكنترولر
|
||||||
|
</div>
|
||||||
|
|
||||||
|
[MapDriverController](file:///Users/hamzaaleghwairyeen/development/App/Siro/siro_driver/lib/controller/home/captin/map_driver_controller.dart)
|
||||||
|
|
||||||
|
<div dir="rtl" align="right">
|
||||||
|
وفق التسلسل التالي:
|
||||||
|
1. **عند قبول الطلب**: يتم مسح خط الوجهة، ورسم خط الملاحة الجاري باتجاه الراكب (باللون الأصفر) عبر استدعاء
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
[getRoute](file:///Users/hamzaaleghwairyeen/development/App/Siro/siro_driver/lib/controller/home/captin/map_driver_controller.dart#L1885)
|
[getRoute](file:///Users/hamzaaleghwairyeen/development/App/Siro/siro_driver/lib/controller/home/captin/map_driver_controller.dart#L1885)
|
||||||
|
|
||||||
### أ. تفادي انهيار الخرائط عند المسافات الصفرية (Same-Device Crash Protection)
|
<div dir="rtl" align="right">
|
||||||
|
2. **عند وصول السائق لموقع الراكب**: بمجرد ضغط السائق على زر "وصلت" وتأكيده، يتم استدعاء الدالة
|
||||||
|
</div>
|
||||||
|
|
||||||
|
[clearPolyline](file:///Users/hamzaaleghwairyeen/development/App/Siro/siro_driver/lib/controller/home/captin/map_driver_controller.dart#L422)
|
||||||
|
|
||||||
|
<div dir="rtl" align="right">
|
||||||
|
والتي تقوم بمسح وحذف المسار الجاري الأول (الخط الأصفر الموصل للراكب) بالكامل من الخريطة لتنظيف الشاشة.
|
||||||
|
3. **عند بدء الرحلة الفعلي**: يتم الاستعلام ورسم المسار الأزرق/الأسود الجديد المؤدي للوجهة النهائية مباشرة باتجاه وجهة الراكب عبر إعادة استدعاء دالة المسار `getRoute` للوجهة.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
### ج. تفادي انهيار الخرائط عند المسافات الصفرية (Same-Device Crash Protection)
|
||||||
<div dir="rtl" align="right">
|
<div dir="rtl" align="right">
|
||||||
عند تشغيل اختبارات الرحلة وكون موقع السائق والراكب متطابقين تماماً (مسافة أقل من 10 أمتار)، ينهار محرك الملاحة المكتوب بلغة C++ بسبب إحداثيات الصندوق المحيط (Bounds) ذات العرض الصفرى مطلقةً استثناء `std::domain_error`. لمنع ذلك، يقوم الكود بفحص المسافة، وفي حال كانت متطابقة يقوم بإظهار نافذة تنبيه
|
عند تشغيل اختبارات الرحلة وكون موقع السائق والراكب متطابقين تماماً (مسافة أقل من 10 أمتار)، ينهار محرك الملاحة المكتوب بلغة C++ بسبب إحداثيات الصندوق المحيط (Bounds) ذات العرض الصفرى مطلقةً استثناء `std::domain_error`. لمنع ذلك، يقوم الكود بفحص المسافة، وفي حال كانت متطابقة يقوم بإظهار نافذة تنبيه
|
||||||
</div>
|
</div>
|
||||||
@@ -193,7 +249,7 @@ _cameraFollowTimer = Timer.periodic(const Duration(seconds: 8), (timer) { ... })
|
|||||||
ثم الانتقال قسرياً لتطبيق زوم تقريبي آمن بدلاً من احتواء الحدود الصفرية.
|
ثم الانتقال قسرياً لتطبيق زوم تقريبي آمن بدلاً من احتواء الحدود الصفرية.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
### ب. تحديث المسار المقطوع بنظام النافذة المنزلقة (Bidirectional Sliding Window)
|
### د. تحديث المسار المقطوع بنظام النافذة المنزلقة (Bidirectional Sliding Window)
|
||||||
<div dir="rtl" align="right">
|
<div dir="rtl" align="right">
|
||||||
لمنع إعادة رسم كامل خط المسار (Polyline) عند كل إرسال للموقع، يتم استخدام نافذة بحث منزلقة ثنائية الاتجاه تتكون من 60 نقطة (30 للخلف و 30 للأمام) في الدالة
|
لمنع إعادة رسم كامل خط المسار (Polyline) عند كل إرسال للموقع، يتم استخدام نافذة بحث منزلقة ثنائية الاتجاه تتكون من 60 نقطة (30 للخلف و 30 للأمام) في الدالة
|
||||||
</div>
|
</div>
|
||||||
@@ -204,7 +260,7 @@ _cameraFollowTimer = Timer.periodic(const Duration(seconds: 8), (timer) { ... })
|
|||||||
تحدد الدالة أقرب نقطة لموقع السائق الحالي على المسار المخزن، وتقوم بقطع الـ Polyline إلى جزأين: مسار مقطوع بلون رمادي ومسار متبقي بلون أزرق/أصفر، وتحديث الخريطة فقط عند تجاوز إزاحة تزيد عن 50 متراً.
|
تحدد الدالة أقرب نقطة لموقع السائق الحالي على المسار المخزن، وتقوم بقطع الـ Polyline إلى جزأين: مسار مقطوع بلون رمادي ومسار متبقي بلون أزرق/أصفر، وتحديث الخريطة فقط عند تجاوز إزاحة تزيد عن 50 متراً.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
### ج. رسم خطوط المشي المنقطة (Passenger Walk Dotted Line)
|
### هـ. رسم خطوط المشي المنقطة (Passenger Walk Dotted Line)
|
||||||
<div dir="rtl" align="right">
|
<div dir="rtl" align="right">
|
||||||
عندما يكون موقع الراكب الفعلي بعيداً عن أقرب طريق إسفلتي متاح للسيارات، يتم استدعاء الدالة
|
عندما يكون موقع الراكب الفعلي بعيداً عن أقرب طريق إسفلتي متاح للسيارات، يتم استدعاء الدالة
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1317,7 +1317,7 @@
|
|||||||
|
|
||||||
<!-- Passenger Marker -->
|
<!-- Passenger Marker -->
|
||||||
<div class="marker" id="marker-pax" style="top: 250px; left: 450px; display: none;">
|
<div class="marker" id="marker-pax" style="top: 250px; left: 450px; display: none;">
|
||||||
<div class="marker-label">Mahmoud (Pickup)</div>
|
<div class="marker-label" id="pax-label">Mahmoud (Pickup)</div>
|
||||||
<div class="marker-dot marker-pax">
|
<div class="marker-dot marker-pax">
|
||||||
<i data-lucide="user" style="width: 14px; height: 14px; color: #000;"></i>
|
<i data-lucide="user" style="width: 14px; height: 14px; color: #000;"></i>
|
||||||
</div>
|
</div>
|
||||||
@@ -1325,7 +1325,7 @@
|
|||||||
|
|
||||||
<!-- Destination Marker -->
|
<!-- Destination Marker -->
|
||||||
<div class="marker" id="marker-dest" style="top: 450px; left: 450px; display: none;">
|
<div class="marker" id="marker-dest" style="top: 450px; left: 450px; display: none;">
|
||||||
<div class="marker-label">Mezzeh (Destination)</div>
|
<div class="marker-label" id="dest-label">Mezzeh (Destination)</div>
|
||||||
<div class="marker-dot marker-dest">
|
<div class="marker-dot marker-dest">
|
||||||
<i data-lucide="flag" style="width: 14px; height: 14px; color: #fff;"></i>
|
<i data-lucide="flag" style="width: 14px; height: 14px; color: #fff;"></i>
|
||||||
</div>
|
</div>
|
||||||
@@ -1784,6 +1784,17 @@
|
|||||||
fillEl.style.strokeDashoffset = '0';
|
fillEl.style.strokeDashoffset = '0';
|
||||||
document.getElementById('txt-overlay-timer').innerText = overlayTimer;
|
document.getElementById('txt-overlay-timer').innerText = overlayTimer;
|
||||||
|
|
||||||
|
// Draw BOTH routes on the map during the Order Request view
|
||||||
|
drawRoute(driverPos, paxPos, 'both');
|
||||||
|
|
||||||
|
// Show both markers on map
|
||||||
|
document.getElementById('marker-pax').style.display = 'flex';
|
||||||
|
document.getElementById('marker-dest').style.display = 'flex';
|
||||||
|
|
||||||
|
// Update markers to act as dynamic Info Windows (Time & Distance)
|
||||||
|
document.getElementById('pax-label').innerHTML = `Mahmoud (Pickup)<br><span style="color:var(--accent-gold); font-weight:bold; font-size:0.7rem;">2.1 km | 5 min</span>`;
|
||||||
|
document.getElementById('dest-label').innerHTML = `Mezzeh (Destination)<br><span style="color:var(--accent-blue); font-weight:bold; font-size:0.7rem;">12.5 km | 20 min</span>`;
|
||||||
|
|
||||||
overlayInterval = setInterval(() => {
|
overlayInterval = setInterval(() => {
|
||||||
overlayTimer--;
|
overlayTimer--;
|
||||||
document.getElementById('txt-overlay-timer').innerText = overlayTimer;
|
document.getElementById('txt-overlay-timer').innerText = overlayTimer;
|
||||||
@@ -1798,6 +1809,11 @@
|
|||||||
addLog("Order Request timed out. Missed count incremented.", "error");
|
addLog("Order Request timed out. Missed count incremented.", "error");
|
||||||
orderOverlay.style.display = 'none';
|
orderOverlay.style.display = 'none';
|
||||||
driverState = 'ONLINE';
|
driverState = 'ONLINE';
|
||||||
|
|
||||||
|
// Clear map state
|
||||||
|
document.getElementById('marker-pax').style.display = 'none';
|
||||||
|
document.getElementById('marker-dest').style.display = 'none';
|
||||||
|
ctx.clearRect(0, 0, routeCanvas.width, routeCanvas.height);
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
@@ -1842,11 +1858,13 @@
|
|||||||
document.getElementById('panel-online').style.display = 'none';
|
document.getElementById('panel-online').style.display = 'none';
|
||||||
document.getElementById('panel-ride-active').style.display = 'flex';
|
document.getElementById('panel-ride-active').style.display = 'flex';
|
||||||
|
|
||||||
|
// Show passenger marker and hide destination marker initially
|
||||||
document.getElementById('marker-pax').style.display = 'flex';
|
document.getElementById('marker-pax').style.display = 'flex';
|
||||||
document.getElementById('marker-dest').style.display = 'flex';
|
document.getElementById('marker-dest').style.display = 'none'; // Clear destination marker
|
||||||
|
document.getElementById('pax-label').innerHTML = `Mahmoud (Pickup)`; // Restore default label
|
||||||
document.getElementById('radar-pulse').style.display = 'none';
|
document.getElementById('radar-pulse').style.display = 'none';
|
||||||
|
|
||||||
// Draw route on map
|
// Draw ONLY the yellow route to passenger
|
||||||
drawRoute(driverPos, paxPos, 'yellow');
|
drawRoute(driverPos, paxPos, 'yellow');
|
||||||
|
|
||||||
// Update TBT instructions Panel
|
// Update TBT instructions Panel
|
||||||
@@ -1858,6 +1876,37 @@
|
|||||||
// Draw lines on canvas
|
// Draw lines on canvas
|
||||||
function drawRoute(start, end, type = 'yellow') {
|
function drawRoute(start, end, type = 'yellow') {
|
||||||
ctx.clearRect(0, 0, routeCanvas.width, routeCanvas.height);
|
ctx.clearRect(0, 0, routeCanvas.width, routeCanvas.height);
|
||||||
|
|
||||||
|
if (type === 'both') {
|
||||||
|
// 1. Draw yellow route (driver to passenger)
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.lineWidth = 6;
|
||||||
|
ctx.strokeStyle = '#eab308'; // Amber
|
||||||
|
ctx.moveTo(start.x, start.y);
|
||||||
|
ctx.lineTo(end.x, start.y);
|
||||||
|
ctx.lineTo(end.x, end.y);
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// Draw dotted walk line from road (corner) to actual offroad pax position
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.lineWidth = 3;
|
||||||
|
ctx.strokeStyle = '#94a3b8';
|
||||||
|
ctx.setLineDash([6, 6]);
|
||||||
|
ctx.moveTo(end.x, start.y);
|
||||||
|
ctx.lineTo(end.x + 20, end.y - 30);
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.setLineDash([]);
|
||||||
|
|
||||||
|
// 2. Draw blue route (passenger to destination)
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.lineWidth = 6;
|
||||||
|
ctx.strokeStyle = '#3b82f6'; // Blue
|
||||||
|
ctx.moveTo(end.x, end.y);
|
||||||
|
ctx.lineTo(destPos.x, destPos.y);
|
||||||
|
ctx.stroke();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.lineWidth = 6;
|
ctx.lineWidth = 6;
|
||||||
ctx.strokeStyle = type === 'yellow' ? '#eab308' : '#3b82f6';
|
ctx.strokeStyle = type === 'yellow' ? '#eab308' : '#3b82f6';
|
||||||
@@ -1920,6 +1969,9 @@
|
|||||||
addLog("Driver marked Arrived. Waiting compensation timer started.", "info");
|
addLog("Driver marked Arrived. Waiting compensation timer started.", "info");
|
||||||
document.getElementById('btn-arrive-action').style.display = 'none';
|
document.getElementById('btn-arrive-action').style.display = 'none';
|
||||||
|
|
||||||
|
// Clear route line (deletes the first yellow polyline)
|
||||||
|
ctx.clearRect(0, 0, routeCanvas.width, routeCanvas.height);
|
||||||
|
|
||||||
// Update badge
|
// Update badge
|
||||||
document.getElementById('txt-lifecycle-phase').innerHTML = `
|
document.getElementById('txt-lifecycle-phase').innerHTML = `
|
||||||
<div class="status-dot" style="background-color: var(--accent-emerald); box-shadow: 0 0 8px var(--accent-emerald);"></div>
|
<div class="status-dot" style="background-color: var(--accent-emerald); box-shadow: 0 0 8px var(--accent-emerald);"></div>
|
||||||
@@ -2025,6 +2077,10 @@
|
|||||||
document.getElementById('marker-driver').style.left = driverPos.x + 'px';
|
document.getElementById('marker-driver').style.left = driverPos.x + 'px';
|
||||||
document.getElementById('marker-driver').style.top = driverPos.y + 'px';
|
document.getElementById('marker-driver').style.top = driverPos.y + 'px';
|
||||||
|
|
||||||
|
// Show destination marker on map
|
||||||
|
document.getElementById('marker-dest').style.display = 'flex';
|
||||||
|
document.getElementById('dest-label').innerHTML = `Mezzeh (Destination)`;
|
||||||
|
|
||||||
// Draw Trip Route (solid blue/black)
|
// Draw Trip Route (solid blue/black)
|
||||||
drawRoute(driverPos, destPos, 'blue');
|
drawRoute(driverPos, destPos, 'blue');
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user