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