fix(tactical): resolve DTO types and TS1272 build error for isolatedModules

This commit is contained in:
Hamza-Ayed
2026-08-18 09:46:04 +03:00
parent bd287d155b
commit 941420f5c0
86 changed files with 3614 additions and 1556 deletions
+104
View File
@@ -361,3 +361,107 @@ export async function calculateLineOfSight(
azimuthDegrees: Math.round(azimuthDegrees * 10) / 10,
};
}
export interface Viewshed360Result {
centerLat: number;
centerLng: number;
centerElevation: number;
radiusMeters: number;
polygonGeoJson: any;
totalRays: number;
visibleAreaKm2: number;
visiblePercentage: number;
}
/**
* Calculates 360-degree Radial Viewshed (كشف الميدان الدائري) around observer
*/
export async function calculateRadialViewshed(
centerLat: number,
centerLng: number,
obsHeight: number = 2,
radiusMeters: number = 5000,
numRays: number = 36
): Promise<Viewshed360Result> {
const centerGroundElev = await sampleElevationAt(centerLat, centerLng);
const observerElevation = centerGroundElev + obsHeight;
const R_earth = 6371000;
const k_refraction = 0.13;
const effectiveEarthRadius = R_earth / (1 - k_refraction);
const polygonCoordinates: [number, number][] = [];
const samplesPerRay = 12;
let totalVisibleDistanceSum = 0;
for (let rayIdx = 0; rayIdx < numRays; rayIdx++) {
const angleDeg = (rayIdx * 360) / numRays;
const angleRad = (angleDeg * Math.PI) / 180;
const dLat = (radiusMeters / R_earth) * (180 / Math.PI) * Math.cos(angleRad);
const dLng = (radiusMeters / (R_earth * Math.cos((centerLat * Math.PI) / 180))) * (180 / Math.PI) * Math.sin(angleRad);
const endLat = centerLat + dLat;
const endLng = centerLng + dLng;
let maxAngleSoFar = -Infinity;
let visibleHorizonDist = radiusMeters;
let visibleHorizonLat = endLat;
let visibleHorizonLng = endLng;
for (let step = 1; step <= samplesPerRay; step++) {
const frac = step / samplesPerRay;
const sLat = centerLat + (endLat - centerLat) * frac;
const sLng = centerLng + (endLng - centerLng) * frac;
const sDist = radiusMeters * frac;
const sElev = await sampleElevationAt(sLat, sLng);
const earthCurvatureDrop = (sDist * sDist) / (2 * effectiveEarthRadius);
const apparentElev = sElev - earthCurvatureDrop;
const angle = (apparentElev - observerElevation) / sDist;
if (angle >= maxAngleSoFar) {
maxAngleSoFar = angle;
visibleHorizonDist = sDist;
visibleHorizonLat = sLat;
visibleHorizonLng = sLng;
}
}
totalVisibleDistanceSum += visibleHorizonDist;
polygonCoordinates.push([visibleHorizonLng, visibleHorizonLat]);
}
if (polygonCoordinates.length > 0) {
polygonCoordinates.push(polygonCoordinates[0]);
}
const polygonGeoJson = {
type: 'Feature',
properties: {
centerLat,
centerLng,
radiusMeters
},
geometry: {
type: 'Polygon',
coordinates: [polygonCoordinates]
}
};
const avgVisibleDist = totalVisibleDistanceSum / numRays;
const theoreticalMaxArea = Math.PI * Math.pow(radiusMeters / 1000, 2);
const actualVisibleArea = Math.PI * Math.pow(avgVisibleDist / 1000, 2);
const visiblePercentage = Math.min(100, Math.round((actualVisibleArea / theoreticalMaxArea) * 100));
return {
centerLat,
centerLng,
centerElevation: Math.round(observerElevation),
radiusMeters,
polygonGeoJson,
totalRays: numRays,
visibleAreaKm2: Math.round(actualVisibleArea * 10) / 10,
visiblePercentage
};
}