import React, { useState, useEffect, useRef } from 'react'; import maplibregl from 'maplibre-gl'; import 'maplibre-gl/dist/maplibre-gl.css'; import { getSharedDemSource } from '../utils/demSource'; 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, Lock, Unlock, Key, ShieldAlert, ShieldCheck, LogOut, Clock, Zap, Share2 } 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' | 'isochrone'; export interface EmergencyHubPreset { id: string; name: string; lat: number; lng: number; type: 'hospital' | 'civil_defense' | 'security'; badge: string; description: string; } export const EMERGENCY_HUBS: EmergencyHubPreset[] = [ { id: 'hub-1', name: 'المدينة الطبية الملكية (عمان)', lat: 31.9865, lng: 35.8340, type: 'hospital', badge: 'مستشفى رئيسي', description: 'مجمع طبي عسكري متقدم - غرب عمان' }, { id: 'hub-2', name: 'مستشفى الجامعة الأردنية', lat: 32.0080, lng: 35.8720, type: 'hospital', badge: 'مستشفى جامعي', description: 'مركز طوارئ وإسعاف رئيسي شمال عمان' }, { id: 'hub-3', name: 'مستشفى البشير الحكومي (عمان)', lat: 31.9425, lng: 35.9450, type: 'hospital', badge: 'طوارئ عامة', description: 'أكبر مجمع إسعاف وطوارئ في شرق ووسط عمان' }, { id: 'hub-4', name: 'مديرية دفاع مدني العبدلي / وسط البلد', lat: 31.9680, lng: 35.9120, type: 'civil_defense', badge: 'مركز إطفاء وإنقاذ', description: 'مركز إطفاء وإنقاذ رئيسي لوسط العاصمة' }, { id: 'hub-5', name: 'مركز دفاع مدني غرب عمان (خلدا/صويلح)', lat: 31.9960, lng: 35.8450, type: 'civil_defense', badge: 'إسعاف فوري', description: 'تغطية سريعة لمحاور شمال وغرب عمان' }, { id: 'hub-6', name: 'مستشفى الملك المؤسس عبدالله الجامعي (إربد)', lat: 32.4950, lng: 35.9920, type: 'hospital', badge: 'إقليم الشمال', description: 'صرح طبي متقدم يخدم محافظات الشمال' }, { id: 'hub-7', name: 'مديرية دفاع مدني إربد المركزية', lat: 32.5590, lng: 35.8450, type: 'civil_defense', badge: 'مركز رئيسي', description: 'مركز قيادة عمليات الإنقاذ والإسعاف في الشمال' }, { id: 'hub-8', name: 'مستشفى الزرقاء الحكومي الجديد', lat: 32.0620, lng: 36.1280, type: 'hospital', badge: 'طوارئ الزرقاء', description: 'تغطية الطوارئ والحوادث لمحافظة الزرقاء' }, { id: 'hub-9', name: 'مستشفى الأمير هاشم العسكري (العقبة)', lat: 29.5350, lng: 35.0080, type: 'hospital', badge: 'إقليم الجنوب', description: 'مستشفى عسكري وإسناد طارئ لخليج العقبة' }, ]; 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); // Tactical License & Access Clearance State const [authKey, setAuthKey] = useState(() => { return new URLSearchParams(window.location.search).get('key') || new URLSearchParams(window.location.search).get('api_key') || sessionStorage.getItem('tactical_api_key') || ''; }); const [authStatus, setAuthStatus] = useState<'validating' | 'authorized' | 'unauthorized'>('validating'); const [authInfo, setAuthInfo] = useState<{ tenantName?: string; plan?: string; keyName?: string } | null>(null); const [authError, setAuthError] = useState(''); const [inputKey, setInputKey] = useState(''); const [isVerifying, setIsVerifying] = useState(false); const verifyTacticalLicense = async (keyCandidate: string) => { if (!keyCandidate || !keyCandidate.trim()) { setAuthStatus('unauthorized'); return; } setIsVerifying(true); setAuthError(''); try { const apiUrl = (import.meta as any).env.VITE_API_URL || '/api'; const res = await fetch(`${apiUrl}/tactical/verify-license`, { headers: { 'x-api-key': keyCandidate.trim() } }); if (res.ok) { const data = await res.json(); setAuthInfo(data); setAuthKey(keyCandidate.trim()); sessionStorage.setItem('tactical_api_key', keyCandidate.trim()); setAuthStatus('authorized'); } else { sessionStorage.removeItem('tactical_api_key'); setAuthStatus('unauthorized'); setAuthError('مفتاح التصريح التكتيكي غير صالح، أو منتهي الصلاحية، أو تم إلغاؤه من قبل الإدارة.'); } } catch (e) { sessionStorage.removeItem('tactical_api_key'); setAuthStatus('unauthorized'); setAuthError('تعذر التحقق من خادم التخويل التكتيكي. يرجى التأكد من الاتصال.'); } finally { setIsVerifying(false); } }; useEffect(() => { if (authKey) { verifyTacticalLicense(authKey); } else { setAuthStatus('unauthorized'); } }, []); const handleRevokeOrLock = () => { sessionStorage.removeItem('tactical_api_key'); setAuthKey(''); setAuthStatus('unauthorized'); setAuthInfo(null); setAuthError(''); }; 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 [showContours, setShowContours] = useState(true); 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); // 8. Isochrone / Emergency Reachability State (خارطة زمن الوصول والاستجابة) const [isochroneCenter, setIsochroneCenter] = useState<[number, number]>([31.9865, 35.8340]); // King Hussein Medical City const [isochroneProfile, setIsochroneProfile] = useState('emergency'); const [isochroneTimes, setIsochroneTimes] = useState([300, 600, 900]); const [isochroneData, setIsochroneData] = useState(null); const [isochroneLoading, setIsochroneLoading] = useState(false); const [selectedHubName, setSelectedHubName] = useState('المدينة الطبية الملكية (عمان)'); const [copiedIsochroneGeoJson, setCopiedIsochroneGeoJson] = useState(false); // 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 isochroneCenterRef = useRef(isochroneCenter); isochroneCenterRef.current = isochroneCenter; 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)); // 8. Isochrone / Response Reachability Hub if (mode === 'isochrone' && isochroneCenter) { updateMarker( 'isochrone-c', isochroneCenter, `🚑 ${selectedHubName.split(' ')[0]} (نقطة الانطلاق)`, '#10b981', (pos) => { setIsochroneCenter(pos); setSelectedHubName(`نقطة مخصصة (${pos[0].toFixed(4)}, ${pos[1].toFixed(4)})`); } ); } else { updateMarker('isochrone-c', null, '', '', () => {}); } }, [terrainCenter, terrainResult, showPeaksAndValleys, mode, losPointA, losPointB, viewshedCenter, gunPos, targetPos, mineStart, mineEnd, hlzCenter, isochroneCenter, selectedHubName]); // 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; } if (placement === 'isochrone-center') { setIsochroneCenter([clickedLat, clickedLng]); setSelectedHubName(`نقطة مخصصة (${clickedLat.toFixed(4)}, ${clickedLng.toFixed(4)})`); 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 === 'isochrone') { setIsochroneCenter([clickedLat, clickedLng]); setSelectedHubName(`نقطة مخصصة (${clickedLat.toFixed(4)}, ${clickedLng.toFixed(4)})`); } 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] } }); // Dynamic On-the-Fly Contours from Elevation DEM const demSource = getSharedDemSource(); 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', 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'], 'cliff', 0.38, 'urban', 0.20, 'hazard', 0.24, 'slope-sector', 0.14, 0.15 ] } }); initialMap.addLayer({ id: 'tactical-terrain-spatial-poly-line', type: 'line', source: 'tactical-terrain-spatial-src', filter: ['==', '$type', 'Polygon'], paint: { 'line-color': [ 'match', ['get', 'layerType'], 'cliff', '#ef4444', 'rgba(255, 255, 255, 0.10)' ], 'line-width': [ 'match', ['get', 'layerType'], 'cliff', 1.8, 0.4 ] } }); 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'], 'cliff', 3.5, 'road', 4.0, 'wadi', 3.5, 'ridge', 2.5, 2.5 ], 'line-dasharray': [ 'match', ['get', 'layerType'], 'cliff', ['literal', [2, 1]], 'ridge', ['literal', [3, 1.5]], 'road', ['literal', [1, 0]], 'wadi', ['literal', [1, 0]], ['literal', [1, 0]] ] } }); initialMap.addLayer({ id: 'tactical-terrain-spatial-points', type: 'circle', source: 'tactical-terrain-spatial-src', filter: ['==', '$type', 'Point'], paint: { 'circle-radius': 5, 'circle-color': ['coalesce', ['get', 'color'], '#38bdf8'], 'circle-stroke-width': 2, 'circle-stroke-color': '#ffffff' } }); initialMap.addLayer({ id: 'tactical-terrain-spatial-labels', type: 'symbol', source: 'tactical-terrain-spatial-src', filter: ['all', ['==', '$type', 'Point'], ['has', 'title']], layout: { 'text-field': ['get', 'title'], 'text-size': 12, 'text-font': ['Noto Sans Bold', 'Open Sans Bold'], 'text-anchor': 'bottom', 'text-offset': [0, -0.6], 'symbol-placement': 'point', 'text-allow-overlap': false }, paint: { 'text-color': '#ffffff', 'text-halo-color': '#0f172a', 'text-halo-width': 2.5 } }); // 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 } }); // 6. Isochrone Reachability Polygons Layer (خارطة زمن الاستجابة والوصول) initialMap.addSource('tactical-isochrone-src', { type: 'geojson', data: { type: 'FeatureCollection', features: [] } }); initialMap.addLayer({ id: 'tactical-isochrone-fill', type: 'fill', source: 'tactical-isochrone-src', paint: { 'fill-color': ['coalesce', ['get', 'color'], '#22c55e'], 'fill-opacity': 0.32 } }); initialMap.addLayer({ id: 'tactical-isochrone-line', type: 'line', source: 'tactical-isochrone-src', paint: { 'line-color': ['coalesce', ['get', 'color'], '#22c55e'], 'line-width': 2.5, 'line-opacity': 0.95 } }); }); 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)); }; // 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; 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' || f.properties.layerType === 'ridge') 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' || f.properties.layerType === 'cliff') 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 keyToSend = authKey || (import.meta as any).env.VITE_API_KEY || ''; const res = await fetch(`${apiUrl}/tactical/artillery-fire-mission`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': keyToSend }, 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 keyToSend = authKey || (import.meta as any).env.VITE_API_KEY || ''; const res = await fetch(`${apiUrl}/tactical/hlz-assessment?lat=${hlzCenter[0]}&lng=${hlzCenter[1]}&radius=4000`, { headers: { 'x-api-key': keyToSend } }); const data = await res.json(); setHlzResult(data); } catch (e) { console.error('HLZ error:', e); } finally { setHlzLoading(false); } }; // Helper for High-Precision Tactical Isochrone Polygons (نطاقات زمن الوصول والاستجابة) const generateLocalIsochrone = (lat: number, lng: number, timeBuckets: number[], profile: string) => { const avgSpeedKmh = profile === 'emergency' ? 60 : profile === 'patrol' ? 50 : 38; const colors = ['#22c55e', '#eab308', '#ef4444', '#8b5cf6']; const tiers = timeBuckets.map((seconds, idx) => { const minutes = Math.round(seconds / 60); const effectiveRadiusM = (avgSpeedKmh * 1000 / 3600) * seconds * 0.70; const numPoints = 36; const ringCoords: [number, number][] = []; for (let i = 0; i < numPoints; i++) { const angle = (i * 2 * Math.PI) / numPoints; const roadCorridor = 1 + 0.14 * Math.sin(angle * 4) + 0.08 * Math.cos(angle * 2); const r = effectiveRadiusM * roadCorridor; const dLat = (r / 6371000) * (180 / Math.PI) * Math.cos(angle); const dLng = (r / (6371000 * Math.cos((lat * Math.PI) / 180))) * (180 / Math.PI) * Math.sin(angle); ringCoords.push([Number((lng + dLng).toFixed(6)), Number((lat + dLat).toFixed(6))]); } ringCoords.push(ringCoords[0]); const tierColor = colors[idx % colors.length]; const areaKm2 = Math.round(Math.PI * Math.pow(effectiveRadiusM / 1000, 2) * 10) / 10; return { timeSeconds: seconds, timeMinutes: minutes, label: `${minutes} دقائق استجابة`, color: tierColor, estimatedAreaKm2: areaKm2, polygon: { type: 'Feature', properties: { timeMinutes: minutes, timeSeconds: seconds, color: tierColor, label: `${minutes} دقائق` }, geometry: { type: 'Polygon', coordinates: [ringCoords] } } }; }); return { center: { lat, lng }, vehicleProfile: profile, tiers, featureCollection: { type: 'FeatureCollection', features: tiers.map(t => t.polygon) } }; }; // Synchronize Isochrone Data to Map useEffect(() => { if (!map.current || !map.current.getSource('tactical-isochrone-src')) return; if (mode === 'isochrone' && isochroneData?.featureCollection) { (map.current.getSource('tactical-isochrone-src') as maplibregl.GeoJSONSource).setData(isochroneData.featureCollection); } else { (map.current.getSource('tactical-isochrone-src') as maplibregl.GeoJSONSource).setData({ type: 'FeatureCollection', features: [] }); } }, [mode, isochroneData]); // Run Isochrone Calculation const runIsochroneCalculation = async (center = isochroneCenter, profile = isochroneProfile, times = isochroneTimes) => { if (!center) return; setIsochroneLoading(true); try { const apiUrl = (import.meta as any).env.VITE_API_URL || '/api'; const keyToSend = authKey || (import.meta as any).env.VITE_API_KEY || ''; const timesParam = times.join(','); const res = await fetch( `${apiUrl}/tactical/isochrone?lat=${center[0]}&lng=${center[1]}&timeBuckets=${timesParam}&profile=${profile}`, { headers: { 'x-api-key': keyToSend } } ); if (res.ok) { const data = await res.json(); setIsochroneData(data); } else { const fallback = generateLocalIsochrone(center[0], center[1], times, profile); setIsochroneData(fallback); } } catch (e) { console.warn('Isochrone API fallback to local GIS engine:', e); const fallback = generateLocalIsochrone(center[0], center[1], times, profile); setIsochroneData(fallback); } finally { setIsochroneLoading(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))]); setIsochroneCenter([Number(lat.toFixed(5)), Number(lng.toFixed(5))]); setSelectedHubName(`قطاع ${sector.name}`); 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: 'isochrone', label: 'زمن الاستجابة (Isochrone)', 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 & Clearance Status */}
LAT: {cursorPos.lat}
LNG: {cursorPos.lng}
ELEV: {cursorPos.elev}m
{authStatus === 'authorized' && (
تصريح: {authInfo?.tenantName || 'Enterprise'}
)}
{/* Quick Sector Presets Bar */}
الانتقال السريع لقطاعات العمليات: {REGIONAL_SECTORS.map((s, idx) => ( ))}
{/* Main Tactical Workspace */}
{/* Left Side Tactical Control Deck */} {/* Tactical Map Container */}
{/* Tactical License Authorization Modal Overlay */} {authStatus !== 'authorized' && (
{/* Top Tactical Glowing Badge */}
{authStatus === 'validating' ? ( ) : ( )}

منظومة القيادة والسيطرة والتحليل التكتيكي

بوابة التخويل الأمني العسكري — يتطلب استخدام أدوات التحليل الطبوغرافي ورمايات المدفعية وحقول الألغام مفتاح تصريح معتمد (Enterprise Key).
{authError && (
{authError}
)}
{ e.preventDefault(); verifyTacticalLicense(inputKey); }} style={{ width: '100%', display: 'flex', flexDirection: 'column', gap: 14 }} >
setInputKey(e.target.value)} placeholder="in_xxxxxxxxxxxxxxxxxxxxxxxxxxxx" autoFocus style={{ width: '100%', background: 'rgba(15, 23, 42, 0.8)', border: '1px solid rgba(255, 255, 255, 0.2)', borderRadius: 10, padding: '12px 14px', color: '#ffffff', fontSize: '0.9rem', fontFamily: 'monospace', outline: 'none', boxSizing: 'border-box' }} />
منصة الخرائط والأنظمة الذكية ← العودة إلى الخريطة العامة
)}
); };