feat(routing): enable elevation profile and steep slope/incline warnings in routing API and frontend

This commit is contained in:
Hamza-Ayed
2026-08-18 12:10:32 +03:00
parent 06543ee8e0
commit 57e3da7ea4
12 changed files with 599 additions and 100 deletions
+80 -9
View File
@@ -392,18 +392,89 @@ function App() {
{/* Route Summary Card */}
{routeData && (
<div className="route-summary glass-morphism" style={{ marginTop: '15px', padding: '12px', borderRadius: '8px', background: 'rgba(15, 23, 42, 0.6)' }}>
<h4 style={{ margin: '0 0 8px 0', fontSize: '0.9rem', color: '#38bdf8' }}>Route Overview / تفاصيل المسار</h4>
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '0.85rem', marginBottom: '4px' }}>
<span style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
<Gauge size={13} color="#38bdf8" />
{(Number(routeData.distance || 0) / 1000).toFixed(1)} km
<div className="route-summary glass-morphism" style={{ marginTop: '15px', padding: '14px', borderRadius: '10px', background: 'rgba(15, 23, 42, 0.75)', border: '1px solid rgba(56, 189, 248, 0.3)' }}>
<h4 style={{ margin: '0 0 10px 0', fontSize: '0.92rem', color: '#38bdf8', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<span>{routeData.routeName || 'تفاصيل المسار'}</span>
<span style={{ fontSize: '0.75rem', background: 'rgba(56, 189, 248, 0.15)', color: '#38bdf8', padding: '2px 8px', borderRadius: 999 }}>
{routeData.tags ? routeData.tags[0] : 'FASTEST'}
</span>
<span style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
<Clock size={13} color="#22c55e" />
{Math.round(Number(routeData.time || routeData.duration || 0) / 60000)} min
</h4>
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '0.85rem', marginBottom: '8px' }}>
<span style={{ display: 'flex', alignItems: 'center', gap: '4px', color: '#e2e8f0' }}>
<Gauge size={14} color="#38bdf8" />
{(Number(routeData.distance || 0) / 1000).toFixed(1)} كم
</span>
<span style={{ display: 'flex', alignItems: 'center', gap: '4px', color: '#4ade80' }}>
<Clock size={14} color="#22c55e" />
{Math.round(Number(routeData.duration || 0) / 60)} دقيقة
</span>
</div>
{/* Elevation & Slope Summary */}
{routeData.elevationSummary && (
<div style={{ marginTop: 10, paddingTop: 8, borderTop: '1px solid rgba(255,255,255,0.08)', fontSize: '0.78rem' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', color: '#94a3b8', marginBottom: 4 }}>
<span>📈 إجمالي الصعود: <strong style={{ color: '#4ade80' }}>+{routeData.elevationSummary.totalAscentMeters}م</strong></span>
<span>📉 إجمالي الهبوط: <strong style={{ color: '#38bdf8' }}>-{routeData.elevationSummary.totalDescentMeters}م</strong></span>
</div>
{(routeData.elevationSummary.maxInclinePercent > 0 || routeData.elevationSummary.maxDeclinePercent < 0) && (
<div style={{ display: 'flex', justifyContent: 'space-between', color: '#94a3b8' }}>
<span>أقصى صعود: <strong style={{ color: '#fbbf24' }}>+{routeData.elevationSummary.maxInclinePercent}%</strong></span>
<span>أقصى انحدار: <strong style={{ color: '#f87171' }}>{routeData.elevationSummary.maxDeclinePercent}%</strong></span>
</div>
)}
</div>
)}
{/* Steep Slope Warnings Alert */}
{routeData.elevationSummary?.steepWarnings && routeData.elevationSummary.steepWarnings.length > 0 && (
<div style={{
marginTop: 10,
padding: '8px 10px',
background: 'rgba(239, 68, 68, 0.15)',
border: '1px solid rgba(239, 68, 68, 0.35)',
borderRadius: 8,
fontSize: '0.75rem',
color: '#fca5a5'
}}>
<div style={{ fontWeight: 700, display: 'flex', alignItems: 'center', gap: 6, marginBottom: 4 }}>
<AlertTriangle size={13} color="#ef4444" />
<span>تحذير تضاريسي للمركبات الثقيلة:</span>
</div>
{routeData.elevationSummary.steepWarnings.slice(0, 2).map((w: any, idx: number) => (
<div key={idx} style={{ marginTop: 2 }}>
• {w.street ? `شارع ${w.street}: ` : ''}{w.slopePercent > 0 ? `صعود حاد (+${w.slopePercent}%)` : `منحدر جبلي شديد (${w.slopePercent}%)`}
</div>
))}
</div>
)}
{/* Turn by turn expandable instructions */}
{routeData.instructions && routeData.instructions.length > 0 && (
<details style={{ marginTop: 10, fontSize: '0.75rem', color: '#cbd5e1' }}>
<summary style={{ cursor: 'pointer', color: '#38bdf8', fontWeight: 600, padding: '4px 0' }}>
عرض خطوات المسار بالتفصيل ({routeData.instructions.length} خطوة)
</summary>
<div style={{ maxHeight: 180, overflowY: 'auto', marginTop: 6, paddingRight: 4, display: 'flex', flexDirection: 'column', gap: 4 }}>
{routeData.instructions.map((inst: any, idx: number) => (
<div key={idx} style={{
padding: '4px 6px',
borderRadius: 4,
background: inst.slopeWarning ? 'rgba(245, 158, 11, 0.12)' : 'rgba(255,255,255,0.03)',
borderRight: inst.slopeWarning ? '3px solid #f59e0b' : '1px solid transparent'
}}>
<div style={{ fontWeight: inst.slopeWarning ? 700 : 400, color: inst.slopeWarning ? '#fbbf24' : '#e2e8f0' }}>
{idx + 1}. {inst.text}
</div>
<div style={{ fontSize: '0.7rem', color: '#94a3b8' }}>
{inst.distance ? `${Math.round(inst.distance)}م` : ''} {inst.slopePercent ? `| انحدار: ${inst.slopePercent}%` : ''}
</div>
</div>
))}
</div>
</details>
)}
</div>
)}
+71 -33
View File
@@ -212,65 +212,103 @@ export const ExecutiveShowcase: React.FC = () => {
display: 'inline-flex',
alignItems: 'center',
gap: 8,
background: 'rgba(79, 70, 229, 0.07)',
border: '1px solid rgba(79, 70, 229, 0.18)',
padding: '6px 16px',
background: 'linear-gradient(135deg, rgba(79, 70, 229, 0.1), rgba(6, 182, 212, 0.1))',
border: '1px solid rgba(79, 70, 229, 0.25)',
padding: '7px 20px',
borderRadius: 999,
color: '#4f46e5',
fontSize: '0.84rem',
fontWeight: 700,
marginBottom: 20
color: '#4338ca',
fontSize: '0.86rem',
fontWeight: 800,
marginBottom: 22,
boxShadow: '0 2px 10px rgba(79, 70, 229, 0.08)'
}}>
<Sparkles size={16} /> البديل الوطني المتكامل لمنظومات إزري (ArcGIS) في الأردن والشرق الأوسط
<Flame size={17} color="#f97316" /> سؤال السيادة الوطنية لعام 2026: هل نمتلك خرائطنا أم نستأجرها؟
</div>
<h2 style={{
fontSize: 'clamp(1.7rem, 3.4vw, 2.6rem)',
fontWeight: 800,
lineHeight: 1.32,
margin: '0 auto 18px auto',
maxWidth: 920,
fontSize: 'clamp(1.8rem, 3.6vw, 2.75rem)',
fontWeight: 900,
lineHeight: 1.3,
margin: '0 auto 20px auto',
maxWidth: 960,
color: '#0f172a',
letterSpacing: '-0.025em'
letterSpacing: '-0.03em'
}}>
منصة استخبارات جغرافية وطنية وتوليد ذاتي للشوارع والتضاريس التكتيكية
معركة السيادة المكانية: كيف نكسر احتكار إزري ونمتلك محرك الخرائط والذكاء التكتيكي الأسرع في المنطقة؟
</h2>
<p style={{
fontSize: '1.02rem',
color: '#475569',
maxWidth: 820,
margin: '0 auto 32px auto',
lineHeight: 1.8,
fontWeight: 400
fontSize: '1.08rem',
color: '#334155',
maxWidth: 860,
margin: '0 auto 28px auto',
lineHeight: 1.85,
fontWeight: 500
}}>
منظومة سيادية متكاملة تجمع بين <strong>محرك خرائط عالي الأداء معزول سحابياً (Air-Gapped)</strong>، وقدرات استخبارات تكتيكية متقدمة تشمل <strong>دراسة الأرض ثلاثية الأبعاد (21×21 DEM)</strong>، فحص الجروف الصخرية، خط النظر الكروي، مسارات المدفعية، وحساب حقول الألغام ومهابط الطيران.
استثمار وطني استراتيجي مزدوج: <strong>تطبيق نقل وأساطيل ذكي في الواجهة المدنية</strong>، و<strong>محرك خرائط عسكري سيادي في الخلفية (C4ISR & GEOINT)</strong> يكتشف الشوارع والمسارب الوعرة تلقائياً من حركة الآليات، ويمنح القوات المسلحة تفوقاً تكتيكياً كاملاً داخل خوادم معزولة تماماً (Air-Gapped).
</p>
{/* 3 Strategic Shock Cards (حقائق تصدم صانع القرار) */}
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))',
gap: 16,
maxWidth: 1040,
margin: '0 auto 32px auto',
textAlign: 'right'
}}>
<div className="apple-card" style={{ background: '#ffffff', padding: '18px 20px', borderRadius: 16, border: '1px solid #fee2e2', borderRight: '4px solid #ef4444' }}>
<div style={{ fontWeight: 800, color: '#dc2626', fontSize: '0.92rem', marginBottom: 4, display: 'flex', alignItems: 'center', gap: 6 }}>
<ShieldAlert size={16} /> 1. صدمة التبعية والسرية
</div>
<div style={{ fontSize: '0.82rem', color: '#64748b', lineHeight: 1.6 }}>
لماذا تمر بيانات تحركاتنا ومواقعنا عبر خوادم إزري السحابية في أمريكا بينما يمكننا تشغيلها بالكامل داخل سيرفرات المركز الجغرافي المعزولة؟
</div>
</div>
<div className="apple-card" style={{ background: '#ffffff', padding: '18px 20px', borderRadius: 16, border: '1px solid #fef3c7', borderRight: '4px solid #f59e0b' }}>
<div style={{ fontWeight: 800, color: '#d97706', fontSize: '0.92rem', marginBottom: 4, display: 'flex', alignItems: 'center', gap: 6 }}>
<Zap size={16} /> 2. الخريطة التي ترسم نفسها
</div>
<div style={{ fontSize: '0.82rem', color: '#64748b', lineHeight: 1.6 }}>
حركة دوريات وشاحنات الجيش تولد مسارات الأودية والحدود والصحراء آلياً فور عبورها، دون انتظار طائرات مسح أو صور أقمار صناعية دورية!
</div>
</div>
<div className="apple-card" style={{ background: '#ffffff', padding: '18px 20px', borderRadius: 16, border: '1px solid #dbeafe', borderRight: '4px solid #3b82f6' }}>
<div style={{ fontWeight: 800, color: '#2563eb', fontSize: '0.92rem', marginBottom: 4, display: 'flex', alignItems: 'center', gap: 6 }}>
<Crosshair size={16} /> 3. درع العمليات والرماية التكتيكي
</div>
<div style={{ fontSize: '0.82rem', color: '#64748b', lineHeight: 1.6 }}>
أول محرك عربي مدمج به: رماية المدفعية البالستية، كشف الميدان 360°، تحذيرات المنحدرات الجبلية، وحساب زوايا الموقع بالميللي العسكري.
</div>
</div>
</div>
{/* Quick Metrics Bar (Light Apple Glass) */}
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(210px, 1fr))',
gap: 16,
gap: 14,
maxWidth: 1040,
margin: '0 auto'
}}>
<div className="apple-card" style={{ background: '#ffffff', padding: '20px 16px', borderRadius: 16, border: '1px solid #e2e8f0', boxShadow: '0 4px 16px rgba(0,0,0,0.03)' }}>
<div style={{ fontSize: '1.85rem', fontWeight: 900, color: '#4f46e5', letterSpacing: '-0.02em' }}>441 نقطة</div>
<div style={{ fontSize: '0.84rem', fontWeight: 600, color: '#64748b', marginTop: 4 }}>مسح طبوغرافي عالي الدقة (21×21)</div>
</div>
<div className="apple-card" style={{ background: '#ffffff', padding: '20px 16px', borderRadius: 16, border: '1px solid #e2e8f0', boxShadow: '0 4px 16px rgba(0,0,0,0.03)' }}>
<div style={{ fontSize: '1.85rem', fontWeight: 900, color: '#059669', letterSpacing: '-0.02em' }}>60 FPS</div>
<div className="apple-card" style={{ background: '#ffffff', padding: '18px 16px', borderRadius: 16, border: '1px solid #e2e8f0', boxShadow: '0 4px 16px rgba(0,0,0,0.03)' }}>
<div style={{ fontSize: '1.85rem', fontWeight: 900, color: '#4f46e5', letterSpacing: '-0.02em' }}>60 FPS</div>
<div style={{ fontSize: '0.84rem', fontWeight: 600, color: '#64748b', marginTop: 4 }}>رندرة متجهات فورية (GPU Tiles)</div>
</div>
<div className="apple-card" style={{ background: '#ffffff', padding: '20px 16px', borderRadius: 16, border: '1px solid #e2e8f0', boxShadow: '0 4px 16px rgba(0,0,0,0.03)' }}>
<div style={{ fontSize: '1.85rem', fontWeight: 900, color: '#d97706', letterSpacing: '-0.02em' }}>100%</div>
<div className="apple-card" style={{ background: '#ffffff', padding: '18px 16px', borderRadius: 16, border: '1px solid #e2e8f0', boxShadow: '0 4px 16px rgba(0,0,0,0.03)' }}>
<div style={{ fontSize: '1.85rem', fontWeight: 900, color: '#059669', letterSpacing: '-0.02em' }}>100%</div>
<div style={{ fontSize: '0.84rem', fontWeight: 600, color: '#64748b', marginTop: 4 }}>معزولة أمنياً (Air-Gapped On-Prem)</div>
</div>
<div className="apple-card" style={{ background: '#ffffff', padding: '20px 16px', borderRadius: 16, border: '1px solid #e2e8f0', boxShadow: '0 4px 16px rgba(0,0,0,0.03)' }}>
<div style={{ fontSize: '1.85rem', fontWeight: 900, color: '#0284c7', letterSpacing: '-0.02em' }}>$0</div>
<div className="apple-card" style={{ background: '#ffffff', padding: '18px 16px', borderRadius: 16, border: '1px solid #e2e8f0', boxShadow: '0 4px 16px rgba(0,0,0,0.03)' }}>
<div style={{ fontSize: '1.85rem', fontWeight: 900, color: '#d97706', letterSpacing: '-0.02em' }}>$0</div>
<div style={{ fontSize: '0.84rem', fontWeight: 600, color: '#64748b', marginTop: 4 }}>تكاليف تراخيص سنوية أو قيود مستخدمين</div>
</div>
<div className="apple-card" style={{ background: '#ffffff', padding: '18px 16px', borderRadius: 16, border: '1px solid #e2e8f0', boxShadow: '0 4px 16px rgba(0,0,0,0.03)' }}>
<div style={{ fontSize: '1.85rem', fontWeight: 900, color: '#0284c7', letterSpacing: '-0.02em' }}>3 دول</div>
<div style={{ fontSize: '0.84rem', fontWeight: 600, color: '#64748b', marginTop: 4 }}>الأردن 🇯🇴 • سوريا 🇸🇾 • مصر 🇪🇬</div>
</div>
</div>
</div>
</section>