feat(tactical): real DEM grid slope analysis, dynamic contour lines, and topography-driven wadi/ridge features

This commit is contained in:
Hamza-Ayed
2026-08-18 11:15:56 +03:00
parent b1ee482830
commit b039281972
2 changed files with 372 additions and 289 deletions
+122 -1
View File
@@ -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<boolean>(false);
// Terrain Visual Layers Toggles on Map
const [showContours, setShowContours] = useState<boolean>(true);
const [showPeaksAndValleys, setShowPeaksAndValleys] = useState<boolean>(true);
const [showSlopeSectors, setShowSlopeSectors] = useState<boolean>(true);
const [showNaturalObstacles, setShowNaturalObstacles] = useState<boolean>(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
<Sliders size={14} /> طبقات الإظهار اللوني على الخريطة:
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 6 }}>
<button
onClick={() => setShowContours(!showContours)}
style={{
background: showContours ? 'rgba(245, 158, 11, 0.2)' : 'rgba(255,255,255,0.05)',
border: showContours ? '1px solid #f59e0b' : '1px solid rgba(255,255,255,0.1)',
color: showContours ? '#f59e0b' : '#94a3b8',
padding: '5px 8px',
borderRadius: 6,
fontSize: '0.68rem',
fontWeight: 700,
cursor: 'pointer',
textAlign: 'right',
display: 'flex',
alignItems: 'center',
gap: 4
}}
>
<span>{showContours ? '✅' : '⬜'}</span>
<span>〰️ خطوط الكنتور</span>
</button>
<button
onClick={() => setShowPeaksAndValleys(!showPeaksAndValleys)}
style={{