diff --git a/apps/web/src/pages/TacticalDefenseView.tsx b/apps/web/src/pages/TacticalDefenseView.tsx
index d61775b..2795a63 100644
--- a/apps/web/src/pages/TacticalDefenseView.tsx
+++ b/apps/web/src/pages/TacticalDefenseView.tsx
@@ -551,11 +551,11 @@ export const TacticalDefenseView: React.FC = () => {
'fill-opacity': [
'match',
['get', 'layerType'],
- 'cliff', 0.50,
- 'urban', 0.28,
- 'hazard', 0.32,
- 'slope-sector', 0.18,
- 0.2
+ 'cliff', 0.38,
+ 'urban', 0.20,
+ 'hazard', 0.24,
+ 'slope-sector', 0.14,
+ 0.15
]
}
});
@@ -566,18 +566,17 @@ export const TacticalDefenseView: React.FC = () => {
source: 'tactical-terrain-spatial-src',
filter: ['==', '$type', 'Polygon'],
paint: {
- 'line-color': ['get', 'color'],
+ 'line-color': [
+ 'match',
+ ['get', 'layerType'],
+ 'cliff', '#ef4444',
+ 'rgba(255, 255, 255, 0.10)'
+ ],
'line-width': [
'match',
['get', 'layerType'],
- 'cliff', 3.5,
- 2
- ],
- 'line-dasharray': [
- 'match',
- ['get', 'layerType'],
- 'cliff', ['literal', [2, 1]],
- ['literal', [3, 2]]
+ 'cliff', 1.8,
+ 0.4
]
}
});
@@ -592,10 +591,10 @@ export const TacticalDefenseView: React.FC = () => {
'line-width': [
'match',
['get', 'layerType'],
- 'cliff', 4.5,
- 'road', 4.5,
- 'wadi', 3.5,
- 3
+ 'cliff', 3.5,
+ 'road', 4.0,
+ 'wadi', 3.0,
+ 2.5
],
'line-dasharray': [
'match',
@@ -608,21 +607,37 @@ export const TacticalDefenseView: React.FC = () => {
}
});
+ initialMap.addLayer({
+ id: 'tactical-terrain-spatial-points',
+ type: 'circle',
+ source: 'tactical-terrain-spatial-src',
+ filter: ['==', '$type', 'Point'],
+ paint: {
+ 'circle-radius': 5,
+ 'circle-color': ['coalesce', ['get', 'color'], '#38bdf8'],
+ 'circle-stroke-width': 2,
+ 'circle-stroke-color': '#ffffff'
+ }
+ });
+
initialMap.addLayer({
id: 'tactical-terrain-spatial-labels',
type: 'symbol',
source: 'tactical-terrain-spatial-src',
+ filter: ['all', ['==', '$type', 'Point'], ['has', 'title']],
layout: {
'text-field': ['get', 'title'],
- 'text-size': 11,
+ 'text-size': 12,
'text-font': ['Noto Sans Bold', 'Open Sans Bold'],
- 'text-anchor': 'center',
- 'symbol-placement': 'point'
+ 'text-anchor': 'bottom',
+ 'text-offset': [0, -0.6],
+ 'symbol-placement': 'point',
+ 'text-allow-overlap': false
},
paint: {
'text-color': '#ffffff',
'text-halo-color': '#0f172a',
- 'text-halo-width': 2
+ 'text-halo-width': 2.5
}
});
@@ -1674,14 +1689,14 @@ ${terrainResult.tacticalRecommendations.map((r, i) => `${i + 1}. ${r}`).join('\n
-
-
25°): ${terrainResult.slopeDistribution.cliffPercentage}%`} />
+
+
■ منبسط (<7°): {terrainResult.slopeDistribution.flatPercentage}%
■ متموج (7-15°): {terrainResult.slopeDistribution.moderatePercentage}%
- ■ وعر (15-25°): {terrainResult.slopeDistribution.steepPercentage}%
- ■ جروف صخرية (>25°): {terrainResult.slopeDistribution.cliffPercentage}%
+ ■ وعر (15-28°): {terrainResult.slopeDistribution.steepPercentage}%
+ ■ جروف صخرية (≥28°): {terrainResult.slopeDistribution.cliffPercentage}%
diff --git a/apps/web/src/utils/elevationService.ts b/apps/web/src/utils/elevationService.ts
index 2a66376..ab57ec2 100644
--- a/apps/web/src/utils/elevationService.ts
+++ b/apps/web/src/utils/elevationService.ts
@@ -837,11 +837,11 @@ export async function calculateTerrainStudy(
const p4 = [p4Coord.lng, p4Coord.lat];
// Military Grade Slope & Barrier Classification:
- // - Flat (GO): < 7°
- // - Rolling (SLOW-GO): 7° - 15°
- // - Rugged / Steep (SEVERE SLOW-GO): 15° - 25°
- // - Cliffs & Natural Rock Barriers (NO-GO): > 25° or vertical drop >= 12m
- const isCliff = slopeDeg >= 25 || (slopeDeg >= 18 && localDrop >= 10) || localDrop >= 18;
+ // - Flat (GO): < 7° (< 12%)
+ // - Rolling (SLOW-GO): 7° - 15° (12% - 27%)
+ // - Rugged / Mountainous (SEVERE SLOW-GO): 15° - 28° (27% - 53%)
+ // - Natural Cliffs & Rock Escarpments (NO-GO): >= 28° (Physical vertical barrier)
+ const isCliff = slopeDeg >= 28;
if (isCliff) {
cliffCount++;
@@ -850,18 +850,17 @@ export async function calculateTerrainStudy(
lng: Number(cellCenterLng.toFixed(5)),
dropMeters: localDrop,
slopeDegrees: slopeDeg,
- label: `🧗♂️ قاطع صخري وجرف حاد (سقوط ${localDrop}م - ميل ${slopeDeg}°)`,
+ label: `🧗♂️ جرف صخري حاد (${localDrop}م / ${slopeDeg}°)`,
tacticalImpact: 'مانع طبيعي قطعي - غير قابل لاجتياز الدروع والآليات، يفرض ممرات إجبارية'
});
- // Add prominent cliff barrier polygon & line
+ // Add cliff barrier polygon without title label spam
spatialFeatures.push({
type: 'Feature',
properties: {
layerType: 'cliff',
category: 'natural-obstacle',
- color: '#dc2626',
- title: `🧗♂️ قاطع صخري وجرف حاد (${localDrop}م / ${slopeDeg}°)`,
+ color: '#ef4444',
slope: slopeDeg,
drop: localDrop,
elevation: cellElevAvg
@@ -879,7 +878,6 @@ export async function calculateTerrainStudy(
layerType: 'slope-sector',
category: 'severe-slow-go',
color: '#f97316',
- title: `انحدار جبلي وعر (SEVERE SLOW-GO) [${slopeDeg}°]`,
slope: slopeDeg,
elevation: cellElevAvg
},
@@ -896,7 +894,6 @@ export async function calculateTerrainStudy(
layerType: 'slope-sector',
category: 'slow-go',
color: '#eab308',
- title: `تضاريس متموجة (SLOW-GO) [${slopeDeg}°]`,
slope: slopeDeg,
elevation: cellElevAvg
},
@@ -913,7 +910,6 @@ export async function calculateTerrainStudy(
layerType: 'slope-sector',
category: 'go',
color: '#22c55e',
- title: `ممر حركة منبسط (GO) [${slopeDeg}°]`,
slope: slopeDeg,
elevation: cellElevAvg
},
@@ -926,6 +922,37 @@ export async function calculateTerrainStudy(
}
}
+ // Add Key Terrain POI Points (Only these will display text badges on the map)
+ spatialFeatures.push({
+ type: 'Feature',
+ properties: {
+ category: 'peak',
+ layerType: 'poi',
+ title: `▲ أعلى قمة: ${highestPoint.elevation}م`,
+ elevation: highestPoint.elevation,
+ color: '#22c55e'
+ },
+ geometry: {
+ type: 'Point',
+ coordinates: [highestPoint.lng, highestPoint.lat]
+ }
+ });
+
+ spatialFeatures.push({
+ type: 'Feature',
+ properties: {
+ category: 'valley',
+ layerType: 'poi',
+ title: `▼ أخفض نقطة: ${lowestPoint.elevation}م`,
+ elevation: lowestPoint.elevation,
+ color: '#06b6d4'
+ },
+ geometry: {
+ type: 'Point',
+ coordinates: [lowestPoint.lng, lowestPoint.lat]
+ }
+ });
+
// 5. Statistics of Slope Distribution
const totalCells = Math.max(1, slopeValues.length);
const flatPct = Math.round((flatCount / totalCells) * 100);