feat(tactical): real DEM grid slope analysis, dynamic contour lines, and topography-driven wadi/ridge features
This commit is contained in:
@@ -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={{
|
||||
|
||||
Reference in New Issue
Block a user