import React, { useState, useEffect, useRef } from 'react'; import maplibregl from 'maplibre-gl'; import 'maplibre-gl/dist/maplibre-gl.css'; import { Shield, Crosshair, Eye, Radio, Mountain, AlertTriangle, CheckCircle2, XCircle, Layers, Trash2, Navigation, Flame, Activity, Plus, Play, RotateCcw, Award, AlertOctagon, MapPin, Target, LocateFixed, Move, Building, TreePine, Printer, FileText, Compass, Check, EyeOff, Sliders } from 'lucide-react'; import { calculateLineOfSight, calculateRadialViewshed, calculateMinefieldAnalysis, calculateTerrainStudy, LineOfSightResult, Viewshed360Result, MinefieldAnalysisResult, TerrainStudyResult, calculateDistance, calculateAzimuth } from '../utils/elevationService'; type TacticalMode = 'terrain' | 'los' | 'viewshed' | 'artillery' | 'minefield' | 'hlz' | 'symbols' | 'study'; interface TacticalSymbolItem { id: string; type: 'friendly' | 'hostile' | 'artillery' | 'radar' | 'checkpoint' | 'minefield' | 'op' | 'hlz'; name: string; lat: number; lng: number; } const REGIONAL_SECTORS = [ { name: 'عمّان الوسطى', lat: 31.9539, lng: 35.9106, zoom: 12 }, { name: 'إربد / اليرموك (الشمال)', lat: 32.5568, lng: 35.8469, zoom: 12 }, { name: 'قاطع المفرق (الشرق)', lat: 32.3421, lng: 36.2081, zoom: 11 }, { name: 'غور الأردن / البحر الميت', lat: 31.7500, lng: 35.5800, zoom: 12 }, { name: 'مرتفعات الكرك والشوبك', lat: 31.1853, lng: 35.7048, zoom: 11 }, { name: 'وادي رم الجنوبي', lat: 29.5700, lng: 35.4200, zoom: 12 }, ]; const DEFAULT_SYMBOLS: TacticalSymbolItem[] = [ { id: 'sym-1', type: 'friendly', name: 'كتيبة المشاة الآلية 4', lat: 31.9800, lng: 35.8800 }, { id: 'sym-2', type: 'artillery', name: 'مربض مدفعية م109', lat: 31.9300, lng: 35.9100 }, { id: 'sym-3', type: 'radar', name: 'رادار كشف أرضي تكتيكي', lat: 32.0200, lng: 35.8400 }, { id: 'sym-4', type: 'checkpoint', name: 'نقطة سيطرة وغلق أمني', lat: 31.9500, lng: 35.9500 }, { id: 'sym-5', type: 'op', name: 'نقطة مراقبة واستطلاع أمامي', lat: 32.0600, lng: 35.8100 }, ]; export const TacticalDefenseView: React.FC = () => { const mapContainer = useRef(null); const map = useRef(null); const [mode, setMode] = useState('terrain'); const [cursorPos, setCursorPos] = useState({ lat: 31.95, lng: 35.93, elev: 850 }); const [activePlacement, setActivePlacement] = useState(null); // 1. Terrain Tactical Study State (دراسة الأرض) const [terrainCenter, setTerrainCenter] = useState<[number, number] | null>([31.9539, 35.9106]); const [terrainRadius, setTerrainRadius] = useState(4000); const [terrainResult, setTerrainResult] = useState(null); const [terrainLoading, setTerrainLoading] = useState(false); const [copiedReport, setCopiedReport] = useState(false); // Terrain Visual Layers Toggles on Map const [showPeaksAndValleys, setShowPeaksAndValleys] = useState(true); const [showSlopeSectors, setShowSlopeSectors] = useState(true); const [showNaturalObstacles, setShowNaturalObstacles] = useState(true); const [showRoadCorridors, setShowRoadCorridors] = useState(true); const [showUrbanZones, setShowUrbanZones] = useState(true); const [showHazardousGround, setShowHazardousGround] = useState(true); // 2. Line of Sight State const [losPointA, setLosPointA] = useState<[number, number] | null>([31.9539, 35.9106]); const [losPointB, setLosPointB] = useState<[number, number] | null>([32.0125, 35.8540]); const [losObsHeight, setLosObsHeight] = useState(2); const [losTgtHeight, setLosTgtHeight] = useState(2); const [losResult, setLosResult] = useState(null); const [losLoading, setLosLoading] = useState(false); // 3. Viewshed State const [viewshedCenter, setViewshedCenter] = useState<[number, number] | null>([31.9800, 35.8800]); const [viewshedRadius, setViewshedRadius] = useState(5000); const [viewshedHeight, setViewshedHeight] = useState(10); const [viewshedResult, setViewshedResult] = useState(null); const [viewshedLoading, setViewshedLoading] = useState(false); // 4. Artillery Fire Mission State const [gunPos, setGunPos] = useState<[number, number] | null>([31.9300, 35.9100]); const [targetPos, setTargetPos] = useState<[number, number] | null>([32.0400, 35.8200]); const [caliber, setCaliber] = useState('155mm Howitzer (M109)'); const [muzzleVel, setMuzzleVel] = useState(827); const [artilleryResult, setArtilleryResult] = useState(null); const [artilleryLoading, setArtilleryLoading] = useState(false); // 5. Minefield Analysis State const [mineStart, setMineStart] = useState<[number, number] | null>([31.9700, 35.8600]); const [mineEnd, setMineEnd] = useState<[number, number] | null>([31.9950, 35.8750]); const [mineDensity, setMineDensity] = useState<'standard' | 'dense' | 'light'>('standard'); const [mineResult, setMineResult] = useState(null); const [mineLoading, setMineLoading] = useState(false); // 6. HLZ State const [hlzCenter, setHlzCenter] = useState<[number, number] | null>([31.9600, 35.9000]); const [hlzResult, setHlzResult] = useState(null); const [hlzLoading, setHlzLoading] = useState(false); // 7. Tactical Symbols State const [symbols, setSymbols] = useState(DEFAULT_SYMBOLS); const [selectedSymbolType, setSelectedSymbolType] = useState('friendly'); const [newSymbolName, setNewSymbolName] = useState(''); const [customSymbolLat, setCustomSymbolLat] = useState(31.95); const [customSymbolLng, setCustomSymbolLng] = useState(35.93); // Synchronized Refs to avoid stale closures in MapLibre event listeners const modeRef = useRef(mode); modeRef.current = mode; const activePlacementRef = useRef(activePlacement); activePlacementRef.current = activePlacement; const terrainCenterRef = useRef(terrainCenter); terrainCenterRef.current = terrainCenter; const losPointARef = useRef(losPointA); losPointARef.current = losPointA; const losPointBRef = useRef(losPointB); losPointBRef.current = losPointB; const viewshedCenterRef = useRef(viewshedCenter); viewshedCenterRef.current = viewshedCenter; const gunPosRef = useRef(gunPos); gunPosRef.current = gunPos; const targetPosRef = useRef(targetPos); targetPosRef.current = targetPos; const mineStartRef = useRef(mineStart); mineStartRef.current = mineStart; const mineEndRef = useRef(mineEnd); mineEndRef.current = mineEnd; const hlzCenterRef = useRef(hlzCenter); hlzCenterRef.current = hlzCenter; const selectedSymbolTypeRef = useRef(selectedSymbolType); selectedSymbolTypeRef.current = selectedSymbolType; const newSymbolNameRef = useRef(newSymbolName); newSymbolNameRef.current = newSymbolName; const symbolsRef = useRef(symbols); symbolsRef.current = symbols; // Draggable Markers Ref Store const markersRef = useRef<{ [key: string]: maplibregl.Marker }>({}); // Helper to create or update draggable marker const updateMarker = (id: string, coords: [number, number] | null, label: string, color: string, onDrag: (pos: [number, number]) => void) => { if (!map.current) return; if (!coords) { if (markersRef.current[id]) { markersRef.current[id].remove(); delete markersRef.current[id]; } return; } if (markersRef.current[id]) { markersRef.current[id].setLngLat([coords[1], coords[0]]); const el = markersRef.current[id].getElement(); if (el) el.innerText = label; } else { const el = document.createElement('div'); el.className = 'tactical-marker'; el.style.display = 'flex'; el.style.alignItems = 'center'; el.style.justifyContent = 'center'; el.style.background = color; el.style.color = '#ffffff'; el.style.fontSize = '11px'; el.style.fontWeight = 'bold'; el.style.padding = '4px 8px'; el.style.borderRadius = '20px'; el.style.border = '2px solid #ffffff'; el.style.boxShadow = '0 0 12px rgba(0,0,0,0.6)'; el.style.cursor = 'grab'; el.style.userSelect = 'none'; el.innerText = label; const marker = new maplibregl.Marker({ element: el, draggable: true }) .setLngLat([coords[1], coords[0]]) .addTo(map.current); marker.on('dragend', () => { const lngLat = marker.getLngLat(); onDrag([Number(lngLat.lat.toFixed(5)), Number(lngLat.lng.toFixed(5))]); }); markersRef.current[id] = marker; } }; // Sync Draggable Markers to Map useEffect(() => { if (!map.current) return; // Terrain Study Center updateMarker('terrain-c', terrainCenter, '⛰️ مركز دراسة الأرض', '#6366f1', (pos) => setTerrainCenter(pos)); // Terrain Highest & Lowest Points (Dynamic Pins) if (terrainResult?.highestPoint && showPeaksAndValleys && mode === 'terrain') { updateMarker( 'terrain-peak', [terrainResult.highestPoint.lat, terrainResult.highestPoint.lng], `🔺 ${terrainResult.highestPoint.label}`, '#0284c7', () => {} ); } else { updateMarker('terrain-peak', null, '', '', () => {}); } if (terrainResult?.lowestPoint && showPeaksAndValleys && mode === 'terrain') { updateMarker( 'terrain-valley', [terrainResult.lowestPoint.lat, terrainResult.lowestPoint.lng], `🔻 ${terrainResult.lowestPoint.label}`, '#e11d48', () => {} ); } else { updateMarker('terrain-valley', null, '', '', () => {}); } // LOS updateMarker('los-a', losPointA, '📍 راصد A', '#0284c7', (pos) => setLosPointA(pos)); updateMarker('los-b', losPointB, '🎯 هدف B', '#d97706', (pos) => setLosPointB(pos)); // Viewshed updateMarker('viewshed-c', viewshedCenter, '📡 مركز الرصد', '#16a34a', (pos) => setViewshedCenter(pos)); // Artillery updateMarker('art-gun', gunPos, '💥 مربض المدفعية', '#ea580c', (pos) => setGunPos(pos)); updateMarker('art-tgt', targetPos, '🎯 هدف مدفعية', '#dc2626', (pos) => setTargetPos(pos)); // Minefield updateMarker('mine-s', mineStart, '⛔ بداية حقل الألغام', '#b91c1c', (pos) => setMineStart(pos)); updateMarker('mine-e', mineEnd, '⛔ نهاية حقل الألغام', '#991b1b', (pos) => setMineEnd(pos)); // HLZ updateMarker('hlz-c', hlzCenter, '🚁 مركز HLZ', '#059669', (pos) => setHlzCenter(pos)); }, [terrainCenter, terrainResult, showPeaksAndValleys, mode, losPointA, losPointB, viewshedCenter, gunPos, targetPos, mineStart, mineEnd, hlzCenter]); // Map Initialization useEffect(() => { if (map.current) return; if (!mapContainer.current) return; const initialMap = new maplibregl.Map({ container: mapContainer.current, style: '/style.json', center: [35.9106, 31.9539], zoom: 11, pitch: 35, attributionControl: false }); map.current = initialMap; const handleResize = () => { if (initialMap) initialMap.resize(); }; window.addEventListener('resize', handleResize); initialMap.on('mousemove', (e) => { setCursorPos({ lat: Number(e.lngLat.lat.toFixed(5)), lng: Number(e.lngLat.lng.toFixed(5)), elev: 820 + Math.round(Math.sin(e.lngLat.lat * 20) * 150) }); }); // Fresh Map Click Handler reading latest Refs initialMap.on('click', (e) => { const clickedLat = Number(e.lngLat.lat.toFixed(5)); const clickedLng = Number(e.lngLat.lng.toFixed(5)); const placement = activePlacementRef.current; const currentMode = modeRef.current; // 1. Explicit Active Placement from Buttons if (placement === 'terrain-center') { setTerrainCenter([clickedLat, clickedLng]); setActivePlacement(null); return; } if (placement === 'los-a') { setLosPointA([clickedLat, clickedLng]); setActivePlacement(null); return; } if (placement === 'los-b') { setLosPointB([clickedLat, clickedLng]); setActivePlacement(null); return; } if (placement === 'viewshed-center') { setViewshedCenter([clickedLat, clickedLng]); setActivePlacement(null); return; } if (placement === 'gun') { setGunPos([clickedLat, clickedLng]); setActivePlacement(null); return; } if (placement === 'target') { setTargetPos([clickedLat, clickedLng]); setActivePlacement(null); return; } if (placement === 'mine-start') { setMineStart([clickedLat, clickedLng]); setActivePlacement(null); return; } if (placement === 'mine-end') { setMineEnd([clickedLat, clickedLng]); setActivePlacement(null); return; } if (placement === 'hlz-center') { setHlzCenter([clickedLat, clickedLng]); setActivePlacement(null); return; } // 2. Default Context-Aware Click by Active Mode if (currentMode === 'terrain') { setTerrainCenter([clickedLat, clickedLng]); } else if (currentMode === 'los') { if (!losPointARef.current) { setLosPointA([clickedLat, clickedLng]); } else if (!losPointBRef.current) { setLosPointB([clickedLat, clickedLng]); } else { setLosPointA([clickedLat, clickedLng]); setLosPointB(null); } } else if (currentMode === 'viewshed') { setViewshedCenter([clickedLat, clickedLng]); } else if (currentMode === 'artillery') { if (!gunPosRef.current) { setGunPos([clickedLat, clickedLng]); } else if (!targetPosRef.current) { setTargetPos([clickedLat, clickedLng]); } else { setTargetPos([clickedLat, clickedLng]); } } else if (currentMode === 'minefield') { if (!mineStartRef.current) { setMineStart([clickedLat, clickedLng]); } else if (!mineEndRef.current) { setMineEnd([clickedLat, clickedLng]); } else { setMineStart([clickedLat, clickedLng]); setMineEnd(null); } } else if (currentMode === 'hlz') { setHlzCenter([clickedLat, clickedLng]); } else if (currentMode === 'symbols') { const typeLabels: Record = { friendly: 'وحدة صديقة', hostile: 'هدف معادي', artillery: 'مربض مدفعية', radar: 'محطة رادار', checkpoint: 'نقطة سيطرة', op: 'نقطة مراقبة', minefield: 'حقل ألغام', hlz: 'مهبط مروحيات' }; const newSym: TacticalSymbolItem = { id: `sym-${Date.now()}`, type: selectedSymbolTypeRef.current as any, name: newSymbolNameRef.current || `${typeLabels[selectedSymbolTypeRef.current] || 'رمز'} ${symbolsRef.current.length + 1}`, lat: clickedLat, lng: clickedLng }; setSymbols(prev => [...prev, newSym]); setNewSymbolName(''); } }); initialMap.on('load', () => { // 0. Terrain Study Circle & Spatial Layers initialMap.addSource('tactical-terrain-src', { type: 'geojson', data: { type: 'FeatureCollection', features: [] } }); initialMap.addLayer({ id: 'tactical-terrain-fill', type: 'fill', source: 'tactical-terrain-src', paint: { 'fill-color': '#6366f1', 'fill-opacity': 0.08 } }); initialMap.addLayer({ id: 'tactical-terrain-line', type: 'line', source: 'tactical-terrain-src', paint: { 'line-color': '#818cf8', 'line-width': 2.5, 'line-dasharray': [3, 2] } }); // Rich Spatial Feature Layers for Terrain Study (Slope, Wadis, Roads, Urban, Hazards) initialMap.addSource('tactical-terrain-spatial-src', { type: 'geojson', data: { type: 'FeatureCollection', features: [] } }); initialMap.addLayer({ id: 'tactical-terrain-spatial-poly-fill', type: 'fill', source: 'tactical-terrain-spatial-src', filter: ['==', '$type', 'Polygon'], paint: { 'fill-color': ['get', 'color'], 'fill-opacity': [ 'match', ['get', 'layerType'], 'urban', 0.28, 'hazard', 0.32, 'slope-sector', 0.18, 0.2 ] } }); initialMap.addLayer({ id: 'tactical-terrain-spatial-poly-line', type: 'line', source: 'tactical-terrain-spatial-src', filter: ['==', '$type', 'Polygon'], paint: { 'line-color': ['get', 'color'], 'line-width': 2, 'line-dasharray': [3, 2] } }); initialMap.addLayer({ id: 'tactical-terrain-spatial-lines', type: 'line', source: 'tactical-terrain-spatial-src', filter: ['==', '$type', 'LineString'], paint: { 'line-color': ['get', 'color'], 'line-width': [ 'match', ['get', 'layerType'], 'road', 4.5, 'wadi', 3.5, 3 ], 'line-dasharray': [ 'match', ['get', 'layerType'], 'road', ['literal', [1, 0]], 'wadi', ['literal', [2, 1.5]], ['literal', [1, 0]] ] } }); initialMap.addLayer({ id: 'tactical-terrain-spatial-labels', type: 'symbol', source: 'tactical-terrain-spatial-src', layout: { 'text-field': ['get', 'title'], 'text-size': 11, 'text-font': ['Noto Sans Bold', 'Open Sans Bold'], 'text-anchor': 'center', 'symbol-placement': 'point' }, paint: { 'text-color': '#ffffff', 'text-halo-color': '#0f172a', 'text-halo-width': 2 } }); // 1. Line of Sight Layer initialMap.addSource('tactical-los-src', { type: 'geojson', data: { type: 'FeatureCollection', features: [] } }); initialMap.addLayer({ id: 'tactical-los-line', type: 'line', source: 'tactical-los-src', filter: ['==', '$type', 'LineString'], paint: { 'line-color': ['case', ['==', ['get', 'blocked'], true], '#ef4444', '#22c55e'], 'line-width': 4, 'line-dasharray': [2, 1] } }); // 2. Viewshed 360 Polygon Layer initialMap.addSource('tactical-viewshed-src', { type: 'geojson', data: { type: 'FeatureCollection', features: [] } }); initialMap.addLayer({ id: 'tactical-viewshed-fill', type: 'fill', source: 'tactical-viewshed-src', paint: { 'fill-color': '#22c55e', 'fill-opacity': 0.25 } }); initialMap.addLayer({ id: 'tactical-viewshed-outline', type: 'line', source: 'tactical-viewshed-src', paint: { 'line-color': '#4ade80', 'line-width': 2, 'line-dasharray': [3, 2] } }); // 3. Artillery Trajectory Layer initialMap.addSource('tactical-artillery-src', { type: 'geojson', data: { type: 'FeatureCollection', features: [] } }); initialMap.addLayer({ id: 'tactical-artillery-line', type: 'line', source: 'tactical-artillery-src', paint: { 'line-color': '#f97316', 'line-width': 4 } }); // 4. Minefield Barrier Layer initialMap.addSource('tactical-minefield-src', { type: 'geojson', data: { type: 'FeatureCollection', features: [] } }); initialMap.addLayer({ id: 'tactical-minefield-line', type: 'line', source: 'tactical-minefield-src', paint: { 'line-color': '#dc2626', 'line-width': 6, 'line-dasharray': [1, 1] } }); // 5. Tactical Symbols Layer initialMap.addSource('tactical-symbols-src', { type: 'geojson', data: { type: 'FeatureCollection', features: DEFAULT_SYMBOLS.map(s => ({ type: 'Feature', properties: { type: s.type, name: s.name }, geometry: { type: 'Point', coordinates: [s.lng, s.lat] } })) as any } }); initialMap.addLayer({ id: 'tactical-symbols-pts', type: 'circle', source: 'tactical-symbols-src', paint: { 'circle-radius': 9, 'circle-color': [ 'match', ['get', 'type'], 'friendly', '#3b82f6', 'hostile', '#ef4444', 'artillery', '#f97316', 'radar', '#06b6d4', 'checkpoint', '#eab308', 'minefield', '#dc2626', 'hlz', '#10b981', '#a855f7' ], 'circle-stroke-color': '#ffffff', 'circle-stroke-width': 2.5 } }); initialMap.addLayer({ id: 'tactical-symbols-lbls', type: 'symbol', source: 'tactical-symbols-src', layout: { 'text-field': ['get', 'name'], 'text-size': 11, 'text-font': ['Noto Sans Bold', 'Open Sans Bold'], 'text-offset': [0, 1.4], 'text-anchor': 'top' }, paint: { 'text-color': '#ffffff', 'text-halo-color': '#0f172a', 'text-halo-width': 2 } }); }); return () => { if (map.current) { map.current.remove(); map.current = null; } }; }, []); // Run Terrain Tactical Study const runTerrainStudy = () => { if (!terrainCenter) return; setTerrainLoading(true); calculateTerrainStudy(terrainCenter[0], terrainCenter[1], terrainRadius) .then((res) => { setTerrainResult(res); setTerrainLoading(false); // 1. Draw sector boundary polygon on map if (map.current && map.current.getSource('tactical-terrain-src')) { const points = 48; const coords: [number, number][] = []; const R_earth = 6371000; for (let i = 0; i <= points; i++) { const angle = (i / points) * 2 * Math.PI; const dLat = (terrainRadius / R_earth) * (180 / Math.PI) * Math.cos(angle); const dLng = (terrainRadius / (R_earth * Math.cos((terrainCenter[0] * Math.PI) / 180))) * (180 / Math.PI) * Math.sin(angle); coords.push([terrainCenter[1] + dLng, terrainCenter[0] + dLat]); } (map.current.getSource('tactical-terrain-src') as maplibregl.GeoJSONSource).setData({ type: 'FeatureCollection', features: [ { type: 'Feature', properties: {}, geometry: { type: 'Polygon', coordinates: [coords] } } ] }); } }) .catch(() => setTerrainLoading(false)); }; // Sync Spatial GeoJSON Features with Active Layer Toggles useEffect(() => { if (!map.current || !map.current.getSource('tactical-terrain-spatial-src')) return; if (!terrainResult?.spatialGeoJson || mode !== 'terrain') { (map.current.getSource('tactical-terrain-spatial-src') as maplibregl.GeoJSONSource).setData({ type: 'FeatureCollection', features: [] }); return; } 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 === 'road') return showRoadCorridors; if (f.properties.layerType === 'urban') return showUrbanZones; if (f.properties.layerType === 'hazard') return showHazardousGround; return true; }); (map.current.getSource('tactical-terrain-spatial-src') as maplibregl.GeoJSONSource).setData({ type: 'FeatureCollection', features: filtered }); }, [terrainResult, showPeaksAndValleys, showSlopeSectors, showNaturalObstacles, showRoadCorridors, showUrbanZones, showHazardousGround, mode]); // Auto-run initial terrain study on mount & coordinates change useEffect(() => { runTerrainStudy(); }, [terrainCenter, terrainRadius]); // Update Line of Sight Calculations & Map Layer useEffect(() => { if (!losPointA || !losPointB) { if (map.current && map.current.getSource('tactical-los-src')) { (map.current.getSource('tactical-los-src') as maplibregl.GeoJSONSource).setData({ type: 'FeatureCollection', features: [] }); } setLosResult(null); return; } setLosLoading(true); calculateLineOfSight(losPointA[0], losPointA[1], losPointB[0], losPointB[1], losObsHeight, losTgtHeight, 70) .then((res) => { setLosResult(res); setLosLoading(false); if (map.current && map.current.getSource('tactical-los-src')) { const feats: any[] = [ { type: 'Feature', properties: { role: 'ray', blocked: !res.isDirectlyVisible }, geometry: { type: 'LineString', coordinates: [[losPointA[1], losPointA[0]], [losPointB[1], losPointB[0]]] } } ]; if (res.highestObstacle) { feats.push({ type: 'Feature', properties: { role: 'obstacle', label: 'عائق' }, geometry: { type: 'Point', coordinates: [res.highestObstacle.lng, res.highestObstacle.lat] } }); } (map.current.getSource('tactical-los-src') as maplibregl.GeoJSONSource).setData({ type: 'FeatureCollection', features: feats }); } }) .catch(() => setLosLoading(false)); }, [losPointA, losPointB, losObsHeight, losTgtHeight]); // Update Minefield Analysis & Map Layer useEffect(() => { if (!mineStart || !mineEnd) { if (map.current && map.current.getSource('tactical-minefield-src')) { (map.current.getSource('tactical-minefield-src') as maplibregl.GeoJSONSource).setData({ type: 'FeatureCollection', features: [] }); } setMineResult(null); return; } setMineLoading(true); calculateMinefieldAnalysis(mineStart[0], mineStart[1], mineEnd[0], mineEnd[1], mineDensity) .then((res) => { setMineResult(res); setMineLoading(false); if (map.current && map.current.getSource('tactical-minefield-src')) { (map.current.getSource('tactical-minefield-src') as maplibregl.GeoJSONSource).setData({ type: 'FeatureCollection', features: [ { type: 'Feature', properties: {}, geometry: { type: 'LineString', coordinates: [[mineStart[1], mineStart[0]], [mineEnd[1], mineEnd[0]]] } } ] }); } }) .catch(() => setMineLoading(false)); }, [mineStart, mineEnd, mineDensity]); // Update Artillery Trajectory useEffect(() => { if (!gunPos || !targetPos) { if (map.current && map.current.getSource('tactical-artillery-src')) { (map.current.getSource('tactical-artillery-src') as maplibregl.GeoJSONSource).setData({ type: 'FeatureCollection', features: [] }); } setArtilleryResult(null); return; } runArtilleryMission(); }, [gunPos, targetPos, caliber, muzzleVel]); // Update 360 Viewshed const runViewshed360 = () => { if (!viewshedCenter) return; setViewshedLoading(true); calculateRadialViewshed(viewshedCenter[0], viewshedCenter[1], viewshedHeight, viewshedRadius, 36) .then((res) => { setViewshedResult(res); setViewshedLoading(false); if (map.current && map.current.getSource('tactical-viewshed-src')) { (map.current.getSource('tactical-viewshed-src') as maplibregl.GeoJSONSource).setData({ type: 'FeatureCollection', features: [res.polygonGeoJson] }); } }) .catch(() => setViewshedLoading(false)); }; // Run Artillery Fire Mission const runArtilleryMission = async () => { if (!gunPos || !targetPos) return; setArtilleryLoading(true); try { const apiUrl = (import.meta as any).env.VITE_API_URL || '/api'; const apiKey = (import.meta as any).env.VITE_API_KEY || 'zP9vL5mK2nQ8xR7jT4wS1yB6hG3fV0cX'; const res = await fetch(`${apiUrl}/tactical/artillery-fire-mission`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey }, body: JSON.stringify({ gunLat: gunPos[0], gunLng: gunPos[1], targetLat: targetPos[0], targetLng: targetPos[1], caliber, muzzleVelocity: muzzleVel }) }); const data = await res.json(); setArtilleryResult(data); if (map.current && map.current.getSource('tactical-artillery-src')) { const lineCoords = (data.trajectoryPoints || []).map((p: any) => [p.lng, p.lat]); (map.current.getSource('tactical-artillery-src') as maplibregl.GeoJSONSource).setData({ type: 'FeatureCollection', features: [ { type: 'Feature', properties: {}, geometry: { type: 'LineString', coordinates: lineCoords } } ] }); } } catch (e) { console.error('Artillery calculation error:', e); } finally { setArtilleryLoading(false); } }; // Run HLZ Assessment const runHLZAssessment = async () => { if (!hlzCenter) return; setHlzLoading(true); try { const apiUrl = (import.meta as any).env.VITE_API_URL || '/api'; const apiKey = (import.meta as any).env.VITE_API_KEY || 'zP9vL5mK2nQ8xR7jT4wS1yB6hG3fV0cX'; const res = await fetch(`${apiUrl}/tactical/hlz-assessment?lat=${hlzCenter[0]}&lng=${hlzCenter[1]}&radius=4000`, { headers: { 'x-api-key': apiKey } }); const data = await res.json(); setHlzResult(data); } catch (e) { console.error('HLZ error:', e); } finally { setHlzLoading(false); } }; // Synchronize Tactical Symbols to Map useEffect(() => { if (!map.current || !map.current.getSource('tactical-symbols-src')) return; const feats = symbols.map(s => ({ type: 'Feature', properties: { type: s.type, name: s.name }, geometry: { type: 'Point', coordinates: [s.lng, s.lat] } })); (map.current.getSource('tactical-symbols-src') as maplibregl.GeoJSONSource).setData({ type: 'FeatureCollection', features: feats as any }); }, [symbols]); // Jump to Sector and Adapt Operational Points const flyToSector = (sector: typeof REGIONAL_SECTORS[0]) => { if (!map.current) return; map.current.flyTo({ center: [sector.lng, sector.lat], zoom: sector.zoom, pitch: 45, essential: true }); const lat = sector.lat; const lng = sector.lng; setTerrainCenter([Number(lat.toFixed(5)), Number(lng.toFixed(5))]); setLosPointA([Number((lat - 0.015).toFixed(5)), Number((lng - 0.015).toFixed(5))]); setLosPointB([Number((lat + 0.025).toFixed(5)), Number((lng + 0.025).toFixed(5))]); setViewshedCenter([Number(lat.toFixed(5)), Number(lng.toFixed(5))]); setGunPos([Number((lat - 0.03).toFixed(5)), Number((lng - 0.02).toFixed(5))]); setTargetPos([Number((lat + 0.04).toFixed(5)), Number((lng + 0.03).toFixed(5))]); setMineStart([Number((lat + 0.01).toFixed(5)), Number((lng - 0.015).toFixed(5))]); setMineEnd([Number((lat + 0.02).toFixed(5)), Number((lng + 0.015).toFixed(5))]); setHlzCenter([Number(lat.toFixed(5)), Number(lng.toFixed(5))]); }; // Copy Intelligence Dossier to Clipboard const handleCopyReport = () => { if (!terrainResult) return; const text = ` === تقرير الاستخبارات الجغرافية ودراسة الأرض التكتيكية === القطاع: ${terrainResult.sectorName} الإحداثيات: ${terrainResult.centerLat}, ${terrainResult.centerLng} نصف القطر: ${terrainResult.radiusMeters / 1000} كم | المساحة: ${terrainResult.areaKm2} كم² طبيعة الأرض: ${terrainResult.terrainClassificationAr} حالة الحركة والمناورة: ${terrainResult.mobilityStatusAr} المناسيب: أعلى قمة ${terrainResult.maxElevation}م (${terrainResult.highestPoint.lat}, ${terrainResult.highestPoint.lng}) | أخفض نقطة ${terrainResult.minElevation}م (${terrainResult.lowestPoint.lat}, ${terrainResult.lowestPoint.lng}) | الفارق التضاريسي ${terrainResult.reliefMeters}م الانحدار: متوسط ${terrainResult.avgSlopeDegrees}° | أقصى انحدار ${terrainResult.maxSlopeDegrees}° [الموانع الطبيعية]: ${terrainResult.naturalObstacles.map(o => `- ${o.name}: ${o.description}`).join('\n')} [الموانع الصناعية والبنية التحتية]: ${terrainResult.manMadeObstacles.map(m => `- ${m.name}: ${m.description} (${m.tacticalNote})`).join('\n')} [المناطق المأهولة والعمران]: - الكثافة: ${terrainResult.urbanAndDemographics.densityAr} - التجمعات: ${terrainResult.urbanAndDemographics.settlements.join('، ')} - بيئة قتال المباني: ${terrainResult.urbanAndDemographics.moutComplexity} [التوصيات التكتيكية]: ${terrainResult.tacticalRecommendations.map((r, i) => `${i + 1}. ${r}`).join('\n')} `; navigator.clipboard.writeText(text); setCopiedReport(true); setTimeout(() => setCopiedReport(false), 2500); }; return (
{/* Top Military HUD Header */}
{/* Title */}
منظومة القيادة والسيطرة والتحليل التكتيكي C4ISR Grid
{/* Tactical Modes Selector */}
{[ { id: 'terrain', label: 'دراسة الأرض', icon: }, { id: 'los', label: 'تبادل الرؤية (LOS)', icon: }, { id: 'viewshed', label: 'كشف 360°', icon: }, { id: 'artillery', label: 'موقع المدفعية', icon: }, { id: 'minefield', label: 'حقل الألغام', icon: }, { id: 'hlz', label: 'مهابط المروحيات', icon: }, { id: 'symbols', label: 'الرموز والتشكيلات', icon: }, { id: 'study', label: 'دراسة التنظيم', icon: }, ].map(m => ( ))}
{/* Realtime Cursor HUD */}
LAT: {cursorPos.lat}
LNG: {cursorPos.lng}
ELEV: {cursorPos.elev}m
{/* Quick Sector Presets Bar */}
الانتقال السريع لقطاعات العمليات: {REGIONAL_SECTORS.map((s, idx) => ( ))}
{/* Main Tactical Workspace */}
{/* Left Side Tactical Control Deck */} {/* Tactical Map Container */}
); };