stable: finalized GIS aesthetics and Syria/Jordan routing synchronization

This commit is contained in:
Hamza-Ayed
2026-03-28 04:12:01 +03:00
parent d6f3b41b53
commit 592af8ce5c
203 changed files with 9711 additions and 119 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

+185
View File
@@ -0,0 +1,185 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Map SaaS | Master-Class Tutorial</title>
<link rel="stylesheet" href="style.css">
<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=Inter:wght@400;600;700;900&family=Noto+Sans+Arabic:wght@400;700;800&family=Fira+Code&display=swap" rel="stylesheet">
</head>
<body>
<div class="bg-glow"></div>
<div class="app-container">
<aside>
<div class="brand">
<div class="brand-icon">M</div>
<h2>Master-Class</h2>
</div>
<nav>
<ul>
<li><a href="#intro" class="active">01. Sovereignty</a></li>
<li><a href="#acquisition">02. Data Acquisition</a></li>
<li><a href="#engine">03. The Spatial Engine</a></li>
<li><a href="#import">04. Mastering Import</a></li>
<li><a href="#serving">05. Serving Geometry</a></li>
<li><a href="#routing">06. Intelligent Routing</a></li>
<li><a href="#interface">07. Modern Interface</a></li>
</ul>
</nav>
</aside>
<main>
<header class="hero" id="intro">
<img src="hero.png" alt="Map Data Hero" class="hero-img">
<div class="hero-content">
<span class="badge">Advanced Tutorial</span>
<h1>Building the Future of Maps</h1>
<p>A ground-up engineering guide to creating a fully sovereign mapping infrastructure.</p>
</div>
</header>
<div class="sections-wrapper">
<!-- 01. Sovereignty -->
<section id="intro">
<span class="section-label">Concept</span>
<h2>Digital Sovereignty</h2>
<div class="deep-dive">
<div class="en-side">
<p>In the age of expensive APIs and data lock-ins, building your own map stack isn't just a technical choice—it's a declaration of <strong>Sovereignty</strong>. By orchestrating OpenStreetMap, PostGIS, and high-performance tile servers, we eliminate per-view costs and gain absolute control over every pixel.</p>
</div>
<div class="ar-side">
<p>في عصر واجهات برمجة التطبيقات المكلفة وتقييد البيانات، لا يعد بناء مجموعة أدوات الخرائط الخاصة بك مجرد خيار تقني، بل هو إعلان عن <strong>السيادة الرقمية</strong>. من خلال تنظيم OpenStreetMap وPostGIS وخوادم البلاطات عالية الأداء، نقوم بإلغاء تكاليف العرض لكل خريطة ونكتسب تحكماً مطلقاً في كل بكسل.</p>
</div>
</div>
</section>
<!-- 02. Data Acquisition -->
<section id="acquisition">
<span class="section-label">Source</span>
<h2>Phase 1: Raw Data Acquisition</h2>
<div class="deep-dive">
<div class="en-side">
<p>Where does the world's map data come from? For this project, we rely on <strong>OpenStreetMap (OSM)</strong>. We obtain high-quality, pre-extracted geographic fragments from <strong>Geofabrik</strong>.</p>
<div class="highlight-box">
<h4>Why Geofabrik?</h4>
<p>Downloading the entire world is 1.6TB. Geofabrik allows us to target specific regions (like Jordan) in the `.osm.pbf` format—a highly compressed binary structure.</p>
</div>
</div>
<div class="ar-side">
<p>من أين تأتي بيانات خرائط العالم؟ في هذا المشروع، نعتمد على <strong>OpenStreetMap (OSM)</strong>. نحصل على قطع جغرافية عالية الجودة ومستخرجة مسبقاً من <strong>Geofabrik</strong>.</p>
<div class="highlight-box">
<h4>لماذا Geofabrik؟</h4>
<p>تحميل العالم بالكامل يزن 1.6 تيرابايت. يسمح لنا Geofabrik باستهداف مناطق معينة (مثل الأردن) بتنسيق `.osm.pbf` - وهو هيكل ثنائي مضغوط للغاية.</p>
</div>
</div>
</div>
<p>Source URL: <code>http://download.geofabrik.de/asia/jordan-latest.osm.pbf</code></p>
</section>
<!-- 03. The Spatial Engine -->
<section id="engine">
<span class="section-label">Infrastructure</span>
<h2>Phase 2: The Spatial Database</h2>
<div class="deep-dive">
<div class="en-side">
<p>Standard databases store text and numbers. A mapping engine needs to store <strong>Geometry</strong>. We use **PostGIS**, an extension for PostgreSQL that adds support for geographic objects.</p>
<div class="highlight-box">
<h4>The "Why" of PostGIS</h4>
<p>PostGIS allows us to perform spatial SQL queries. For example, finding all roads that intersect with a specific park, or calculating the area of a building block instantly.</p>
</div>
</div>
<div class="ar-side">
<p>قواعد البيانات العادية تخزن النصوص والأرقام. أما محرك الخرائط فيحتاج إلى تخزين <strong>الهندسة (Geometry)</strong>. نحن نستخدم **PostGIS**، وهي إضافة لـ PostgreSQL تضيف دعماً للأجسام الجغرافية.</p>
<div class="highlight-box">
<h4>سبب استخدام PostGIS</h4>
<p>يسمح لنا PostGIS بإجراء استعلامات SQL مكانية. على سبيل المثال، العثور على جميع الطرق التي تتقاطع مع حديقة معينة، أو حساب مساحة كتلة بناء بشكل فوري.</p>
</div>
</div>
</div>
</section>
<!-- 04. Mastering Import -->
<section id="import">
<span class="section-label">Processing</span>
<h2>Phase 3: Data Transformation</h2>
<div class="deep-dive">
<div class="en-side">
<p>To move data from a PBF file into PostGIS, we use <strong>osm2pgsql</strong>. This isn't just a copy-paste tool; it's a sophisticated translator.</p>
</div>
<div class="ar-side">
<p>لنقل البيانات من ملف PBF إلى PostGIS، نستخدم <strong>osm2pgsql</strong>. هذه ليست مجرد أداة نسخ ولصق؛ بل هي مترجم متطور.</p>
</div>
</div>
<pre><code>osm2pgsql --create --slim --cache 1000 --database map_db --host db jordan-latest.osm.pbf</code></pre>
<div class="tech-grid">
<div class="tech-card">
<h3>--create</h3>
<p>Initializes a fresh schema. Wipe out old data and start clean.</p>
<p>(AR) لإنشاء مخطط (Schema) جديد تماماً ومسح البيانات القديمة.</p>
</div>
<div class="tech-card">
<h3>--slim</h3>
<p>Stores intermediate data on disk. Essential for keeping RAM usage low during complex imports.</p>
<p>(AR) لتخزين البيانات الوسيطة على القرص، مما يحافظ على استهلاك الذاكرة منخفضاً.</p>
</div>
<div class="tech-card">
<h3>--cache</h3>
<p>Allocates RAM (1GB here) to the tool. More cache means faster translation from PBF to SQL.</p>
<p>(AR) يخصص ذاكرة وصول عشوائي للأداة. ذاكرة أكبر تعني ترجمة أسرع للبيانات.</p>
</div>
</div>
</section>
<!-- 05. Serving Geometry -->
<section id="serving">
<span class="section-label">Real-time</span>
<h2>Phase 4: Vector Tile Serving</h2>
<div class="deep-dive">
<div class="en-side">
<p>Mapping in 2026 relies on <strong>Vector Tiles</strong>. Instead of static pictures, we send mathematical geometry to the browser. We use <strong>Martin</strong> because it generates these tiles dynamically from PostGIS SQL tables.</p>
</div>
<div class="ar-side">
<p>تعتمد الخرائط في عام 2026 على <strong>بلاطات المتجهات (Vector Tiles)</strong>. بدلاً من الصور الثابتة، نرسل أجزاء هندسية رياضية إلى المتصفح. نستخدم <strong>Martin</strong> لأنه ينشئ هذه البلاطات ديناميكياً من جداول SQL.</p>
</div>
</div>
</section>
<!-- 06. Intelligent Routing -->
<section id="routing">
<span class="section-label">Logistics</span>
<h2>Phase 5: Pathfinding with GraphHopper</h2>
<div class="deep-dive">
<div class="en-side">
<p>Maps are for travel. <strong>GraphHopper</strong> is our logistics brain. It reads the imported road network and applies Dijkstra-based algorithms to find the fastest path between any two points in Jordan.</p>
</div>
<div class="ar-side">
<p>الخرائط هي للسفر والتحرك. <strong>GraphHopper</strong> هو دماغ اللوجستيات الخاص بنا. يقرأ شبكة الطرق المستوردة ويطبق خوارزميات (Dijkstra) للعثور على أسرع مسار بين أي نقطتين في الأردن.</p>
</div>
</div>
</section>
<!-- 07. Modern Interface -->
<section id="interface">
<span class="section-label">Interaction</span>
<h2>Phase 6: The Full-Stack Bridge</h2>
<div class="deep-dive">
<div class="en-side">
<p>Last, we bridge it all. <strong>NestJS</strong> provides the secured API proxy, and <strong>React + MapLibre GL</strong> provides the high-performance visualization layer.</p>
</div>
<div class="ar-side">
<p>أخيراً، نجمع كل شيء. يوفر <strong>NestJS</strong> بوابة آمنة (API Proxy)، ويوفر <strong>React + MapLibre GL</strong> طبقة التصور المرئي عالية الأداء.</p>
</div>
</div>
</section>
<footer style="margin-top: 10rem; padding-top: 4rem; border-top: 1px solid var(--border); text-align: center;">
<p>© 2026 Map SaaS Engineering Tutorial. All Rights Reserved.</p>
</footer>
</div>
</main>
</div>
<script src="script.js"></script>
</body>
</html>
+80
View File
@@ -0,0 +1,80 @@
document.addEventListener('DOMContentLoaded', () => {
const navLinks = document.querySelectorAll('nav a');
const sections = document.querySelectorAll('section');
// Smooth scroll with offset for sticky header/sidebar
navLinks.forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const targetId = link.getAttribute('href').substring(1);
const targetElement = document.getElementById(targetId);
if (targetElement) {
window.scrollTo({
top: targetElement.offsetTop - 50,
behavior: 'smooth'
});
}
});
});
// Intersection Observer for Sidebar Active State
const observerOptions = {
root: null,
rootMargin: '-20% 0px -70% 0px',
threshold: 0
};
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const id = entry.target.getAttribute('id');
navLinks.forEach(link => {
link.classList.remove('active');
if (link.getAttribute('href') === `#${id}`) {
link.classList.add('active');
}
});
}
});
}, observerOptions);
sections.forEach(section => observer.observe(section));
// Advanced Reveal Animations on Scroll
const revealElements = document.querySelectorAll('.deep-dive, .tech-card, .highlight-box, pre');
const revealObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('revealed');
}
});
}, { threshold: 0.1 });
revealElements.forEach(el => {
el.style.opacity = '0';
el.style.transform = 'translateY(40px)';
el.style.transition = 'all 1s cubic-bezier(0.2, 0.8, 0.2, 1)';
revealObserver.observe(el);
});
// CSS injection for reveal animation class
const style = document.createElement('style');
style.textContent = `
.revealed {
opacity: 1 !important;
transform: translateY(0) !important;
}
`;
document.head.appendChild(style);
// Parallax Hero Effect
window.addEventListener('scroll', () => {
const hero = document.querySelector('.hero-img');
const scroll = window.pageYOffset;
if (hero) {
hero.style.transform = `scale(1.1) translateY(${scroll * 0.4}px)`;
}
});
});
+340
View File
@@ -0,0 +1,340 @@
:root {
--primary: #6366f1;
--primary-glow: rgba(99, 102, 241, 0.4);
--secondary: #06b6d4;
--accent: #f59e0b;
--bg-dark: #020617;
--bg-card: rgba(15, 23, 42, 0.8);
--text-main: #f1f5f9;
--text-dim: #94a3b8;
--glass: rgba(255, 255, 255, 0.03);
--border: rgba(255, 255, 255, 0.08);
--font-en: 'Inter', system-ui, -apple-system, sans-serif;
--font-ar: 'Noto Sans Arabic', sans-serif;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background-color: var(--bg-dark);
color: var(--text-main);
font-family: var(--font-en);
line-height: 1.7;
overflow-x: hidden;
}
/* Custom Scrollbar */
::-webkit-scrollbar {
width: 6px;
}
::-webkit-scrollbar-track {
background: var(--bg-dark);
}
::-webkit-scrollbar-thumb {
background: linear-gradient(to bottom, var(--primary), var(--secondary));
border-radius: 10px;
}
/* Background Elements */
.bg-glow {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: radial-gradient(circle at 10% 20%, rgba(99, 102, 241, 0.05) 0%, transparent 40%),
radial-gradient(circle at 90% 80%, rgba(6, 118, 212, 0.05) 0%, transparent 40%);
pointer-events: none;
z-index: -1;
}
/* Layout */
.app-container {
display: flex;
min-height: 100vh;
}
/* Sidebar Navigation */
aside {
width: 320px;
height: 100vh;
position: sticky;
top: 0;
background: var(--glass);
backdrop-filter: blur(20px);
border-right: 1px solid var(--border);
padding: 3rem 2rem;
display: flex;
flex-direction: column;
gap: 2.5rem;
z-index: 100;
}
.brand {
display: flex;
align-items: center;
gap: 1rem;
}
.brand-icon {
width: 40px;
height: 40px;
background: linear-gradient(135deg, var(--primary), var(--secondary));
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 0 20px var(--primary-glow);
}
.brand h2 {
font-size: 1.5rem;
font-weight: 800;
letter-spacing: -0.5px;
}
nav ul {
list-style: none;
}
nav li {
margin-bottom: 0.5rem;
}
nav a {
color: var(--text-dim);
text-decoration: none;
font-size: 0.95rem;
font-weight: 500;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1rem;
border-radius: 0.75rem;
}
nav a:hover, nav a.active {
color: #fff;
background: rgba(255, 255, 255, 0.05);
transform: translateX(5px);
}
nav a.active {
background: linear-gradient(to right, rgba(99, 102, 241, 0.1), transparent);
border-left: 2px solid var(--primary);
}
/* Main Content Area */
main {
flex: 1;
position: relative;
}
.hero {
position: relative;
height: 60vh;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
border-bottom: 1px solid var(--border);
}
.hero::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 200px;
background: linear-gradient(to top, var(--bg-dark), transparent);
}
.hero-img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
filter: brightness(0.4) saturate(1.2);
transform: scale(1.1);
transition: transform 20s ease-out;
}
.hero:hover .hero-img {
transform: scale(1.2);
}
.hero-content {
position: relative;
z-index: 10;
text-align: center;
max-width: 800px;
padding: 0 2rem;
}
.hero h1 {
font-size: 4rem;
font-weight: 900;
margin-bottom: 1.5rem;
background: linear-gradient(to bottom, #fff, var(--text-dim));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
line-height: 1.1;
}
.badge {
display: inline-block;
padding: 0.5rem 1rem;
background: var(--primary-glow);
border: 1px solid var(--primary);
border-radius: 50px;
font-size: 0.8rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 2px;
margin-bottom: 2rem;
}
/* Tutorial Sections */
.sections-wrapper {
max-width: 1100px;
margin: 0 auto;
padding: 6rem 2rem;
}
section {
margin-bottom: 10rem;
scroll-margin-top: 4rem;
}
.section-label {
color: var(--primary);
font-weight: 700;
font-size: 0.9rem;
text-transform: uppercase;
letter-spacing: 3px;
margin-bottom: 1rem;
display: block;
}
h2 {
font-size: 2.75rem;
font-weight: 800;
margin-bottom: 3rem;
letter-spacing: -1px;
}
/* Detailed Bilingual Blocks */
.deep-dive {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4rem;
align-items: start;
}
.en-side, .ar-side {
flex: 1;
}
.ar-side {
direction: rtl;
font-family: var(--font-ar);
}
.deep-dive p {
font-size: 1.15rem;
color: var(--text-dim);
margin-bottom: 2rem;
}
.highlight-box {
background: var(--glass);
border: 1px solid var(--border);
padding: 2rem;
border-radius: 1.5rem;
margin: 2rem 0;
position: relative;
overflow: hidden;
}
.highlight-box::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 4px;
height: 100%;
background: var(--primary);
}
/* Grid Cards */
.tech-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
margin: 4rem 0;
}
.tech-card {
background: var(--bg-card);
border: 1px solid var(--border);
padding: 2.5rem;
border-radius: 2rem;
transition: all 0.5s ease;
position: relative;
}
.tech-card:hover {
transform: translateY(-10px);
border-color: var(--primary);
box-shadow: 0 20px 40px rgba(0,0,0,0.4);
}
.tech-card i {
font-size: 2.5rem;
color: var(--primary);
margin-bottom: 1.5rem;
display: block;
}
/* Code Mirror Styles */
pre {
background: #010409;
padding: 2.5rem;
border-radius: 1.5rem;
border: 1px solid var(--border);
overflow-x: auto;
margin: 3rem 0;
box-shadow: inset 0 0 20px rgba(0,0,0,0.5);
}
code {
font-family: 'Fira Code', monospace;
font-size: 0.95rem;
color: #e6edf3;
line-height: 1.6;
}
/* Responsive */
@media (max-width: 1200px) {
.deep-dive {
grid-template-columns: 1fr;
gap: 4rem;
}
}
@media (max-width: 768px) {
aside {
display: none;
}
.hero h1 {
font-size: 2.5rem;
}
}