chore: add build artifacts, map assets, and routing data

This commit is contained in:
Hamza-Ayed
2026-09-19 12:34:24 +03:00
parent aa2b9f131f
commit 43ad2e0ad4
327 changed files with 52421 additions and 1810 deletions
+660
View File
@@ -0,0 +1,660 @@
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>وثيقة الشراكة الاستراتيجية والسيادة المكانية | شركة سفر لتكنولوجيا المعلومات والمركز الجغرافي الملكي</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans+Arabic:wght@300;400;500;600;700;800&family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
<style>
:root {
--primary: #0f4c81; /* كحلي ملكي رسمي */
--primary-dark: #082a4a;
--primary-light: #eef5fc;
--accent: #854d0e; /* برونزي ذهبي وقور */
--accent-light: #fef9c3;
--slate-900: #0f172a;
--slate-800: #1e293b;
--slate-700: #334155;
--slate-600: #475569;
--slate-500: #64748b;
--slate-200: #e2e8f0;
--slate-100: #f1f5f9;
--slate-50: #f8fafc;
--bg: #f8fafc;
--success: #059669;
--success-light: #d1fae5;
--danger: #b91c1c;
--danger-light: #fee2e2;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'IBM Plex Sans Arabic', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background-color: var(--bg);
color: var(--slate-900);
line-height: 1.85;
padding: 30px 15px;
-webkit-font-smoothing: antialiased;
}
.document-wrapper {
max-width: 1080px;
margin: 0 auto;
background: #ffffff;
border: 1px solid #e2e8f0;
border-radius: 12px;
box-shadow: 0 4px 25px rgba(15, 23, 42, 0.05);
padding: 45px 55px;
}
/* Top Action Bar */
.action-bar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
padding-bottom: 18px;
border-bottom: 2px solid var(--slate-100);
}
.btn-print {
background: var(--primary);
color: #ffffff;
border: none;
padding: 10px 22px;
border-radius: 8px;
font-weight: 700;
cursor: pointer;
font-family: inherit;
font-size: 0.92rem;
display: inline-flex;
align-items: center;
gap: 8px;
box-shadow: 0 2px 8px rgba(15, 76, 129, 0.2);
transition: all 0.2s ease;
}
.btn-print:hover {
background: var(--primary-dark);
transform: translateY(-1px);
}
/* Official Letterhead Header */
.letterhead {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 22px;
border-bottom: 3px double var(--slate-200);
margin-bottom: 30px;
}
.company-title-ar {
font-size: 1.45rem;
font-weight: 800;
color: var(--primary);
letter-spacing: -0.02em;
}
.company-sub-ar {
font-size: 0.88rem;
color: var(--slate-600);
font-weight: 600;
}
.doc-badge {
display: inline-block;
background: var(--primary-light);
border: 1px solid #bfdbfe;
color: var(--primary);
padding: 4px 14px;
border-radius: 6px;
font-size: 0.82rem;
font-weight: 700;
margin-bottom: 6px;
}
/* Document Title Box */
.title-box {
background: linear-gradient(135deg, #f0f7ff, #f8fafc);
border-right: 6px solid var(--primary);
border-radius: 0 12px 12px 0;
padding: 26px 30px;
margin-bottom: 32px;
border-top: 1px solid #e2e8f0;
border-bottom: 1px solid #e2e8f0;
border-left: 1px solid #e2e8f0;
}
.addressed-to {
font-size: 1.22rem;
font-weight: 800;
color: var(--primary-dark);
margin-bottom: 10px;
}
.doc-subject {
font-size: 1.48rem;
font-weight: 800;
color: var(--slate-900);
line-height: 1.45;
margin-bottom: 12px;
}
.doc-summary {
font-size: 0.98rem;
color: var(--slate-700);
line-height: 1.75;
}
/* Section Styling */
.section {
margin-bottom: 32px;
}
.section-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 2px solid var(--primary-light);
}
.section-num {
width: 32px;
height: 32px;
background: var(--primary);
color: #ffffff;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 800;
font-size: 0.95rem;
flex-shrink: 0;
}
.section-title {
font-size: 1.25rem;
font-weight: 800;
color: var(--primary-dark);
}
p {
margin-bottom: 14px;
color: var(--slate-700);
text-align: justify;
font-size: 0.96rem;
}
/* Cards & Grids */
.grid-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 18px;
margin: 18px 0;
}
.grid-3 {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
margin: 18px 0;
}
.card {
background: #ffffff;
border: 1px solid var(--slate-200);
border-radius: 10px;
padding: 20px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.02);
}
.card-title {
font-size: 1.02rem;
font-weight: 700;
color: var(--primary);
margin-bottom: 8px;
display: flex;
align-items: center;
gap: 8px;
}
.card-body {
font-size: 0.9rem;
color: var(--slate-600);
line-height: 1.65;
}
.badge-ready {
background: var(--success-light);
color: var(--success);
border: 1px solid #a7f3d0;
padding: 2px 8px;
border-radius: 4px;
font-size: 0.72rem;
font-weight: 700;
margin-inline-start: auto;
}
/* Callout Box */
.callout-box {
background: var(--slate-50);
border-right: 5px solid var(--accent);
padding: 18px 22px;
border-radius: 0 10px 10px 0;
margin: 22px 0;
border-top: 1px solid var(--slate-200);
border-bottom: 1px solid var(--slate-200);
border-left: 1px solid var(--slate-200);
}
.callout-title {
font-weight: 800;
color: var(--accent);
margin-bottom: 6px;
font-size: 0.98rem;
}
/* Comparison Table */
.table-container {
margin: 22px 0;
overflow-x: auto;
border: 1px solid var(--slate-200);
border-radius: 10px;
}
table {
width: 100%;
border-collapse: collapse;
font-size: 0.9rem;
text-align: right;
}
th {
background: var(--slate-100);
color: var(--slate-800);
font-weight: 700;
padding: 13px 16px;
border-bottom: 2px solid var(--slate-200);
}
td {
padding: 12px 16px;
border-bottom: 1px solid var(--slate-200);
color: var(--slate-700);
}
tr:nth-child(even) {
background: var(--slate-50);
}
.status-yes {
color: var(--success);
font-weight: 700;
}
.status-no {
color: var(--danger);
font-weight: 700;
}
/* Sign-off Block */
.signoff-box {
margin-top: 40px;
padding-top: 22px;
border-top: 2px solid var(--slate-200);
display: flex;
justify-content: space-between;
align-items: flex-end;
}
.signoff-name {
font-size: 1.18rem;
font-weight: 800;
color: var(--primary);
margin-bottom: 4px;
}
.signoff-title {
font-size: 0.92rem;
font-weight: 700;
color: var(--slate-800);
margin-bottom: 2px;
}
.signoff-company {
font-size: 0.88rem;
color: var(--slate-600);
}
.meta-tag {
display: inline-block;
background: var(--slate-100);
padding: 4px 12px;
border-radius: 6px;
font-size: 0.82rem;
color: var(--slate-600);
font-weight: 600;
}
/* Print Styles */
@media print {
body {
background: #ffffff;
padding: 0;
}
.document-wrapper {
border: none;
box-shadow: none;
padding: 0;
max-width: 100%;
}
.action-bar { display: none; }
.card, .title-box, .callout-box {
break-inside: avoid;
}
table {
page-break-inside: auto;
}
tr {
page-break-inside: avoid;
page-break-after: auto;
}
}
</style>
</head>
<body>
<div class="document-wrapper">
<!-- Action Bar -->
<div class="action-bar">
<div style="display: flex; align-items: center; gap: 12px;">
<span style="font-size: 1.5rem;">🏛️</span>
<div>
<strong style="font-size: 1rem; color: var(--primary-dark);">وثيقة مبادرة الشراكة الاستراتيجية والسيادة المكانية الوطنية</strong>
<div style="font-size: 0.8rem; color: var(--slate-500);">المركز الجغرافي الملكي الأردني & شركة سفر لتكنولوجيا المعلومات</div>
</div>
</div>
<button class="btn-print" onclick="window.print()">
<span>🖨️ طباعة الوثيقة / حفظ PDF</span>
</button>
</div>
<!-- Letterhead -->
<div class="letterhead">
<div style="text-align: right;">
<div class="company-title-ar">شركة سفر لتكنولوجيا المعلومات</div>
<div class="company-sub-ar">Sefer Information Technology (سجل تجاري معتمد)</div>
<div style="font-size: 0.78rem; color: var(--slate-500); margin-top: 2px;">قطاع البنى التحتية المكانية، تطبيقات النقل الذكي، والحلول التكتيكية</div>
</div>
<div style="text-align: center;">
<div class="doc-badge">وثيقة تعاون وطني رفيعة المستوى</div>
<div style="font-size: 0.78rem; color: var(--slate-600); font-weight: 600;">المملكة الأردنية الهاشمية — عمان</div>
</div>
<div style="text-align: left; direction: ltr;">
<div style="font-weight: 800; font-size: 1.1rem; color: var(--primary);">SEFER TECH</div>
<div style="font-size: 0.78rem; color: var(--slate-500);">Sovereign Spatial & Mobility Systems</div>
<div style="font-size: 0.75rem; color: var(--slate-400);">Ref: SEF-RJGC-2026/03</div>
</div>
</div>
<!-- Title & Addressed Box -->
<div class="title-box">
<div class="addressed-to">
سعادة مدير عام المركز الجغرافي الملكي الأردني المحترم،
</div>
<div class="doc-subject">
الموضوع: مبادرة التكامل التكنولوجي والسيادة الرقمية لتشغيل منظومة الخرائط الوطنية الموحدة (للاستخدام التجاري، تطبيقات النقل والتوصيل، والمنظومة الدفاعية في بيئات التشويش وانقطاع الـ GPS).
</div>
<div class="doc-summary">
تحية طيبة وبعد؛ يسر <strong>شركة سفر لتكنولوجيا المعلومات</strong> أن تضع بين أيديكم هذه الوثيقة الاستراتيجية، التي تتضمن حلاً هندسياً وتقنياً وطنياً متكاملاً وجاهزاً للتشغيل الفوري، يهدف إلى تحقيق <strong>السيادة المكانية الكاملة للمملكة</strong> من خلال إرساء بديل وطني شامل لخرائط جوجل والمنصات الأجنبية؛ يغذي تطبيقات النقل الذكي، شركات التوصيل والخدمات اللوجستية، والقطاعات التجارية، بالتزامن مع توفير <strong>منظومة استدلال وتموضع مستقلة تعمل بكفاءة تامة في بيئات الحرب الإلكترونية وانقطاع إشارات الأقمار الصناعية والإنترنت</strong>، بما يحقق عوائد استثمارية واقتصادية وتفوقاً تكتيكياً حاسماً.
</div>
</div>
<!-- Section 1: Executive Dual Purpose -->
<div class="section">
<div class="section-header">
<div class="section-num">1</div>
<div class="section-title">الهدف الاستراتيجي المزدوج: العائد التجاري والاستخدام السيادي والدفاعي</div>
</div>
<p>
إن الهدف المحوري لهذه المبادرة هو بناء <strong>المنصة الجيومكانية الوطنية الموحدة</strong> القادرة على تلبية متطلبات السوق التجاري والقطاعات الأمنية والعسكرية على حد سواء، من خلال محاور تشغيلية جاهزة ومختبرة ميدانياً:
</p>
<div class="grid-3">
<div class="card" style="border-top: 4px solid var(--primary);">
<div class="card-title" style="color: var(--primary);">🚗 1. قطاع النقل والتوصيل (المرخص وغير المرخص)</div>
<div class="card-body">
تكامل فوري مع كافة تطبيقات النقل الذكي، أساطيل التوصيل، والخدمات اللوجستية، وتزويدها بمحركات ملاحة وتوجيه محلي متفوقة على جوجل، مع <strong>توفير ملايين الدنانير</strong> من رسوم استدعاء خرائط جوجل المرهقة للشركات الوطنية، وتمكين الجهات التنظيمية من إلزام التطبيقات بالاعتماد على الخريطة الوطنية لحفظ بيانات التحركات داخل حدود المملكة.
</div>
</div>
<div class="card" style="border-top: 4px solid var(--accent);">
<div class="card-title" style="color: var(--accent);">🎖️ 2. العمليات التكتيكية والدفاعية</div>
<div class="card-body">
تأمين القوات المسلحة والأجهزة الأمنية بنظام ملاحة وتوجيه وتضاريس ثلاثي الأبعاد لا يعتمد إطلاقاً على شبكة الإنترنت أو إشارات الأقمار الصناعية، مما يحصن قيادة العمليات ضد أي تشويش إلكتروني معادي.
</div>
</div>
<div class="card" style="border-top: 4px solid var(--success);">
<div class="card-title" style="color: var(--success);">💰 3. العائد الاستثماري وحفظ السيادة الرقمية</div>
<div class="card-body">
اعتماد خريطة المركز الجغرافي وسفر الجديدة والمتوافقة مع كافة الأجهزة المحمولة والأنظمة كـ <strong>المصدر المرجعي الإلزامي والحصري</strong> لكافة التطبيقات والوزارات والقطاع الخاص، مما يضمن حفظ سيادة البيانات الوطنية ويحقق عوائد تجارية دورية ضخمة ومستدامة.
</div>
</div>
</div>
<div class="callout-box">
<div class="callout-title">الغاية الاستراتيجية ومسار الاعتماد الوطني:</div>
تأتي هذه المبادرة والشراكة التكنولوجية مع المركز الجغرافي الملكي الأردني كخطوة تأسيسية ومحورية تهدف إلى مواءمة وتكامل المنظومة الوطنية وإعدادها المشترك، <strong>لرفعها رسمياً إلى عطوفة رئيس هيئة الأركان المشتركة والقيادة العامة للقوات المسلحة الأردنية — الجيش العربي</strong>، بهدف المصادقة والاعتماد النهائي لهذه الإجراءات والحلول السيادية بالكامل، وتبني <strong>تطبيق سيرو (Siro)</strong> للنقل الذكي والخرائط التكتيكية كمنظومة وطنية معتمدة لكافة التشكيلات والقطاعات.
</div>
</div>
<!-- Section 2: Core Technological Capabilities -->
<div class="section">
<div class="section-header">
<div class="section-num">2</div>
<div class="section-title">الحلول الهندسية المنجزة لدى شركة سفر (جاهزة ومختبرة 100%)</div>
</div>
<p>
تمتلك شركة سفر بنية برمجية وتقنية كاملة طُوّرت بجهود وخبرات وطنية، وتتميز بحلول حصرية ومحمية الملكية الفكرية:
</p>
<div class="grid-2">
<!-- Solution 1: Proprietary Autonomous Positioning -->
<div class="card">
<div class="card-title">
<span>📍 1. منظومة التموضع الذاتي والاستدلال المستقل عند انقطاع الـ GPS</span>
<span class="badge-ready">ابتكار حصري</span>
</div>
<div class="card-body">
<strong>معالجة معضلة انقطاع الأقمار الصناعية:</strong> من خلال حلول ونماذج رياضية خاصة ومحمية طورتها شركة سفر، تمكنت المنظومة من استخراج الموقع الجغرافي الدقيق وحساب الإحداثيات الميدانية على أجهزة الهواتف واللوحيات العسكرية في الميدان <strong>بدون شريحة اتصال، بدون إنترنت، وبدون إشارة GPS نهائياً</strong>، متفوقة على كافة الأنظمة التجارية المتاحة ومحققة الاستقلالية التامة أثناء عمليات التشويش والحرب الإلكترونية.
</div>
</div>
<!-- Solution 2: 100-120k POI Local Database -->
<div class="card">
<div class="card-title">
<span>🗺️ 2. قاعدة بيانات جغرافية غنية (100 - 120 ألف معلم محلي)</span>
<span class="badge-ready">أعلى دقة محلية</span>
</div>
<div class="card-body">
<strong>خريطة تفصيلية باللغة العربية:</strong> تضم خريطتنا أكثر من <strong>100 إلى 120 ألف اسم ومعلم ومنشأة وشارع محلي موثق ومحدث بالعربية</strong> في كافة محافظات المملكة، وتتفوق في كثافتها الميدانية وتفاصيلها الواقعية على خرائط ماب بوكس وOpenStreetMap والمنصات الأجنبية، مما يجعلها الأكثر ملاءمة للتطبيقات التجارية وللمهام الميدانية.
</div>
</div>
<!-- Solution 3: Fleet Continuous Ingestion -->
<div class="card">
<div class="card-title">
<span>🔄 3. التغذية والتحديث المستمر عبر أساطيل النقل اليومية</span>
<span class="badge-ready">تحديث ذاتي</span>
</div>
<div class="card-body">
<strong>استشعار مكاني ذاتي ومستمر:</strong> توظيف حركة أساطيل النقل الذكي والمركبات الميدانية كمجسات ذكية ترصد وتلتقط الطرق المستحدثة، التحويلات، والإغلاقات تلقائياً عبر تقنيات الرؤية الحاسوبية على الأجهزة المحمولة، لتحديث الخريطة الوطنية باستمرار وبكلفة صفرية دون الحاجة لحملات مسح ميداني مكلفة.
</div>
</div>
<!-- Solution 4: Air-Gapped Local AI -->
<div class="card">
<div class="card-title">
<span>🤖 4. المستشار الاستراتيجي بالذكاء الاصطناعي المحلي المنعزل</span>
<span class="badge-ready">أمان وسرية 100%</span>
</div>
<div class="card-body">
<strong>تحليل استخباري محلي بدون اتصال بالإنترنت:</strong> دمج نماذج ذكاء اصطناعي متقدمة تعمل محلياً داخل خوادم القوات المسلحة المنعزلة تماماً، ومدربة تكتيكياً على الطبوغرافيا الأردنية لصياغة تقارير تقدير الموقف، محاكاة مسارات القوافل وتجنب الموانع، وتقديم استشارات ميدانية فورية دون تسريب أي معلومة للخارج.
</div>
</div>
</div>
</div>
<!-- Section 3: Comparative Capability Matrix -->
<div class="section">
<div class="section-header">
<div class="section-num">3</div>
<div class="section-title">مصفوفة المقارنة الاستراتيجية والتشغيلية</div>
</div>
<div class="table-container">
<table>
<thead>
<tr>
<th>المعيار والقدرة التشغيلية</th>
<th>منظومة سفر + المركز الجغرافي الملكي</th>
<th>خرائط جوجل (Google Maps)</th>
<th>المنصات الأجنبية الأخرى (Esri / Mapbox)</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>السيادة واستضافة البيانات</strong></td>
<td><span class="status-yes">✓ خوادم وطنية داخل المملكة 100%</span></td>
<td><span class="status-no">✗ سحابية أجنبية خاضعة لرقابة خارجية</span></td>
<td><span class="status-no">✗ خوادم خارجية وتراخيص استيرادية</span></td>
</tr>
<tr>
<td><strong>العمل في بيئة التشويش (انقطاع الـ GPS)</strong></td>
<td><span class="status-yes">✓ تعمل بكفاءة تامة (خوارزميات سفر الخاصة)</span></td>
<td><span class="status-no">✗ تتوقف تماماً عن العمل وتفقد الموقع</span></td>
<td><span class="status-no">✗ لا تملك حلول التموضع بدون أقمار</span></td>
</tr>
<tr>
<td><strong>كثافة المعالم والأسماء المحلية بالعربية</strong></td>
<td><span class="status-yes">✓ 100 - 120 ألف معلم محلي محدث بالعربية</span></td>
<td><span class="status-yes">✓ كثيفة ولكن بأسعار وتكاليف باهظة</span></td>
<td><span class="status-no">✗ فقيرة جداً في العناوين والأسماء المحلية</span></td>
</tr>
<tr>
<td><strong>التكامل مع تطبيقات النقل والتوصيل</strong></td>
<td><span class="status-yes">✓ تكامل محلي منخفض الكلفة وعالي السرعة</span></td>
<td><span class="status-no">✗ تكلفة باهظة تستنزف الشركات الوطنية</span></td>
<td><span class="status-no">✗ تفتقر لمحركات التوجيه المحلي الدقيق</span></td>
</tr>
<tr>
<td><strong>أدوات التحليل العسكري والتضاريسي ثلاثي الأبعاد</strong></td>
<td><span class="status-yes">✓ خطوط كنتور، تبادل رؤية، كشف تضاريسي 360°</span></td>
<td><span class="status-no">✗ غير متوفر نهائياً (خرائط مدنية عامة)</span></td>
<td><span class="status-no">✗ برمجيات مكتبية بطيئة ومعقدة</span></td>
</tr>
<tr>
<td><strong>الذكاء الاصطناعي المنعزل عن الإنترنت</strong></td>
<td><span class="status-yes">✓ نموذج محلي مدرب عسكرياً بدون إنترنت</span></td>
<td><span class="status-no">✗ محظور أمنياً في القوات المسلحة</span></td>
<td><span class="status-no">✗ غير متوفر محلياً</span></td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Section 4: Institutional Partnership Model -->
<div class="section">
<div class="section-header">
<div class="section-num">4</div>
<div class="section-title">نموذج الشراكة المؤسسية والتكامل الاستراتيجي المطلوب</div>
</div>
<p>
تقوم الشراكة المقترحة على مبدأ التكامل المؤسسي الذكي، حيث يركز كل طرف على أعلى نقاط قوته:
</p>
<div class="grid-2">
<div class="card" style="background: var(--slate-50);">
<div class="card-title">🏛️ المطلوب من المركز الجغرافي الملكي</div>
<div class="card-body">
<ul style="margin: 6px 18px; font-size: 0.92rem; color: var(--slate-700); line-height: 1.75;">
<li><strong>الإشراف والمصادقة الوطنية:</strong> منح الاعتماد السيادي الرسمي للمنظومة كخريطة رقمية وطنية معتمدة في المملكة.</li>
<li><strong>التكامل والمطابقة:</strong> إجراء اختبارات مقارنة ومطابقة للبيانات المرجعية لرفع المعايير الطبوغرافية.</li>
<li><strong>الواجهة الرسمية والتجارية:</strong> رعاية تسويق البوابة المكانية لكافة الوزارات، الهيئات الحكومية، والقطاعات الاقتصادية.</li>
</ul>
</div>
</div>
<div class="card" style="background: var(--slate-50);">
<div class="card-title">⚡ ما تقدمه شركة سفر</div>
<div class="card-body">
<ul style="margin: 6px 18px; font-size: 0.92rem; color: var(--slate-700); line-height: 1.75;">
<li><strong>المنظومة البرمجية المتكاملة:</strong> محركات بث الخرائط، خوادم التوجيه الملاحي، وخوارزميات التموضع بدون GPS.</li>
<li><strong>التطبيقات الذكية الجاهزة:</strong> تطبيقات الهواتف واللوحيات والأجهزة التكتيكية بنظام يعمل 100% بدون إنترنت.</li>
<li><strong>تغذية تطبيقات النقل واللوجستيات:</strong> ربط قطاع النقل الذكي والتوصيل بالمنصة وتحقيق العوائد التجارية المشتركة.</li>
<li><strong>التشغيل والصيانة والدعم:</strong> إدارة الخوادم، تطوير نماذج الذكاء الاصطناعي المحلية، وتحديث النظام باستمرار.</li>
</ul>
</div>
</div>
</div>
<div class="callout-box">
<div class="callout-title">العائد الوطني والاستثماري المحقق:</div>
تأسيس بنية تحتية مكانية وطنية تجارية ورسمية تدر أرباحاً تشغيلية مستدامة، تحمي البيانات الحساسة للمملكة، وتوفر للقوات المسلحة والأجهزة الأمنية تفوقاً ميدانياً مطلقاً وقت الأزمات.
</div>
</div>
<!-- Section 5: Executive Readiness & Live Demonstration -->
<div class="section">
<div class="section-header">
<div class="section-num">5</div>
<div class="section-title">الجاهزية التشغيلية والخطوة العملية القادمة</div>
</div>
<p>
إن المنظومة التكنولوجية المطورة لدى <strong>شركة سفر</strong> تعمل بكامل طاقتها وجاهزة للاختبار الفوري والميداني.
</p>
<p>
يسعدنا ويشرفنا توجيه الدعوة لسعادتكم ولفريقكم الفني والعملياتي لعقد <strong>جلسة عرض تشغيلي ومحاكاة حية</strong>؛ لمعاينة:
</p>
<ol style="margin: 12px 24px; font-size: 0.95rem; color: var(--slate-800); line-height: 1.85;">
<li>تجربة عملية لتحديد الموقع والإحداثيات بدون GPS عبر خوارزميات سفر المستقلة.</li>
<li>معاينة كفاءة وسرعة محرك الخرائط والملاحة ومقارنتها الحية مع خرائط جوجل.</li>
<li>استعراض تكامل المنصة مع تطبيقات النقل الذكي واللوجستيات، والتحليلات التكتيكية للتضاريس.</li>
<li>مناقشة آلية الإشراف والمصادقة الوطنية وتوقيع مذكرة التفاهم الاستراتيجي.</li>
</ol>
<div style="margin-top: 20px; padding: 14px 18px; background: var(--primary-light); border-radius: 8px; border: 1px solid #bfdbfe; font-size: 0.88rem; color: var(--primary-dark);">
🌐 <strong>بوابة المعاينة الحية للمنظومة:</strong> يمكن استعراض البنية التفاعلية للمنصة مباشرة عبر الرابط المعتمد: <span style="font-family: 'Inter', sans-serif; font-weight: 700; direction: ltr; display: inline-block;">https://map-saas.intaleqapp.com</span> (يتم تزويد الفريق الفني بمفتاح الدخول التجريبي المعتمد بناءً على طلبكم الرسمي).
</div>
</div>
<!-- Official Sign-off -->
<div class="signoff-box">
<div>
<div class="signoff-name">المقدم المتقاعد حمزة عايد الغويري</div>
<div class="signoff-title">المؤسس والمهندس المعماري الرئيسي لمنظومة الخرائط والذكاء التكتيكي</div>
<div class="signoff-company">شركة سفر لتكنولوجيا المعلومات (Sefer Tech)</div>
</div>
<div style="text-align: left;">
<div class="meta-tag">المملكة الأردنية الهاشمية — عمان</div>
<div style="font-size: 0.78rem; color: var(--slate-500); margin-top: 4px;">تاريخ التحرير: أغسطس 2026</div>
</div>
</div>
</div>
</body>
</html>
+513
View File
@@ -0,0 +1,513 @@
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>وثيقة الإيجاز الأمني والمعمارية التقنية | شركة سفر لتكنولوجيا المعلومات والمركز الجغرافي الملكي</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans+Arabic:wght@300;400;500;600;700;800&family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
<style>
:root {
--primary: #0f4c81; /* كحلي ملكي سيادي */
--primary-dark: #082a4a;
--primary-light: #eef5fc;
--accent: #854d0e; /* برونزي وقور */
--accent-light: #fef9c3;
--slate-900: #0f172a;
--slate-800: #1e293b;
--slate-700: #334155;
--slate-600: #475569;
--slate-500: #64748b;
--slate-200: #e2e8f0;
--slate-100: #f1f5f9;
--slate-50: #f8fafc;
--bg: #f8fafc;
--success: #059669;
--success-light: #d1fae5;
--danger: #b91c1c;
--danger-light: #fee2e2;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'IBM Plex Sans Arabic', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background-color: var(--bg);
color: var(--slate-900);
line-height: 1.85;
padding: 30px 15px;
-webkit-font-smoothing: antialiased;
direction: rtl;
text-align: right;
}
.document-wrapper {
max-width: 1080px;
margin: 0 auto;
background: #ffffff;
border: 1px solid #e2e8f0;
border-radius: 12px;
box-shadow: 0 4px 25px rgba(15, 23, 42, 0.05);
padding: 45px 55px;
}
.action-bar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
padding-bottom: 18px;
border-bottom: 2px solid var(--slate-100);
}
.btn-print {
background: var(--primary);
color: #ffffff;
border: none;
padding: 10px 22px;
border-radius: 8px;
font-weight: 700;
cursor: pointer;
font-family: inherit;
font-size: 0.92rem;
display: inline-flex;
align-items: center;
gap: 8px;
box-shadow: 0 2px 8px rgba(15, 76, 129, 0.2);
transition: all 0.2s ease;
}
.btn-print:hover {
background: var(--primary-dark);
transform: translateY(-1px);
}
.letterhead {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 22px;
border-bottom: 3px double var(--slate-200);
margin-bottom: 30px;
}
.company-title-ar {
font-size: 1.45rem;
font-weight: 800;
color: var(--primary);
letter-spacing: -0.02em;
}
.company-sub-ar {
font-size: 0.88rem;
color: var(--slate-600);
font-weight: 600;
}
.doc-badge {
display: inline-block;
background: #fee2e2;
border: 1px solid #fca5a5;
color: #991b1b;
padding: 4px 14px;
border-radius: 6px;
font-size: 0.82rem;
font-weight: 700;
margin-bottom: 6px;
}
.title-box {
background: linear-gradient(135deg, #f0f7ff, #f8fafc);
border-right: 6px solid var(--primary);
border-radius: 0 12px 12px 0;
padding: 26px 30px;
margin-bottom: 32px;
border-top: 1px solid #e2e8f0;
border-bottom: 1px solid #e2e8f0;
border-left: 1px solid #e2e8f0;
}
.addressed-to {
font-size: 1.15rem;
font-weight: 800;
color: var(--primary-dark);
margin-bottom: 8px;
}
.doc-subject {
font-size: 1.4rem;
font-weight: 800;
color: var(--slate-900);
line-height: 1.45;
margin-bottom: 10px;
}
.doc-summary {
font-size: 0.95rem;
color: var(--slate-700);
line-height: 1.75;
}
.section {
margin-bottom: 32px;
}
.section-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 2px solid var(--primary-light);
}
.section-num {
width: 32px;
height: 32px;
background: var(--primary);
color: #ffffff;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 800;
font-size: 0.95rem;
flex-shrink: 0;
}
.section-title {
font-size: 1.22rem;
font-weight: 800;
color: var(--primary-dark);
}
p {
margin-bottom: 14px;
color: var(--slate-700);
text-align: justify;
font-size: 0.96rem;
}
.grid-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 20px;
}
@media (max-width: 768px) {
.grid-2 {
grid-template-columns: 1fr;
}
.document-wrapper {
padding: 25px 20px;
}
}
.card {
background: #ffffff;
border: 1px solid var(--slate-200);
border-radius: 10px;
padding: 20px;
box-shadow: 0 2px 10px rgba(0,0,0,0.02);
}
.card-military {
border-top: 4px solid #1e3a8a;
background: #f8fafc;
}
.card-commercial {
border-top: 4px solid #059669;
background: #f0fdf4;
}
.card-title {
font-size: 1.05rem;
font-weight: 700;
color: var(--slate-900);
margin-bottom: 12px;
display: flex;
align-items: center;
gap: 8px;
}
.custom-table {
width: 100%;
border-collapse: collapse;
margin: 16px 0 24px 0;
font-size: 0.92rem;
}
.custom-table th, .custom-table td {
border: 1px solid var(--slate-200);
padding: 12px 16px;
text-align: right;
}
.custom-table th {
background: var(--primary-light);
color: var(--primary-dark);
font-weight: 700;
}
.custom-table tr:nth-child(even) {
background: var(--slate-50);
}
.callout-box {
background: #f8fafc;
border-right: 4px solid #0f4c81;
padding: 18px 22px;
border-radius: 0 8px 8px 0;
margin: 20px 0;
font-size: 0.94rem;
border-top: 1px solid #e2e8f0;
border-bottom: 1px solid #e2e8f0;
border-left: 1px solid #e2e8f0;
}
.callout-title {
font-weight: 700;
color: #0f4c81;
margin-bottom: 6px;
display: flex;
align-items: center;
gap: 6px;
}
.signoff-box {
margin-top: 40px;
padding-top: 25px;
border-top: 2px solid var(--slate-200);
display: flex;
justify-content: space-between;
align-items: flex-end;
}
.signoff-name {
font-size: 1.12rem;
font-weight: 800;
color: var(--primary-dark);
}
.signoff-title {
font-size: 0.88rem;
color: var(--slate-600);
font-weight: 600;
}
.signoff-company {
font-size: 0.84rem;
color: var(--slate-500);
margin-top: 2px;
}
@media print {
body {
padding: 0;
background: #ffffff;
}
.action-bar {
display: none;
}
.document-wrapper {
border: none;
box-shadow: none;
padding: 0;
}
.section {
page-break-inside: avoid;
}
}
</style>
</head>
<body>
<div class="document-wrapper">
<!-- Top Action Bar -->
<div class="action-bar">
<div>
<span class="doc-badge">مقيّد — للاطلاع الفني الداخلي فقط</span>
<div style="font-size: 0.82rem; color: var(--slate-500);">الرقم المرجعي: س ف / أ م ن / 2026 / 04</div>
</div>
<button class="btn-print" onclick="window.print()">
🖨️ طباعة أو حفظ كملف PDF
</button>
</div>
<!-- Official Header -->
<div class="letterhead">
<div>
<div class="company-title-ar">شركة سفر لتكنولوجيا المعلومات</div>
<div class="company-sub-ar">Sefer Tech — حلول السيادة المكانية والأنظمة التكتيكية المتقدمة</div>
</div>
<div style="text-align: left; direction: ltr;">
<div style="font-size: 0.84rem; font-weight: 700; color: var(--slate-700);">Sefer Technology Ltd.</div>
<div style="font-size: 0.78rem; color: var(--slate-500);">Regional Tech Center — Cairo / Amman</div>
</div>
</div>
<!-- Title Box -->
<div class="title-box">
<div class="addressed-to">عطوفة مدير عام المركز الجغرافي الملكي الأردني والمديريات الفنية المختصة</div>
<div class="doc-subject">وثيقة الإيجاز الأمني والمعمارية التقنية الشاملة لمنظومة السيادة المكانية الوطنية</div>
<div class="doc-summary">
إيجاز فني وأمني رسمي يستعرض معمارية الفصل التام بين المسارين العسكري والتجاري، طبقات تحصين التطبيقات، كشف محاولات الاختراق والسرقة، وتأمين البنية التحتية للخوادم والـ APIs وفق أعلى المعايير الدفاعية.
</div>
</div>
<!-- Section 1: Architecture Split -->
<div class="section">
<div class="section-header">
<div class="section-num">1</div>
<div class="section-title">المعمارية التقنية ثنائية المسار (Military & Commercial Split)</div>
</div>
<p>
تقوم المنظومة على مبدأ الفصل الفيزيائي والمنطقي الكامل بين البيئة العملياتية العسكرية والبيئة التجارية العامة، لضمان عدم وجود أي نقطة تقاطع أو تسريب للبيانات الحساسة:
</p>
<div class="grid-2">
<div class="card card-military">
<div class="card-title">🛡️ المسار الأول: البيئة العسكرية والتكتيكية (Air-Gapped)</div>
<ul style="margin: 6px 18px; font-size: 0.92rem; color: var(--slate-700); line-height: 1.8;">
<li><strong>شبكة داخلية معزولة بالكامل (Intranet):</strong> لا تتصل بالإنترنت العام إطلاقاً، وتعمل بنسبة 100% Offline.</li>
<li><strong>خوادم داخلية سيادية:</strong> مستضافة فيزيائياً داخل مقرات المركز الجغرافي أو القيادة العامة.</li>
<li><strong>بيانات طبوغرافية مشفرة:</strong> قواعد بيانات التضاريس والارتفاعات مخزنة محلياً بالكامل ولا تخرج من الشبكة.</li>
<li><strong>تطبيقات ميدانية مسبقة التثبيت:</strong> تعمل على أجهزة لوحية مخصصة بدون أي شريحة إنترنت أو اتصال خارجي.</li>
</ul>
</div>
<div class="card card-commercial">
<div class="card-title">🌐 المسار الثاني: البيئة التجارية والخدمات العامة</div>
<ul style="margin: 6px 18px; font-size: 0.92rem; color: var(--slate-700); line-height: 1.8;">
<li><strong>بوابة خوادم مؤمّنة:</strong> مخصصة لتغذية قطاع النقل الذكي وتطبيقات التوصيل والخدمات اللوجستية في الأردن.</li>
<li><strong>بيانات خرائط مدنية فقط:</strong> لا تحتوي على أي معلومات أو منشآت أو مسارات عسكرية أو أمنية.</li>
<li><strong>واجهات برمجة مؤمّنة (APIs/SDKs):</strong> محمية بمفاتيح رقمية مشفرة ونطاقات وصول محددة للشركات المرخصة.</li>
<li><strong>عوائد استثمارية مستدامة:</strong> تدر إيرادات مباشرة للمركز الجغرافي الملكي من الاستخدام التجاري.</li>
</ul>
</div>
</div>
</div>
<!-- Section 2: Application Hardening -->
<div class="section">
<div class="section-header">
<div class="section-num">2</div>
<div class="section-title">طبقات تحصين وتأمين التطبيق الميداني (Defense-in-Depth)</div>
</div>
<p>
تم بناء التطبيق الميداني وفق 5 طبقات حماية متسلسلة تضمن استحالة استغلاله أو قراءته حتى في حال وقوع الجهاز في أيدي معادية:
</p>
<table class="custom-table">
<thead>
<tr>
<th style="width: 25%;">طبقة الحماية</th>
<th style="width: 45%;">الآلية التقنية المطبقة</th>
<th style="width: 30%;">الهدف الأمني المباشر</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>تظليل الكود البرمجي (Obfuscation)</strong></td>
<td>تشفير وإخفاء أسماء الدوال والمتغيرات وهيكلية الكود في طبقات Swift و Kotlin و C++.</td>
<td>منع الهندسة العكسية وفك تشفير التطبيق كلياً.</td>
</tr>
<tr>
<td><strong>كشف كسر الحماية (Jailbreak / Root)</strong></td>
<td>فحص نواتي عميق (Native C++ / Swift) يكشف وجود SuperSU, Magisk, Cydia, ومحاولات الحقن الديناميكي.</td>
<td>إيقاف التطبيق فوراً ومسح الذاكرة المؤقتة في حال التلاعب بنظام التشغيل.</td>
</tr>
<tr>
<td><strong>التخزين المؤمّن عتادياً (Hardware Keystore)</strong></td>
<td>استخدام Flutter Secure Storage المدعوم بـ iOS Keychain و Android Keystore المشفر عتادياً.</td>
<td>حماية المفاتيح الرقمية والبصمات حتى لو تم نسخ ملفات الجهاز فيزيائياً.</td>
</tr>
<tr>
<td><strong>التوزيع والتثبيت المغلق (Restricted Provisioning)</strong></td>
<td>التطبيق غير منشور نهائياً على المتاجر العامة (App Store / Google Play)، ويثبت مسبقاً داخل المركز فقط.</td>
<td>حصر استخدام المنظومة بالأجهزة المعتمدة والمصرح لها رسمياً.</td>
</tr>
<tr>
<td><strong>بروتوكول مكافحة السرقة والتدمير الذاتي</strong></td>
<td>كشف تغيير شريحة الاتصال، ومسح ذاتي للبيانات المشفرة عند تكرار إدخال كلمات مرور خاطئة أو كشف اختراق.</td>
<td>ضمان عدم تسريب أي أثر للبيانات في حال فقدان الجهاز في الميدان.</td>
</tr>
</tbody>
</table>
</div>
<!-- Section 3: Server & Network Security -->
<div class="section">
<div class="section-header">
<div class="section-num">3</div>
<div class="section-title">أمن الخوادم وواجهات الربط البرمجي (Server & API Hardening)</div>
</div>
<div class="grid-2">
<div class="card">
<div class="card-title">🔒 إدارة وتأمين واجهات البرمجة (APIs)</div>
<ul style="margin: 6px 18px; font-size: 0.92rem; color: var(--slate-700); line-height: 1.8;">
<li><strong>مفاتيح رقمية فريدة ومشفرة:</strong> تخصيص مفتاح وصول لكل جهة مع تحديد النطاق الجغرافي وعدد الطلبات بالثانية (Rate Limiting).</li>
<li><strong>تثبيت الشهادات الرقمية (Certificate Pinning):</strong> لمنع هجمات اعتراض حركة المرور (Man-in-the-Middle).</li>
<li><strong>تشفير قنوات الاتصال:</strong> الاعتماد الحصري على بروتوكول TLS 1.3 مع حظر البروتوكولات القديمة.</li>
</ul>
</div>
<div class="card">
<div class="card-title">🏢 تحصين البنية التحتية وجدران الحماية</div>
<ul style="margin: 6px 18px; font-size: 0.92rem; color: var(--slate-700); line-height: 1.8;">
<li><strong>جدار حماية تطبيقات الويب (WAF):</strong> كشف وصد محاولات الحقن (SQLi) وهجمات الحرمان من الخدمة (DDoS).</li>
<li><strong>سجلات تدقيق أمنية غير قابلة للتعديل (Immutable Audit Logs):</strong> رصد وتسجيل كل عملية دخول وطلب على المنظومة للمراجعة الأمنية.</li>
<li><strong>جاهزية تامة لاختبارات الاختراق:</strong> استعداد كامل لإخضاع المنظومة للفحص الأمني الشامل من قبل قسم الأمن السيبراني لديكم.</li>
</ul>
</div>
</div>
</div>
<!-- Section 4: Operational Readiness & Future Roadmap -->
<div class="section">
<div class="section-header">
<div class="section-num">4</div>
<div class="section-title">القدرات التشغيلية الحالية والتطلعات التطويرية المشتركة</div>
</div>
<div class="callout-box">
<div class="callout-title">⚡ القدرات الجاهزة للاختبار الفوري في جلسة العرض الحي:</div>
محرك الملاحة والتوجيه المحلي السريع، قاعدة المعالم الأردنية الضخمة (120 ألف معلم بالعربية)، خوارزميات التموضع بدون GPS في بيئات التشويش (100% Offline)، والتحليل التضاريسي ثلاثي الأبعاد وخطوط الكنتور.
</div>
<div class="card" style="background: var(--slate-50);">
<div class="card-title">🎯 التطلعات المستقبلية المخطط لتطويرها بالشراكة مع المركز:</div>
<ol style="margin: 8px 20px; font-size: 0.93rem; color: var(--slate-800); line-height: 1.85;">
<li><strong>منظومة التصوير البانورامي التكتيكي 360° (Street-Level Tactical Reconnaissance):</strong> مسح بصري رقمي للشوارع والمسارات الحيوية لخدمة غرف العمليات وإدارة الأزمات.</li>
<li><strong>منظومة محاكاة المناورات والعمليات المشتركة (Tactical Wargaming Platform):</strong> بيئة محاكاة تفاعلية ثلاثية الأبعاد لاختبار تحركات وتوزيع القوات وتكتيكات الدفاع والهجوم على طبوغرافية حقيقية.</li>
<li><strong>الذكاء الاصطناعي التكتيكي المحلي (Edge Offline AI):</strong> نماذج ذكاء اصطناعي تعمل على الأجهزة الميدانية بدون اتصال لتحليل التضاريس وتقدير المخاطر العملياتية.</li>
</ol>
</div>
</div>
<!-- Official Sign-off -->
<div class="signoff-box">
<div>
<div class="signoff-name">المقدم المتقاعد حمزة عايد الغويري</div>
<div class="signoff-title">المؤسس والمهندس المعماري الرئيسي للمنظومة</div>
<div class="signoff-company">شركة سفر لتكنولوجيا المعلومات (Sefer Tech)</div>
</div>
<div style="text-align: left;">
<div style="font-size: 0.88rem; font-weight: 700; color: var(--primary);">المملكة الأردنية الهاشمية — عمان</div>
<div style="font-size: 0.78rem; color: var(--slate-500); margin-top: 4px;">تاريخ التحرير: آب / أغسطس 2026</div>
</div>
</div>
</div>
</body>
</html>
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+390
View File
@@ -0,0 +1,390 @@
const fs = require('fs');
const path = require('path');
const stylePath = path.join(__dirname, '../public/style.json');
const tacticalStylePath = path.join(__dirname, '../public/tactical-style.json');
const baseStyle = JSON.parse(fs.readFileSync(stylePath, 'utf8'));
// Clone the base style and add custom tactical vector sources
const tacticalStyle = {
...baseStyle,
name: "Intaleq Sovereign Tactical Military Style (منظومة الدفاع التكتيكية)",
metadata: {
...baseStyle.metadata,
version: "3.2.0-tactical",
description: "Sovereign Military Tactical Style with Hillshade, Rock Escarpments, Cliffs, Retaining Walls, Berms, Wadis, Quarries, and Man-Made Obstacles"
},
sources: {
...baseStyle.sources,
"tactical-obstacles": {
type: "vector",
tiles: ["https://tiles.intaleqapp.com/tactical_terrain_obstacles/{z}/{x}/{y}"],
maxzoom: 16
},
"overture_land_cover": {
type: "vector",
tiles: ["https://tiles.intaleqapp.com/overture_land_cover/{z}/{x}/{y}"],
maxzoom: 14
}
}
};
// Define Tactical Obstacle & Terrain Layers with explicit source-layer
const tacticalLayers = [
// ── 1. OVERTURE LAND COVER (الصخور الجرداء والموانع الطبيعية من خرائط أوفرتشر) ──
{
id: "tactical-overture-barren-rock",
type: "fill",
source: "overture_land_cover",
"source-layer": "overture_land_cover",
filter: ["any",
["in", "subtype", "barren", "rock", "sand"],
["in", "class", "barren", "rock", "bare_ground"]
],
paint: {
"fill-color": "#78716c",
"fill-opacity": 0.4
}
},
{
id: "tactical-overture-barren-outline",
type: "line",
source: "overture_land_cover",
"source-layer": "overture_land_cover",
filter: ["any",
["in", "subtype", "barren", "rock", "sand"],
["in", "class", "barren", "rock", "bare_ground"]
],
paint: {
"line-color": "#57534e",
"line-width": 1.6,
"line-dasharray": [3, 2]
}
},
{
id: "tactical-overture-wetland",
type: "fill",
source: "overture_land_cover",
"source-layer": "overture_land_cover",
filter: ["any",
["==", "subtype", "wetland"],
["==", "class", "wetland"]
],
paint: {
"fill-color": "#0d9488",
"fill-opacity": 0.35
}
},
// ── 2. NATURAL ROCK & TERRAIN POLYGONS (المقاطع والكتل الصخرية والمقالع) ──
{
id: "tactical-bare-rock-poly",
type: "fill",
source: "local-osm-polygons",
"source-layer": "planet_osm_polygon",
filter: ["in", "natural", "bare_rock", "rock", "scree", "shingle", "bedrock"],
paint: {
"fill-color": "#78716c",
"fill-opacity": 0.45
}
},
{
id: "tactical-bare-rock-outline",
type: "line",
source: "local-osm-polygons",
"source-layer": "planet_osm_polygon",
filter: ["in", "natural", "bare_rock", "rock", "scree", "shingle", "bedrock"],
paint: {
"line-color": "#57534e",
"line-width": 1.8,
"line-dasharray": [3, 2]
}
},
{
id: "tactical-quarry-poly",
type: "fill",
source: "local-osm-polygons",
"source-layer": "planet_osm_polygon",
filter: ["any", ["==", "landuse", "quarry"], ["==", "man_made", "quarry"], ["==", "landuse", "surface_mining"]],
paint: {
"fill-color": "#d97706",
"fill-opacity": 0.35
}
},
{
id: "tactical-quarry-outline",
type: "line",
source: "local-osm-polygons",
"source-layer": "planet_osm_polygon",
filter: ["any", ["==", "landuse", "quarry"], ["==", "man_made", "quarry"], ["==", "landuse", "surface_mining"]],
paint: {
"line-color": "#b45309",
"line-width": 2.5,
"line-dasharray": [4, 2]
}
},
{
id: "tactical-wetland-poly",
type: "fill",
source: "local-osm-polygons",
"source-layer": "planet_osm_polygon",
filter: ["any", ["==", "natural", "wetland"], ["==", "wetland", "marsh"], ["==", "natural", "marsh"]],
paint: {
"fill-color": "#0d9488",
"fill-opacity": 0.35
}
},
// ── 3. HYDROLOGICAL DEFILES & WADIS (مجاري الأودية والسيول الجافة) ──
{
id: "tactical-wadis-lines",
type: "line",
source: "local-osm-lines",
"source-layer": "planet_osm_line",
filter: ["in", "waterway", "wadi", "dry_stream", "drain", "ditch"],
paint: {
"line-color": "#0891b2",
"line-width": 2.4,
"line-dasharray": [3, 2],
"line-opacity": 0.95
}
},
// ── 4. CLIFFS & ESCARPMENTS (الجروف الصخرية الشاهقة والنتوءات الحادة) ──
{
id: "tactical-cliffs-casing",
type: "line",
source: "local-osm-lines",
"source-layer": "planet_osm_line",
filter: ["==", "natural", "cliff"],
paint: {
"line-color": "#451a03",
"line-width": 5.0,
"line-opacity": 0.95
}
},
{
id: "tactical-cliffs-inner",
type: "line",
source: "local-osm-lines",
"source-layer": "planet_osm_line",
filter: ["==", "natural", "cliff"],
paint: {
"line-color": "#dc2626",
"line-width": 2.5,
"line-dasharray": [2, 2]
}
},
{
id: "tactical-ridges-lines",
type: "line",
source: "local-osm-lines",
"source-layer": "planet_osm_line",
filter: ["in", "natural", "ridge", "arete"],
paint: {
"line-color": "#92400e",
"line-width": 2.8,
"line-dasharray": [4, 2]
}
},
// ── 5. POSTGIS DEDICATED TACTICAL OBSTACLES TABLE (جدول الموانع التكتيكية المدمج) ──
{
id: "tactical-db-cliffs",
type: "line",
source: "tactical-obstacles",
"source-layer": "tactical_terrain_obstacles",
filter: ["==", "severity", "SEVERE_NO_GO"],
paint: {
"line-color": "#dc2626",
"line-width": 4.5,
"line-dasharray": [3, 1]
}
},
{
id: "tactical-db-retaining-walls",
type: "line",
source: "tactical-obstacles",
"source-layer": "tactical_terrain_obstacles",
filter: ["==", "severity", "RESTRICTED"],
paint: {
"line-color": "#e11d48",
"line-width": 3.2
}
},
{
id: "tactical-db-barriers",
type: "line",
source: "tactical-obstacles",
"source-layer": "tactical_terrain_obstacles",
filter: ["==", "severity", "TACTICAL_BARRIER"],
paint: {
"line-color": "#f59e0b",
"line-width": 3.0,
"line-dasharray": [3, 2]
}
},
{
id: "tactical-db-wadis",
type: "line",
source: "tactical-obstacles",
"source-layer": "tactical_terrain_obstacles",
filter: ["==", "severity", "DRAINAGE_DEFILE"],
paint: {
"line-color": "#0891b2",
"line-width": 2.6,
"line-dasharray": [3, 2]
}
},
// ── 6. MAN-MADE BARRIERS (الجدران الاستنادية، السواتر الترابية، الحواجز والأسوار) ──
{
id: "tactical-retaining-walls",
type: "line",
source: "local-osm-lines",
"source-layer": "planet_osm_line",
filter: ["==", "barrier", "retaining_wall"],
paint: {
"line-color": "#e11d48",
"line-width": 3.2,
"line-opacity": 1.0
}
},
{
id: "tactical-berms-embankments",
type: "line",
source: "local-osm-lines",
"source-layer": "planet_osm_line",
filter: ["any", ["==", "barrier", "berm"], ["==", "man_made", "embankment"]],
paint: {
"line-color": "#f59e0b",
"line-width": 3.0,
"line-dasharray": [3, 1]
}
},
{
id: "tactical-trenches-ditches",
type: "line",
source: "local-osm-lines",
"source-layer": "planet_osm_line",
filter: ["==", "barrier", "ditch"],
paint: {
"line-color": "#881337",
"line-width": 2.5,
"line-dasharray": [2, 2]
}
},
{
id: "tactical-fences-walls",
type: "line",
source: "local-osm-lines",
"source-layer": "planet_osm_line",
filter: ["in", "barrier", "wall", "jersey_barrier", "fence", "wire_fence"],
paint: {
"line-color": "#991b1b",
"line-width": 2.0,
"line-dasharray": [3, 2]
}
},
// ── 7. TACTICAL POINTS (فتحات المغاور والكهوف، الشلالات، والمكاشف الصخرية) ──
{
id: "tactical-cave-entrances",
type: "circle",
source: "local-osm-points",
"source-layer": "planet_osm_point",
filter: ["==", "natural", "cave_entrance"],
paint: {
"circle-radius": 6.0,
"circle-color": "#1e293b",
"circle-stroke-color": "#f59e0b",
"circle-stroke-width": 2.5
}
},
{
id: "tactical-waterfalls",
type: "circle",
source: "local-osm-points",
"source-layer": "planet_osm_point",
filter: ["==", "waterway", "waterfall"],
paint: {
"circle-radius": 5.5,
"circle-color": "#06b6d4",
"circle-stroke-color": "#ffffff",
"circle-stroke-width": 2
}
},
{
id: "tactical-outcrops",
type: "circle",
source: "local-osm-points",
"source-layer": "planet_osm_point",
filter: ["==", "geological", "outcrop"],
paint: {
"circle-radius": 5.0,
"circle-color": "#c2410c",
"circle-stroke-color": "#ffffff",
"circle-stroke-width": 1.5
}
},
// ── 8. TACTICAL LABELS (تسميات الموانع والمعالم التكتيكية الحاكمة) ──
{
id: "tactical-obstacle-text-lines",
type: "symbol",
source: "local-osm-lines",
"source-layer": "planet_osm_line",
filter: ["any",
["==", "natural", "cliff"],
["==", "barrier", "retaining_wall"],
["==", "waterway", "wadi"],
["in", "natural", "ridge", "arete"]
],
minzoom: 12,
layout: {
"symbol-placement": "line",
"text-field": "{name}",
"text-size": 11,
"text-font": ["Noto Sans Regular"],
"text-letter-spacing": 0.05
},
paint: {
"text-color": "#7f1d1d",
"text-halo-color": "#ffffff",
"text-halo-width": 2.0
}
},
{
id: "tactical-obstacle-text-poly",
type: "symbol",
source: "local-osm-polygons",
"source-layer": "planet_osm_polygon",
filter: ["any",
["==", "landuse", "quarry"],
["in", "natural", "bare_rock", "rock", "scree", "wetland"]
],
minzoom: 12,
layout: {
"text-field": "{name}",
"text-size": 11,
"text-font": ["Noto Sans Regular"]
},
paint: {
"text-color": "#78350f",
"text-halo-color": "#ffffff",
"text-halo-width": 2.0
}
}
];
// Insert the tactical layers before road labels and text symbols for optimal z-ordering
const textIndex = tacticalStyle.layers.findIndex((l) => l.id && (l.id.includes('label') || l.type === 'symbol'));
if (textIndex > -1) {
tacticalStyle.layers.splice(textIndex, 0, ...tacticalLayers);
} else {
tacticalStyle.layers.push(...tacticalLayers);
}
fs.writeFileSync(tacticalStylePath, JSON.stringify(tacticalStyle, null, 2), 'utf8');
console.log(`✅ Successfully generated tactical-style.json with ${tacticalLayers.length} tactical layers and Overture/PostGIS sources!`);
+265
View File
@@ -0,0 +1,265 @@
const fs = require('fs');
const path = require('path');
const stylePath = path.join(__dirname, '../public/style.json');
const tacticalStylePath = path.join(__dirname, '../public/tactical-style.json');
const baseStyle = JSON.parse(fs.readFileSync(stylePath, 'utf8'));
// Clone the base style
const tacticalStyle = {
...baseStyle,
name: "Intaleq Sovereign Tactical Military Style (منظومة الدفاع التكتيكية)",
metadata: {
...baseStyle.metadata,
version: "3.0.0-tactical",
description: "Sovereign Military Tactical Style with Rock Escarpments, Cliffs, Retaining Walls, Berms, Wadis, Quarries, and Man-Made Obstacles"
}
};
// Define Tactical Obstacle & Terrain Layers
const tacticalLayers = [
// ── 1. NATURAL ROCK & TERRAIN POLYGONS (المقاطع والكتل الصخرية والمقالع) ──
{
id: "tactical-bare-rock-poly",
type: "fill",
source: "local-osm-polygons",
filter: ["in", "natural", "bare_rock", "rock", "scree", "shingle", "bedrock"],
paint: {
"fill-color": "#78716c",
"fill-opacity": 0.45
}
},
{
id: "tactical-bare-rock-outline",
type: "line",
source: "local-osm-polygons",
filter: ["in", "natural", "bare_rock", "rock", "scree", "shingle", "bedrock"],
paint: {
"line-color": "#57534e",
"line-width": 1.8,
"line-dasharray": [3, 2]
}
},
{
id: "tactical-quarry-poly",
type: "fill",
source: "local-osm-polygons",
filter: ["any", ["==", "landuse", "quarry"], ["==", "man_made", "quarry"], ["==", "landuse", "surface_mining"]],
paint: {
"fill-color": "#d97706",
"fill-opacity": 0.3
}
},
{
id: "tactical-quarry-outline",
type: "line",
source: "local-osm-polygons",
filter: ["any", ["==", "landuse", "quarry"], ["==", "man_made", "quarry"], ["==", "landuse", "surface_mining"]],
paint: {
"line-color": "#b45309",
"line-width": 2.2,
"line-dasharray": [4, 2]
}
},
{
id: "tactical-wetland-poly",
type: "fill",
source: "local-osm-polygons",
filter: ["any", ["==", "natural", "wetland"], ["==", "wetland", "marsh"], ["==", "natural", "marsh"]],
paint: {
"fill-color": "#0d9488",
"fill-opacity": 0.35
}
},
// ── 2. HYDROLOGICAL DEFILES & WADIS (مجاري الأودية والسيول الجافة) ──
{
id: "tactical-wadis-lines",
type: "line",
source: "local-osm-lines",
filter: ["in", "waterway", "wadi", "dry_stream", "drain", "ditch"],
paint: {
"line-color": "#0891b2",
"line-width": 2.4,
"line-dasharray": [3, 2],
"line-opacity": 0.95
}
},
// ── 3. CLIFFS & ESCARPMENTS (الجروف الصخرية الشاهقة والنتوءات الحادة) ──
{
id: "tactical-cliffs-casing",
type: "line",
source: "local-osm-lines",
filter: ["==", "natural", "cliff"],
paint: {
"line-color": "#451a03",
"line-width": 5.0,
"line-opacity": 0.95
}
},
{
id: "tactical-cliffs-inner",
type: "line",
source: "local-osm-lines",
filter: ["==", "natural", "cliff"],
paint: {
"line-color": "#dc2626",
"line-width": 2.5,
"line-dasharray": [2, 2]
}
},
{
id: "tactical-ridges-lines",
type: "line",
source: "local-osm-lines",
filter: ["in", "natural", "ridge", "arete"],
paint: {
"line-color": "#92400e",
"line-width": 2.8,
"line-dasharray": [4, 2]
}
},
// ── 4. MAN-MADE BARRIERS (الجدران الاستنادية، السواتر الترابية، الحواجز والأسوار) ──
{
id: "tactical-retaining-walls",
type: "line",
source: "local-osm-lines",
filter: ["==", "barrier", "retaining_wall"],
paint: {
"line-color": "#e11d48",
"line-width": 3.2,
"line-opacity": 1.0
}
},
{
id: "tactical-berms-embankments",
type: "line",
source: "local-osm-lines",
filter: ["any", ["==", "barrier", "berm"], ["==", "man_made", "embankment"]],
paint: {
"line-color": "#f59e0b",
"line-width": 3.0,
"line-dasharray": [3, 1]
}
},
{
id: "tactical-trenches-ditches",
type: "line",
source: "local-osm-lines",
filter: ["==", "barrier", "ditch"],
paint: {
"line-color": "#881337",
"line-width": 2.5,
"line-dasharray": [2, 2]
}
},
{
id: "tactical-fences-walls",
type: "line",
source: "local-osm-lines",
filter: ["in", "barrier", "wall", "jersey_barrier", "fence", "wire_fence"],
paint: {
"line-color": "#991b1b",
"line-width": 2.0,
"line-dasharray": [3, 2]
}
},
// ── 5. TACTICAL POINTS (فتحات المغاور والكهوف، الشلالات، والمكاشف الصخرية) ──
{
id: "tactical-cave-entrances",
type: "circle",
source: "local-osm-points",
filter: ["==", "natural", "cave_entrance"],
paint: {
"circle-radius": 6.0,
"circle-color": "#1e293b",
"circle-stroke-color": "#f59e0b",
"circle-stroke-width": 2.5
}
},
{
id: "tactical-waterfalls",
type: "circle",
source: "local-osm-points",
filter: ["==", "waterway", "waterfall"],
paint: {
"circle-radius": 5.5,
"circle-color": "#06b6d4",
"circle-stroke-color": "#ffffff",
"circle-stroke-width": 2
}
},
{
id: "tactical-outcrops",
type: "circle",
source: "local-osm-points",
filter: ["==", "geological", "outcrop"],
paint: {
"circle-radius": 5.0,
"circle-color": "#c2410c",
"circle-stroke-color": "#ffffff",
"circle-stroke-width": 1.5
}
},
// ── 6. TACTICAL LABELS (تسميات الموانع والمعالم التكتيكية الحاكمة) ──
{
id: "tactical-obstacle-text-lines",
type: "symbol",
source: "local-osm-lines",
filter: ["any",
["==", "natural", "cliff"],
["==", "barrier", "retaining_wall"],
["==", "waterway", "wadi"],
["in", "natural", "ridge", "arete"]
],
minzoom: 12,
layout: {
"symbol-placement": "line",
"text-field": "{name}",
"text-size": 11,
"text-font": ["Noto Sans Arabic Bold", "Open Sans Bold"],
"text-letter-spacing": 0.05
},
paint: {
"text-color": "#7f1d1d",
"text-halo-color": "#ffffff",
"text-halo-width": 2.0
}
},
{
id: "tactical-obstacle-text-poly",
type: "symbol",
source: "local-osm-polygons",
filter: ["any",
["==", "landuse", "quarry"],
["in", "natural", "bare_rock", "rock", "scree", "wetland"]
],
minzoom: 12,
layout: {
"text-field": "{name}",
"text-size": 11,
"text-font": ["Noto Sans Arabic Bold", "Open Sans Bold"]
},
paint: {
"text-color": "#78350f",
"text-halo-color": "#ffffff",
"text-halo-width": 2.0
}
}
];
// Insert the tactical layers before road labels and text symbols for optimal z-ordering
const textIndex = tacticalStyle.layers.findIndex((l) => l.id && (l.id.includes('label') || l.type === 'symbol'));
if (textIndex > -1) {
tacticalStyle.layers.splice(textIndex, 0, ...tacticalLayers);
} else {
tacticalStyle.layers.push(...tacticalLayers);
}
fs.writeFileSync(tacticalStylePath, JSON.stringify(tacticalStyle, null, 2), 'utf8');
console.log(`✅ Successfully generated tactical-style.json with ${tacticalLayers.length} tactical obstacle layers!`);
+265
View File
@@ -0,0 +1,265 @@
import * as fs from 'fs';
import * as path from 'path';
const stylePath = path.join(__dirname, '../public/style.json');
const tacticalStylePath = path.join(__dirname, '../public/tactical-style.json');
const baseStyle = JSON.parse(fs.readFileSync(stylePath, 'utf8'));
// Clone the base style
const tacticalStyle = {
...baseStyle,
name: "Intaleq Sovereign Tactical Military Style (منظومة الدفاع التكتيكية)",
metadata: {
...baseStyle.metadata,
version: "3.0.0-tactical",
description: "Sovereign Military Tactical Style with Rock Escarpments, Cliffs, Retaining Walls, Berms, Wadis, Quarries, and Man-Made Obstacles"
}
};
// Define Tactical Obstacle & Terrain Layers
const tacticalLayers = [
// ── 1. NATRUAL ROCK & TERRAIN POLYGONS (المقاطع والكتل الصخرية والمقالع) ──
{
id: "tactical-bare-rock-poly",
type: "fill",
source: "local-osm-polygons",
filter: ["in", "natural", "bare_rock", "rock", "scree", "shingle", "bedrock"],
paint: {
"fill-color": "#78716c",
"fill-opacity": 0.4
}
},
{
id: "tactical-bare-rock-outline",
type: "line",
source: "local-osm-polygons",
filter: ["in", "natural", "bare_rock", "rock", "scree", "shingle", "bedrock"],
paint: {
"line-color": "#57534e",
"line-width": 1.5,
"line-dasharray": [3, 2]
}
},
{
id: "tactical-quarry-poly",
type: "fill",
source: "local-osm-polygons",
filter: ["any", ["==", "landuse", "quarry"], ["==", "man_made", "quarry"], ["==", "landuse", "surface_mining"]],
paint: {
"fill-color": "#d97706",
"fill-opacity": 0.25
}
},
{
id: "tactical-quarry-outline",
type: "line",
source: "local-osm-polygons",
filter: ["any", ["==", "landuse", "quarry"], ["==", "man_made", "quarry"], ["==", "landuse", "surface_mining"]],
paint: {
"line-color": "#b45309",
"line-width": 2.0,
"line-dasharray": [4, 2]
}
},
{
id: "tactical-wetland-poly",
type: "fill",
source: "local-osm-polygons",
filter: ["any", ["==", "natural", "wetland"], ["==", "wetland", "marsh"], ["==", "natural", "marsh"]],
paint: {
"fill-color": "#0d9488",
"fill-opacity": 0.35
}
},
// ── 2. HYDROLOGICAL DEFILS & WADIS (مجاري الأودية والسيول الجافة) ──
{
id: "tactical-wadis-lines",
type: "line",
source: "local-osm-lines",
filter: ["in", "waterway", "wadi", "dry_stream", "drain", "ditch"],
paint: {
"line-color": "#0891b2",
"line-width": 2.2,
"line-dasharray": [3, 2],
"line-opacity": 0.95
}
},
// ── 3. CLIFFS & ESCARPMENTS (الجروف الصخرية الشاهقة والنتوءات الحادة) ──
{
id: "tactical-cliffs-casing",
type: "line",
source: "local-osm-lines",
filter: ["==", "natural", "cliff"],
paint: {
"line-color": "#451a03",
"line-width": 4.5,
"line-opacity": 0.9
}
},
{
id: "tactical-cliffs-inner",
type: "line",
source: "local-osm-lines",
filter: ["==", "natural", "cliff"],
paint: {
"line-color": "#dc2626",
"line-width": 2.2,
"line-dasharray": [2, 2]
}
},
{
id: "tactical-ridges-lines",
type: "line",
source: "local-osm-lines",
filter: ["in", "natural", "ridge", "arete"],
paint: {
"line-color": "#92400e",
"line-width": 2.5,
"line-dasharray": [4, 2]
}
},
// ── 4. MAN-MADE BARRIERS (الجدران الاستنادية، السواتر الترابية، الحواجز والأسوار) ──
{
id: "tactical-retaining-walls",
type: "line",
source: "local-osm-lines",
filter: ["==", "barrier", "retaining_wall"],
paint: {
"line-color": "#e11d48",
"line-width": 3.0,
"line-opacity": 1.0
}
},
{
id: "tactical-berms-embankments",
type: "line",
source: "local-osm-lines",
filter: ["any", ["==", "barrier", "berm"], ["==", "man_made", "embankment"]],
paint: {
"line-color": "#f59e0b",
"line-width": 2.8,
"line-dasharray": [3, 1]
}
},
{
id: "tactical-trenches-ditches",
type: "line",
source: "local-osm-lines",
filter: ["==", "barrier", "ditch"],
paint: {
"line-color": "#881337",
"line-width": 2.5,
"line-dasharray": [2, 2]
}
},
{
id: "tactical-fences-walls",
type: "line",
source: "local-osm-lines",
filter: ["in", "barrier", "wall", "jersey_barrier", "fence", "wire_fence"],
paint: {
"line-color": "#991b1b",
"line-width": 1.8,
"line-dasharray": [3, 2]
}
},
// ── 5. TACTICAL POINTS (فتحات المغاور والكهوف، الشلالات، والمكاشف الصخرية) ──
{
id: "tactical-cave-entrances",
type: "circle",
source: "local-osm-points",
filter: ["==", "natural", "cave_entrance"],
paint: {
"circle-radius": 5.5,
"circle-color": "#1e293b",
"circle-stroke-color": "#f59e0b",
"circle-stroke-width": 2
}
},
{
id: "tactical-waterfalls",
type: "circle",
source: "local-osm-points",
filter: ["==", "waterway", "waterfall"],
paint: {
"circle-radius": 5,
"circle-color": "#06b6d4",
"circle-stroke-color": "#ffffff",
"circle-stroke-width": 2
}
},
{
id: "tactical-outcrops",
type: "circle",
source: "local-osm-points",
filter: ["==", "geological", "outcrop"],
paint: {
"circle-radius": 4.5,
"circle-color": "#c2410c",
"circle-stroke-color": "#ffffff",
"circle-stroke-width": 1.5
}
},
// ── 6. TACTICAL LABELS (تسميات الموانع والمعالم التكتيكية الحاكمة) ──
{
id: "tactical-obstacle-text-lines",
type: "symbol",
source: "local-osm-lines",
filter: ["any",
["==", "natural", "cliff"],
["==", "barrier", "retaining_wall"],
["==", "waterway", "wadi"],
["in", "natural", "ridge", "arete"]
],
minzoom: 12,
layout: {
"symbol-placement": "line",
"text-field": "{name}",
"text-size": 11,
"text-font": ["Noto Sans Arabic Bold", "Open Sans Bold"],
"text-letter-spacing": 0.05
},
paint: {
"text-color": "#7f1d1d",
"text-halo-color": "#ffffff",
"text-halo-width": 2.0
}
},
{
id: "tactical-obstacle-text-poly",
type: "symbol",
source: "local-osm-polygons",
filter: ["any",
["==", "landuse", "quarry"],
["in", "natural", "bare_rock", "rock", "scree", "wetland"]
],
minzoom: 12,
layout: {
"text-field": "{name}",
"text-size": 11,
"text-font": ["Noto Sans Arabic Bold", "Open Sans Bold"]
},
paint: {
"text-color": "#78350f",
"text-halo-color": "#ffffff",
"text-halo-width": 2.0
}
}
];
// Insert the tactical layers before road labels and text symbols for optimal z-ordering
const textIndex = tacticalStyle.layers.findIndex((l: any) => l.id.includes('label') || l.type === 'symbol');
if (textIndex > -1) {
tacticalStyle.layers.splice(textIndex, 0, ...tacticalLayers);
} else {
tacticalStyle.layers.push(...tacticalLayers);
}
fs.writeFileSync(tacticalStylePath, JSON.stringify(tacticalStyle, null, 2), 'utf8');
console.log(`✅ Successfully generated tactical-style.json with ${tacticalLayers.length} tactical obstacle layers!`);
File diff suppressed because it is too large Load Diff
+109
View File
@@ -144,6 +144,9 @@ const MapComponent: React.FC<MapProps> = ({
if (map.current.getLayer('hillshading')) {
map.current.setLayoutProperty('hillshading', 'visibility', showTerrain ? 'visible' : 'none');
}
if (map.current.getLayer('terrain-hillshade')) {
map.current.setLayoutProperty('terrain-hillshade', 'visibility', showTerrain ? 'visible' : 'none');
}
// Toggle Contours
const contourLayers = ['contour-lines-minor', 'contour-lines-major', 'contour-labels'];
@@ -498,6 +501,33 @@ const MapComponent: React.FC<MapProps> = ({
});
}
// 3D Elevation Hillshade Relief (315° North-West Illumination)
if (!initialMap.getSource('terrain-dem')) {
initialMap.addSource('terrain-dem', {
type: 'raster-dem',
tiles: ['https://tiles.intaleqapp.com/raster_dem/{z}/{x}/{y}.png'],
tileSize: 256,
maxzoom: 14,
});
initialMap.addLayer({
id: 'terrain-hillshade',
type: 'hillshade',
source: 'terrain-dem',
layout: {
visibility: showTerrain ? 'visible' : 'none',
},
paint: {
'hillshade-illumination-direction': 315,
'hillshade-illumination-anchor': 'viewport',
'hillshade-shadow-color': '#3b1c06',
'hillshade-highlight-color': '#ffffff',
'hillshade-accent-color': '#9a3412',
'hillshade-exaggeration': 0.65,
},
});
}
const demSource = getSharedDemSource();
const contourUrl = demSource.contourProtocolUrl({
thresholds: {
@@ -576,6 +606,85 @@ const MapComponent: React.FC<MapProps> = ({
'text-halo-width': 2,
},
});
// Hydro-Flattening Water Mask over Contour Layers
if (!initialMap.getLayer('water-mask-hydroflat-map')) {
initialMap.addLayer({
id: 'water-mask-hydroflat-map',
type: 'fill',
source: 'local-osm-polygons',
'source-layer': 'planet_osm_polygon',
filter: [
'any',
['==', 'natural', 'water'],
['==', 'water', 'reservoir'],
['==', 'water', 'dam'],
['==', 'water', 'lake'],
['==', 'water', 'pond'],
['==', 'waterway', 'riverbank'],
['==', 'landuse', 'reservoir'],
['==', 'landuse', 'basin']
],
paint: {
'fill-color': '#90C3D4',
'fill-opacity': 1.0
}
});
}
// Military Mountain Peak Spot Heights (✕ [ele]m)
if (!initialMap.getLayer('tactical-peaks-spot-heights')) {
initialMap.addLayer({
id: 'tactical-peaks-spot-heights',
type: 'symbol',
source: 'local-osm-points',
'source-layer': 'planet_osm_point',
minzoom: 9,
filter: [
'any',
['in', 'natural', 'peak', 'volcano', 'ridge', 'hill', 'cliff'],
['in', 'place', 'isolated_dwelling', 'locality']
],
layout: {
'text-field': [
'case',
['has', 'ele'],
[
'concat',
'✕ ',
['to-string', ['get', 'ele']],
'م\n',
['coalesce', ['get', 'name:ar'], ['get', 'name'], '']
],
[
'concat',
'✕\n',
['coalesce', ['get', 'name:ar'], ['get', 'name'], 'مرتفع']
]
],
'text-font': ['Noto Sans Bold', 'Open Sans Bold'],
'text-size': [
'interpolate',
['linear'],
['zoom'],
9, 9,
11, 10,
13, 11.5,
15, 13,
17, 15
],
'text-anchor': 'center',
'text-justify': 'center',
'text-padding': 8,
'text-allow-overlap': false
},
paint: {
'text-color': '#7F1D1D',
'text-halo-color': 'rgba(255,255,255,0.95)',
'text-halo-width': 2.2
}
});
}
}
onMapLoad(map.current!);
@@ -0,0 +1,351 @@
import React from 'react';
interface TacticalMarkdownRendererProps {
content: string;
isChat?: boolean;
}
// Inline parser for bold, italic, inline-code, and tags
const parseInline = (text: string): React.ReactNode[] => {
if (!text) return [];
// Match bold (**text**), inline code (`text`), italic (*text* or _text_)
const tokens: React.ReactNode[] = [];
let remaining = text;
let key = 0;
while (remaining.length > 0) {
// Check for bold **...** or __...__
const boldMatch = remaining.match(/^(\*\*|__)(.*?)\1/);
if (boldMatch) {
tokens.push(
<strong
key={key++}
style={{
fontWeight: 800,
color: '#0f172a',
background: 'rgba(241, 245, 249, 0.6)',
padding: '1px 4px',
borderRadius: '4px',
margin: '0 1px'
}}
>
{parseInline(boldMatch[2])}
</strong>
);
remaining = remaining.slice(boldMatch[0].length);
continue;
}
// Check for inline code `...`
const codeMatch = remaining.match(/^`([^`]+)`/);
if (codeMatch) {
tokens.push(
<code
key={key++}
style={{
background: '#e0e7ff',
color: '#4338ca',
padding: '2px 6px',
borderRadius: '4px',
fontSize: '0.85em',
fontFamily: 'monospace',
fontWeight: 700,
direction: 'ltr',
display: 'inline-block'
}}
>
{codeMatch[1]}
</code>
);
remaining = remaining.slice(codeMatch[0].length);
continue;
}
// Check for italic *...* or _..._
const italicMatch = remaining.match(/^(\*|_)(.*?)\1/);
if (italicMatch) {
tokens.push(
<em key={key++} style={{ fontStyle: 'italic', color: '#475569' }}>
{parseInline(italicMatch[2])}
</em>
);
remaining = remaining.slice(italicMatch[0].length);
continue;
}
// Regular text until the next special character
const nextSpecial = remaining.search(/[\*_`]/);
if (nextSpecial === -1) {
tokens.push(remaining);
break;
} else if (nextSpecial === 0) {
// Stray character, consume it as plain text
tokens.push(remaining[0]);
remaining = remaining.slice(1);
} else {
tokens.push(remaining.slice(0, nextSpecial));
remaining = remaining.slice(nextSpecial);
}
}
return tokens;
};
export const TacticalMarkdownRenderer: React.FC<TacticalMarkdownRendererProps> = ({
content,
isChat = false
}) => {
if (!content) return null;
const lines = content.split('\n');
const elements: React.ReactNode[] = [];
let currentList: { type: 'ul' | 'ol'; items: React.ReactNode[] } | null = null;
let elementKey = 0;
const flushList = () => {
if (currentList) {
if (currentList.type === 'ul') {
elements.push(
<ul
key={elementKey++}
style={{
margin: '8px 0 12px 0',
paddingRight: isChat ? '16px' : '20px',
paddingLeft: 0,
display: 'flex',
flexDirection: 'column',
gap: isChat ? '4px' : '6px',
listStyleType: 'disc'
}}
>
{currentList.items.map((item, idx) => (
<li key={idx} style={{ lineHeight: isChat ? 1.6 : 1.85, color: '#1e293b' }}>
{item}
</li>
))}
</ul>
);
} else {
elements.push(
<ol
key={elementKey++}
style={{
margin: '8px 0 12px 0',
paddingRight: isChat ? '18px' : '24px',
paddingLeft: 0,
display: 'flex',
flexDirection: 'column',
gap: isChat ? '4px' : '6px'
}}
>
{currentList.items.map((item, idx) => (
<li key={idx} style={{ lineHeight: isChat ? 1.6 : 1.85, color: '#1e293b', fontWeight: 600 }}>
<span style={{ fontWeight: 400 }}>{item}</span>
</li>
))}
</ol>
);
}
currentList = null;
}
};
for (let i = 0; i < lines.length; i++) {
const rawLine = lines[i];
const trimmed = rawLine.trim();
// Empty lines
if (!trimmed) {
flushList();
continue;
}
// Horizontal Rule (--- or *** or ___)
if (/^(\-{3,}|\*{3,}|_{3,})$/.test(trimmed)) {
flushList();
elements.push(
<hr
key={elementKey++}
style={{
border: 'none',
height: '1px',
background: 'linear-gradient(to left, transparent, #cbd5e1, transparent)',
margin: isChat ? '10px 0' : '16px 0'
}}
/>
);
continue;
}
// Headers
const h1Match = trimmed.match(/^#\s+(.+)$/);
if (h1Match) {
flushList();
elements.push(
<h1
key={elementKey++}
style={{
fontSize: isChat ? '1.1rem' : '1.35rem',
fontWeight: 900,
color: '#0f172a',
marginTop: isChat ? '10px' : '18px',
marginBottom: isChat ? '6px' : '10px',
borderBottom: '2px solid #e2e8f0',
paddingBottom: '6px'
}}
>
{parseInline(h1Match[1])}
</h1>
);
continue;
}
const h2Match = trimmed.match(/^##\s+(.+)$/);
if (h2Match) {
flushList();
elements.push(
<h2
key={elementKey++}
style={{
fontSize: isChat ? '1rem' : '1.2rem',
fontWeight: 900,
color: '#1e293b',
marginTop: isChat ? '8px' : '16px',
marginBottom: isChat ? '4px' : '8px',
display: 'flex',
alignItems: 'center',
gap: 8
}}
>
<span style={{ width: 4, height: 16, background: '#9333ea', borderRadius: 2, display: 'inline-block' }}></span>
<span>{parseInline(h2Match[1])}</span>
</h2>
);
continue;
}
const h3Match = trimmed.match(/^###\s+(.+)$/);
if (h3Match) {
flushList();
elements.push(
<h3
key={elementKey++}
style={{
fontSize: isChat ? '0.92rem' : '1.05rem',
fontWeight: 800,
color: '#334155',
marginTop: isChat ? '8px' : '14px',
marginBottom: isChat ? '4px' : '6px',
background: isChat ? 'rgba(241, 245, 249, 0.8)' : '#f8fafc',
padding: '4px 10px',
borderRadius: 6,
borderRight: '3px solid #6366f1'
}}
>
{parseInline(h3Match[1])}
</h3>
);
continue;
}
const h4Match = trimmed.match(/^####\s+(.+)$/);
if (h4Match) {
flushList();
elements.push(
<h4
key={elementKey++}
style={{
fontSize: isChat ? '0.86rem' : '0.96rem',
fontWeight: 800,
color: '#475569',
marginTop: isChat ? '6px' : '10px',
marginBottom: '4px'
}}
>
{parseInline(h4Match[1])}
</h4>
);
continue;
}
// Unordered List Items (- or * or •)
const ulMatch = trimmed.match(/^[-*•]\s+(.+)$/);
if (ulMatch) {
if (!currentList || currentList.type !== 'ul') {
flushList();
currentList = { type: 'ul', items: [] };
}
currentList.items.push(parseInline(ulMatch[1]));
continue;
}
// Ordered List Items (1. , 2. )
const olMatch = trimmed.match(/^(\d+)\.\s+(.+)$/);
if (olMatch) {
if (!currentList || currentList.type !== 'ol') {
flushList();
currentList = { type: 'ol', items: [] };
}
currentList.items.push(parseInline(olMatch[2]));
continue;
}
// Blockquote (> ...)
const bqMatch = trimmed.match(/^>\s*(.+)$/);
if (bqMatch) {
flushList();
elements.push(
<blockquote
key={elementKey++}
style={{
margin: '8px 0',
padding: '6px 14px',
background: '#f8fafc',
borderRight: '4px solid #0071e3',
color: '#334155',
fontSize: isChat ? '0.84rem' : '0.9rem',
lineHeight: 1.7,
borderRadius: '0 8px 8px 0'
}}
>
{parseInline(bqMatch[1])}
</blockquote>
);
continue;
}
// Regular Paragraph
flushList();
elements.push(
<p
key={elementKey++}
style={{
margin: isChat ? '4px 0 6px 0' : '6px 0 10px 0',
lineHeight: isChat ? 1.7 : 1.9,
color: '#1e293b',
fontSize: isChat ? '0.86rem' : '0.92rem'
}}
>
{parseInline(trimmed)}
</p>
);
}
flushList();
return (
<div
className="tactical-markdown-content"
style={{
direction: 'rtl',
textAlign: 'right',
fontFamily: 'inherit',
color: '#1e293b',
wordBreak: 'break-word'
}}
>
{elements}
</div>
);
};
+4 -3
View File
@@ -25,14 +25,15 @@ body {
.app {
display: flex;
height: 100vh;
width: 100vw;
height: 100%;
width: 100%;
position: relative;
}
.sidebar {
width: 350px;
height: 100%;
max-height: 100vh;
max-height: 100%;
padding: 24px 20px;
z-index: 10;
display: flex;
+194 -78
View File
@@ -6,7 +6,6 @@ import CompareView from './pages/CompareView'
import IntelligenceDashboard from './pages/IntelligenceDashboard'
import { ExecutiveShowcase } from './pages/ExecutiveShowcase'
import { TacticalDefenseView } from './pages/TacticalDefenseView'
import { SiroStrategicReport } from './pages/SiroStrategicReport'
import './index.css'
maplibregl.setRTLTextPlugin(
@@ -15,62 +14,175 @@ maplibregl.setRTLTextPlugin(
true
);
// Lightweight hash router
// Lightweight hash router with Sovereign Executive Styling
const NAV = [
{ hash: '#siro', label: 'تقرير المركز الجغرافي (سيرو)', icon: '🏛️' },
{ hash: '#tactical', label: 'المنظومة التكتيكية العسكرية', icon: '🎖️' },
{ hash: '#map', label: 'الخريطة والطقس والملاحة', icon: '🗺️' },
{ hash: '#compare', label: 'مقارنة العمالقة والأسعار', icon: '⚖️' },
{ hash: '#review', label: 'تدقيق الطرق الذكي', icon: '🔍' },
]
function ViewNav({ hash }: { hash: string }) {
const currentHash = hash || '#siro'
function MasterHeader({ hash }: { hash: string }) {
const currentHash = hash || '#tactical'
const [isFullscreen, setIsFullscreen] = useState(false);
const toggleFullscreen = () => {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen().catch(() => {});
setIsFullscreen(true);
} else {
document.exitFullscreen().catch(() => {});
setIsFullscreen(false);
}
};
return (
<div style={{
<header style={{
position: 'fixed',
top: 12,
left: '50%',
transform: 'translateX(-50%)',
top: 0,
left: 0,
right: 0,
height: 52,
zIndex: 9999,
display: 'flex',
alignItems: 'center',
gap: 4,
background: 'rgba(255, 255, 255, 0.88)',
backdropFilter: 'blur(20px)',
border: '1px solid rgba(0, 0, 0, 0.1)',
boxShadow: '0 4px 20px rgba(0, 0, 0, 0.08)',
borderRadius: 999,
padding: '4px 6px',
justifyContent: 'space-between',
padding: '0 16px',
background: 'rgba(9, 14, 26, 0.94)',
backdropFilter: 'blur(24px)',
WebkitBackdropFilter: 'blur(24px)',
borderBottom: '1px solid rgba(255, 255, 255, 0.08)',
boxShadow: '0 4px 20px rgba(0, 0, 0, 0.4)',
fontFamily: "'IBM Plex Sans Arabic', Inter, system-ui, sans-serif",
direction: 'rtl',
maxWidth: 'calc(100vw - 24px)',
overflowX: 'auto'
userSelect: 'none'
}}>
{NAV.map(n => {
const active = currentHash === n.hash
return (
<a key={n.hash} href={n.hash}
style={{
textDecoration: 'none',
fontSize: 12,
fontWeight: 700,
color: active ? '#fff' : '#64748b',
background: active ? 'linear-gradient(135deg, #0071e3, #4f46e5)' : 'transparent',
boxShadow: active ? '0 2px 8px rgba(0, 113, 227, 0.35)' : 'none',
padding: '6px 14px',
borderRadius: 999,
{/* Right Section: Brand & Sovereign Security Clearance */}
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<a href="#tactical" style={{ textDecoration: 'none', display: 'flex', alignItems: 'center', gap: 8 }}>
<div style={{
width: 32,
height: 32,
borderRadius: 9,
background: 'linear-gradient(135deg, #0071e3, #6366f1)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
boxShadow: '0 0 14px rgba(0, 113, 227, 0.5)'
}}>
<span style={{ fontSize: 16 }}>⚡</span>
</div>
<div>
<div style={{
color: '#ffffff',
fontSize: '0.85rem',
fontWeight: 800,
letterSpacing: '-0.02em',
display: 'flex',
alignItems: 'center',
gap: 6,
transition: 'all 0.15s ease'
gap: 6
}}>
<span>{n.icon}</span>
<span>{n.label}</span>
</a>
)
})}
</div>
<span>منظومة انطلاق المكانية</span>
<span style={{
fontSize: '9.5px',
background: 'rgba(0, 113, 227, 0.25)',
border: '1px solid rgba(0, 113, 227, 0.5)',
color: '#38bdf8',
padding: '1px 5px',
borderRadius: 4,
fontWeight: 700
}}>
Sovereign Cloud v3.2
</span>
</div>
</div>
</a>
</div>
{/* Center Section: Master Navigation Segmented Controller */}
<nav style={{
display: 'flex',
alignItems: 'center',
gap: 3,
background: 'rgba(255, 255, 255, 0.05)',
border: '1px solid rgba(255, 255, 255, 0.1)',
padding: '3px 4px',
borderRadius: 12,
boxShadow: 'inset 0 1px 4px rgba(0, 0, 0, 0.3)'
}}>
{NAV.map(n => {
const active = currentHash === n.hash
return (
<a key={n.hash} href={n.hash}
style={{
textDecoration: 'none',
fontSize: 12,
fontWeight: active ? 800 : 600,
color: active ? '#ffffff' : '#94a3b8',
background: active
? 'linear-gradient(135deg, #0071e3, #4f46e5)'
: 'transparent',
boxShadow: active ? '0 2px 10px rgba(0, 113, 227, 0.45)' : 'none',
padding: '6px 12px',
borderRadius: 9,
display: 'flex',
alignItems: 'center',
gap: 6,
transition: 'all 0.15s ease'
}}>
<span style={{ fontSize: 13 }}>{n.icon}</span>
<span>{n.label}</span>
</a>
)
})}
</nav>
{/* Left Section: Live Sovereign Telemetry & Fullscreen */}
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<div style={{
display: 'flex',
alignItems: 'center',
gap: 6,
background: 'rgba(34, 197, 94, 0.12)',
border: '1px solid rgba(34, 197, 94, 0.3)',
color: '#4ade80',
padding: '4px 10px',
borderRadius: 8,
fontSize: '0.72rem',
fontWeight: 700
}}>
<span style={{
display: 'inline-block',
width: 7,
height: 7,
borderRadius: '50%',
background: '#22c55e',
boxShadow: '0 0 8px #22c55e'
}} />
<span>السحابة السيادية متصلة</span>
</div>
<button
onClick={toggleFullscreen}
title="ملء الشاشة"
style={{
background: 'rgba(255, 255, 255, 0.06)',
border: '1px solid rgba(255, 255, 255, 0.12)',
color: '#cbd5e1',
padding: '5px 9px',
borderRadius: 8,
fontSize: '0.75rem',
cursor: 'pointer',
fontWeight: 600,
display: 'flex',
alignItems: 'center',
gap: 4
}}
>
<span>{isFullscreen ? '🗗' : '🗖'}</span>
</button>
</div>
</header>
)
}
@@ -97,8 +209,8 @@ class ErrorBoundary extends React.Component<{ children: React.ReactNode }, { has
alignItems: 'center',
justifyContent: 'center',
height: '100vh',
background: '#f8fafc',
color: '#1d1d1f',
background: '#090e1a',
color: '#ffffff',
fontFamily: 'system-ui, sans-serif',
direction: 'rtl',
padding: 24,
@@ -106,7 +218,7 @@ class ErrorBoundary extends React.Component<{ children: React.ReactNode }, { has
}}>
<div style={{ fontSize: '3rem', marginBottom: 16 }}>⚠️</div>
<h2 style={{ fontSize: '1.4rem', fontWeight: 700, marginBottom: 8 }}>حدث خطأ أثناء تحميل الواجهة</h2>
<p style={{ color: '#64748b', fontSize: '0.9rem', maxWidth: 450, marginBottom: 20 }}>
<p style={{ color: '#94a3b8', fontSize: '0.9rem', maxWidth: 450, marginBottom: 20 }}>
{this.state.error?.message || 'تعذر تحميل أحد مكونات الخريطة'}
</p>
<button
@@ -132,11 +244,11 @@ class ErrorBoundary extends React.Component<{ children: React.ReactNode }, { has
}
function Root() {
const [hash, setHash] = useState(window.location.hash || '#executive');
const [hash, setHash] = useState(window.location.hash || '#tactical');
useEffect(() => {
const on = () => {
const h = window.location.hash || '#executive';
const h = window.location.hash || '#tactical';
setHash(h);
setTimeout(() => {
window.dispatchEvent(new Event('resize'));
@@ -146,8 +258,7 @@ function Root() {
return () => window.removeEventListener('hashchange', on);
}, []);
const isSiro = hash === '#siro' || hash === '#report' || hash === '' || hash === '#executive';
const isTactical = hash === '#tactical' || hash === '#military';
const isTactical = hash === '#tactical' || hash === '#military' || hash === '' || hash === '#siro';
const isExecutive = hash === '#showcase' || hash === '#pitch';
const isCompare = hash === '#compare';
const isReview = hash === '#review';
@@ -155,43 +266,48 @@ function Root() {
return (
<ErrorBoundary>
<ViewNav hash={hash} />
<MasterHeader hash={hash} />
{isSiro && (
<div style={{ width: '100vw', height: '100vh', position: 'absolute', top: 0, left: 0, overflowY: 'auto', overflowX: 'hidden' }}>
<SiroStrategicReport />
</div>
)}
<main style={{
position: 'absolute',
top: 52,
bottom: 0,
left: 0,
right: 0,
width: '100vw',
height: 'calc(100vh - 52px)',
overflow: 'hidden'
}}>
{isTactical && (
<div style={{ width: '100%', height: '100%' }}>
<TacticalDefenseView />
</div>
)}
{isTactical && (
<div style={{ width: '100vw', height: '100vh', position: 'absolute', top: 0, left: 0 }}>
<TacticalDefenseView />
</div>
)}
{isExecutive && (
<div style={{ width: '100%', height: '100%', overflowY: 'auto', overflowX: 'hidden' }}>
<ExecutiveShowcase />
</div>
)}
{isExecutive && (
<div style={{ width: '100vw', height: '100vh', position: 'absolute', top: 0, left: 0, overflowY: 'auto', overflowX: 'hidden' }}>
<ExecutiveShowcase />
</div>
)}
{isCompare && (
<div style={{ width: '100%', height: '100%' }}>
<CompareView />
</div>
)}
{isCompare && (
<div style={{ width: '100vw', height: '100vh', position: 'absolute', top: 0, left: 0 }}>
<CompareView />
</div>
)}
{isReview && (
<div style={{ width: '100%', height: '100%' }}>
<IntelligenceDashboard />
</div>
)}
{isReview && (
<div style={{ width: '100vw', height: '100vh', position: 'absolute', top: 0, left: 0 }}>
<IntelligenceDashboard />
</div>
)}
{isMap && (
<div style={{ width: '100vw', height: '100vh', position: 'absolute', top: 0, left: 0 }}>
<App />
</div>
)}
{isMap && (
<div style={{ width: '100%', height: '100%' }}>
<App />
</div>
)}
</main>
</ErrorBoundary>
);
}
+30 -12
View File
@@ -242,24 +242,42 @@ const CompareView: React.FC = () => {
});
return (
<div style={{ display: 'flex', flexDirection: 'column', height: '100vh', background: '#0f172a', color: '#e2e8f0', fontFamily: 'Inter, system-ui, sans-serif' }}>
<div style={{ display: 'flex', flexDirection: 'column', height: '100%', width: '100%', background: '#090e1a', color: '#e2e8f0', fontFamily: 'Inter, system-ui, sans-serif' }}>
{/* Toolbar */}
<div style={{ display: 'flex', alignItems: 'center', gap: '10px', padding: '8px 14px', paddingRight: '280px', borderBottom: '1px solid #1e293b', background: '#0f172a', flexWrap: 'wrap' }}>
<strong style={{ fontSize: '0.85rem', whiteSpace: 'nowrap' }}>⚖️ Compare</strong>
<div style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
gap: '12px',
padding: '8px 16px',
borderBottom: '1px solid rgba(255, 255, 255, 0.08)',
background: '#0d1322',
flexWrap: 'nowrap',
overflowX: 'auto',
zIndex: 20
}}>
{/* Left Map Layer Toggles */}
<div style={{ display: 'flex', gap: '5px', alignItems: 'center', background: 'rgba(30,41,59,0.7)', padding: '2px 6px', borderRadius: '8px', border: '1px solid #334155' }}>
<span style={{ fontSize: '0.68rem', color: '#94a3b8' }}>Our Map:</span>
<button onClick={() => setShowTerrain(!showTerrain)} style={toggleBtn(showTerrain)}>🏔️ Relief</button>
<button onClick={() => setShowContours(!showContours)} style={toggleBtn(showContours)}>〰️ Contours</button>
<button onClick={() => setShowAdmin(!showAdmin)} style={toggleBtn(showAdmin)}>🏛️ Admin</button>
<button onClick={() => setShow3D(!show3D)} style={toggleBtn(show3D)}>🏢 3D</button>
<div style={{ display: 'flex', gap: '6px', alignItems: 'center', background: 'rgba(30,41,59,0.7)', padding: '3px 8px', borderRadius: '10px', border: '1px solid rgba(255,255,255,0.1)', flexShrink: 0 }}>
<span style={{ fontSize: '0.72rem', color: '#94a3b8', fontWeight: 600 }}>طبقات خريطتنا:</span>
<button onClick={() => setShowTerrain(!showTerrain)} style={toggleBtn(showTerrain)}>🏔️ تضاريس 3D</button>
<button onClick={() => setShowContours(!showContours)} style={toggleBtn(showContours)}>〰️ كنتور</button>
<button onClick={() => setShowAdmin(!showAdmin)} style={toggleBtn(showAdmin)}>🏛️ حدود</button>
<button onClick={() => setShow3D(!show3D)} style={toggleBtn(show3D)}>🏢 مباني 3D</button>
</div>
<div style={{ display: 'flex', gap: '5px', marginInlineStart: 'auto', overflowX: 'auto', paddingBottom: '2px' }}>
{/* Reference Map Selectors */}
<div style={{ display: 'flex', gap: '6px', alignItems: 'center', overflowX: 'auto', flexShrink: 0 }}>
<span style={{ fontSize: '0.72rem', color: '#94a3b8', fontWeight: 600, marginInlineEnd: 4 }}>المقارنة المرجعية:</span>
{(Object.keys(REFS) as RefKey[]).map(k => (
<button key={k} onClick={() => setRefKey(k)}
style={{ ...btn, whiteSpace: 'nowrap', flexShrink: 0, ...(refKey === k ? { borderColor: '#6366f1', color: '#fff', background: 'rgba(99,102,241,0.35)', fontWeight: 600 } : {}) }}>
style={{
...btn,
whiteSpace: 'nowrap',
flexShrink: 0,
...(refKey === k
? { borderColor: '#6366f1', color: '#fff', background: 'linear-gradient(135deg, #4f46e5, #6366f1)', fontWeight: 700, boxShadow: '0 2px 8px rgba(99,102,241,0.4)' }
: {})
}}>
{REFS[k].label}
</button>
))}
+1 -1
View File
@@ -570,7 +570,7 @@ const IntelligenceDashboard: React.FC = () => {
}
return (
<div style={{ display: 'flex', height: '100vh', background: '#0f172a', color: '#f8fafc', fontFamily: 'Inter,system-ui,sans-serif', overflow: 'hidden' }}>
<div style={{ display: 'flex', height: '100%', width: '100%', background: '#090e1a', color: '#f8fafc', fontFamily: 'Inter,system-ui,sans-serif', overflow: 'hidden' }}>
{/* ── SIDEBAR ── */}
<div style={{ width: '370px', flexShrink: 0, display: 'flex', flexDirection: 'column', borderRight: '1px solid #1e293b', overflow: 'hidden', zIndex: 10 }}>
+421 -235
View File
@@ -250,6 +250,7 @@ export const TacticalDefenseView: React.FC = () => {
const [showVisualResection, setShowVisualResection] = useState<boolean>(false);
const [showFileImporter, setShowFileImporter] = useState<boolean>(false);
const [showIPBStudio, setShowIPBStudio] = useState<boolean>(false);
const [ipbStudioTab, setIpbStudioTab] = useState<'overlays' | 'report' | 'ai_advisor'>('overlays');
const [resectionPosition, setResectionPosition] = useState<[number, number] | null>(null);
// 10. Live Digital IPB Overlays Engine (منظومة الشفافات التكتيكية الميدانية الحية)
@@ -615,13 +616,13 @@ export const TacticalDefenseView: React.FC = () => {
// Terrain Study Center
updateMarker('terrain-c', terrainCenter, '⛰️ مركز دراسة الأرض', '#6366f1', (pos) => setTerrainCenter(pos));
// Terrain Highest & Lowest Points (Dynamic Pins)
// Terrain Highest & Lowest Points (Dynamic Pins - Military Spot Heights)
if (terrainResult?.highestPoint && showPeaksAndValleys && mode === 'terrain') {
updateMarker(
'terrain-peak',
[terrainResult.highestPoint.lat, terrainResult.highestPoint.lng],
`🔺 ${terrainResult.highestPoint.label}`,
'#0284c7',
`✕ ${Math.round(terrainResult.highestPoint.elevation || 0)}م (القمة الحاكمة)`,
'#881337',
() => {}
);
} else {
@@ -678,7 +679,7 @@ export const TacticalDefenseView: React.FC = () => {
const initialMap = new maplibregl.Map({
container: mapContainer.current,
style: '/style.json',
style: '/tactical-style.json',
center: [35.9106, 31.9539],
zoom: 11,
pitch: 35,
@@ -875,6 +876,31 @@ export const TacticalDefenseView: React.FC = () => {
});
if (!initialMap.getSource('contour-source')) {
// ── 0. Terrain Hillshade Layer for Steep Slope & Cliff 3D Relief ──
if (!initialMap.getSource('raster-dem-src')) {
initialMap.addSource('raster-dem-src', {
type: 'raster-dem',
tiles: ['https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png'],
encoding: 'terrarium',
tileSize: 256,
maxzoom: 15
});
initialMap.addLayer({
id: 'tactical-hillshade',
type: 'hillshade',
source: 'raster-dem-src',
paint: {
'hillshade-exaggeration': 0.65,
'hillshade-shadow-color': '#3b1c06',
'hillshade-highlight-color': '#ffffff',
'hillshade-accent-color': '#9a3412',
'hillshade-illumination-direction': 315,
'hillshade-illumination-anchor': 'viewport'
}
});
}
initialMap.addSource('contour-source', {
type: 'vector',
tiles: [contourUrl],
@@ -934,6 +960,53 @@ export const TacticalDefenseView: React.FC = () => {
'text-halo-width': 2,
},
});
// ── Hydro-Flattening Water Mask: Covers and eliminates contour swirls over dams, lakes, and reservoirs ──
if (!initialMap.getLayer('tactical-water-mask-hydroflat')) {
initialMap.addLayer({
id: 'tactical-water-mask-hydroflat',
type: 'fill',
source: 'local-osm-polygons',
'source-layer': 'planet_osm_polygon',
filter: [
'any',
['==', 'natural', 'water'],
['==', 'water', 'reservoir'],
['==', 'water', 'dam'],
['==', 'water', 'lake'],
['==', 'water', 'pond'],
['==', 'waterway', 'riverbank'],
['==', 'landuse', 'reservoir'],
['==', 'landuse', 'basin']
],
paint: {
'fill-color': '#90C3D4',
'fill-opacity': 1.0
}
});
initialMap.addLayer({
id: 'tactical-water-mask-hydroflat-outline',
type: 'line',
source: 'local-osm-polygons',
'source-layer': 'planet_osm_polygon',
filter: [
'any',
['==', 'natural', 'water'],
['==', 'water', 'reservoir'],
['==', 'water', 'dam'],
['==', 'water', 'lake'],
['==', 'water', 'pond'],
['==', 'waterway', 'riverbank'],
['==', 'landuse', 'reservoir'],
['==', 'landuse', 'basin']
],
paint: {
'line-color': '#559cb3',
'line-width': 3.0
}
});
}
}
// Rich Spatial Feature Layers for Terrain Study (Slope, Wadis, Roads, Urban, Hazards)
@@ -1224,9 +1297,22 @@ export const TacticalDefenseView: React.FC = () => {
id: 'tactical-ipb-line',
type: 'line',
source: 'tactical-ipb-src',
filter: [
'any',
['==', '$type', 'LineString'],
['==', ['get', 'layer'], 'layer_10'],
['==', ['get', 'layer'], 'layer_7'],
['==', ['get', 'layer'], 'layer_4']
],
paint: {
'line-color': ['coalesce', ['get', 'strokeColor'], ['get', 'color'], '#0284c7'],
'line-width': 2.5,
'line-width': [
'case',
['==', ['get', 'layer'], 'layer_10'], 3.0,
['==', ['get', 'layer'], 'layer_7'], 4.0,
['==', ['get', 'layer'], 'layer_4'], 2.5,
2.0
],
'line-opacity': 0.95
}
});
@@ -1235,10 +1321,18 @@ export const TacticalDefenseView: React.FC = () => {
id: 'tactical-ipb-symbol',
type: 'symbol',
source: 'tactical-ipb-src',
filter: [
'all',
['has', 'label'],
['!=', ['get', 'layer'], 'layer_6'],
['!=', ['get', 'layer'], 'layer_9'],
['!=', ['get', 'layer'], 'layer_1'],
['!=', ['get', 'layer'], 'layer_2']
],
layout: {
'text-field': ['get', 'label'],
'text-font': ['Open Sans Bold', 'Arial Unicode MS Bold'],
'text-size': 11,
'text-font': ['Noto Sans Regular'],
'text-size': 12,
'text-offset': [0, 1.2],
'text-anchor': 'top',
'text-allow-overlap': false
@@ -1246,7 +1340,7 @@ export const TacticalDefenseView: React.FC = () => {
paint: {
'text-color': '#ffffff',
'text-halo-color': '#0f172a',
'text-halo-width': 2
'text-halo-width': 2.5
}
});
});
@@ -1311,7 +1405,7 @@ export const TacticalDefenseView: React.FC = () => {
// Sync Spatial GeoJSON Features with Active Layer Toggles
useEffect(() => {
if (!map.current || !map.current.getSource('tactical-terrain-spatial-src')) return;
if (!terrainResult?.spatialGeoJson || mode !== 'terrain') {
if (!terrainResult?.spatialGeoJson || (mode !== 'terrain' && mode !== 'ipb')) {
(map.current.getSource('tactical-terrain-spatial-src') as maplibregl.GeoJSONSource).setData({
type: 'FeatureCollection',
features: []
@@ -1339,6 +1433,9 @@ export const TacticalDefenseView: React.FC = () => {
useEffect(() => {
if (mode === 'ipb') {
runIPBCalculation(ipbCenter, ipbRadiusKm);
// Sync Terrain Study center to IPB center so the high-res terrain baseline displays perfectly underneath IPB
setTerrainCenter(ipbCenter);
setTerrainRadius(ipbRadiusKm * 1000);
}
}, [mode, ipbCenter, ipbRadiusKm]);
@@ -1722,8 +1819,8 @@ ${terrainResult.tacticalRecommendations.map((r, i) => `${i + 1}. ${r}`).join('\n
return (
<div style={{
width: '100vw',
height: '100vh',
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
background: '#0a0e17',
@@ -1731,245 +1828,274 @@ ${terrainResult.tacticalRecommendations.map((r, i) => `${i + 1}. ${r}`).join('\n
fontFamily: '-apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display", "SF Pro Arabic", system-ui, sans-serif',
direction: 'rtl',
overflow: 'hidden',
paddingTop: 56
paddingTop: 0
}}>
{/* Top Military HUD Header */}
{/* Tier 1: C4ISR Tactical Operations Ribbon */}
<header style={{
height: 52,
background: '#0f172a',
borderBottom: '1px solid #1e293b',
height: 48,
background: '#0a0f1d',
borderBottom: '1px solid rgba(255, 255, 255, 0.08)',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '0 18px',
padding: '0 14px',
zIndex: 50,
gap: 12,
flexShrink: 0
gap: 10,
flexShrink: 0,
overflowX: 'auto'
}}>
{/* Title */}
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
{/* Right: C4ISR Title & Tactical Modes */}
<div style={{ display: 'flex', alignItems: 'center', gap: 10, flexShrink: 0 }}>
<div style={{
width: 32,
height: 32,
borderRadius: 8,
background: 'linear-gradient(135deg, #4f46e5, #06b6d4)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
boxShadow: '0 0 16px rgba(79, 70, 229, 0.5)'
gap: 6,
background: 'rgba(79, 70, 229, 0.15)',
border: '1px solid rgba(99, 102, 241, 0.3)',
padding: '3px 8px',
borderRadius: 7,
fontSize: '0.78rem',
fontWeight: 800,
color: '#a5b4fc'
}}>
<Shield size={17} color="#ffffff" />
<Shield size={14} color="#818cf8" />
<span>C4ISR Grid</span>
</div>
<div>
<div style={{ fontWeight: 800, fontSize: '0.92rem', letterSpacing: '-0.02em', display: 'flex', alignItems: 'center', gap: 6 }}>
<span>منظومة القيادة والسيطرة والتحليل التكتيكي</span>
<span style={{ fontSize: '10px', background: 'rgba(34, 197, 94, 0.2)', color: '#4ade80', padding: '1px 6px', borderRadius: 4 }}>
C4ISR Grid
</span>
</div>
{/* Tactical Modes Selector Ribbon */}
<div style={{
display: 'flex',
gap: 3,
background: 'rgba(255, 255, 255, 0.04)',
padding: 3,
borderRadius: 10,
border: '1px solid rgba(255,255,255,0.08)'
}}>
{[
{ id: 'ipb', label: 'منظومة الشفافات (IPB)', icon: <LayersIcon size={12} /> },
{ id: 'terrain', label: 'دراسة الأرض', icon: <Mountain size={12} /> },
{ id: 'isochrone', label: 'زمن الاستجابة (Isochrone)', icon: <Clock size={12} /> },
{ id: 'los', label: 'تبادل الرؤية (LOS)', icon: <Eye size={12} /> },
{ id: 'viewshed', label: 'كشف 360°', icon: <Radio size={12} /> },
{ id: 'artillery', label: 'موقع المدفعية', icon: <Flame size={12} /> },
{ id: 'minefield', label: 'حقل الألغام', icon: <AlertOctagon size={12} /> },
{ id: 'hlz', label: 'مهابط المروحيات', icon: <Crosshair size={12} /> },
{ id: 'symbols', label: 'الرموز والتشكيلات', icon: <Layers size={12} /> },
{ id: 'study', label: 'دراسة التنظيم', icon: <Award size={12} /> },
].map(m => (
<button
key={m.id}
onClick={() => {
setMode(m.id as TacticalMode);
setActivePlacement(null);
}}
style={{
background: mode === m.id
? 'linear-gradient(135deg, #4f46e5, #6366f1)'
: 'transparent',
boxShadow: mode === m.id ? '0 2px 8px rgba(79, 70, 229, 0.45)' : 'none',
border: 'none',
color: mode === m.id ? '#ffffff' : '#94a3b8',
borderRadius: 7,
padding: '4px 9px',
fontSize: '0.74rem',
fontWeight: mode === m.id ? 800 : 600,
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
gap: 5,
whiteSpace: 'nowrap',
transition: 'all 0.15s ease'
}}
>
{m.icon}
<span>{m.label}</span>
</button>
))}
</div>
</div>
{/* Tactical Modes Selector */}
<div style={{ display: 'flex', gap: 4, background: 'rgba(255, 255, 255, 0.05)', padding: 3, borderRadius: 10, border: '1px solid rgba(255,255,255,0.1)' }}>
{[
{ id: 'ipb', label: 'منظومة الشفافات (IPB)', icon: <LayersIcon size={13} /> },
{ id: 'terrain', label: 'دراسة الأرض', icon: <Mountain size={13} /> },
{ id: 'isochrone', label: 'زمن الاستجابة (Isochrone)', icon: <Clock size={13} /> },
{ id: 'los', label: 'تبادل الرؤية (LOS)', icon: <Eye size={13} /> },
{ id: 'viewshed', label: 'كشف 360°', icon: <Radio size={13} /> },
{ id: 'artillery', label: 'موقع المدفعية', icon: <Flame size={13} /> },
{ id: 'minefield', label: 'حقل الألغام', icon: <AlertOctagon size={13} /> },
{ id: 'hlz', label: 'مهابط المروحيات', icon: <Crosshair size={13} /> },
{ id: 'symbols', label: 'الرموز والتشكيلات', icon: <Layers size={13} /> },
{ id: 'study', label: 'دراسة التنظيم', icon: <Award size={13} /> },
].map(m => (
{/* Left: Strategic Quick Action Launchers */}
<div style={{ display: 'flex', alignItems: 'center', gap: 6, flexShrink: 0 }}>
{/* AI Strategic Advisor Button (Gemini 3.7 Flash) */}
<button
onClick={() => {
setIpbStudioTab('ai_advisor');
setShowIPBStudio(true);
}}
title="المستشار الاستراتيجي التفاعلي للذكاء الاصطناعي (Gemini 3.7 Flash)"
style={{
background: 'linear-gradient(135deg, rgba(147, 51, 234, 0.35), rgba(79, 70, 229, 0.4))',
border: '1px solid rgba(192, 132, 252, 0.7)',
color: '#f3e8ff',
padding: '4px 10px',
borderRadius: 7,
fontSize: '0.72rem',
fontWeight: 800,
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
gap: 5,
boxShadow: '0 2px 8px rgba(147, 51, 234, 0.3)'
}}
>
<Zap size={13} color="#fde047" />
<span>المستشار الاستراتيجي AI</span>
</button>
{/* GPS-Denied Visual Resection Button */}
<button
onClick={() => setShowVisualResection(true)}
title="تحديد الموقع بالتقاطع البصري عند انقطاع الـ GPS"
style={{
background: 'linear-gradient(135deg, rgba(0, 240, 255, 0.15), rgba(0, 113, 227, 0.2))',
border: '1px solid rgba(0, 240, 255, 0.4)',
color: '#38bdf8',
padding: '4px 9px',
borderRadius: 7,
fontSize: '0.72rem',
fontWeight: 700,
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
gap: 5
}}
>
<Camera size={12} />
<span>التقاطع البصري</span>
</button>
{/* Flash Drive Live Importer Button */}
<button
onClick={() => setShowFileImporter(true)}
title="استيراد ملفات المركز الجغرافي من الفلاشة (Shapefiles, GeoJSON)"
style={{
background: 'linear-gradient(135deg, rgba(99, 102, 241, 0.15), rgba(79, 70, 229, 0.2))',
border: '1px solid rgba(99, 102, 241, 0.4)',
color: '#a5b4fc',
padding: '4px 9px',
borderRadius: 7,
fontSize: '0.72rem',
fontWeight: 700,
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
gap: 5
}}
>
<FolderUp size={12} />
<span>استيراد ملفات</span>
</button>
{/* Digital IPB Overlay Studio Button */}
<button
onClick={() => {
setIpbStudioTab('overlays');
setShowIPBStudio(true);
}}
title="منظومة الشفافات التكتيكية الرقمية وإعداد ساحة المعركة (IPB Studio)"
style={{
background: 'linear-gradient(135deg, rgba(16, 185, 129, 0.2), rgba(5, 150, 105, 0.25))',
border: '1px solid rgba(16, 185, 129, 0.5)',
color: '#4ade80',
padding: '4px 9px',
borderRadius: 7,
fontSize: '0.72rem',
fontWeight: 700,
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
gap: 5
}}
>
<LayersIcon size={12} />
<span>الشفافات (IPB)</span>
</button>
{authStatus === 'authorized' && (
<button
key={m.id}
onClick={() => {
setMode(m.id as TacticalMode);
setActivePlacement(null);
}}
onClick={handleRevokeOrLock}
title="قفل المنظومة التكتيكية / خروج"
style={{
background: mode === m.id ? 'linear-gradient(135deg, #4f46e5, #6366f1)' : 'transparent',
border: 'none',
color: mode === m.id ? '#ffffff' : '#94a3b8',
background: 'rgba(239, 68, 68, 0.12)',
border: '1px solid rgba(239, 68, 68, 0.3)',
color: '#f87171',
padding: '4px 7px',
borderRadius: 7,
padding: '5px 10px',
fontSize: '0.78rem',
fontSize: '0.7rem',
fontWeight: 700,
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
gap: 5,
transition: 'all 0.15s ease'
gap: 4
}}
>
{m.icon}
<span>{m.label}</span>
<Lock size={12} />
<span>قفل</span>
</button>
))}
</div>
{/* Realtime Cursor HUD & Clearance Status */}
<div style={{ display: 'flex', alignItems: 'center', gap: 14, fontSize: '0.72rem', fontFamily: 'monospace', color: '#94a3b8' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<div>LAT: <span style={{ color: '#38bdf8' }}>{cursorPos.lat}</span></div>
<div>LNG: <span style={{ color: '#38bdf8' }}>{cursorPos.lng}</span></div>
<div>ELEV: <span style={{ color: '#4ade80' }}>{cursorPos.elev}m</span></div>
</div>
{authStatus === 'authorized' && (
<div style={{ display: 'flex', alignItems: 'center', gap: 8, borderRight: '1px solid rgba(255,255,255,0.15)', paddingRight: 10 }}>
<div style={{
display: 'flex',
alignItems: 'center',
gap: 5,
background: 'rgba(34, 197, 94, 0.15)',
border: '1px solid rgba(34, 197, 94, 0.35)',
color: '#4ade80',
padding: '3px 8px',
borderRadius: 6,
fontSize: '0.7rem',
fontWeight: 700,
fontFamily: 'inherit'
}}>
<ShieldCheck size={13} />
<span>تصريح: {authInfo?.tenantName || 'Enterprise'}</span>
</div>
{/* GPS-Denied Visual Resection Button */}
<button
onClick={() => setShowVisualResection(true)}
title="تحديد الموقع بالتقاطع البصري عند انقطاع الـ GPS"
style={{
background: 'linear-gradient(135deg, rgba(0, 240, 255, 0.2), rgba(0, 113, 227, 0.25))',
border: '1px solid rgba(0, 240, 255, 0.5)',
color: '#38bdf8',
padding: '4px 10px',
borderRadius: 6,
fontSize: '0.72rem',
fontWeight: 800,
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
gap: 5,
boxShadow: '0 2px 8px rgba(0, 240, 255, 0.2)'
}}
>
<Camera size={13} />
<span>التقاطع البصري (GPS-Denied)</span>
</button>
{/* Flash Drive Live Importer Button */}
<button
onClick={() => setShowFileImporter(true)}
title="استيراد ملفات المركز الجغرافي من الفلاشة (Shapefiles, GeoJSON)"
style={{
background: 'linear-gradient(135deg, rgba(99, 102, 241, 0.2), rgba(79, 70, 229, 0.25))',
border: '1px solid rgba(99, 102, 241, 0.5)',
color: '#a5b4fc',
padding: '4px 10px',
borderRadius: 6,
fontSize: '0.72rem',
fontWeight: 800,
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
gap: 5,
boxShadow: '0 2px 8px rgba(99, 102, 241, 0.2)'
}}
>
<FolderUp size={13} />
<span>استيراد ملفات الفلاشة</span>
</button>
{/* Digital IPB Overlay Studio Button */}
<button
onClick={() => setShowIPBStudio(true)}
title="منظومة الشفافات التكتيكية الرقمية وإعداد ساحة المعركة (IPB Studio)"
style={{
background: 'linear-gradient(135deg, rgba(16, 185, 129, 0.25), rgba(5, 150, 105, 0.3))',
border: '1px solid rgba(16, 185, 129, 0.6)',
color: '#4ade80',
padding: '4px 10px',
borderRadius: 6,
fontSize: '0.72rem',
fontWeight: 800,
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
gap: 5,
boxShadow: '0 2px 8px rgba(16, 185, 129, 0.2)'
}}
>
<LayersIcon size={13} />
<span>منظومة الشفافات (IPB Studio)</span>
</button>
<button
onClick={handleRevokeOrLock}
title="قفل المنظومة التكتيكية / خروج"
style={{
background: 'rgba(239, 68, 68, 0.15)',
border: '1px solid rgba(239, 68, 68, 0.3)',
color: '#f87171',
padding: '3px 8px',
borderRadius: 6,
fontSize: '0.7rem',
fontWeight: 700,
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
gap: 4,
fontFamily: 'inherit',
transition: 'all 0.15s ease'
}}
>
<Lock size={12} />
<span>قفل</span>
</button>
</div>
)}
</div>
</header>
{/* Quick Sector Presets Bar */}
{/* Tier 2: Quick Sector Presets Bar & Real-time Cursor Coordinates */}
<div style={{
height: 38,
background: '#0b1329',
borderBottom: '1px solid rgba(255,255,255,0.08)',
height: 36,
background: '#070b16',
borderBottom: '1px solid rgba(255,255,255,0.06)',
display: 'flex',
alignItems: 'center',
padding: '0 16px',
gap: 8,
fontSize: '0.75rem',
justifyContent: 'space-between',
padding: '0 14px',
fontSize: '0.73rem',
overflowX: 'auto',
flexShrink: 0
}}>
<span style={{ color: '#94a3b8', display: 'flex', alignItems: 'center', gap: 4, fontWeight: 700 }}>
<MapPin size={13} color="#38bdf8" /> الانتقال السريع لقطاعات العمليات:
</span>
{REGIONAL_SECTORS.map((s, idx) => (
<button
key={idx}
onClick={() => flyToSector(s)}
style={{
background: 'rgba(56, 189, 248, 0.1)',
border: '1px solid rgba(56, 189, 248, 0.25)',
color: '#38bdf8',
padding: '3px 10px',
borderRadius: 6,
fontSize: '0.72rem',
fontWeight: 600,
cursor: 'pointer',
whiteSpace: 'nowrap',
transition: 'all 0.15s ease'
}}
>
{s.name}
</button>
))}
{/* Right: Sector Presets */}
<div style={{ display: 'flex', alignItems: 'center', gap: 6, flexShrink: 0 }}>
<span style={{ color: '#64748b', display: 'flex', alignItems: 'center', gap: 4, fontWeight: 700 }}>
<MapPin size={12} color="#38bdf8" /> القطاعات الميدانية:
</span>
{REGIONAL_SECTORS.map((s, idx) => (
<button
key={idx}
onClick={() => flyToSector(s)}
style={{
background: 'rgba(56, 189, 248, 0.08)',
border: '1px solid rgba(56, 189, 248, 0.2)',
color: '#38bdf8',
padding: '2px 8px',
borderRadius: 5,
fontSize: '0.7rem',
fontWeight: 600,
cursor: 'pointer',
whiteSpace: 'nowrap',
transition: 'all 0.15s ease'
}}
>
{s.name}
</button>
))}
</div>
{/* Left: Realtime Cursor Coordinates HUD */}
<div style={{ display: 'flex', alignItems: 'center', gap: 12, fontSize: '0.72rem', fontFamily: 'monospace', color: '#94a3b8', flexShrink: 0 }}>
<div>LAT: <span style={{ color: '#38bdf8' }}>{cursorPos.lat}</span></div>
<div>LNG: <span style={{ color: '#38bdf8' }}>{cursorPos.lng}</span></div>
<div>ELEV: <span style={{ color: '#4ade80' }}>{cursorPos.elev}m</span></div>
{authInfo?.tenantName && (
<span style={{
background: 'rgba(34, 197, 94, 0.15)',
color: '#4ade80',
padding: '1px 6px',
borderRadius: 4,
fontFamily: 'inherit',
fontSize: '0.68rem',
fontWeight: 700
}}>
تصريح: {authInfo.tenantName}
</span>
)}
</div>
</div>
{/* Main Tactical Workspace */}
@@ -2047,25 +2173,53 @@ ${terrainResult.tacticalRecommendations.map((r, i) => `${i + 1}. ${r}`).join('\n
رسم الشفافات التعبوية الـ 10 حياً على الخريطة بقاطع مخصص (4 كم - 30 كم)
</p>
</div>
<button
onClick={() => setShowIPBStudio(true)}
style={{
background: 'rgba(16, 185, 129, 0.2)',
border: '1px solid #10b981',
color: '#4ade80',
padding: '4px 8px',
borderRadius: 6,
fontSize: '0.7rem',
fontWeight: 700,
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
gap: 4
}}
>
<FileText size={12} />
<span>التقرير الرسمي</span>
</button>
<div style={{ display: 'flex', gap: 6 }}>
<button
onClick={() => {
setIpbStudioTab('ai_advisor');
setShowIPBStudio(true);
}}
style={{
background: 'linear-gradient(135deg, rgba(147, 51, 234, 0.3), rgba(79, 70, 229, 0.35))',
border: '1px solid rgba(192, 132, 252, 0.6)',
color: '#e9d5ff',
padding: '4px 8px',
borderRadius: 6,
fontSize: '0.7rem',
fontWeight: 800,
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
gap: 4
}}
>
<Zap size={12} color="#fde047" />
<span>المستشار AI</span>
</button>
<button
onClick={() => {
setIpbStudioTab('report');
setShowIPBStudio(true);
}}
style={{
background: 'rgba(16, 185, 129, 0.2)',
border: '1px solid #10b981',
color: '#4ade80',
padding: '4px 8px',
borderRadius: 6,
fontSize: '0.7rem',
fontWeight: 700,
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
gap: 4
}}
>
<FileText size={12} />
<span>التقرير</span>
</button>
</div>
</div>
{/* AO Center & Radius Customization Card */}
@@ -4216,7 +4370,10 @@ ${terrainResult.tacticalRecommendations.map((r, i) => `${i + 1}. ${r}`).join('\n
{/* Quick Dossier Report Button */}
<button
onClick={() => setShowIPBStudio(true)}
onClick={() => {
setIpbStudioTab('report');
setShowIPBStudio(true);
}}
style={{
background: 'linear-gradient(135deg, #0071e3, #4f46e5)',
border: 'none',
@@ -4235,6 +4392,31 @@ ${terrainResult.tacticalRecommendations.map((r, i) => `${i + 1}. ${r}`).join('\n
<FileText size={14} />
<span>تقرير تقدير موقف الأرض</span>
</button>
{/* AI Strategic Advisor Button (Gemini 3.7 Flash) */}
<button
onClick={() => {
setIpbStudioTab('ai_advisor');
setShowIPBStudio(true);
}}
style={{
background: 'linear-gradient(135deg, #9333ea, #7c3aed, #4f46e5)',
border: '1px solid rgba(216, 180, 254, 0.5)',
color: '#ffffff',
padding: '6px 15px',
borderRadius: 10,
fontSize: '0.78rem',
fontWeight: 800,
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
gap: 6,
boxShadow: '0 2px 14px rgba(147, 51, 234, 0.45)'
}}
>
<Zap size={14} color="#fde047" />
<span>المستشار الاستراتيجي AI</span>
</button>
</div>
)}
</div>
@@ -4428,6 +4610,10 @@ ${terrainResult.tacticalRecommendations.map((r, i) => `${i + 1}. ${r}`).join('\n
<IPBAnalysisStudio
active={showIPBStudio}
onClose={() => setShowIPBStudio(false)}
ipbData={ipbCalculatedData}
center={ipbCenter}
radiusKm={ipbRadiusKm}
initialTab={ipbStudioTab}
/>
</div>
+164 -135
View File
@@ -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
};