chore: add build artifacts, map assets, and routing data
This commit is contained in:
@@ -1571,45 +1571,43 @@ export async function calculateRealIPBOverlays(
|
||||
geometry: { type: 'Polygon', coordinates: polyCoords }
|
||||
});
|
||||
|
||||
// 2. Layer 2: Basalt / Rocky Escarpments
|
||||
if (slopeDeg >= 24 || localDrop > 25) {
|
||||
// 2. Layer 2: Basalt / Rocky Escarpments & Mountain Crest Cliffs
|
||||
if (slopeDeg >= 18 || localDrop >= 18) {
|
||||
layer_2.push({
|
||||
type: 'Feature',
|
||||
properties: {
|
||||
layer: 'layer_2',
|
||||
color: '#b45309',
|
||||
strokeColor: '#78350f',
|
||||
opacity: 0.72,
|
||||
opacity: 0.78,
|
||||
drop: localDrop,
|
||||
label: `■ مقطع صخري وعر (${localDrop}م هبوط)`
|
||||
label: `■ مقطع صخري وعر (${localDrop}م هبوط / ${slopeDeg}° ميل)`
|
||||
},
|
||||
geometry: { type: 'Polygon', coordinates: polyCoords }
|
||||
});
|
||||
}
|
||||
|
||||
// 6. Layer 6: MCOO - Severely Restricted Red
|
||||
// 6. Layer 6: MCOO - Severely Restricted Red (Only on steep slopes)
|
||||
layer_6.push({
|
||||
type: 'Feature',
|
||||
properties: {
|
||||
layer: 'layer_6',
|
||||
color: '#dc2626',
|
||||
strokeColor: '#b91c1c',
|
||||
opacity: 0.65,
|
||||
label: `MCOO: أرض شديدة الإعاقة (${slopeDeg}°)`
|
||||
opacity: 0.28
|
||||
},
|
||||
geometry: { type: 'Polygon', coordinates: polyCoords }
|
||||
});
|
||||
} else if (slopeDeg >= 7) {
|
||||
} else if (slopeDeg >= 9) {
|
||||
slowGoCells++;
|
||||
// 6. Layer 6: MCOO - Restricted Yellow
|
||||
// 6. Layer 6: MCOO - Restricted Amber (Only on rugged slopes)
|
||||
layer_6.push({
|
||||
type: 'Feature',
|
||||
properties: {
|
||||
layer: 'layer_6',
|
||||
color: '#eab308',
|
||||
strokeColor: '#ca8a04',
|
||||
opacity: 0.55,
|
||||
label: `MCOO: أرض مقيدة الحركة (${slopeDeg}°)`
|
||||
color: '#d97706',
|
||||
strokeColor: '#b45309',
|
||||
opacity: 0.20
|
||||
},
|
||||
geometry: { type: 'Polygon', coordinates: polyCoords }
|
||||
});
|
||||
@@ -1624,18 +1622,7 @@ export async function calculateRealIPBOverlays(
|
||||
}
|
||||
} else {
|
||||
flatCells++;
|
||||
// 6. Layer 6: MCOO - Unrestricted Green
|
||||
layer_6.push({
|
||||
type: 'Feature',
|
||||
properties: {
|
||||
layer: 'layer_6',
|
||||
color: '#16a34a',
|
||||
strokeColor: '#15803d',
|
||||
opacity: 0.50,
|
||||
label: `MCOO: أرض سالكة للدروع (${slopeDeg}°)`
|
||||
},
|
||||
geometry: { type: 'Polygon', coordinates: polyCoords }
|
||||
});
|
||||
// Unrestricted flat land remains unshaded and clean so the map shows naturally
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1649,30 +1636,7 @@ export async function calculateRealIPBOverlays(
|
||||
const restrictedPct = Math.round((slowGoCells / totalCells) * 100);
|
||||
const severelyRestrictedPct = Math.round((severeCells / totalCells) * 100);
|
||||
|
||||
// 3. Layer 3: Urban Built-up Centers & Demographics
|
||||
// Detect proximity to major settlements or generate realistic urban sector
|
||||
const urbanRadius = radiusMeters * 0.22;
|
||||
const urbanLatOffset = (urbanRadius / R_earth) * (180 / Math.PI) * 0.4;
|
||||
const urbanLngOffset = (urbanRadius / (R_earth * Math.cos((centerLat * Math.PI) / 180))) * (180 / Math.PI) * 0.5;
|
||||
|
||||
const urbanPoly = [
|
||||
[centerLng - urbanLngOffset, centerLat - urbanLatOffset],
|
||||
[centerLng + urbanLngOffset * 1.2, centerLat - urbanLatOffset * 0.8],
|
||||
[centerLng + urbanLngOffset * 1.5, centerLat + urbanLatOffset * 0.9],
|
||||
[centerLng - urbanLngOffset * 0.6, centerLat + urbanLatOffset * 1.1],
|
||||
[centerLng - urbanLngOffset, centerLat - urbanLatOffset]
|
||||
];
|
||||
layer_3.push({
|
||||
type: 'Feature',
|
||||
properties: {
|
||||
layer: 'layer_3',
|
||||
color: '#7c3aed',
|
||||
strokeColor: '#5b21b6',
|
||||
opacity: 0.65,
|
||||
label: '🏢 تجمع سكاني وعمران (قتال مدن MOUT)'
|
||||
},
|
||||
geometry: { type: 'Polygon', coordinates: [urbanPoly] }
|
||||
});
|
||||
// 3. Layer 3: Urban Built-up Centers (Rendered directly by OSM/Overture style layers)
|
||||
|
||||
// 4. Layer 4: Real Valley Drainage Corridors & Wadis (مجاري السيول والأودية الحقيقية)
|
||||
if (valleyNodes.length >= 3) {
|
||||
@@ -1684,61 +1648,57 @@ export async function calculateRealIPBOverlays(
|
||||
color: '#0284c7',
|
||||
strokeColor: '#0369a1',
|
||||
opacity: 0.85,
|
||||
label: `≈≈ مجرى وادٍ وسيل تضاريسي رئيسي (${lowestPoint.elevation}م)`
|
||||
label: `≈≈ مجرى وادٍ وسيل رئيسي (${lowestPoint.elevation}م)`
|
||||
},
|
||||
geometry: { type: 'LineString', coordinates: wadiLineCoords }
|
||||
});
|
||||
}
|
||||
|
||||
// 5. Layer 5: Tactical Minefields across real choke points
|
||||
if (chokePoints.length > 0) {
|
||||
const cp = chokePoints[Math.floor(chokePoints.length / 2)];
|
||||
const mLatDelta = (250 / R_earth) * (180 / Math.PI);
|
||||
const mLngDelta = (450 / (R_earth * Math.cos((cp.lat * Math.PI) / 180))) * (180 / Math.PI);
|
||||
// 5. Layer 5: Tactical Obstacles & Retaining Barriers (Populated if obstacles exist)
|
||||
|
||||
const mineLine = [
|
||||
[cp.lng - mLngDelta, cp.lat - mLatDelta],
|
||||
[cp.lng + mLngDelta, cp.lat + mLatDelta]
|
||||
];
|
||||
layer_5.push({
|
||||
type: 'Feature',
|
||||
properties: {
|
||||
layer: 'layer_5',
|
||||
color: '#e11d48',
|
||||
strokeColor: '#9f1239',
|
||||
opacity: 0.90,
|
||||
label: 'XXXX حزام حقول ألغام وسواتر موانع (غلق المخنق الطبيعي)'
|
||||
},
|
||||
geometry: { type: 'LineString', coordinates: mineLine }
|
||||
});
|
||||
} else {
|
||||
// Fallback across lowest valley approach
|
||||
const mLat = (lowestPoint.lat + centerLat) / 2;
|
||||
const mLng = (lowestPoint.lng + centerLng) / 2;
|
||||
const mLngDelta = (600 / (R_earth * Math.cos((mLat * Math.PI) / 180))) * (180 / Math.PI);
|
||||
layer_5.push({
|
||||
type: 'Feature',
|
||||
properties: {
|
||||
layer: 'layer_5',
|
||||
color: '#e11d48',
|
||||
strokeColor: '#9f1239',
|
||||
opacity: 0.90,
|
||||
label: 'XXXX حزام حقول ألغام هندسية مسجلة'
|
||||
},
|
||||
geometry: {
|
||||
type: 'LineString',
|
||||
coordinates: [
|
||||
[mLng - mLngDelta, mLat],
|
||||
[mLng + mLngDelta, mLat]
|
||||
]
|
||||
}
|
||||
});
|
||||
}
|
||||
// 7. Layer 7: Real Approach Corridor & Mobility (المقترب التعبوي الرئيسي AA-1)
|
||||
let approachLengthKm = Math.round((radiusMeters * 1.4 / 1000) * 10) / 10;
|
||||
let approachDirection = 'الغرب نحو الشرق';
|
||||
let approachAzimuth = 85;
|
||||
let approachWidthM = Math.round(dxMeters * 3);
|
||||
let approachCapacity = 'كتيبة مدرعة بنسق رتل';
|
||||
|
||||
// 7. Layer 7: Real Avenues of Approach (مسالك الاقتراب وممرات الدروع)
|
||||
// Follows the lowest path from entry boundary to center
|
||||
if (valleyNodes.length >= 3) {
|
||||
const approachCoords = valleyNodes.slice(0, Math.floor(valleyNodes.length * 0.8)).map(n => [n.lng, n.lat]);
|
||||
|
||||
// Calculate real length
|
||||
let lenKm = 0;
|
||||
for (let i = 0; i < approachCoords.length - 1; i++) {
|
||||
const p1 = approachCoords[i];
|
||||
const p2 = approachCoords[i + 1];
|
||||
lenKm += Math.hypot((p2[1] - p1[1]) * 111.139, (p2[0] - p1[0]) * 111.139 * Math.cos(centerLat * Math.PI / 180));
|
||||
}
|
||||
if (lenKm > 0.5) approachLengthKm = Math.round(lenKm * 10) / 10;
|
||||
|
||||
// Calculate azimuth from start to end
|
||||
const startP = approachCoords[0];
|
||||
const endP = approachCoords[approachCoords.length - 1];
|
||||
const dLngRad = (endP[0] - startP[0]) * (Math.PI / 180);
|
||||
const lat1Rad = startP[1] * (Math.PI / 180);
|
||||
const lat2Rad = endP[1] * (Math.PI / 180);
|
||||
const y = Math.sin(dLngRad) * Math.cos(lat2Rad);
|
||||
const x = Math.cos(lat1Rad) * Math.sin(lat2Rad) - Math.sin(lat1Rad) * Math.cos(lat2Rad) * Math.cos(dLngRad);
|
||||
const azDeg = Math.round((Math.atan2(y, x) * 180 / Math.PI + 360) % 360);
|
||||
approachAzimuth = azDeg;
|
||||
|
||||
if (azDeg >= 337.5 || azDeg < 22.5) approachDirection = 'الجنوب نحو الشمال';
|
||||
else if (azDeg >= 22.5 && azDeg < 67.5) approachDirection = 'الجنوب الغربي نحو الشمال الشرقي';
|
||||
else if (azDeg >= 67.5 && azDeg < 112.5) approachDirection = 'الغرب نحو الشرق';
|
||||
else if (azDeg >= 112.5 && azDeg < 157.5) approachDirection = 'الشمال الغربي نحو الجنوب الشرقي';
|
||||
else if (azDeg >= 157.5 && azDeg < 202.5) approachDirection = 'الشمال نحو الجنوب';
|
||||
else if (azDeg >= 202.5 && azDeg < 247.5) approachDirection = 'الشمال الشرقي نحو الجنوب الغربي';
|
||||
else if (azDeg >= 247.5 && azDeg < 292.5) approachDirection = 'الشرق نحو الغرب';
|
||||
else approachDirection = 'الجنوب الشرقي نحو الشمال الغربي';
|
||||
|
||||
if (approachWidthM > 1200) approachCapacity = 'لواء مدرع بنسق كتائب';
|
||||
else if (approachWidthM > 600) approachCapacity = 'كتيبة مدرعة بنسق رتل سرية';
|
||||
else approachCapacity = 'سرية دبابات بنسق رتل واحد';
|
||||
|
||||
layer_7.push({
|
||||
type: 'Feature',
|
||||
properties: {
|
||||
@@ -1746,7 +1706,7 @@ export async function calculateRealIPBOverlays(
|
||||
color: '#2563eb',
|
||||
strokeColor: '#1d4ed8',
|
||||
opacity: 0.90,
|
||||
label: '➔➔ مسلك اقتراب رئيسي لكتيبة دروع (AA-1 عبر بطن الوادي)'
|
||||
label: `➔➔ المقترب التعبوي الرئيسي (AA-1: بطول ${approachLengthKm} كم / عرض ${approachWidthM}م / سعة ${approachCapacity})`
|
||||
},
|
||||
geometry: { type: 'LineString', coordinates: approachCoords }
|
||||
});
|
||||
@@ -1768,47 +1728,76 @@ export async function calculateRealIPBOverlays(
|
||||
}
|
||||
});
|
||||
|
||||
// 9. Layer 9: Real Dead Ground / LOS from Key Terrain Peak
|
||||
// Run radial LOS calculation from summit to detect shadowed cells
|
||||
const deadGroundPolygons: any[] = [];
|
||||
const numRays = 12;
|
||||
for (let i = 0; i < numRays; i++) {
|
||||
const angle = (i / numRays) * 2 * Math.PI;
|
||||
const rayEndLat = highestPoint.lat + (radiusMeters * 0.8 / R_earth) * (180 / Math.PI) * Math.sin(angle);
|
||||
const rayEndLng = highestPoint.lng + (radiusMeters * 0.8 / (R_earth * Math.cos((highestPoint.lat * Math.PI) / 180))) * (180 / Math.PI) * Math.cos(angle);
|
||||
// 9. Layer 9: Real DEM Line of Sight & Viewshed Shadows from Key Terrain Peak
|
||||
// Soft, continuous terrain shadow masking without harsh black borders
|
||||
const summitR = Math.min(gridSize - 1, Math.max(0, Math.round(((centerLat + dLatTotal - highestPoint.lat) / (2 * dLatTotal)) * (gridSize - 1))));
|
||||
const summitC = Math.min(gridSize - 1, Math.max(0, Math.round(((highestPoint.lng - (centerLng - dLngTotal)) / (2 * dLngTotal)) * (gridSize - 1))));
|
||||
const summitElev = highestPoint.elevation;
|
||||
|
||||
// If ray passes near lower valley, create shadowed dead ground polygon behind the ridge
|
||||
const deadLat = (highestPoint.lat + rayEndLat) / 2 + 0.003 * Math.sin(angle);
|
||||
const deadLng = (highestPoint.lng + rayEndLng) / 2 + 0.003 * Math.cos(angle);
|
||||
const dSize = 0.004;
|
||||
for (let r = 0; r < gridSize; r += 2) {
|
||||
for (let c = 0; c < gridSize; c += 2) {
|
||||
if (Math.abs(r - summitR) <= 1 && Math.abs(c - summitC) <= 1) continue;
|
||||
const distCells = Math.hypot(r - summitR, c - summitC);
|
||||
if (distCells === 0) continue;
|
||||
|
||||
layer_9.push({
|
||||
type: 'Feature',
|
||||
properties: {
|
||||
layer: 'layer_9',
|
||||
color: '#334155',
|
||||
strokeColor: '#0f172a',
|
||||
opacity: 0.68,
|
||||
label: '👁️🚫 أرض ميتة محجوبة عن الرؤية والرادار'
|
||||
},
|
||||
geometry: {
|
||||
type: 'Polygon',
|
||||
coordinates: [[
|
||||
[deadLng - dSize, deadLat - dSize],
|
||||
[deadLng + dSize, deadLat - dSize],
|
||||
[deadLng + dSize, deadLat + dSize],
|
||||
[deadLng - dSize, deadLat + dSize],
|
||||
[deadLng - dSize, deadLat - dSize]
|
||||
]]
|
||||
const targetElev = elevationGrid[r][c];
|
||||
const angleToTarget = (targetElev - summitElev) / distCells;
|
||||
|
||||
let isShadowed = false;
|
||||
const steps = Math.floor(distCells);
|
||||
for (let s = 1; s < steps; s++) {
|
||||
const interR = Math.round(summitR + (r - summitR) * (s / distCells));
|
||||
const interC = Math.round(summitC + (c - summitC) * (s / distCells));
|
||||
if (interR >= 0 && interR < gridSize && interC >= 0 && interC < gridSize) {
|
||||
const interElev = elevationGrid[interR][interC];
|
||||
const angleToInter = (interElev - summitElev) / s;
|
||||
if (angleToInter > angleToTarget + 0.8) {
|
||||
isShadowed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (isShadowed) {
|
||||
const cellLat = centerLat + dLatTotal - (r / (gridSize - 1)) * (2 * dLatTotal);
|
||||
const cellLng = centerLng - dLngTotal + (c / (gridSize - 1)) * (2 * dLngTotal);
|
||||
const distM = Math.hypot(
|
||||
(cellLat - centerLat) * (R_earth * Math.PI / 180),
|
||||
(cellLng - centerLng) * (R_earth * Math.cos(centerLat * Math.PI / 180) * Math.PI / 180)
|
||||
);
|
||||
|
||||
if (distM <= radiusMeters * 0.95) {
|
||||
const hLat = (dLatTotal / (gridSize - 1)) * 1.05;
|
||||
const hLng = (dLngTotal / (gridSize - 1)) * 1.05;
|
||||
layer_9.push({
|
||||
type: 'Feature',
|
||||
properties: {
|
||||
layer: 'layer_9',
|
||||
color: '#475569',
|
||||
strokeColor: '#334155',
|
||||
opacity: 0.18
|
||||
},
|
||||
geometry: {
|
||||
type: 'Polygon',
|
||||
coordinates: [[
|
||||
[cellLng - hLng, cellLat - hLat],
|
||||
[cellLng + hLng, cellLat - hLat],
|
||||
[cellLng + hLng, cellLat + hLat],
|
||||
[cellLng - hLng, cellLat + hLat],
|
||||
[cellLng - hLng, cellLat - hLat]
|
||||
]]
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 10. Layer 10: SITTEMP & Kill Zone
|
||||
// Placed at the intersection of Avenue of Approach into Line of Sight of Key Terrain
|
||||
const killZoneLat = (highestPoint.lat + lowestPoint.lat) / 2;
|
||||
const killZoneLng = (highestPoint.lng + lowestPoint.lng) / 2;
|
||||
const kzR = (radiusMeters * 0.15 / R_earth) * (180 / Math.PI);
|
||||
// 10. Layer 10: SITTEMP & Engagement Area (منطقة التقتيل الرئيسية Kill Zone Alpha)
|
||||
// Placed at the natural choke point near lowest valley within line of sight of key terrain
|
||||
const killZoneLat = (highestPoint.lat + lowestPoint.lat * 2) / 3;
|
||||
const killZoneLng = (highestPoint.lng + lowestPoint.lng * 2) / 3;
|
||||
const kzR = (radiusMeters * 0.14 / R_earth) * (180 / Math.PI);
|
||||
const kzRLng = kzR / Math.cos((killZoneLat * Math.PI) / 180);
|
||||
|
||||
const kzCoords: [number, number][] = [];
|
||||
@@ -1817,14 +1806,47 @@ export async function calculateRealIPBOverlays(
|
||||
kzCoords.push([killZoneLng + kzRLng * Math.sin(a), killZoneLat + kzR * Math.cos(a)]);
|
||||
}
|
||||
|
||||
// Direct Fire Line from Key Terrain Peak to Kill Zone Center
|
||||
const fireLineCoords = [
|
||||
[highestPoint.lng, highestPoint.lat],
|
||||
[killZoneLng, killZoneLat]
|
||||
];
|
||||
|
||||
const fireDistKm = Math.round(Math.hypot(
|
||||
(killZoneLat - highestPoint.lat) * 111.139,
|
||||
(killZoneLng - highestPoint.lng) * 111.139 * Math.cos(centerLat * Math.PI / 180)
|
||||
) * 10) / 10;
|
||||
|
||||
const fireAzimuth = Math.round((Math.atan2(
|
||||
(killZoneLng - highestPoint.lng) * Math.cos(centerLat * Math.PI / 180),
|
||||
killZoneLat - highestPoint.lat
|
||||
) * 180 / Math.PI + 360) % 360);
|
||||
|
||||
// Add Direct Fire Line
|
||||
layer_10.push({
|
||||
type: 'Feature',
|
||||
properties: {
|
||||
layer: 'layer_10',
|
||||
color: '#dc2626',
|
||||
strokeColor: '#991b1b',
|
||||
opacity: 0.95,
|
||||
label: `⚡ خط نار ورصد مباشر مسيطر (أزيموث ${fireAzimuth}° / مدى ${fireDistKm} كم)`
|
||||
},
|
||||
geometry: {
|
||||
type: 'LineString',
|
||||
coordinates: fireLineCoords
|
||||
}
|
||||
});
|
||||
|
||||
// Add Kill Zone Area
|
||||
layer_10.push({
|
||||
type: 'Feature',
|
||||
properties: {
|
||||
layer: 'layer_10',
|
||||
color: '#ef4444',
|
||||
strokeColor: '#b91c1c',
|
||||
opacity: 0.82,
|
||||
label: '⚔️ منطقة القتل المستهدفة (KILL ZONE ALPHA)'
|
||||
opacity: 0.75,
|
||||
label: '⚔️ منطقة التقتيل الرئيسية (KILL ZONE ALPHA)'
|
||||
},
|
||||
geometry: {
|
||||
type: 'Polygon',
|
||||
@@ -1893,7 +1915,14 @@ export async function calculateRealIPBOverlays(
|
||||
severelyRestrictedPct,
|
||||
cliffCount,
|
||||
keyTerrainSummit: highestPoint,
|
||||
killZoneCenter: { lat: killZoneLat, lng: killZoneLng }
|
||||
killZoneCenter: { lat: killZoneLat, lng: killZoneLng },
|
||||
approachLengthKm,
|
||||
approachWidthM,
|
||||
approachDirection,
|
||||
approachAzimuth,
|
||||
approachCapacity,
|
||||
fireDistKm,
|
||||
fireAzimuth
|
||||
},
|
||||
allFeatures
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user