fix(tactical): resolve DTO types and TS1272 build error for isolatedModules
This commit is contained in:
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user