From ff97821fd07d60b979f4c747a81c4bef5a907730 Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Fri, 21 Aug 2026 02:34:39 +0300 Subject: [PATCH] fix(elevation): Continuous smooth IDW DEM model for realistic gradients without jumps --- apps/api/src/maps/maps.service.ts | 80 +++++++++++++++++++++++++------ 1 file changed, 66 insertions(+), 14 deletions(-) diff --git a/apps/api/src/maps/maps.service.ts b/apps/api/src/maps/maps.service.ts index 4bb8a09..edff1c8 100644 --- a/apps/api/src/maps/maps.service.ts +++ b/apps/api/src/maps/maps.service.ts @@ -321,12 +321,14 @@ export class MapsService { 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; + const dist = Math.max(30, 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); + // Clamp physical road gradient to realistic engineering limits [-16%, +16%] + 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; @@ -596,20 +598,70 @@ export class MapsService { return text; } + private static readonly JORDAN_DEM_NODES: { lat: number; lng: number; elev: number }[] = [ + // Amman Metropolitan & Balqa + { lat: 31.9835, lng: 35.8285, elev: 1010 }, // Dabouq + { lat: 32.0250, lng: 35.8450, elev: 1070 }, // Sweileh + { lat: 31.9540, lng: 35.9350, elev: 850 }, // Amman Citadel + { lat: 31.9615, lng: 35.9130, elev: 880 }, // Abdali + { lat: 31.9580, lng: 35.8680, elev: 990 }, // 6th Circle + { lat: 32.0390, lng: 35.7280, elev: 790 }, // Salt Center + { lat: 31.7680, lng: 35.7250, elev: 810 }, // Mount Nebo + { lat: 31.7160, lng: 35.7930, elev: 760 }, // Madaba + { lat: 31.6980, lng: 35.9520, elev: 730 }, // Queen Alia Airport / Giza + + // Zarqa & Eastern Basin + { lat: 32.0608, lng: 36.0880, elev: 590 }, // Zarqa City Center + { lat: 32.0910, lng: 36.0950, elev: 610 }, // Hashemiya / Zarqa North + { lat: 32.1350, lng: 36.1400, elev: 640 }, // Sukhna / Zarqa Basin + { lat: 32.3400, lng: 36.2000, elev: 700 }, // Mafraq + { lat: 32.1800, lng: 36.8000, elev: 620 }, // Azraq + + // North (Irbid, Jerash, Ajloun) + { lat: 32.5560, lng: 35.8500, elev: 590 }, // Irbid Center + { lat: 32.2720, lng: 35.8900, elev: 610 }, // Jerash + { lat: 32.3320, lng: 35.7270, elev: 1120 }, // Ajloun Castle + { lat: 32.5800, lng: 36.0100, elev: 520 }, // Ramtha + + // Jordan Valley & Dead Sea Rift (-Elevations) + { lat: 31.7200, lng: 35.5800, elev: -390 }, // Dead Sea North + { lat: 31.5000, lng: 35.5000, elev: -430 }, // Dead Sea Central + { lat: 32.3000, lng: 35.6000, elev: -210 }, // Deir Alla (Jordan Valley) + { lat: 32.5000, lng: 35.6000, elev: -180 }, // North Shuna + + // South (Karak, Tafila, Petra, Maan, Aqaba) + { lat: 31.1850, lng: 35.7050, elev: 940 }, // Karak Castle + { lat: 30.8350, lng: 35.6050, elev: 1150 }, // Tafila + { lat: 30.6000, lng: 35.6100, elev: 1480 }, // Dana Biosphere / Shobak + { lat: 30.3280, lng: 35.4440, elev: 1180 }, // Petra / Wadi Musa + { lat: 30.1900, lng: 35.7300, elev: 1070 }, // Maan + { lat: 29.9800, lng: 35.4800, elev: 1570 }, // Ras En Naqb Pass + { lat: 29.5300, lng: 35.0050, elev: 25 }, // Aqaba Gulf + { lat: 29.6300, lng: 35.4300, elev: 950 }, // Wadi Rum + ]; + + /** + * Smooth, continuous topographic elevation estimation using Inverse Distance Weighting (IDW). + */ private estimateElevation(lat: number, lng: number): number { - if (lng < 35.6 && lat < 32.2 && lat > 31.0) { - return -400 + Math.abs(lng - 35.5) * 3000; + let totalWeight = 0; + let weightedElevSum = 0; + + for (const node of MapsService.JORDAN_DEM_NODES) { + const dLat = (lat - node.lat) * 111.32; // km + const dLng = (lng - node.lng) * 94.5; // km + const distSq = dLat * dLat + dLng * dLng; + + if (distSq < 0.0001) { + return node.elev; + } + + const weight = 1 / Math.pow(distSq + 0.25, 1.5); + totalWeight += weight; + weightedElevSum += node.elev * weight; } - 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; + + return totalWeight > 0 ? Math.round(weightedElevSum / totalWeight) : 750; } private haversineDistance(lat1: number, lon1: number, lat2: number, lon2: number): number {