feat(tactical): add comprehensive Terrain Study (دراسة الأرض) with natural/manmade obstacles and OAKOC intelligence report
This commit is contained in:
@@ -23,20 +23,28 @@ import {
|
||||
MapPin,
|
||||
Target,
|
||||
LocateFixed,
|
||||
Move
|
||||
Move,
|
||||
Building,
|
||||
TreePine,
|
||||
Printer,
|
||||
FileText,
|
||||
Compass,
|
||||
Check
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
calculateLineOfSight,
|
||||
calculateRadialViewshed,
|
||||
calculateMinefieldAnalysis,
|
||||
calculateTerrainStudy,
|
||||
LineOfSightResult,
|
||||
Viewshed360Result,
|
||||
MinefieldAnalysisResult,
|
||||
TerrainStudyResult,
|
||||
calculateDistance,
|
||||
calculateAzimuth
|
||||
} from '../utils/elevationService';
|
||||
|
||||
type TacticalMode = 'los' | 'viewshed' | 'artillery' | 'minefield' | 'hlz' | 'symbols' | 'study';
|
||||
type TacticalMode = 'terrain' | 'los' | 'viewshed' | 'artillery' | 'minefield' | 'hlz' | 'symbols' | 'study';
|
||||
|
||||
interface TacticalSymbolItem {
|
||||
id: string;
|
||||
@@ -67,11 +75,18 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
const mapContainer = useRef<HTMLDivElement>(null);
|
||||
const map = useRef<maplibregl.Map | null>(null);
|
||||
|
||||
const [mode, setMode] = useState<TacticalMode>('los');
|
||||
const [mode, setMode] = useState<TacticalMode>('terrain');
|
||||
const [cursorPos, setCursorPos] = useState({ lat: 31.95, lng: 35.93, elev: 850 });
|
||||
const [activePlacement, setActivePlacement] = useState<string | null>(null);
|
||||
|
||||
// Line of Sight State
|
||||
// 1. Terrain Tactical Study State (دراسة الأرض)
|
||||
const [terrainCenter, setTerrainCenter] = useState<[number, number] | null>([31.9539, 35.9106]);
|
||||
const [terrainRadius, setTerrainRadius] = useState<number>(4000);
|
||||
const [terrainResult, setTerrainResult] = useState<TerrainStudyResult | null>(null);
|
||||
const [terrainLoading, setTerrainLoading] = useState<boolean>(false);
|
||||
const [copiedReport, setCopiedReport] = useState<boolean>(false);
|
||||
|
||||
// 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<number>(2);
|
||||
@@ -79,14 +94,14 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
const [losResult, setLosResult] = useState<LineOfSightResult | null>(null);
|
||||
const [losLoading, setLosLoading] = useState<boolean>(false);
|
||||
|
||||
// Viewshed State
|
||||
// 3. Viewshed State
|
||||
const [viewshedCenter, setViewshedCenter] = useState<[number, number] | null>([31.9800, 35.8800]);
|
||||
const [viewshedRadius, setViewshedRadius] = useState<number>(5000);
|
||||
const [viewshedHeight, setViewshedHeight] = useState<number>(10);
|
||||
const [viewshedResult, setViewshedResult] = useState<Viewshed360Result | null>(null);
|
||||
const [viewshedLoading, setViewshedLoading] = useState<boolean>(false);
|
||||
|
||||
// Artillery Fire Mission State
|
||||
// 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<string>('155mm Howitzer (M109)');
|
||||
@@ -94,19 +109,19 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
const [artilleryResult, setArtilleryResult] = useState<any | null>(null);
|
||||
const [artilleryLoading, setArtilleryLoading] = useState<boolean>(false);
|
||||
|
||||
// Minefield Analysis State
|
||||
// 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<MinefieldAnalysisResult | null>(null);
|
||||
const [mineLoading, setMineLoading] = useState<boolean>(false);
|
||||
|
||||
// HLZ State
|
||||
// 6. HLZ State
|
||||
const [hlzCenter, setHlzCenter] = useState<[number, number] | null>([31.9600, 35.9000]);
|
||||
const [hlzResult, setHlzResult] = useState<any | null>(null);
|
||||
const [hlzLoading, setHlzLoading] = useState<boolean>(false);
|
||||
|
||||
// Tactical Symbols State
|
||||
// 7. Tactical Symbols State
|
||||
const [symbols, setSymbols] = useState<TacticalSymbolItem[]>(DEFAULT_SYMBOLS);
|
||||
const [selectedSymbolType, setSelectedSymbolType] = useState<string>('friendly');
|
||||
const [newSymbolName, setNewSymbolName] = useState<string>('');
|
||||
@@ -120,6 +135,9 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
const activePlacementRef = useRef(activePlacement);
|
||||
activePlacementRef.current = activePlacement;
|
||||
|
||||
const terrainCenterRef = useRef(terrainCenter);
|
||||
terrainCenterRef.current = terrainCenter;
|
||||
|
||||
const losPointARef = useRef(losPointA);
|
||||
losPointARef.current = losPointA;
|
||||
|
||||
@@ -203,6 +221,8 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
// Sync Draggable Markers to Map
|
||||
useEffect(() => {
|
||||
if (!map.current) return;
|
||||
// Terrain Study Center
|
||||
updateMarker('terrain-c', terrainCenter, '⛰️ مركز دراسة الأرض', '#6366f1', (pos) => setTerrainCenter(pos));
|
||||
// LOS
|
||||
updateMarker('los-a', losPointA, '📍 راصد A', '#0284c7', (pos) => setLosPointA(pos));
|
||||
updateMarker('los-b', losPointB, '🎯 هدف B', '#d97706', (pos) => setLosPointB(pos));
|
||||
@@ -216,7 +236,7 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
updateMarker('mine-e', mineEnd, '⛔ نهاية حقل الألغام', '#991b1b', (pos) => setMineEnd(pos));
|
||||
// HLZ
|
||||
updateMarker('hlz-c', hlzCenter, '🚁 مركز HLZ', '#059669', (pos) => setHlzCenter(pos));
|
||||
}, [losPointA, losPointB, viewshedCenter, gunPos, targetPos, mineStart, mineEnd, hlzCenter]);
|
||||
}, [terrainCenter, losPointA, losPointB, viewshedCenter, gunPos, targetPos, mineStart, mineEnd, hlzCenter]);
|
||||
|
||||
// Map Initialization
|
||||
useEffect(() => {
|
||||
@@ -256,6 +276,11 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
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);
|
||||
@@ -298,7 +323,9 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
}
|
||||
|
||||
// 2. Default Context-Aware Click by Active Mode
|
||||
if (currentMode === 'los') {
|
||||
if (currentMode === 'terrain') {
|
||||
setTerrainCenter([clickedLat, clickedLng]);
|
||||
} else if (currentMode === 'los') {
|
||||
if (!losPointARef.current) {
|
||||
setLosPointA([clickedLat, clickedLng]);
|
||||
} else if (!losPointBRef.current) {
|
||||
@@ -352,6 +379,33 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
});
|
||||
|
||||
initialMap.on('load', () => {
|
||||
// 0. Terrain Study Circle Layer
|
||||
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.15
|
||||
}
|
||||
});
|
||||
|
||||
initialMap.addLayer({
|
||||
id: 'tactical-terrain-line',
|
||||
type: 'line',
|
||||
source: 'tactical-terrain-src',
|
||||
paint: {
|
||||
'line-color': '#818cf8',
|
||||
'line-width': 2.5,
|
||||
'line-dasharray': [3, 2]
|
||||
}
|
||||
});
|
||||
|
||||
// 1. Line of Sight Layer
|
||||
initialMap.addSource('tactical-los-src', {
|
||||
type: 'geojson',
|
||||
@@ -493,6 +547,50 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Run Terrain Tactical Study
|
||||
const runTerrainStudy = () => {
|
||||
if (!terrainCenter) return;
|
||||
setTerrainLoading(true);
|
||||
calculateTerrainStudy(terrainCenter[0], terrainCenter[1], terrainRadius)
|
||||
.then((res) => {
|
||||
setTerrainResult(res);
|
||||
setTerrainLoading(false);
|
||||
|
||||
// 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));
|
||||
};
|
||||
|
||||
// Auto-run initial terrain study on mount
|
||||
useEffect(() => {
|
||||
runTerrainStudy();
|
||||
}, [terrainCenter, terrainRadius]);
|
||||
|
||||
// Update Line of Sight Calculations & Map Layer
|
||||
useEffect(() => {
|
||||
if (!losPointA || !losPointB) {
|
||||
@@ -700,10 +798,10 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
essential: true
|
||||
});
|
||||
|
||||
// Dynamically re-anchor operational points into this region so the user is not stuck with old coordinates
|
||||
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))]);
|
||||
@@ -714,6 +812,38 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
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.minElevation}م | الفارق التضاريسي ${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 (
|
||||
<div style={{
|
||||
width: '100vw',
|
||||
@@ -767,6 +897,7 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
{/* Tactical Modes Selector */}
|
||||
<div style={{ display: 'flex', gap: 4, background: 'rgba(255, 255, 255, 0.05)', padding: 3, borderRadius: 10, border: '1px solid rgba(255,255,255,0.1)' }}>
|
||||
{[
|
||||
{ id: 'terrain', label: 'دراسة الأرض', icon: <Mountain size={13} /> },
|
||||
{ id: 'los', label: 'تبادل الرؤية (LOS)', icon: <Eye size={13} /> },
|
||||
{ id: 'viewshed', label: 'كشف 360°', icon: <Radio size={13} /> },
|
||||
{ id: 'artillery', label: 'موقع المدفعية', icon: <Flame size={13} /> },
|
||||
@@ -853,7 +984,7 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
|
||||
{/* Left Side Tactical Control Deck */}
|
||||
<aside style={{
|
||||
width: 410,
|
||||
width: 440,
|
||||
background: 'rgba(15, 23, 42, 0.96)',
|
||||
backdropFilter: 'blur(20px)',
|
||||
borderLeft: '1px solid rgba(255, 255, 255, 0.12)',
|
||||
@@ -909,7 +1040,273 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
<span>يمكنك <strong>سحب أي علامة مباشرة بالفأرة</strong> على الخريطة لتغيير موقعها وحساب النتائج فوراً.</span>
|
||||
</div>
|
||||
|
||||
{/* ========================================================================= */}
|
||||
{/* MODE 0: TERRAIN TACTICAL STUDY (دراسة الأرض والموانع والعمران) */}
|
||||
{/* ========================================================================= */}
|
||||
{mode === 'terrain' && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
<div style={{ paddingBottom: 8, borderBottom: '1px solid rgba(255,255,255,0.08)', display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
|
||||
<div>
|
||||
<h3 style={{ margin: 0, fontSize: '1.05rem', fontWeight: 800, color: '#818cf8', display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<Mountain size={18} /> دراسة الأرض والاستخبارات الجغرافية
|
||||
</h3>
|
||||
<p style={{ margin: '4px 0 0 0', fontSize: '0.75rem', color: '#94a3b8' }}>
|
||||
تحليل طبيعة الأرض، الموانع الطبيعية والصناعية، والمناطق المأهولة
|
||||
</p>
|
||||
</div>
|
||||
{terrainResult && (
|
||||
<button
|
||||
onClick={handleCopyReport}
|
||||
style={{
|
||||
background: copiedReport ? 'rgba(34, 197, 94, 0.2)' : 'rgba(255,255,255,0.08)',
|
||||
border: copiedReport ? '1px solid #22c55e' : '1px solid rgba(255,255,255,0.15)',
|
||||
color: copiedReport ? '#4ade80' : '#cbd5e1',
|
||||
padding: '4px 8px',
|
||||
borderRadius: 6,
|
||||
fontSize: '0.7rem',
|
||||
fontWeight: 700,
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 4
|
||||
}}
|
||||
>
|
||||
{copiedReport ? <Check size={12} /> : <FileText size={12} />}
|
||||
<span>{copiedReport ? 'تم نسخ التقرير' : 'نسخ التقرير'}</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Terrain Center Coordinate Card */}
|
||||
<div style={{ background: 'rgba(255,255,255,0.04)', padding: 10, borderRadius: 8, border: '1px solid rgba(99, 102, 241, 0.25)' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 6 }}>
|
||||
<span style={{ fontSize: '0.78rem', fontWeight: 800, color: '#a5b4fc' }}>📍 مركز قطاع دراسة الأرض:</span>
|
||||
<button
|
||||
onClick={() => setActivePlacement('terrain-center')}
|
||||
style={{
|
||||
background: activePlacement === 'terrain-center' ? '#4f46e5' : 'rgba(99, 102, 241, 0.15)',
|
||||
border: '1px solid #6366f1',
|
||||
color: '#ffffff',
|
||||
padding: '3px 8px',
|
||||
borderRadius: 6,
|
||||
fontSize: '0.7rem',
|
||||
fontWeight: 700,
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
{activePlacement === 'terrain-center' ? '🎯 انقر على الخريطة...' : '📍 التقاط من الخريطة'}
|
||||
</button>
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 6 }}>
|
||||
<div>
|
||||
<label style={{ fontSize: '0.65rem', color: '#94a3b8' }}>خط العرض (Lat):</label>
|
||||
<input
|
||||
type="number"
|
||||
step="0.0001"
|
||||
value={terrainCenter ? terrainCenter[0] : ''}
|
||||
onChange={e => setTerrainCenter([Number(e.target.value), terrainCenter ? terrainCenter[1] : 35.9])}
|
||||
style={{ width: '100%', background: '#0f172a', border: '1px solid rgba(255,255,255,0.2)', color: '#fff', padding: '5px 8px', borderRadius: 6, fontSize: '0.78rem' }}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label style={{ fontSize: '0.65rem', color: '#94a3b8' }}>خط الطول (Lng):</label>
|
||||
<input
|
||||
type="number"
|
||||
step="0.0001"
|
||||
value={terrainCenter ? terrainCenter[1] : ''}
|
||||
onChange={e => setTerrainCenter([terrainCenter ? terrainCenter[0] : 31.9, Number(e.target.value)])}
|
||||
style={{ width: '100%', background: '#0f172a', border: '1px solid rgba(255,255,255,0.2)', color: '#fff', padding: '5px 8px', borderRadius: 6, fontSize: '0.78rem' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Radius Slider */}
|
||||
<div>
|
||||
<label style={{ fontSize: '0.72rem', color: '#94a3b8', display: 'flex', justifyContent: 'space-between', marginBottom: 4 }}>
|
||||
<span>نطاق دراسة القطاع:</span>
|
||||
<strong style={{ color: '#a5b4fc' }}>{(terrainRadius / 1000).toFixed(1)} كم (المساحة: {(Math.PI * Math.pow(terrainRadius / 1000, 2)).toFixed(1)} كم²)</strong>
|
||||
</label>
|
||||
<input
|
||||
type="range"
|
||||
min={1000}
|
||||
max={12000}
|
||||
step={500}
|
||||
value={terrainRadius}
|
||||
onChange={e => setTerrainRadius(Number(e.target.value))}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={runTerrainStudy}
|
||||
disabled={terrainLoading}
|
||||
style={{
|
||||
background: 'linear-gradient(135deg, #4f46e5, #6366f1)',
|
||||
border: 'none',
|
||||
color: '#fff',
|
||||
padding: '9px',
|
||||
borderRadius: 8,
|
||||
fontSize: '0.82rem',
|
||||
fontWeight: 700,
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: 6
|
||||
}}
|
||||
>
|
||||
<Mountain size={15} />
|
||||
<span>{terrainLoading ? 'جاري تحليل التضاريس والموانع...' : 'تحديث دراسة الأرض والتقرير التكتيكي'}</span>
|
||||
</button>
|
||||
|
||||
{/* Terrain Intelligence Dossier Output */}
|
||||
{terrainResult && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 12, marginTop: 4 }}>
|
||||
|
||||
{/* Top Mobility & Classification Badge */}
|
||||
<div style={{
|
||||
background: terrainResult.mobilityStatus === 'GO'
|
||||
? 'rgba(34, 197, 94, 0.15)'
|
||||
: terrainResult.mobilityStatus === 'SLOW-GO'
|
||||
? 'rgba(234, 179, 8, 0.15)'
|
||||
: 'rgba(239, 68, 68, 0.15)',
|
||||
border: terrainResult.mobilityStatus === 'GO'
|
||||
? '1px solid #22c55e'
|
||||
: terrainResult.mobilityStatus === 'SLOW-GO'
|
||||
? '1px solid #eab308'
|
||||
: '1px solid #ef4444',
|
||||
padding: '10px 12px',
|
||||
borderRadius: 8,
|
||||
}}>
|
||||
<div style={{ fontSize: '0.72rem', color: '#94a3b8', marginBottom: 2 }}>{terrainResult.sectorName}</div>
|
||||
<div style={{ fontSize: '0.92rem', fontWeight: 800, color: '#f8fafc', marginBottom: 4 }}>
|
||||
{terrainResult.terrainClassificationAr}
|
||||
</div>
|
||||
<div style={{
|
||||
fontSize: '0.78rem',
|
||||
fontWeight: 700,
|
||||
color: terrainResult.mobilityStatus === 'GO' ? '#4ade80' : terrainResult.mobilityStatus === 'SLOW-GO' ? '#fde047' : '#f87171'
|
||||
}}>
|
||||
⚡ {terrainResult.mobilityStatusAr}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Elevation & Slope Metrics Grid */}
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 6 }}>
|
||||
<div style={{ background: 'rgba(255,255,255,0.04)', padding: 7, borderRadius: 6, textAlign: 'center' }}>
|
||||
<div style={{ fontSize: '0.62rem', color: '#94a3b8' }}>أعلى قمة</div>
|
||||
<div style={{ fontSize: '0.9rem', fontWeight: 800, color: '#38bdf8' }}>{terrainResult.maxElevation}م</div>
|
||||
</div>
|
||||
<div style={{ background: 'rgba(255,255,255,0.04)', padding: 7, borderRadius: 6, textAlign: 'center' }}>
|
||||
<div style={{ fontSize: '0.62rem', color: '#94a3b8' }}>أخفض نقطة</div>
|
||||
<div style={{ fontSize: '0.9rem', fontWeight: 800, color: '#38bdf8' }}>{terrainResult.minElevation}م</div>
|
||||
</div>
|
||||
<div style={{ background: 'rgba(255,255,255,0.04)', padding: 7, borderRadius: 6, textAlign: 'center' }}>
|
||||
<div style={{ fontSize: '0.62rem', color: '#94a3b8' }}>الفارق التضاريسي</div>
|
||||
<div style={{ fontSize: '0.9rem', fontWeight: 800, color: '#fb923c' }}>{terrainResult.reliefMeters}م</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Slope Distribution Bar */}
|
||||
<div style={{ background: 'rgba(255,255,255,0.04)', padding: 10, borderRadius: 8 }}>
|
||||
<div style={{ fontSize: '0.72rem', fontWeight: 700, color: '#cbd5e1', marginBottom: 6, display: 'flex', justifyContent: 'space-between' }}>
|
||||
<span>توزيع درجات الانحدار (Slope Analysis):</span>
|
||||
<span style={{ color: '#94a3b8' }}>متوسط: {terrainResult.avgSlopeDegrees}°</span>
|
||||
</div>
|
||||
<div style={{ height: 10, width: '100%', display: 'flex', borderRadius: 5, overflow: 'hidden', marginBottom: 6 }}>
|
||||
<div style={{ width: `${terrainResult.slopeDistribution.flatPercentage}%`, background: '#22c55e' }} title={`منبسط: ${terrainResult.slopeDistribution.flatPercentage}%`} />
|
||||
<div style={{ width: `${terrainResult.slopeDistribution.moderatePercentage}%`, background: '#eab308' }} title={`متوسط: ${terrainResult.slopeDistribution.moderatePercentage}%`} />
|
||||
<div style={{ width: `${terrainResult.slopeDistribution.steepPercentage}%`, background: '#ef4444' }} title={`شديد الوعورة: ${terrainResult.slopeDistribution.steepPercentage}%`} />
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '0.68rem', color: '#94a3b8' }}>
|
||||
<span style={{ color: '#4ade80' }}>■ منبسط (<5°): {terrainResult.slopeDistribution.flatPercentage}%</span>
|
||||
<span style={{ color: '#facc15' }}>■ متوسط (5-15°): {terrainResult.slopeDistribution.moderatePercentage}%</span>
|
||||
<span style={{ color: '#f87171' }}>■ وعر (>15°): {terrainResult.slopeDistribution.steepPercentage}%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* SECTION 1: NATURAL OBSTACLES */}
|
||||
<div style={{ background: 'rgba(255,255,255,0.04)', padding: 10, borderRadius: 8, border: '1px solid rgba(239, 68, 68, 0.2)' }}>
|
||||
<div style={{ fontSize: '0.78rem', fontWeight: 800, color: '#f87171', marginBottom: 6, display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<AlertOctagon size={14} /> 1. الموانع الطبيعية (Natural Obstacles):
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||
{terrainResult.naturalObstacles.map((obs, idx) => (
|
||||
<div key={idx} style={{ background: 'rgba(0,0,0,0.2)', padding: '6px 8px', borderRadius: 6, fontSize: '0.72rem' }}>
|
||||
<div style={{ fontWeight: 700, color: '#fca5a5' }}>• {obs.name} ({obs.type})</div>
|
||||
<div style={{ color: '#cbd5e1', marginTop: 2 }}>{obs.description}</div>
|
||||
<div style={{ color: '#94a3b8', fontSize: '0.68rem', marginTop: 2 }}><strong>الأثر التكتيكي:</strong> {obs.impact}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* SECTION 2: MAN-MADE OBSTACLES & INFRASTRUCTURE */}
|
||||
<div style={{ background: 'rgba(255,255,255,0.04)', padding: 10, borderRadius: 8, border: '1px solid rgba(245, 158, 11, 0.2)' }}>
|
||||
<div style={{ fontSize: '0.78rem', fontWeight: 800, color: '#f59e0b', marginBottom: 6, display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<Building size={14} /> 2. الموانع الصناعية والبنية التحتية (Man-Made Features):
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||
{terrainResult.manMadeObstacles.map((obs, idx) => (
|
||||
<div key={idx} style={{ background: 'rgba(0,0,0,0.2)', padding: '6px 8px', borderRadius: 6, fontSize: '0.72rem' }}>
|
||||
<div style={{ fontWeight: 700, color: '#fde047' }}>• {obs.name}</div>
|
||||
<div style={{ color: '#cbd5e1', marginTop: 2 }}>{obs.description}</div>
|
||||
<div style={{ color: '#94a3b8', fontSize: '0.68rem', marginTop: 2 }}><strong>ملاحظة تكتيكية:</strong> {obs.tacticalNote}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* SECTION 3: URBAN AREAS & DEMOGRAPHICS */}
|
||||
<div style={{ background: 'rgba(255,255,255,0.04)', padding: 10, borderRadius: 8, border: '1px solid rgba(56, 189, 248, 0.2)' }}>
|
||||
<div style={{ fontSize: '0.78rem', fontWeight: 800, color: '#38bdf8', marginBottom: 6, display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<Layers size={14} /> 3. المناطق المأهولة بالسكان والعمران:
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4, fontSize: '0.72rem', color: '#cbd5e1' }}>
|
||||
<div><strong>الكثافة السكانية:</strong> {terrainResult.urbanAndDemographics.densityAr}</div>
|
||||
<div><strong>التجمعات والقرى الرئيسية:</strong> {terrainResult.urbanAndDemographics.settlements.join('، ')}</div>
|
||||
<div><strong>قتال المناطق المبنية (MOUT):</strong> {terrainResult.urbanAndDemographics.moutComplexity}</div>
|
||||
<div><strong>مخاطر الأضرار الجانبية:</strong> {terrainResult.urbanAndDemographics.collateralDamageRisk}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* SECTION 4: SOIL & CONCEALMENT */}
|
||||
<div style={{ background: 'rgba(255,255,255,0.04)', padding: 10, borderRadius: 8, border: '1px solid rgba(16, 185, 129, 0.2)' }}>
|
||||
<div style={{ fontSize: '0.78rem', fontWeight: 800, color: '#10b981', marginBottom: 6, display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<TreePine size={14} /> 4. التربة وإمكانات الإخفاء والتمويه:
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4, fontSize: '0.72rem', color: '#cbd5e1' }}>
|
||||
<div><strong>نوع التربة:</strong> {terrainResult.landCoverAndCover.soilType}</div>
|
||||
<div><strong>صلاحية السير للآليات:</strong> {terrainResult.landCoverAndCover.trafficability}</div>
|
||||
<div><strong>الإخفاء من الرصد الأرضي:</strong> {terrainResult.landCoverAndCover.concealmentRating}</div>
|
||||
<div><strong>الانكشاف للرصد الجوي:</strong> {terrainResult.landCoverAndCover.airObservationExposure}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* SECTION 5: COMMANDER'S TACTICAL RECOMMENDATIONS (OAKOC) */}
|
||||
<div style={{ background: 'linear-gradient(135deg, rgba(79, 70, 229, 0.15), rgba(99, 102, 241, 0.15))', padding: 12, borderRadius: 8, border: '1px solid #6366f1' }}>
|
||||
<div style={{ fontSize: '0.82rem', fontWeight: 800, color: '#a5b4fc', marginBottom: 8, display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<Award size={16} /> 5. التقييم التكتيكي للقائد والتوصيات الميدانية:
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6, fontSize: '0.72rem', color: '#f8fafc', lineHeight: 1.45 }}>
|
||||
{terrainResult.tacticalRecommendations.map((rec, i) => (
|
||||
<div key={i} style={{ display: 'flex', gap: 6 }}>
|
||||
<span style={{ color: '#818cf8', fontWeight: 700 }}>{i + 1}.</span>
|
||||
<span>{rec}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ========================================================================= */}
|
||||
{/* MODE 1: LINE OF SIGHT */}
|
||||
{/* ========================================================================= */}
|
||||
{mode === 'los' && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
<div style={{ paddingBottom: 8, borderBottom: '1px solid rgba(255,255,255,0.08)' }}>
|
||||
@@ -1080,7 +1477,9 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ========================================================================= */}
|
||||
{/* MODE 2: 360 VIEWSHED */}
|
||||
{/* ========================================================================= */}
|
||||
{mode === 'viewshed' && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
<div style={{ paddingBottom: 8, borderBottom: '1px solid rgba(255,255,255,0.08)' }}>
|
||||
@@ -1198,7 +1597,9 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ========================================================================= */}
|
||||
{/* MODE 3: ARTILLERY FIRE MISSION */}
|
||||
{/* ========================================================================= */}
|
||||
{mode === 'artillery' && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
<div style={{ paddingBottom: 8, borderBottom: '1px solid rgba(255,255,255,0.08)' }}>
|
||||
@@ -1366,7 +1767,9 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ========================================================================= */}
|
||||
{/* MODE 4: MINEFIELD ANALYSIS */}
|
||||
{/* ========================================================================= */}
|
||||
{mode === 'minefield' && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
<div style={{ paddingBottom: 8, borderBottom: '1px solid rgba(255,255,255,0.08)' }}>
|
||||
@@ -1543,7 +1946,9 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ========================================================================= */}
|
||||
{/* MODE 5: HLZ ASSESSMENT */}
|
||||
{/* ========================================================================= */}
|
||||
{mode === 'hlz' && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
<div style={{ paddingBottom: 8, borderBottom: '1px solid rgba(255,255,255,0.08)' }}>
|
||||
@@ -1637,7 +2042,9 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ========================================================================= */}
|
||||
{/* MODE 6: TACTICAL SYMBOLS & DEPLOYMENT */}
|
||||
{/* ========================================================================= */}
|
||||
{mode === 'symbols' && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
<div style={{ paddingBottom: 8, borderBottom: '1px solid rgba(255,255,255,0.08)' }}>
|
||||
@@ -1787,7 +2194,9 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ========================================================================= */}
|
||||
{/* MODE 7: TACTICAL FEASIBILITY STUDY & ORGANIZATION REPORT */}
|
||||
{/* ========================================================================= */}
|
||||
{mode === 'study' && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
<div style={{ paddingBottom: 8, borderBottom: '1px solid rgba(255,255,255,0.08)' }}>
|
||||
|
||||
@@ -576,3 +576,276 @@ export async function calculateMinefieldAnalysis(
|
||||
};
|
||||
}
|
||||
|
||||
export interface TerrainStudyResult {
|
||||
centerLat: number;
|
||||
centerLng: number;
|
||||
radiusMeters: number;
|
||||
areaKm2: number;
|
||||
sectorName: string;
|
||||
centerElevation: number;
|
||||
minElevation: number;
|
||||
maxElevation: number;
|
||||
reliefMeters: number;
|
||||
avgSlopeDegrees: number;
|
||||
maxSlopeDegrees: number;
|
||||
slopeDistribution: {
|
||||
flatPercentage: number; // < 5 deg (Go)
|
||||
moderatePercentage: number; // 5-15 deg (Slow-Go)
|
||||
steepPercentage: number; // > 15 deg (No-Go / Obstacle)
|
||||
};
|
||||
terrainClassification: string;
|
||||
terrainClassificationAr: string;
|
||||
mobilityStatus: 'GO' | 'SLOW-GO' | 'NO-GO';
|
||||
mobilityStatusAr: string;
|
||||
naturalObstacles: Array<{
|
||||
name: string;
|
||||
type: string;
|
||||
description: string;
|
||||
impact: string;
|
||||
}>;
|
||||
manMadeObstacles: Array<{
|
||||
name: string;
|
||||
type: string;
|
||||
description: string;
|
||||
tacticalNote: string;
|
||||
}>;
|
||||
urbanAndDemographics: {
|
||||
density: 'HIGH' | 'MEDIUM' | 'LOW' | 'SPARSE';
|
||||
densityAr: string;
|
||||
settlements: string[];
|
||||
moutComplexity: string;
|
||||
collateralDamageRisk: string;
|
||||
};
|
||||
landCoverAndCover: {
|
||||
soilType: string;
|
||||
trafficability: string;
|
||||
concealmentRating: string;
|
||||
airObservationExposure: string;
|
||||
};
|
||||
oakocAssessment: {
|
||||
obstacles: string;
|
||||
avenuesOfApproach: string;
|
||||
keyTerrain: string;
|
||||
observationAndFieldsOfFire: string;
|
||||
coverAndConcealment: string;
|
||||
};
|
||||
tacticalRecommendations: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Military Tactical Terrain Intelligence & Environmental Study (OAKOC Doctrine)
|
||||
* دراسة الأرض الشاملة: التضاريس، الموانع الطبيعية والصناعية، والمناطق المأهولة
|
||||
*/
|
||||
export async function calculateTerrainStudy(
|
||||
centerLat: number,
|
||||
centerLng: number,
|
||||
radiusMeters: number = 3000
|
||||
): Promise<TerrainStudyResult> {
|
||||
const centerElev = await sampleElevationAt(centerLat, centerLng);
|
||||
const areaKm2 = Math.round(Math.PI * Math.pow(radiusMeters / 1000, 2) * 10) / 10;
|
||||
|
||||
// Multi-point radial sampling across the sector
|
||||
const samples = 24;
|
||||
const elevations: number[] = [centerElev];
|
||||
const slopes: number[] = [];
|
||||
|
||||
for (let i = 0; i < samples; i++) {
|
||||
const angleRad = (i / samples) * 2 * Math.PI;
|
||||
const dist = radiusMeters * (0.3 + (i % 3) * 0.35);
|
||||
const R_earth = 6371000;
|
||||
const dLat = (dist / R_earth) * (180 / Math.PI) * Math.cos(angleRad);
|
||||
const dLng = (dist / (R_earth * Math.cos((centerLat * Math.PI) / 180))) * (180 / Math.PI) * Math.sin(angleRad);
|
||||
|
||||
const sLat = centerLat + dLat;
|
||||
const sLng = centerLng + dLng;
|
||||
const el = await sampleElevationAt(sLat, sLng);
|
||||
elevations.push(el);
|
||||
|
||||
const slopeDeg = Math.atan(Math.abs(el - centerElev) / dist) * (180 / Math.PI);
|
||||
slopes.push(slopeDeg);
|
||||
}
|
||||
|
||||
const minElev = Math.min(...elevations);
|
||||
const maxElev = Math.max(...elevations);
|
||||
const relief = maxElev - minElev;
|
||||
|
||||
const avgSlope = Math.round((slopes.reduce((a, b) => a + b, 0) / slopes.length) * 10) / 10;
|
||||
const maxSlope = Math.round(Math.max(...slopes) * 10) / 10;
|
||||
|
||||
const flatCount = slopes.filter(s => s < 5).length;
|
||||
const modCount = slopes.filter(s => s >= 5 && s <= 15).length;
|
||||
const steepCount = slopes.filter(s => s > 15).length;
|
||||
|
||||
const flatPct = Math.round((flatCount / slopes.length) * 100);
|
||||
const modPct = Math.round((modCount / slopes.length) * 100);
|
||||
const steepPct = Math.max(0, 100 - flatPct - modPct);
|
||||
|
||||
// Sector Geolocation and Regional Characteristics
|
||||
let sectorName = 'قطاع عمليات ميداني';
|
||||
let terrainClassificationAr = 'هضاب وتلال متوسطة الوعورة';
|
||||
let terrainClassification = 'Rolling Hills & Ridges';
|
||||
let urbanDensity: 'HIGH' | 'MEDIUM' | 'LOW' | 'SPARSE' = 'LOW';
|
||||
let urbanDensityAr = 'كثافة منخفضة / ريفية';
|
||||
let settlements: string[] = ['تجمعات سكنية متفرقة', 'مزارع ومنشآت ريفية'];
|
||||
let soilType = 'تربة طينية صخرية جافة';
|
||||
let trafficability = 'جيدة للمركبات المدولبة والمجنزرة مع الحذر من الانزلاقات في المنحدرات';
|
||||
|
||||
if (centerLat > 32.3 && centerLng < 36.0) {
|
||||
sectorName = 'قاطع الشمال / حوض اليرموك وإربد';
|
||||
terrainClassificationAr = 'مرتفعات جبلية تتخللها أودية سحيقة وخوانق مائية';
|
||||
terrainClassification = 'Highland Plateaus with Deep Gorges';
|
||||
urbanDensity = 'MEDIUM';
|
||||
urbanDensityAr = 'كثافة متوسطة (قرى وضواحي ممتدة)';
|
||||
settlements = ['بلدات وضواحي إربد', 'قرى حوض اليرموك', 'مزارع ومحميات وادي الشلالة'];
|
||||
soilType = 'تربة زراعية خصبة وصخور كلسية';
|
||||
trafficability = 'صعبة على حواف الأودية - تتطلب محاور طرق رئيسية';
|
||||
} else if (centerLat >= 31.8 && centerLat <= 32.2 && centerLng >= 35.8 && centerLng <= 36.1) {
|
||||
sectorName = 'قاطع الوسط / إقليم العاصمة عمان';
|
||||
terrainClassificationAr = 'هضاب وكتل جبلية حضرية مكتظة بالعمران';
|
||||
terrainClassification = 'Urbanized Highland Terrain';
|
||||
urbanDensity = 'HIGH';
|
||||
urbanDensityAr = 'كثافة عالية (بيئة قتال مباني MOUT)';
|
||||
settlements = ['أحياء ومجمعات العاصمة عمان', 'المناطق التجارية والتقاطعات المرورية', 'مناطق صناعية متقدمة'];
|
||||
soilType = 'أرض معبدة وأرصفة إسمنتية مع تربة صخرية';
|
||||
trafficability = 'ممتازة على شبكة الطرق ولكنها مقيدة بالاختناقات المرورية والمباني';
|
||||
} else if (centerLng > 36.1) {
|
||||
sectorName = 'قاطع البادية والشرق / المفرق والصفاوي';
|
||||
terrainClassificationAr = 'سهول صحراوية مفتوحة وأراضي حرات بازلتية';
|
||||
terrainClassification = 'Open Desert Steppe & Basalt Harrah';
|
||||
urbanDensity = 'SPARSE';
|
||||
urbanDensityAr = 'شبه خالية / تجمعات بدوية ومزارع متفرقة';
|
||||
settlements = ['تجمعات ريفية بدوية', 'مواقع عسكرية ونقاط حدودية', 'مزارع صحراوية'];
|
||||
soilType = 'رمال وحصى بازلتية صلبة';
|
||||
trafficability = 'حركة حرة مفتوحة للدروع والآليات الصحراوية (Cross-Country GO)';
|
||||
} else if (centerLat < 31.8 && centerLng < 35.7) {
|
||||
sectorName = 'قاطع الغور / المنخفض الصدعي والبحر الميت';
|
||||
terrainClassificationAr = 'أخفـض نقطة على سطح الأرض (جروف حادة ومنحدرات قاسية)';
|
||||
terrainClassification = 'Rift Valley Escarpment (-400m MSL)';
|
||||
urbanDensity = 'LOW';
|
||||
urbanDensityAr = 'كثافة منخفضة (مزارع الغور والمنتجعات)';
|
||||
settlements = ['مزارع الأغوار الجنوبية', 'مجمعات الفنادق ومصانع البوتاس', 'قرى غور الصافي'];
|
||||
soilType = 'طمي نهري وسبخات ملحية وصخور رسوبية';
|
||||
trafficability = 'حذرة جداً بجانب السبخات - ممتازة على طريق البحر الميت الرئيسي';
|
||||
} else if (centerLat < 31.5 && centerLat > 30.0) {
|
||||
sectorName = 'قاطع الجنوب الجبلي / مرتفعات الكرك والشوبك';
|
||||
terrainClassificationAr = 'سلاسل جبلية شاهقة وتلال متصلة ذات إشراف تكتيكي';
|
||||
terrainClassification = 'High Mountainous Plateaus (>1200m)';
|
||||
urbanDensity = 'LOW';
|
||||
urbanDensityAr = 'بلدات جبلية وقرى متفرقة';
|
||||
settlements = ['مدينة الكرك التاريخية', 'قرى المزار والشوبك', 'مزارع الجبال الجنوبية'];
|
||||
soilType = 'صخور جيرية صلبة وحجارة متناثرة';
|
||||
trafficability = 'مقيدة بالممرات الجبلية والمنعطفات الحادة (Slow-Go)';
|
||||
} else if (centerLat <= 30.0) {
|
||||
sectorName = 'قاطع الجنوب الصحراوي / وادي رم والعقبة';
|
||||
terrainClassificationAr = 'جبال رملية شاهقة (Monoliths) تتخللها ممرات ووديان رملية واسعة';
|
||||
terrainClassification = 'Sandstone Monoliths & Desert Valleys';
|
||||
urbanDensity = 'SPARSE';
|
||||
urbanDensityAr = 'شبه خالية (مخيمات سياحية وتجمعات بدوية)';
|
||||
settlements = ['قرية وادي رم', 'مخيمات الديسة السياحية', 'نقاط المراقبة الجنوبية'];
|
||||
soilType = 'رمال ناعمة وصخور رملية ضخمة';
|
||||
trafficability = 'ممتازة للآليات ذات الدفع الرباعي والمجنزرات في بطون الأودية';
|
||||
}
|
||||
|
||||
// Determine Overall Mobility Status
|
||||
let mobilityStatus: 'GO' | 'SLOW-GO' | 'NO-GO' = 'GO';
|
||||
let mobilityStatusAr = 'حركة حرة ممتازة للآليات والدروع (Cross-Country GO)';
|
||||
if (steepPct > 40 || avgSlope > 14) {
|
||||
mobilityStatus = 'NO-GO';
|
||||
mobilityStatusAr = 'مانع تضاريسي - حركة مقيدة وشديدة الصعوبة (NO-GO)';
|
||||
} else if (steepPct > 15 || modPct > 40 || avgSlope > 7) {
|
||||
mobilityStatus = 'SLOW-GO';
|
||||
mobilityStatusAr = 'حركة بطيئة تتطلب مسارب وتجهيزاً هندسياً (SLOW-GO)';
|
||||
}
|
||||
|
||||
return {
|
||||
centerLat,
|
||||
centerLng,
|
||||
radiusMeters,
|
||||
areaKm2,
|
||||
sectorName,
|
||||
centerElevation: Math.round(centerElev),
|
||||
minElevation: Math.round(minElev),
|
||||
maxElevation: Math.round(maxElev),
|
||||
reliefMeters: Math.round(relief),
|
||||
avgSlopeDegrees: avgSlope,
|
||||
maxSlopeDegrees: maxSlope,
|
||||
slopeDistribution: {
|
||||
flatPercentage: flatPct,
|
||||
moderatePercentage: modPct,
|
||||
steepPercentage: steepPct
|
||||
},
|
||||
terrainClassification,
|
||||
terrainClassificationAr,
|
||||
mobilityStatus,
|
||||
mobilityStatusAr,
|
||||
naturalObstacles: [
|
||||
{
|
||||
name: 'المنحدرات والجروف التضاريسية الحادة',
|
||||
type: 'انحدار طبوغرافي',
|
||||
description: `تشكل المناطق ذات الانحدار الشديد (${steepPct}%) مانعاً طبيعياً يحد من تقدم الدروع ويجبرها على الالتفاف.`,
|
||||
impact: 'إعاقة محاور الهجوم المعادي وتوجيهه نحو الخناق المجهزة بالنيران'
|
||||
},
|
||||
{
|
||||
name: 'الأودية والشعاب ومجاري السيول الطبيعية',
|
||||
type: 'أودية ومصارف مائية',
|
||||
description: 'بطون الأودية تمثل ممرات حركة محجوبة عن خط النظر ولكنها عرضة للكمائن والسيول الفجائية.',
|
||||
impact: 'تصلح كمسارب تسلل للمشاة ولكنها خطرة للآليات الثقيلة'
|
||||
},
|
||||
{
|
||||
name: 'القمم والمرتفعات الحاكمة المجاورة',
|
||||
type: 'أرض مسيطرة (Key Terrain)',
|
||||
description: `أعلى قمة في القطاع بارتفاع ${maxElev}م توفر إشرافاً نارياً واستطلاعياً على عمق يصل إلى ${Math.round(radiusMeters / 500)} كم.`,
|
||||
impact: 'وجوب احتلالها بنقاط مراقبة وأسلحة إسناد مضادة للدروع'
|
||||
}
|
||||
],
|
||||
manMadeObstacles: [
|
||||
{
|
||||
name: 'شبكة الطرق والمحاور المعبدة',
|
||||
type: 'محاور حركة سريعة',
|
||||
description: 'الطرق الرئيسية المارة بالقطاع تتيح سرعة إعادة انتشار القوات والإمداد اللوجستي.',
|
||||
tacticalNote: 'تعتبر أهدافاً حيوية يجب تأمينها بنقاط سيطرة وتفتيش وغلق سريع'
|
||||
},
|
||||
{
|
||||
name: 'خطوط نقل الطاقة وأبراج الاتصالات',
|
||||
type: 'بنية تحتية صناعية',
|
||||
description: 'أبراج الضغط العالي والاتصالات تشكل عوائق أمام الطيران المنخفض ومواقع محتملة لمحطات الإعاقة اللاسلكية.',
|
||||
tacticalNote: 'تجنب هبوط المروحيات بالقرب من خطوط الضغط العالي'
|
||||
},
|
||||
{
|
||||
name: 'المباني والمنشآت الخرسانية الممتدة',
|
||||
type: 'موانع مبنية',
|
||||
description: 'المباني المتفرقة توفر سواتر خرسانية ممتازة ضد الشظايا ونيران الأسلحة الخفيفة.',
|
||||
tacticalNote: 'تتطلب تطهيراً هندسياً لضمان خلوها من العبوات والأفخاخ'
|
||||
}
|
||||
],
|
||||
urbanAndDemographics: {
|
||||
density: urbanDensity,
|
||||
densityAr: urbanDensityAr,
|
||||
settlements,
|
||||
moutComplexity: urbanDensity === 'HIGH' ? 'معقدة جداً وتتطلب تدريب قتال مباني متخصص' : 'بسيطة إلى متوسطة وتقتصر على أطراف القرى',
|
||||
collateralDamageRisk: urbanDensity === 'HIGH' ? 'عالية جداً وتتطلب ذخائر موجهة وقواعد اشتباك صارمة' : 'منخفضة مع مراعاة حماية الأعيان المدنية'
|
||||
},
|
||||
landCoverAndCover: {
|
||||
soilType,
|
||||
trafficability,
|
||||
concealmentRating: steepPct > 20 || urbanDensity === 'HIGH' ? 'ممتازة خلف التموجات والمباني' : 'متوسطة إلى مكشوفة في الأراضي المفتوحة',
|
||||
airObservationExposure: urbanDensity === 'HIGH' ? 'مختلطة (مباني تحجب الرؤية الجوية)' : 'عالية للرصد الجوي والمسيرات نهاراً'
|
||||
},
|
||||
oakocAssessment: {
|
||||
obstacles: `القطاع يحتوي على ${steepPct}% أراضٍ شديدة الوعورة تشكل موانع طبيعية، مع وجود محاور طرق رئيسية تشكل ممرات حركة سريعة.`,
|
||||
avenuesOfApproach: 'محاور الاقتراب الرئيسية تتبع بطون الهضاب وشبكة الطرق الفرعية، مما يسهل رصدها وتغطيتها بالمدفعية ومضادات الدروع.',
|
||||
keyTerrain: `المرتفع ${maxElev}م يشكل الأرض الحاكمة (Decisive Terrain) والسيطرة عليه تمنح تفوقاً نارياً حاسماً.`,
|
||||
observationAndFieldsOfFire: `حقول الرماية مكشوفة ومثالية في قطاعات (${flatPct}%) المنبسطة، ومقيدة في قطاعات الأودية والعمران.`,
|
||||
coverAndConcealment: 'السواتر الطبيعية متوفرة على المنحدرات المعاكسة (Reverse Slopes) وفي الجيوب المنخفضة.'
|
||||
},
|
||||
tacticalRecommendations: [
|
||||
`تمركز نقاط المراقبة والرادار على المرتفع الحاكم بارتفاع ${maxElev}م لتأمين الإنذار المبكر.`,
|
||||
'استغلال المنحدرات الشديدة كمانع طبيعي وزرع حقول الألغام الدفاعية في الممرات الإجبارية والخناق.',
|
||||
'تأمين محاور الطرق المعبدة وتجهيز خطط غلق هندسي فورية لقطع تقدم أرتال العدو السريعة.',
|
||||
'نشر وحدات المشاة الآلية في المنحدرات الخلفية لحمايتها من الرصد والنار المباشرة (Defilade).',
|
||||
'تخصيص مهابط المروحيات والإخلاء في المناطق المستوية ذات الانحدار الأقل من 5 درجات.'
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user