feat(dem): Universal analytical Jordan DEM surface model for all routes across the Kingdom

This commit is contained in:
Hamza-Ayed
2026-08-21 02:55:15 +03:00
parent 96b22d1fc0
commit 7216d86740
+73 -69
View File
@@ -315,39 +315,29 @@ export class MapsService {
const localizedBaseText = this.localizeInstructionToArabic(inst.text); const localizedBaseText = this.localizeInstructionToArabic(inst.text);
if (startIdx < endIdx) { if (startIdx < endIdx) {
let stepAscent = 0; const c1 = coords[startIdx];
let stepDescent = 0; const c2 = coords[endIdx];
const e1 = this.estimateElevation(c1[1], c1[0]);
const e2 = this.estimateElevation(c2[1], c2[0]);
const elevDiff = e2 - e1;
const dist = Math.max(30, inst.distance || this.haversineDistance(c1[1], c1[0], c2[1], c2[0]) || 1);
for (let i = startIdx; i < endIdx; i++) { if (elevDiff > 0.3) totalAscent += elevDiff;
const c1 = coords[i]; else if (elevDiff < -0.3) totalDescent += Math.abs(elevDiff);
const c2 = coords[i + 1];
const e1 = this.estimateElevation(c1[1], c1[0]);
const e2 = this.estimateElevation(c2[1], c2[0]);
const dDiff = e2 - e1;
if (dDiff > 0) stepAscent += dDiff; // Grade percentage: (rise / run) * 100
else stepDescent += Math.abs(dDiff); const rawSlope = (elevDiff / dist) * 100;
} const slopePercent = Math.max(-16, Math.min(16, Math.round(rawSlope)));
totalAscent += stepAscent;
totalDescent += stepDescent;
const dist = Math.max(40, inst.distance || 1);
const netStepDiff = stepAscent - stepDescent;
// Realistic slope calculation calibrated for highway/urban gradients
const calculatedSlope = Math.round((netStepDiff / dist) * 100);
const slopePercent = Math.max(-14, Math.min(14, calculatedSlope));
if (slopePercent > maxInclinePercent) maxInclinePercent = slopePercent; if (slopePercent > maxInclinePercent) maxInclinePercent = slopePercent;
if (slopePercent < maxDeclinePercent) maxDeclinePercent = slopePercent; if (slopePercent < maxDeclinePercent) maxDeclinePercent = slopePercent;
// Warning only for truly steep inclines/declines (10% or higher) // Warning only for steep grades (6% or higher is standard civil road warning threshold)
let warning_ar: string | null = null; let warning_ar: string | null = null;
if (slopePercent >= 10) { if (slopePercent >= 6) {
warning_ar = `⚠️ تنبيه: صعود حاد (+${slopePercent}%)`; warning_ar = `⚠️ تنبيه: صعود حاد (+${slopePercent}%)`;
steepWarnings.push({ text: localizedBaseText, slopePercent, type: 'incline', street: inst.street_name }); steepWarnings.push({ text: localizedBaseText, slopePercent, type: 'incline', street: inst.street_name });
} else if (slopePercent <= -10) { } else if (slopePercent <= -6) {
warning_ar = `⚠️ تنبيه: منحدر شديد (${slopePercent}%) - خفف السرعة`; warning_ar = `⚠️ تنبيه: منحدر شديد (${slopePercent}%) - خفف السرعة`;
steepWarnings.push({ text: localizedBaseText, slopePercent, type: 'decline', street: inst.street_name }); steepWarnings.push({ text: localizedBaseText, slopePercent, type: 'decline', street: inst.street_name });
} }
@@ -355,7 +345,7 @@ export class MapsService {
return { return {
...inst, ...inst,
slopePercent, slopePercent,
elevationChangeMeters: Math.round(netStepDiff), elevationChangeMeters: Math.round(elevDiff),
slopeWarning: warning_ar || undefined, slopeWarning: warning_ar || undefined,
text: warning_ar ? `${localizedBaseText} (${warning_ar})` : localizedBaseText text: warning_ar ? `${localizedBaseText} (${warning_ar})` : localizedBaseText
}; };
@@ -608,56 +598,70 @@ export class MapsService {
} }
/** /**
* High-Precision Topographic Elevation Model for Jordan * Universal Analytical Topographic Digital Elevation Model (DEM) for the Entire Map of Jordan.
* Continuous multi-scale terrain incorporating valley relief, ridge crests, and natural drainage slopes. * Continuous, seamless mathematical surface modeling all provinces, mountain crests, valleys, and plateaus.
*/ */
private estimateElevation(lat: number, lng: number): number { private estimateElevation(lat: number, lng: number): number {
// 1. Regional Base Elevation Surface (Western Rift to Eastern Desert) // 1. Boundary guard (default fallback for global coordinates outside Jordan)
let baseElev = 750; if (lat < 29.0 || lat > 33.5 || lng < 34.5 || lng > 39.5) {
return 700;
}
// Rift Valley Depression (Jordan Valley / Dead Sea) // 2. Rift Valley Axis (Wadi Araba, Dead Sea, Jordan Valley)
if (lng < 35.65) { // The rift axis runs along a slightly tilted meridian (Lng ~35.56 in North, ~35.00 in South)
const riftDist = Math.max(0, Math.min(1, (35.65 - lng) / 0.15)); const riftLng = 35.00 + (lat - 29.5) * (35.56 - 35.00) / (33.0 - 29.5);
const riftBottom = lat < 31.9 ? -420 : (lat < 32.3 ? -220 : -150); const distFromRiftLng = lng - riftLng; // Negative = West of rift, Positive = East of rift
const highlandEdge = lat > 32.2 ? 1000 : (lat > 31.5 ? 900 : 1300);
baseElev = highlandEdge - riftDist * (highlandEdge - riftBottom); // Elevation along the Rift Valley floor
} let riftFloorElev: number;
// Western Highlands (Ajloun, Balqa, West Amman, Tafila, Dana, Petra) if (lat >= 32.7) riftFloorElev = -200 + (lat - 32.7) * 200; // Sea of Galilee / Yarmouk (-200m to 0m)
else if (lng >= 35.65 && lng < 35.88) { else if (lat >= 31.5) riftFloorElev = -430 + Math.pow((lat - 31.5) / 1.2, 2) * 230; // Dead Sea to Deir Alla (-430m to -200m)
if (lat >= 32.25) baseElev = 950 + (lat - 32.25) * 150; // Ajloun / Jerash Highlands (950m - 1150m) else if (lat >= 30.5) riftFloorElev = -430 + (31.5 - lat) * 450; // Dead Sea south to Gharandal (-430m to +20m)
else if (lat >= 31.85) baseElev = 920 + (lng - 35.75) * 400; // Balqa / West Amman (850m - 1040m) else riftFloorElev = 20 + (lat - 29.5) * 80; // Wadi Araba to Aqaba (+20m to +100m)
else if (lat >= 30.2) baseElev = 1100 + (31.85 - lat) * 150; // Shobak / Dana / Petra (1100m - 1550m)
else baseElev = 800 - (30.2 - lat) * 700; // South towards Aqaba // Crest Elevation of the Eastern Mountain Ridge (Ajloun -> Balqa -> Karak -> Tafila -> Shobak -> Ras En Naqb)
} let highlandCrestElev: number;
// Central Urban Basin (East Amman, Zarqa, Rusaifa, Madaba, Irbid Plateau) const crestDistanceDeg = 0.28; // Distance in degrees from Rift axis to the mountain crest (~28 km)
else if (lng >= 35.88 && lng < 36.25) { if (lat >= 32.2) highlandCrestElev = 1050 + (lat - 32.2) * 100; // Ajloun / Jerash (1050m - 1200m)
if (lat >= 32.35) { else if (lat >= 31.8) highlandCrestElev = 980 + (lat - 31.8) * 150; // Salt / West Amman (980m - 1040m)
baseElev = 580 + (lat - 32.35) * 80; // Irbid Plateau else if (lat >= 31.3) highlandCrestElev = 820 + (lat - 31.3) * 200; // Madaba / Central (820m - 920m)
} else if (lat >= 32.0 && lat < 32.35) { else if (lat >= 30.7) highlandCrestElev = 1100 + (31.3 - lat) * 400; // Karak / Tafila (1100m - 1340m)
// Zarqa & Sukhna Basin Topography (Parabolic valley profile with natural drainage) else if (lat >= 29.9) highlandCrestElev = 1450 + (30.7 - lat) * 150; // Dana / Shobak / Ras En Naqb (1450m - 1570m)
const riverAxisLat = 32.06 + (lng - 36.05) * 0.65; else highlandCrestElev = 850 - (29.9 - lat) * 800; // Drop to Aqaba Mountains
const distFromRiverKm = Math.abs(lat - riverAxisLat) * 111.32;
// Parabolic rise from riverbed (530m) up to surrounding hills (660m) // 3. Physical Cross-Section Profile across Jordan (East-West Topography)
const valleyRelief = 120 * (1 - Math.exp(-Math.pow(distFromRiverKm / 1.5, 2))); let elev: number;
const longitudinalSlope = (32.15 - lat) * 60; // Slopes downwards northwards if (distFromRiftLng <= 0) {
const localHillWave = Math.sin(lat * 80.0) * 18.0 + Math.cos(lng * 90.0) * 14.0; // In the Rift or Western escarpment
baseElev = 540 + valleyRelief + longitudinalSlope + localHillWave; const wFraction = Math.min(1, Math.abs(distFromRiftLng) / 0.15);
} else if (lat >= 31.85 && lat < 32.0) { elev = riftFloorElev + (700 - riftFloorElev) * (wFraction * wFraction);
// Amman 7 Hills & Valleys Topography } else if (distFromRiftLng <= crestDistanceDeg) {
const localAmmanHill = Math.sin((lat - 31.95) * 120) * 45 + Math.cos((lng - 35.92) * 120) * 40; // Steep ascent from Rift Valley floor to Mountain Crest
baseElev = 840 - (lng - 35.90) * 350 + localAmmanHill; const ascentFraction = distFromRiftLng / crestDistanceDeg; // 0 (Rift) -> 1 (Mountain Crest)
} else if (lat < 31.85 && lat >= 31.6) { // S-curve steep escarpment (Sigmoid transition)
baseElev = 740 + (lat - 31.6) * 100; // Airport / Madaba plains const sCurve = Math.sin((ascentFraction - 0.5) * Math.PI) * 0.5 + 0.5;
} else { elev = riftFloorElev + (highlandCrestElev - riftFloorElev) * sCurve;
baseElev = 700; } else {
// East of Mountain Crest: Gentle slope down into the Eastern Plateaus and Basins
const eastDistDeg = distFromRiftLng - crestDistanceDeg;
let easternBasePlateau = 680;
if (lat >= 32.4) easternBasePlateau = 560; // Irbid / Ramtha Plateau
else if (lat >= 31.9) easternBasePlateau = 580; // Amman East / Zarqa Basin
else if (lat >= 31.4) easternBasePlateau = 720; // Airport / Qatranah
else easternBasePlateau = 850; // Maan / Southern Desert Plateau
// Decay from Mountain Crest towards Eastern Base Plateau
const plateauDecay = Math.exp(-eastDistDeg / 0.35);
elev = easternBasePlateau + (highlandCrestElev - easternBasePlateau) * plateauDecay;
// Eastern Desert Azraq depression (Lng 36.8, Lat 31.8)
const azraqDist = Math.hypot((lat - 31.83) * 1.1, (lng - 36.82));
if (azraqDist < 0.6) {
elev -= (1 - azraqDist / 0.6) * 120; // Dips to ~510m in Azraq
} }
} }
// Eastern Desert Plateau (Mafraq, Azraq, Safawi)
else {
baseElev = 640 + (lng - 36.25) * 20 - (lat - 32.0) * 30;
}
return baseElev; return elev;
} }
private haversineDistance(lat1: number, lon1: number, lat2: number, lon2: number): number { private haversineDistance(lat1: number, lon1: number, lat2: number, lon2: number): number {