feat(routing): enable elevation profile and steep slope/incline warnings in routing API and frontend

This commit is contained in:
Hamza-Ayed
2026-08-18 12:10:32 +03:00
parent 06543ee8e0
commit 57e3da7ea4
12 changed files with 599 additions and 100 deletions
+1
View File
@@ -19,6 +19,7 @@ export declare class MapsController {
points: any;
bbox: any;
instructions: any;
elevationSummary: any;
alternatives: any;
}>;
getConfig(): Promise<{
+4
View File
@@ -28,8 +28,12 @@ export declare class MapsService {
points: any;
bbox: any;
instructions: any;
elevationSummary: any;
alternatives: any;
}>;
private enrichInstructionsWithSlopeAnalysis;
private estimateElevation;
private haversineDistance;
private getRouteName;
private decodePolyline;
getMapConfig(): Promise<{
+91 -1
View File
@@ -158,6 +158,7 @@ let MapsService = class MapsService {
const pTrafficFactor = this.trafficGrid.getTrafficFactor(pCoords, hr, dow);
const pDuration = Math.round((p.time / 1000) * pTrafficFactor);
const routeName = this.getRouteName(p.instructions);
const { enrichedInstructions, slopeSummary } = this.enrichInstructionsWithSlopeAnalysis(p.instructions, pCoords);
const tags = [];
if (index === 0)
tags.push('FASTEST');
@@ -175,7 +176,8 @@ let MapsService = class MapsService {
duration: pDuration,
points: p.points,
bbox: p.bbox,
instructions: steps ? p.instructions : undefined
instructions: steps ? enrichedInstructions : undefined,
elevationSummary: slopeSummary
};
});
const mainRoute = processedPaths[0];
@@ -191,6 +193,7 @@ let MapsService = class MapsService {
points: mainRoute.points,
bbox: mainRoute.bbox,
instructions: mainRoute.instructions,
elevationSummary: mainRoute.elevationSummary,
alternatives: altRoutes
};
}
@@ -200,6 +203,93 @@ let MapsService = class MapsService {
throw new common_1.HttpException(`Routing Failure: ${msg}`, common_1.HttpStatus.BAD_GATEWAY);
}
}
enrichInstructionsWithSlopeAnalysis(instructions, coords) {
if (!instructions || !coords || coords.length < 2) {
return {
enrichedInstructions: instructions,
slopeSummary: { totalAscentMeters: 0, totalDescentMeters: 0, maxInclinePercent: 0, maxDeclinePercent: 0, steepWarningsCount: 0, steepWarnings: [] }
};
}
let totalAscent = 0;
let totalDescent = 0;
let maxInclinePercent = 0;
let maxDeclinePercent = 0;
const steepWarnings = [];
const enrichedInstructions = instructions.map((inst) => {
const interval = inst.interval || [0, 0];
const startIdx = Math.min(interval[0], coords.length - 1);
const endIdx = Math.min(interval[1], coords.length - 1);
if (startIdx < endIdx) {
const startCoord = coords[startIdx];
const endCoord = coords[endIdx];
const startElev = this.estimateElevation(startCoord[1], startCoord[0]);
const endElev = this.estimateElevation(endCoord[1], endCoord[0]);
const elevDiff = endElev - startElev;
const dist = inst.distance || this.haversineDistance(startCoord[1], startCoord[0], endCoord[1], endCoord[0]) || 1;
if (elevDiff > 0)
totalAscent += elevDiff;
else
totalDescent += Math.abs(elevDiff);
const slopePercent = Math.round((elevDiff / Math.max(20, dist)) * 100);
if (slopePercent > maxInclinePercent)
maxInclinePercent = slopePercent;
if (slopePercent < maxDeclinePercent)
maxDeclinePercent = slopePercent;
let warning_ar = null;
if (slopePercent >= 9) {
warning_ar = `⚠️ تنبيه: صعود حاد (+${slopePercent}%)`;
steepWarnings.push({ text: inst.text, slopePercent, type: 'incline', street: inst.street_name });
}
else if (slopePercent <= -9) {
warning_ar = `⚠️ تنبيه: منحدر شديد (${slopePercent}%) - خفف السرعة`;
steepWarnings.push({ text: inst.text, slopePercent, type: 'decline', street: inst.street_name });
}
return {
...inst,
slopePercent,
elevationChangeMeters: Math.round(elevDiff),
slopeWarning: warning_ar || undefined,
text: warning_ar ? `${inst.text} (${warning_ar})` : inst.text
};
}
return inst;
});
return {
enrichedInstructions,
slopeSummary: {
totalAscentMeters: Math.round(totalAscent),
totalDescentMeters: Math.round(totalDescent),
maxInclinePercent: Math.round(maxInclinePercent),
maxDeclinePercent: Math.round(maxDeclinePercent),
steepWarningsCount: steepWarnings.length,
steepWarnings
}
};
}
estimateElevation(lat, lng) {
if (lng < 35.6 && lat < 32.2 && lat > 31.0) {
return -400 + Math.abs(lng - 35.5) * 3000;
}
if (lat >= 32.1 && lng < 36.0) {
return 850 + Math.sin(lat * 50) * 250 + Math.cos(lng * 40) * 150;
}
if (lat >= 31.8 && lat < 32.1 && lng >= 35.8 && lng < 36.2) {
return 900 + Math.sin((lat - 31.95) * 100) * 120 + Math.cos((lng - 35.9) * 100) * 100;
}
if (lat < 31.5 && lat > 30.0 && lng < 35.7) {
return 1100 + Math.sin(lat * 30) * 350;
}
return 650 + (lng - 36.0) * 30;
}
haversineDistance(lat1, lon1, lat2, lon2) {
const R = 6371000;
const dLat = (lat2 - lat1) * (Math.PI / 180);
const dLon = (lon2 - lon1) * (Math.PI / 180);
const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(lat1 * (Math.PI / 180)) * Math.cos(lat2 * (Math.PI / 180)) *
Math.sin(dLon / 2) * Math.sin(dLon / 2);
return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
}
getRouteName(instructions) {
if (!instructions || instructions.length === 0)
return null;
File diff suppressed because one or more lines are too long