From b0392819727ec24be79569ad96ec4a77e57ae0ac Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Tue, 18 Aug 2026 11:15:56 +0300 Subject: [PATCH] feat(tactical): real DEM grid slope analysis, dynamic contour lines, and topography-driven wadi/ridge features --- apps/web/src/pages/TacticalDefenseView.tsx | 123 ++++- apps/web/src/utils/elevationService.ts | 538 ++++++++++----------- 2 files changed, 372 insertions(+), 289 deletions(-) diff --git a/apps/web/src/pages/TacticalDefenseView.tsx b/apps/web/src/pages/TacticalDefenseView.tsx index ee6d4c1..744a206 100644 --- a/apps/web/src/pages/TacticalDefenseView.tsx +++ b/apps/web/src/pages/TacticalDefenseView.tsx @@ -1,5 +1,15 @@ import React, { useState, useEffect, useRef } from 'react'; import maplibregl from 'maplibre-gl'; +import mlcontour from 'maplibre-contour'; + +const demSource = new mlcontour.DemSource({ + url: 'https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png', + encoding: 'terrarium', + maxzoom: 15, + worker: true, + cacheSize: 100, +}); +demSource.setupMaplibre(maplibregl); import 'maplibre-gl/dist/maplibre-gl.css'; import { Shield, @@ -89,6 +99,7 @@ export const TacticalDefenseView: React.FC = () => { const [copiedReport, setCopiedReport] = useState(false); // Terrain Visual Layers Toggles on Map + const [showContours, setShowContours] = useState(true); const [showPeaksAndValleys, setShowPeaksAndValleys] = useState(true); const [showSlopeSectors, setShowSlopeSectors] = useState(true); const [showNaturalObstacles, setShowNaturalObstacles] = useState(true); @@ -444,6 +455,86 @@ export const TacticalDefenseView: React.FC = () => { } }); + // Dynamic On-the-Fly Contours from Elevation DEM + const contourUrl = demSource.contourProtocolUrl({ + thresholds: { + 10: [100, 500], + 11: [50, 250], + 12: [25, 100], + 13: [20, 100], + 14: [10, 50], + 15: [10, 50], + 16: [10, 50], + 17: [10, 50], + 18: [10, 50], + }, + elevationKey: 'ele', + levelKey: 'level', + contourLayer: 'contours', + }); + + if (!initialMap.getSource('contour-source')) { + initialMap.addSource('contour-source', { + type: 'vector', + tiles: [contourUrl], + maxzoom: 18, + }); + + initialMap.addLayer({ + id: 'contour-lines-minor', + type: 'line', + source: 'contour-source', + 'source-layer': 'contours', + minzoom: 10, + layout: { + visibility: 'visible', + }, + filter: ['==', ['get', 'level'], 0], + paint: { + 'line-color': '#a86324', + 'line-width': 1.1, + 'line-opacity': 0.85, + }, + }); + + initialMap.addLayer({ + id: 'contour-lines-major', + type: 'line', + source: 'contour-source', + 'source-layer': 'contours', + minzoom: 9, + layout: { + visibility: 'visible', + }, + filter: ['>', ['get', 'level'], 0], + paint: { + 'line-color': '#703800', + 'line-width': 2.0, + 'line-opacity': 0.95, + }, + }); + + initialMap.addLayer({ + id: 'contour-labels', + type: 'symbol', + source: 'contour-source', + 'source-layer': 'contours', + minzoom: 12, + layout: { + visibility: 'visible', + 'symbol-placement': 'line', + 'text-field': ['concat', ['to-string', ['get', 'ele']], ' m'], + 'text-size': 10, + 'text-font': ['Noto Sans Bold', 'Open Sans Bold'], + }, + paint: { + 'text-color': '#5c2d00', + 'text-halo-color': 'rgba(255, 255, 255, 0.95)', + 'text-halo-width': 2, + }, + }); + } + // Rich Spatial Feature Layers for Terrain Study (Slope, Wadis, Roads, Urban, Hazards) initialMap.addSource('tactical-terrain-spatial-src', { type: 'geojson', @@ -702,6 +793,16 @@ export const TacticalDefenseView: React.FC = () => { .catch(() => setTerrainLoading(false)); }; + // Toggle Contour Layers Visibility + useEffect(() => { + if (!map.current) return; + ['contour-lines-minor', 'contour-lines-major', 'contour-labels'].forEach(layerId => { + if (map.current!.getLayer(layerId)) { + map.current!.setLayoutProperty(layerId, 'visibility', showContours ? 'visible' : 'none'); + } + }); + }, [showContours]); + // Sync Spatial GeoJSON Features with Active Layer Toggles useEffect(() => { if (!map.current || !map.current.getSource('tactical-terrain-spatial-src')) return; @@ -716,7 +817,7 @@ export const TacticalDefenseView: React.FC = () => { const filtered = terrainResult.spatialGeoJson.features.filter((f: any) => { if (f.properties.category === 'peak' || f.properties.category === 'valley') return showPeaksAndValleys; if (f.properties.layerType === 'slope-sector') return showSlopeSectors; - if (f.properties.layerType === 'wadi') return showNaturalObstacles; + if (f.properties.layerType === 'wadi' || f.properties.layerType === 'ridge') return showNaturalObstacles; if (f.properties.layerType === 'road') return showRoadCorridors; if (f.properties.layerType === 'urban') return showUrbanZones; if (f.properties.layerType === 'hazard') return showHazardousGround; @@ -1231,6 +1332,26 @@ ${terrainResult.tacticalRecommendations.map((r, i) => `${i + 1}. ${r}`).join('\n طبقات الإظهار اللوني على الخريطة:
+