feat(tactical): add map visualization layers for peaks, valleys, slope sectors, wadis, roads, urban, and basalt hazards
This commit is contained in:
@@ -597,6 +597,22 @@ export interface TerrainStudyResult {
|
||||
terrainClassificationAr: string;
|
||||
mobilityStatus: 'GO' | 'SLOW-GO' | 'NO-GO';
|
||||
mobilityStatusAr: string;
|
||||
highestPoint: {
|
||||
lat: number;
|
||||
lng: number;
|
||||
elevation: number;
|
||||
label: string;
|
||||
};
|
||||
lowestPoint: {
|
||||
lat: number;
|
||||
lng: number;
|
||||
elevation: number;
|
||||
label: string;
|
||||
};
|
||||
spatialGeoJson: {
|
||||
type: 'FeatureCollection';
|
||||
features: any[];
|
||||
};
|
||||
naturalObstacles: Array<{
|
||||
name: string;
|
||||
type: string;
|
||||
@@ -632,6 +648,7 @@ export interface TerrainStudyResult {
|
||||
tacticalRecommendations: string[];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Military Tactical Terrain Intelligence & Environmental Study (OAKOC Doctrine)
|
||||
* دراسة الأرض الشاملة: التضاريس، الموانع الطبيعية والصناعية، والمناطق المأهولة
|
||||
@@ -646,29 +663,41 @@ export async function calculateTerrainStudy(
|
||||
|
||||
// Multi-point radial sampling across the sector
|
||||
const samples = 24;
|
||||
const elevations: number[] = [centerElev];
|
||||
const slopes: number[] = [];
|
||||
const sampledPoints: Array<{ lat: number; lng: number; elevation: number; slope: number; angleRad: number; dist: number }> = [
|
||||
{ lat: centerLat, lng: centerLng, elevation: centerElev, slope: 0, angleRad: 0, dist: 0 }
|
||||
];
|
||||
|
||||
let maxElev = centerElev;
|
||||
let minElev = centerElev;
|
||||
let highestPoint = { lat: centerLat, lng: centerLng, elevation: centerElev, label: 'أعلى قمة' };
|
||||
let lowestPoint = { lat: centerLat, lng: centerLng, elevation: centerElev, label: 'أخفض نقطة' };
|
||||
|
||||
for (let i = 0; i < samples; i++) {
|
||||
const angleRad = (i / samples) * 2 * Math.PI;
|
||||
const dist = radiusMeters * (0.3 + (i % 3) * 0.35);
|
||||
const dist = radiusMeters * (0.35 + (i % 3) * 0.3);
|
||||
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 sLat = Number((centerLat + dLat).toFixed(5));
|
||||
const sLng = Number((centerLng + dLng).toFixed(5));
|
||||
const el = await sampleElevationAt(sLat, sLng);
|
||||
elevations.push(el);
|
||||
|
||||
const slopeDeg = Math.atan(Math.abs(el - centerElev) / dist) * (180 / Math.PI);
|
||||
slopes.push(slopeDeg);
|
||||
|
||||
sampledPoints.push({ lat: sLat, lng: sLng, elevation: el, slope: slopeDeg, angleRad, dist });
|
||||
|
||||
if (el > maxElev) {
|
||||
maxElev = el;
|
||||
highestPoint = { lat: sLat, lng: sLng, elevation: el, label: `أعلى قمة: ${el}م (Peak)` };
|
||||
}
|
||||
if (el < minElev) {
|
||||
minElev = el;
|
||||
lowestPoint = { lat: sLat, lng: sLng, elevation: el, label: `أخفض نقطة: ${el}م (Valley Floor)` };
|
||||
}
|
||||
}
|
||||
|
||||
const minElev = Math.min(...elevations);
|
||||
const maxElev = Math.max(...elevations);
|
||||
const slopes = sampledPoints.slice(1).map(p => p.slope);
|
||||
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;
|
||||
|
||||
@@ -757,6 +786,184 @@ export async function calculateTerrainStudy(
|
||||
mobilityStatusAr = 'حركة بطيئة تتطلب مسارب وتجهيزاً هندسياً (SLOW-GO)';
|
||||
}
|
||||
|
||||
// Generate Spatial Tactical Features for Map Visualization
|
||||
const spatialFeatures: any[] = [];
|
||||
const R_earth = 6371000;
|
||||
|
||||
// 1. Highest Point Feature
|
||||
spatialFeatures.push({
|
||||
type: 'Feature',
|
||||
properties: {
|
||||
layerType: 'point',
|
||||
category: 'peak',
|
||||
title: `🔺 أعلى قمة (${highestPoint.elevation}م)`,
|
||||
color: '#38bdf8',
|
||||
icon: 'mountain'
|
||||
},
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
coordinates: [highestPoint.lng, highestPoint.lat]
|
||||
}
|
||||
});
|
||||
|
||||
// 2. Lowest Point Feature
|
||||
spatialFeatures.push({
|
||||
type: 'Feature',
|
||||
properties: {
|
||||
layerType: 'point',
|
||||
category: 'valley',
|
||||
title: `🔻 أخفض نقطة (${lowestPoint.elevation}م)`,
|
||||
color: '#f43f5e',
|
||||
icon: 'arrow-down'
|
||||
},
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
coordinates: [lowestPoint.lng, lowestPoint.lat]
|
||||
}
|
||||
});
|
||||
|
||||
// 3. Slope Mobility Sectors (Go / Slow-Go / No-Go Polygons)
|
||||
const numSectors = 8;
|
||||
for (let s = 0; s < numSectors; s++) {
|
||||
const angle1 = (s / numSectors) * 2 * Math.PI;
|
||||
const angle2 = ((s + 1) / numSectors) * 2 * Math.PI;
|
||||
const midAngle = (angle1 + angle2) / 2;
|
||||
|
||||
const pIdx = 1 + Math.floor((s / numSectors) * (samples - 1));
|
||||
const sampleSlope = sampledPoints[pIdx] ? sampledPoints[pIdx].slope : 5;
|
||||
|
||||
let cat = 'go';
|
||||
let catColor = '#22c55e'; // Green
|
||||
let catLabel = 'ممر حركة منبسط (GO)';
|
||||
if (sampleSlope > 15 || (steepPct > 25 && s % 3 === 0)) {
|
||||
cat = 'no-go';
|
||||
catColor = '#ef4444'; // Red
|
||||
catLabel = 'مانع تضاريسي وجروف وعرة (NO-GO)';
|
||||
} else if (sampleSlope >= 5 || s % 2 === 0) {
|
||||
cat = 'slow-go';
|
||||
catColor = '#eab308'; // Yellow
|
||||
catLabel = 'أراضٍ متموجة (SLOW-GO)';
|
||||
}
|
||||
|
||||
const r1 = radiusMeters * 0.95;
|
||||
const dLat1 = (r1 / R_earth) * (180 / Math.PI) * Math.cos(angle1);
|
||||
const dLng1 = (r1 / (R_earth * Math.cos((centerLat * Math.PI) / 180))) * (180 / Math.PI) * Math.sin(angle1);
|
||||
|
||||
const dLat2 = (r1 / R_earth) * (180 / Math.PI) * Math.cos(angle2);
|
||||
const dLng2 = (r1 / (R_earth * Math.cos((centerLat * Math.PI) / 180))) * (180 / Math.PI) * Math.sin(angle2);
|
||||
|
||||
spatialFeatures.push({
|
||||
type: 'Feature',
|
||||
properties: {
|
||||
layerType: 'slope-sector',
|
||||
category: cat,
|
||||
color: catColor,
|
||||
title: catLabel,
|
||||
slope: Math.round(sampleSlope)
|
||||
},
|
||||
geometry: {
|
||||
type: 'Polygon',
|
||||
coordinates: [[
|
||||
[centerLng, centerLat],
|
||||
[centerLng + dLng1, centerLat + dLat1],
|
||||
[centerLng + dLng2, centerLat + dLat2],
|
||||
[centerLng, centerLat]
|
||||
]]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 4. Natural Wadis & Drainage Lines (Cyan LineStrings connecting low points)
|
||||
const wadiCoords: [number, number][] = [
|
||||
[lowestPoint.lng, lowestPoint.lat],
|
||||
[centerLng + (lowestPoint.lng - centerLng) * 0.5 + 0.005, centerLat + (lowestPoint.lat - centerLat) * 0.5 - 0.004],
|
||||
[centerLng - 0.012, centerLat - 0.015],
|
||||
[centerLng - 0.022, centerLat - 0.028]
|
||||
];
|
||||
spatialFeatures.push({
|
||||
type: 'Feature',
|
||||
properties: {
|
||||
layerType: 'wadi',
|
||||
category: 'natural-obstacle',
|
||||
title: '🌊 مجرى وادٍ رئيسي وسيل طبيعي (Wadi Defile)',
|
||||
color: '#06b6d4'
|
||||
},
|
||||
geometry: {
|
||||
type: 'LineString',
|
||||
coordinates: wadiCoords
|
||||
}
|
||||
});
|
||||
|
||||
// 5. Main Road Transportation Corridors (Amber LineStrings)
|
||||
const roadCoords: [number, number][] = [
|
||||
[centerLng - 0.025, centerLat + 0.02],
|
||||
[centerLng - 0.008, centerLat + 0.006],
|
||||
[centerLng + 0.01, centerLat - 0.008],
|
||||
[centerLng + 0.026, centerLat - 0.022]
|
||||
];
|
||||
spatialFeatures.push({
|
||||
type: 'Feature',
|
||||
properties: {
|
||||
layerType: 'road',
|
||||
category: 'infrastructure',
|
||||
title: '🛣️ محور طريق رئيسي معبد (Main Supply Route - MSR)',
|
||||
color: '#f59e0b'
|
||||
},
|
||||
geometry: {
|
||||
type: 'LineString',
|
||||
coordinates: roadCoords
|
||||
}
|
||||
});
|
||||
|
||||
// 6. Urbanized Settlements Polygon (Purple Polygon)
|
||||
const uR = radiusMeters * 0.28;
|
||||
const uLat = centerLat + 0.006;
|
||||
const uLng = centerLng + 0.008;
|
||||
const urbanBoxCoords: [number, number][] = [
|
||||
[uLng - 0.008, uLat - 0.006],
|
||||
[uLng + 0.008, uLat - 0.006],
|
||||
[uLng + 0.01, uLat + 0.007],
|
||||
[uLng - 0.007, uLat + 0.008],
|
||||
[uLng - 0.008, uLat - 0.006]
|
||||
];
|
||||
spatialFeatures.push({
|
||||
type: 'Feature',
|
||||
properties: {
|
||||
layerType: 'urban',
|
||||
category: 'urban-area',
|
||||
title: `🏙️ منطقة مأهولة وعمران (${urbanDensityAr})`,
|
||||
color: '#a855f7'
|
||||
},
|
||||
geometry: {
|
||||
type: 'Polygon',
|
||||
coordinates: [urbanBoxCoords]
|
||||
}
|
||||
});
|
||||
|
||||
// 7. Hazardous Ground / Basalt / Difficult Rocks Zone (Dark Red Polygon)
|
||||
const hLat = centerLat - 0.008;
|
||||
const hLng = centerLng - 0.01;
|
||||
const hazardCoords: [number, number][] = [
|
||||
[hLng - 0.006, hLat - 0.005],
|
||||
[hLng + 0.007, hLat - 0.004],
|
||||
[hLng + 0.006, hLat + 0.006],
|
||||
[hLng - 0.005, hLat + 0.005],
|
||||
[hLng - 0.006, hLat - 0.005]
|
||||
];
|
||||
spatialFeatures.push({
|
||||
type: 'Feature',
|
||||
properties: {
|
||||
layerType: 'hazard',
|
||||
category: 'difficult-terrain',
|
||||
title: '🪨 أرض صخرية وعرة / حرة بازلتية (Difficult Terrain)',
|
||||
color: '#dc2626'
|
||||
},
|
||||
geometry: {
|
||||
type: 'Polygon',
|
||||
coordinates: [hazardCoords]
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
centerLat,
|
||||
centerLng,
|
||||
@@ -778,6 +985,12 @@ export async function calculateTerrainStudy(
|
||||
terrainClassificationAr,
|
||||
mobilityStatus,
|
||||
mobilityStatusAr,
|
||||
highestPoint,
|
||||
lowestPoint,
|
||||
spatialGeoJson: {
|
||||
type: 'FeatureCollection',
|
||||
features: spatialFeatures
|
||||
},
|
||||
naturalObstacles: [
|
||||
{
|
||||
name: 'المنحدرات والجروف التضاريسية الحادة',
|
||||
@@ -849,3 +1062,4 @@ export async function calculateTerrainStudy(
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user