feat(routing): Dynamic traffic delay & descent-aware mechanical braking advice

This commit is contained in:
Hamza-Ayed
2026-08-21 02:15:56 +03:00
parent 7572d6b4c5
commit f14d40dac7
2 changed files with 83 additions and 27 deletions
+77 -26
View File
@@ -172,7 +172,8 @@ export class MapsService {
const processedPaths = paths.map((p: any, index: number) => {
const pCoords = this.decodePolyline(p.points);
const pTrafficFactor = this.trafficGrid.getTrafficFactor(pCoords, hr, dow);
const pDuration = Math.round((p.time / 1000) * pTrafficFactor);
const pBaseDuration = p.time / 1000;
const pDuration = Math.round(pBaseDuration * pTrafficFactor);
// For alternative routes, find the distinctive street that differentiates it from the main route
let routeStreet = index === 0
@@ -196,12 +197,16 @@ export class MapsService {
// Analyze slopes and enrich instructions
const { enrichedInstructions, slopeSummary } = this.enrichInstructionsWithSlopeAnalysis(p.instructions, pCoords);
// Calculate Energy, Fuel Consumption & Eco Cost
// Calculate Energy, Fuel Consumption & Eco Cost (combining Distance + Ascent/Descent Physics + Traffic/Time Delays)
const ecoMetrics = this.calculateEcoAndFuelMetrics(
p.distance,
slopeSummary.totalAscentMeters,
slopeSummary.totalDescentMeters,
slopeSummary.maxInclinePercent,
slopeSummary.maxDeclinePercent,
pBaseDuration,
pDuration,
pTrafficFactor,
profile
);
@@ -336,60 +341,104 @@ export class MapsService {
}
/**
* Calculates comprehensive fuel, energy, and eco-cost metrics based on distance, topography and vehicle profile.
* Calculates comprehensive fuel, energy, and eco-cost metrics combining distance, elevation topography, and traffic delays.
*/
private calculateEcoAndFuelMetrics(
distanceMeters: number,
totalAscentMeters: number,
totalDescentMeters: number,
maxInclinePercent: number,
maxDeclinePercent: number,
baseDurationSeconds: number,
actualDurationSeconds: number,
trafficFactor: number,
profile: string = 'car'
) {
const distKm = distanceMeters / 1000;
// Base Fuel: ~0.075 L/km for standard passenger car in urban/suburban driving
const baseFuelRateLPerKm = profile === 'truck' ? 0.28 : (profile === 'bike' || profile === 'foot') ? 0.0 : 0.075;
// 1. Base Cruising Fuel: ~0.070 L/km for standard passenger car
const baseFuelRateLPerKm = profile === 'truck' ? 0.28 : (profile === 'bike' || profile === 'foot') ? 0.0 : 0.070;
const baseGasolineLiters = distKm * baseFuelRateLPerKm;
// Gravity Work Penalty: ~0.16 Liters per 100m vertical ascent
// 2. Gravity Work Penalty on Ascent: +0.16 Liters per 100m vertical ascent
const ascentFuelPenaltyLiters = (totalAscentMeters / 100) * (profile === 'truck' ? 0.45 : 0.16);
// Descent Savings: ~0.04 Liters saved per 100m descent (reduced throttle / engine braking)
// 3. Descent Savings (Gravity Assist / Engine Braking): -0.04 Liters per 100m descent
const descentFuelSavingLiters = (totalDescentMeters / 100) * (profile === 'truck' ? 0.10 : 0.04);
const netGasolineLiters = Math.max(0.05, baseGasolineLiters + ascentFuelPenaltyLiters - descentFuelSavingLiters);
// 4. Traffic & Congestion Delay Fuel (Idling + Stop-and-Go Re-acceleration):
const delayHours = Math.max(0, actualDurationSeconds - baseDurationSeconds) / 3600;
const congestionFactorBonus = trafficFactor > 1.15 ? (trafficFactor - 1.0) * 0.4 : 0.0;
const trafficFuelLiters = (delayHours * 1.1) + (baseGasolineLiters * congestionFactorBonus);
// Net Gasoline Consumption
const netGasolineLiters = Math.max(0.05, baseGasolineLiters + ascentFuelPenaltyLiters - descentFuelSavingLiters + trafficFuelLiters);
// Pricing in Jordan: Gasoline 90 ~ 0.920 JOD/L, Diesel ~ 0.720 JOD/L
const fuelPricePerLiter = profile === 'truck' ? 0.720 : 0.920;
const estimatedCostJOD = netGasolineLiters * fuelPricePerLiter;
// EV Energy Model: ~0.16 kWh/km on flat + ~0.38 kWh/100m ascent - ~0.26 kWh/100m regen descent
const baseEvKWh = distKm * 0.16;
// 5. EV Energy Model (Electric Vehicles):
const baseEvKWh = distKm * 0.15;
const ascentEvKWh = (totalAscentMeters / 100) * 0.38;
const regenEvKWh = (totalDescentMeters / 100) * 0.26;
const netEvKWh = Math.max(0.1, baseEvKWh + ascentEvKWh - regenEvKWh);
const trafficEvKWh = delayHours * 1.5; // HVAC & auxiliary electronics in standstill
const netEvKWh = Math.max(0.1, baseEvKWh + ascentEvKWh - regenEvKWh + trafficEvKWh);
// Carbon Footprint: 2,310g CO2 per liter of gasoline
const co2Grams = Math.round(netGasolineLiters * 2310);
// Eco Score (0 - 100): Evaluates efficiency relative to pure flat line
// 6. Net Elevation Differential (هل المسار صاعد أم هابط؟)
const isPredominantlyDescent = totalDescentMeters > (totalAscentMeters * 1.3);
const isPredominantlyAscent = totalAscentMeters > (totalDescentMeters * 1.3);
// 7. Terrain Difficulty & Mechanical Guidance
let terrainDifficultyArabic = 'طريق مستوٍ مريح';
let mechanicalAdviceArabic = 'القيادة في نطاق السرعة الطبيعي';
if (isPredominantlyDescent) {
if (Math.abs(maxDeclinePercent) >= 8) {
terrainDifficultyArabic = 'منحدر جبلي هابط (نزول حاد)';
mechanicalAdviceArabic = '⚠️ استخدام الغيار المنخفض (Engine Braking) لتخفيف العبء على الفرامل وتجنب ارتفاع حرارتها';
} else {
terrainDifficultyArabic = 'طريق منحدر خفيف (هبوط سلس)';
mechanicalAdviceArabic = 'مسير هابط موفر للوقود مع شحن متجدد لبطارية الـ EV';
}
} else if (isPredominantlyAscent) {
if (maxInclinePercent >= 8) {
terrainDifficultyArabic = 'طريق صاعد جبلي (عقبة صعود حادة)';
mechanicalAdviceArabic = 'يتطلب عزم محرك إضافي واستخدام الغيارات المناسبة لمنع إجهاد المحرك';
} else {
terrainDifficultyArabic = 'طريق صاعد معتدل';
mechanicalAdviceArabic = 'صعود تدريجي بجهد محرك معتدل';
}
} else {
if (maxInclinePercent >= 8 || Math.abs(maxDeclinePercent) >= 8) {
terrainDifficultyArabic = 'تضاريس جبلية وعرة (صعود وهبوط متكرر)';
mechanicalAdviceArabic = 'تدرج مستمر بين عزم الصعود وكبح النزول';
} else if (maxInclinePercent >= 4 || Math.abs(maxDeclinePercent) >= 4) {
terrainDifficultyArabic = 'تضاريس متموجة معتدلة';
mechanicalAdviceArabic = 'قيادة سلسة ومريحة للمركبة';
}
}
// 8. Eco Score & Badge Calculation
const idealFlatFuel = distKm * baseFuelRateLPerKm;
const consumptionRatio = idealFlatFuel > 0 ? (netGasolineLiters / idealFlatFuel) : 1.0;
const rawScore = Math.round(100 - (consumptionRatio - 1.0) * 60);
const ecoScore = Math.max(20, Math.min(100, rawScore));
let rawScore = Math.round(100 - (consumptionRatio - 1.0) * 50);
if (isPredominantlyDescent) rawScore = Math.max(rawScore, 92); // Descent is naturally fuel-efficient
const ecoScore = Math.max(15, Math.min(100, rawScore));
let ecoBadgeArabic = 'مسار قياسي متوازن';
if (ecoScore >= 88 && maxInclinePercent <= 4) {
ecoBadgeArabic = 'مسار اقتصادي موفر للوقود 🌿';
} else if (maxInclinePercent >= 8 || consumptionRatio > 1.4) {
ecoBadgeArabic = 'مسار عالي الاستهلاك ⚠️';
}
let terrainDifficultyArabic = 'طريق مستوٍ مريح';
if (maxInclinePercent >= 8) {
terrainDifficultyArabic = 'طريق جبلي حاد (يتطلب عزم محرك إضافي)';
} else if (maxInclinePercent >= 4) {
terrainDifficultyArabic = 'تضاريس متموجة معتدلة';
if (isPredominantlyDescent && trafficFactor < 1.2) {
ecoBadgeArabic = 'مسار موفر للوقود بالهبوط 🌿 (شحن للـ EV)';
} else if (ecoScore >= 85 && trafficFactor < 1.15) {
ecoBadgeArabic = 'مسار اقتصادي منخفض الاستهلاك 🌿';
} else if (trafficFactor >= 1.35) {
ecoBadgeArabic = 'مسار عالي الاستهلاك (بسبب الازدحام والتوقف) ⏳';
} else if (isPredominantlyAscent && maxInclinePercent >= 8) {
ecoBadgeArabic = 'مسار عالي الاستهلاك في الصعود ⚠️';
}
return {
@@ -398,9 +447,11 @@ export class MapsService {
estimatedEvKWh: Math.round(netEvKWh * 100) / 100,
energyRecoveredEvKWh: Math.round(regenEvKWh * 100) / 100,
co2Kg: Math.round((co2Grams / 1000) * 100) / 100,
trafficDelayMinutes: Math.round(delayHours * 60),
ecoScore,
ecoBadge: ecoBadgeArabic,
terrainDifficulty: terrainDifficultyArabic
terrainDifficulty: terrainDifficultyArabic,
mechanicalAdvice: mechanicalAdviceArabic
};
}
+6 -1
View File
@@ -440,10 +440,15 @@ function App() {
<span>⛽ وقود مقدر: <strong style={{ color: '#f8fafc' }}>{routeData.ecoMetrics.estimatedGasolineLiters} لتر</strong></span>
<span>💰 تكلفة تقديرية: <strong style={{ color: '#4ade80' }}>{routeData.ecoMetrics.estimatedCostJOD} د.أ</strong></span>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', color: '#94a3b8' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', color: '#94a3b8', marginBottom: 4 }}>
<span>⚡ استهلاك EV: <strong style={{ color: '#38bdf8' }}>{routeData.ecoMetrics.estimatedEvKWh} kWh</strong></span>
<span>🌱 انبعاثات الكربون: <strong style={{ color: '#94a3b8' }}>{routeData.ecoMetrics.co2Kg} كغم</strong></span>
</div>
{routeData.ecoMetrics.mechanicalAdvice && (
<div style={{ marginTop: 6, padding: '5px 8px', borderRadius: 6, background: 'rgba(56, 189, 248, 0.1)', color: '#bae6fd', fontSize: '0.74rem' }}>
💡 {routeData.ecoMetrics.mechanicalAdvice}
</div>
)}
</div>
)}