feat(tactical): add interactive location picker, minefield quantities analysis, and organizational feasibility study
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -465,3 +465,114 @@ export async function calculateRadialViewshed(
|
|||||||
visiblePercentage
|
visiblePercentage
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MinefieldAnalysisResult {
|
||||||
|
startLat: number;
|
||||||
|
startLng: number;
|
||||||
|
endLat: number;
|
||||||
|
endLng: number;
|
||||||
|
frontageMeters: number;
|
||||||
|
azimuthDegrees: number;
|
||||||
|
startElevation: number;
|
||||||
|
endElevation: number;
|
||||||
|
avgSlopeDegrees: number;
|
||||||
|
suitabilityScore: number; // 0-100%
|
||||||
|
suitabilityVerdict: string;
|
||||||
|
suitabilityVerdictAr: string;
|
||||||
|
isChokePoint: boolean;
|
||||||
|
quantities: {
|
||||||
|
antiTankMines: number;
|
||||||
|
antiPersonnelMines: number;
|
||||||
|
fuseSets: number;
|
||||||
|
warningSigns: number;
|
||||||
|
rowsCount: number;
|
||||||
|
depthMeters: number;
|
||||||
|
estimatedDeploymentHours: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tactical Minefield Barrier & Sapper Engineering Analysis
|
||||||
|
* دراسة جدوى وكميات حقل الألغام الدفاعي وفحص التضاريس
|
||||||
|
*/
|
||||||
|
export async function calculateMinefieldAnalysis(
|
||||||
|
startLat: number,
|
||||||
|
startLng: number,
|
||||||
|
endLat: number,
|
||||||
|
endLng: number,
|
||||||
|
densityLevel: 'standard' | 'dense' | 'light' = 'standard'
|
||||||
|
): Promise<MinefieldAnalysisResult> {
|
||||||
|
const frontageMeters = calculateDistance(startLat, startLng, endLat, endLng);
|
||||||
|
const azimuthDegrees = calculateAzimuth(startLat, startLng, endLat, endLng);
|
||||||
|
|
||||||
|
const startElev = await sampleElevationAt(startLat, startLng);
|
||||||
|
const endElev = await sampleElevationAt(endLat, endLng);
|
||||||
|
|
||||||
|
// Sample intermediate points to determine valley / ridge / choke point profile
|
||||||
|
const samples = 10;
|
||||||
|
const elevs: number[] = [];
|
||||||
|
for (let i = 0; i <= samples; i++) {
|
||||||
|
const frac = i / samples;
|
||||||
|
const lat = startLat + (endLat - startLat) * frac;
|
||||||
|
const lng = startLng + (endLng - startLng) * frac;
|
||||||
|
const el = await sampleElevationAt(lat, lng);
|
||||||
|
elevs.push(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
const elevDiff = Math.abs(endElev - startElev);
|
||||||
|
const avgSlopeRad = frontageMeters > 0 ? Math.atan(elevDiff / frontageMeters) : 0;
|
||||||
|
const avgSlopeDegrees = Math.round((avgSlopeRad * (180 / Math.PI)) * 10) / 10;
|
||||||
|
|
||||||
|
// Check if center is lower than edges (a defile / valley choke point)
|
||||||
|
const midElev = elevs[Math.floor(samples / 2)];
|
||||||
|
const isChokePoint = midElev < Math.min(startElev, endElev) - 10 || avgSlopeDegrees <= 10;
|
||||||
|
|
||||||
|
const densityFactor = densityLevel === 'dense' ? 1.5 : densityLevel === 'light' ? 0.6 : 1.0;
|
||||||
|
const atMinesPerMeter = 1.0 * densityFactor;
|
||||||
|
const apMinesPerMeter = 2.0 * densityFactor;
|
||||||
|
|
||||||
|
const antiTankMines = Math.round(frontageMeters * atMinesPerMeter);
|
||||||
|
const antiPersonnelMines = Math.round(frontageMeters * apMinesPerMeter);
|
||||||
|
const warningSigns = Math.max(4, Math.round(frontageMeters / 30));
|
||||||
|
const deploymentHours = Math.round(((frontageMeters / 150) * 2 * densityFactor) * 10) / 10;
|
||||||
|
|
||||||
|
let suitabilityScore = 85;
|
||||||
|
let suitabilityVerdict = 'Highly Effective Defensive Barrier';
|
||||||
|
let suitabilityVerdictAr = 'موضع دفاعي نموذجي يسد محور اقتراب الدروع بفعالية';
|
||||||
|
|
||||||
|
if (avgSlopeDegrees > 20) {
|
||||||
|
suitabilityScore = 55;
|
||||||
|
suitabilityVerdict = 'Steep Terrain: Armor movement already restricted by natural slope';
|
||||||
|
suitabilityVerdictAr = 'انحدار جبلي شديد: التضاريس تشكل مانعاً طبيعياً لحركة الدروع';
|
||||||
|
} else if (isChokePoint) {
|
||||||
|
suitabilityScore = 96;
|
||||||
|
suitabilityVerdict = 'Optimal Choke Point Defile Barrier';
|
||||||
|
suitabilityVerdictAr = 'ممر ومضيق إجباري مثالي (Choke Point) يمنع التفاف آليات العدو';
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
startLat,
|
||||||
|
startLng,
|
||||||
|
endLat,
|
||||||
|
endLng,
|
||||||
|
frontageMeters: Math.round(frontageMeters),
|
||||||
|
azimuthDegrees: Math.round(azimuthDegrees * 10) / 10,
|
||||||
|
startElevation: Math.round(startElev),
|
||||||
|
endElevation: Math.round(endElev),
|
||||||
|
avgSlopeDegrees,
|
||||||
|
suitabilityScore,
|
||||||
|
suitabilityVerdict,
|
||||||
|
suitabilityVerdictAr,
|
||||||
|
isChokePoint,
|
||||||
|
quantities: {
|
||||||
|
antiTankMines,
|
||||||
|
antiPersonnelMines,
|
||||||
|
fuseSets: Math.round((antiTankMines + antiPersonnelMines) * 1.1),
|
||||||
|
warningSigns,
|
||||||
|
rowsCount: densityLevel === 'dense' ? 4 : 3,
|
||||||
|
depthMeters: densityLevel === 'dense' ? 75 : 50,
|
||||||
|
estimatedDeploymentHours: Math.max(1, deploymentHours)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user