Backup before syncing server scripts
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
build-sprite.py — يبني sprite sheet لـ MapLibre من مجلد SVG.
|
||||
|
||||
لماذا: MapLibre لا يقرأ ملفات SVG مفردة. يحتاج صورة PNG واحدة تُرصف فيها كل
|
||||
الآيقونات + فهرس JSON يحدد موضع وحجم كل واحدة. كان الستايل يشير إلى
|
||||
demotiles.maplibre.org (خادم تجريبي)، وحين يفشل تحميله يرسم MapLibre النص
|
||||
بلا آيقونة وبصمت — فتبدو الخريطة بأسماء بلا رموز.
|
||||
|
||||
الاستخدام:
|
||||
pip install cairosvg pillow
|
||||
python3 infrastructure/scripts/build-sprite.py
|
||||
|
||||
المخرجات في apps/web/public/:
|
||||
sprite.png / sprite.json (1x)
|
||||
sprite@2x.png / sprite@2x.json (شاشات Retina — MapLibre يطلبها تلقائياً)
|
||||
|
||||
ثم في الستايل: "sprite": "https://map-saas.intaleqapp.com/sprite"
|
||||
(بلا امتداد — MapLibre يضيف .png/.json و@2x بنفسه)
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
try:
|
||||
import cairosvg
|
||||
from PIL import Image
|
||||
except ImportError:
|
||||
sys.exit("ينقص: pip install cairosvg pillow")
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
ICONS_DIR = os.path.join(ROOT, "apps", "web", "public", "icons")
|
||||
OUT_DIR = os.path.join(ROOT, "apps", "web", "public")
|
||||
|
||||
BASE_SIZE = 24 # بكسل للآيقونة عند 1x
|
||||
PADDING = 2 # فاصل يمنع نزّ ألوان الجيران عند التصغير (bleeding)
|
||||
|
||||
|
||||
def build(scale: int, suffix: str) -> None:
|
||||
size = BASE_SIZE * scale
|
||||
pad = PADDING * scale
|
||||
|
||||
svgs = sorted(f for f in os.listdir(ICONS_DIR) if f.endswith(".svg"))
|
||||
if not svgs:
|
||||
sys.exit(f"لا توجد ملفات SVG في {ICONS_DIR}")
|
||||
|
||||
rendered = []
|
||||
for fname in svgs:
|
||||
name = os.path.splitext(fname)[0]
|
||||
png_bytes = cairosvg.svg2png(
|
||||
url=os.path.join(ICONS_DIR, fname),
|
||||
output_width=size,
|
||||
output_height=size,
|
||||
)
|
||||
import io
|
||||
rendered.append((name, Image.open(io.BytesIO(png_bytes)).convert("RGBA")))
|
||||
|
||||
# شبكة مربعة تقريباً: أقل هدراً من صف واحد طويل
|
||||
cols = max(1, int(len(rendered) ** 0.5 + 0.999))
|
||||
rows = (len(rendered) + cols - 1) // cols
|
||||
|
||||
sheet = Image.new(
|
||||
"RGBA",
|
||||
(cols * (size + pad) - pad, rows * (size + pad) - pad),
|
||||
(0, 0, 0, 0),
|
||||
)
|
||||
|
||||
index = {}
|
||||
for i, (name, img) in enumerate(rendered):
|
||||
x = (i % cols) * (size + pad)
|
||||
y = (i // cols) * (size + pad)
|
||||
sheet.paste(img, (x, y))
|
||||
index[name] = {
|
||||
"x": x, "y": y,
|
||||
"width": size, "height": size,
|
||||
"pixelRatio": scale,
|
||||
"sdf": False,
|
||||
}
|
||||
|
||||
sheet.save(os.path.join(OUT_DIR, f"sprite{suffix}.png"))
|
||||
with open(os.path.join(OUT_DIR, f"sprite{suffix}.json"), "w") as fh:
|
||||
json.dump(index, fh, indent=2)
|
||||
|
||||
print(f"✅ sprite{suffix}: {len(index)} آيقونة، {sheet.width}×{sheet.height}px")
|
||||
return list(index)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
names = build(1, "")
|
||||
build(2, "@2x")
|
||||
print("\nالآيقونات المتاحة لـ icon-image:")
|
||||
print(" " + ", ".join(names))
|
||||
print("\n⚠️ طبقات الستايل تطلب أسماء قد لا تكون موجودة أعلاه")
|
||||
print(" (college, rail, tourist ...) — راجعها أو أضف SVG مقابلاً لكل ناقص.")
|
||||
@@ -56,8 +56,11 @@ for c in "${CASES[@]}"; do
|
||||
if [ "$1" = "ok" ]; then
|
||||
echo " ✓ متقارب (نسبة ${2})"
|
||||
else
|
||||
echo " ✗ فارق كبير (نسبة ${2:-؟}) — اتجاه واحد مفروض أو التفاف إجباري"
|
||||
echo " شغّل: bash infrastructure/scripts/normalize-oneway.sh audit"
|
||||
echo " ⚠ فارق كبير (نسبة ${2:-؟}) — اتجاه واحد مفروض أو التفاف إجباري"
|
||||
echo " هذا ليس عطلاً بالضرورة: على شارع أحادي فعلاً هذا هو السلوك الصحيح."
|
||||
echo " تحقّق ميدانياً أولاً. لا تشغّل normalize-oneway.sh apply قبل ذلك —"
|
||||
echo " فهو يكتب oneway=no على البيانات وينتج مسارات غير قانونية."
|
||||
echo " اقرأ: docs/ROUTING_ONEWAY_AR.md"
|
||||
FAIL=1
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -1,82 +1,170 @@
|
||||
#!/bin/bash
|
||||
# --------------------------------------------------------------------------
|
||||
# overture_ingest.sh — سحب وحقن بيانات Overture (مباني، شوارع، أماكن، حدود)
|
||||
#
|
||||
# ⚠️ تغيير جوهري عن النسخة القديمة:
|
||||
# القديمة كانت تبدأ بـ DROP TABLE overture_place, overture_building,
|
||||
# overture_segment — أي أن تشغيلها لأي دولة يمسح بيانات كل الدول الأخرى.
|
||||
# الآن الحذف *محصور بالنطاق الجغرافي* الجاري إعادة حقنه فقط، فتبقى بيانات
|
||||
# بقية الدول سليمة. هذا يجعل السكربت قابلاً للتشغيل الجزئي وإعادة التشغيل.
|
||||
#
|
||||
# الاستخدام:
|
||||
# bash infrastructure/scripts/overture_ingest.sh # كل النطاقات
|
||||
# bash infrastructure/scripts/overture_ingest.sh baghdad # نطاق واحد
|
||||
# bash infrastructure/scripts/overture_ingest.sh iraq baghdad # عدة نطاقات
|
||||
#
|
||||
# ملاحظة عن الأحجام: المباني تُسحب على مستوى المدينة لا الدولة — مباني دولة
|
||||
# كاملة قد تتجاوز عدة جيجابايت GeoJSON ولا يحتملها قرص السيرفر الحالي.
|
||||
# لذلك: building/segment ← نطاق مدينة، place/division_area ← نطاق دولة.
|
||||
# --------------------------------------------------------------------------
|
||||
set -euo pipefail
|
||||
|
||||
# overture_ingest.sh - Data pipeline for Overture Maps
|
||||
# سكريبت سحب وحقن بيانات المباني والشوارع
|
||||
APP_DIR="${APP_DIR:-/home/hamzadoctor/app}"
|
||||
DB_HOST="${DB_HOST:-localhost}"
|
||||
DB_PORT="${DB_PORT:-5432}"
|
||||
DB_USER="${DB_USER:-mapuser}"
|
||||
DB_NAME="${DB_NAME:-mapdb}"
|
||||
DB_PASS="${DB_PASS:-mappass}"
|
||||
|
||||
set -e
|
||||
cd "$APP_DIR"
|
||||
|
||||
# Configuration
|
||||
DB_HOST="localhost"
|
||||
DB_PORT="5432"
|
||||
DB_USER="mapuser"
|
||||
DB_NAME="mapdb"
|
||||
DB_PASS="mappass"
|
||||
|
||||
# Jordan North BBOX (Amman, Irbid, Zarqa)
|
||||
# ── النطاقات: "minlon,minlat,maxlon,maxlat" ───────────────────────────────
|
||||
# نطاقات الدول (للأماكن والحدود الإدارية)
|
||||
BBOX_JORDAN_NORTH="35.5,31.7,39.3,33.4"
|
||||
# Jordan South BBOX (Aqaba, Karak, Ma'an)
|
||||
BBOX_JORDAN_SOUTH="34.9,29.1,38.0,31.7"
|
||||
# Syria BBOX (Full Country)
|
||||
BBOX_SYRIA="35.7,32.3,42.4,37.3"
|
||||
BBOX_EGYPT="24.7,21.9,36.9,31.7"
|
||||
BBOX_IRAQ="38.7,29.0,48.8,37.4"
|
||||
|
||||
echo "🚀 Starting Overture Data Pipeline (Jordan & Syria)..."
|
||||
# نطاقات المدن (للمباني والشوارع — أثقل بكثير)
|
||||
BBOX_AMMAN="35.75,31.75,36.15,32.15"
|
||||
BBOX_DAMASCUS="36.15,33.40,36.45,33.62"
|
||||
BBOX_CAIRO="31.10,29.85,31.50,30.20"
|
||||
# بغداد المركز فقط (~25×17كم). النطاق الأوسع 44.10,33.10,44.70,33.50 تجاوز
|
||||
# 632 ألف مبنى وملأ قرص السيرفر قبل أن ينتهي. وسّعه بعد ترقية التخزين.
|
||||
BBOX_BAGHDAD="44.28,33.25,44.55,33.40"
|
||||
|
||||
echo "🧹 Clearing old Overture tables to prevent duplication..."
|
||||
docker compose -f /home/hamzadoctor/app/docker-compose.yml exec -T db psql -U $DB_USER -d $DB_NAME -c "DROP TABLE IF EXISTS overture_place, overture_building, overture_segment;"
|
||||
psql_run() {
|
||||
docker compose -f "${APP_DIR}/docker-compose.yml" exec -T db \
|
||||
psql -U "$DB_USER" -d "$DB_NAME" "$@"
|
||||
}
|
||||
|
||||
# 1. Setup Python Environment
|
||||
# ... (rest of setup)
|
||||
if [ ! -d "venv_overture" ]; then
|
||||
echo "📦 Creating virtual environment..."
|
||||
python3 -m venv venv_overture
|
||||
fi
|
||||
source venv_overture/bin/activate
|
||||
pip install --upgrade pip
|
||||
pip install overturemaps
|
||||
# ── حذف محصور بالنطاق: ينظّف ما سنعيد حقنه فقط، ولا يمس الدول الأخرى ──────
|
||||
purge_bbox() {
|
||||
local table=$1 bbox=$2
|
||||
IFS=',' read -r minx miny maxx maxy <<< "$bbox"
|
||||
# to_regclass يمنع الفشل في أول تشغيل قبل أن ينشئ ogr2ogr الجدول
|
||||
psql_run -c "
|
||||
DO \$\$
|
||||
BEGIN
|
||||
IF to_regclass('public.${table}') IS NOT NULL THEN
|
||||
DELETE FROM ${table}
|
||||
WHERE location && ST_MakeEnvelope(${minx}, ${miny}, ${maxx}, ${maxy}, 4326);
|
||||
END IF;
|
||||
END \$\$;
|
||||
"
|
||||
}
|
||||
|
||||
# 2. Function to download and ingest
|
||||
process_city() {
|
||||
local city=$1
|
||||
local bbox=$2
|
||||
local theme=$3 # building, segment, or division_area
|
||||
|
||||
echo "🌍 Processing $city - Theme: $theme..."
|
||||
output_file="overture_${city}_${theme}.geojson"
|
||||
|
||||
# Download
|
||||
process() {
|
||||
local region=$1 bbox=$2 theme=$3
|
||||
local output_file="overture_${region}_${theme}.geojson"
|
||||
|
||||
echo "🌍 [$region] السمة: $theme ..."
|
||||
overturemaps download --bbox="$bbox" -f geojson --type="$theme" -o "$output_file"
|
||||
|
||||
# If division_area, we use our NestJS API to import for better control
|
||||
|
||||
if [ "$theme" == "division_area" ]; then
|
||||
echo "📍 Administrative data downloaded: $output_file"
|
||||
echo "💡 Use the NestJS /geocoding/import-boundaries endpoint to ingest this file."
|
||||
echo "📍 الحدود الإدارية جاهزة: $output_file"
|
||||
echo " استوردها عبر: POST /geocoding/import-boundaries?country=<CODE>&filePath=/data/${output_file}"
|
||||
return
|
||||
fi
|
||||
|
||||
# Ingest into PostGIS for buildings and segments
|
||||
echo "🔌 Injecting into PostGIS (Table: overture_${theme})..."
|
||||
echo "🧹 تنظيف نطاق ${region} من overture_${theme} (بدون المساس بغيره)..."
|
||||
purge_bbox "overture_${theme}" "$bbox"
|
||||
|
||||
echo "🔌 الحقن في PostGIS (overture_${theme})..."
|
||||
export PGPASSWORD=$DB_PASS
|
||||
ogr2ogr -f "PostgreSQL" \
|
||||
# `if !` ضروري: مع set -e يخرج السكربت فوراً عند فشل ogr2ogr فلا يُحذف
|
||||
# الملف المؤقت. ملف مباني مدينة كبيرة قد يبلغ عدة جيجابايت، وتَركه بعد
|
||||
# فشل سببه امتلاء القرص أصلاً يجعل المحاولة التالية تفشل أسرع.
|
||||
if ! ogr2ogr -f "PostgreSQL" \
|
||||
PG:"host=$DB_HOST port=$DB_PORT user=$DB_USER dbname=$DB_NAME password=$DB_PASS" \
|
||||
"$output_file" \
|
||||
-nln "overture_${theme}" \
|
||||
-update -append \
|
||||
-nlt PROMOTE_TO_MULTI \
|
||||
-lco GEOMETRY_NAME=location
|
||||
|
||||
echo "✅ Finished $city $theme"
|
||||
rm "$output_file"
|
||||
then
|
||||
echo "❌ فشل حقن ${region}/${theme} — يُحذف الملف المؤقت ($(du -h "$output_file" 2>/dev/null | cut -f1))"
|
||||
rm -f "$output_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "✅ انتهى ${region}/${theme}"
|
||||
rm -f "$output_file"
|
||||
}
|
||||
|
||||
# 3. Execution (Jordan & Syria)
|
||||
# Jordan
|
||||
process_city "jordan_north" "$BBOX_JORDAN_NORTH" "division_area"
|
||||
process_city "jordan_north" "$BBOX_JORDAN_NORTH" "place"
|
||||
process_city "jordan_south" "$BBOX_JORDAN_SOUTH" "division_area"
|
||||
process_city "jordan_south" "$BBOX_JORDAN_SOUTH" "place"
|
||||
# ── تجهيز بيئة بايثون ─────────────────────────────────────────────────────
|
||||
if [ ! -d "venv_overture" ]; then
|
||||
echo "📦 إنشاء بيئة افتراضية..."
|
||||
python3 -m venv venv_overture
|
||||
fi
|
||||
source venv_overture/bin/activate
|
||||
pip install --quiet --upgrade pip
|
||||
pip install --quiet overturemaps
|
||||
|
||||
# Syria
|
||||
process_city "syria" "$BBOX_SYRIA" "division_area"
|
||||
process_city "syria" "$BBOX_SYRIA" "place"
|
||||
# ── تعريف النطاقات القابلة للتشغيل ────────────────────────────────────────
|
||||
run_jordan() { process jordan_north "$BBOX_JORDAN_NORTH" division_area
|
||||
process jordan_north "$BBOX_JORDAN_NORTH" place
|
||||
process jordan_south "$BBOX_JORDAN_SOUTH" division_area
|
||||
process jordan_south "$BBOX_JORDAN_SOUTH" place; }
|
||||
run_syria() { process syria "$BBOX_SYRIA" division_area
|
||||
process syria "$BBOX_SYRIA" place; }
|
||||
run_egypt() { process egypt "$BBOX_EGYPT" division_area
|
||||
process egypt "$BBOX_EGYPT" place; }
|
||||
run_iraq() { process iraq "$BBOX_IRAQ" division_area
|
||||
process iraq "$BBOX_IRAQ" place; }
|
||||
|
||||
echo "🎉 Administrative boundary GeoJSONs ready for import!"
|
||||
run_amman() { process amman "$BBOX_AMMAN" building
|
||||
process amman "$BBOX_AMMAN" segment; }
|
||||
run_damascus() { process damascus "$BBOX_DAMASCUS" building
|
||||
process damascus "$BBOX_DAMASCUS" segment; }
|
||||
run_cairo() { process cairo "$BBOX_CAIRO" building
|
||||
process cairo "$BBOX_CAIRO" segment; }
|
||||
run_baghdad() { process baghdad "$BBOX_BAGHDAD" building
|
||||
process baghdad "$BBOX_BAGHDAD" segment; }
|
||||
|
||||
ALL_REGIONS=(jordan syria egypt iraq amman damascus cairo baghdad)
|
||||
TARGETS=("$@")
|
||||
if [ ${#TARGETS[@]} -eq 0 ]; then
|
||||
TARGETS=("${ALL_REGIONS[@]}")
|
||||
fi
|
||||
|
||||
echo "🚀 نطاقات هذا التشغيل: ${TARGETS[*]}"
|
||||
for r in "${TARGETS[@]}"; do
|
||||
if ! declare -F "run_${r}" >/dev/null; then
|
||||
echo "❌ نطاق غير معروف: ${r} — المتاح: ${ALL_REGIONS[*]}"
|
||||
exit 2
|
||||
fi
|
||||
"run_${r}"
|
||||
done
|
||||
|
||||
# ── فهارس مكانية + تسطيح الأسماء (idempotent) ─────────────────────────────
|
||||
# name_primary إلزامي: عمود names من نوع json وبلاطات MVT لا تحمل كائنات، فلا
|
||||
# يستطيع الستايل قراءة الاسم منه. التفاصيل: infrastructure/sql/09_overture_name_primary.sql
|
||||
for t in overture_building overture_segment overture_place; do
|
||||
psql_run -c "
|
||||
DO \$\$
|
||||
BEGIN
|
||||
IF to_regclass('public.${t}') IS NOT NULL THEN
|
||||
EXECUTE 'CREATE INDEX IF NOT EXISTS idx_${t}_location ON ${t} USING gist (location)';
|
||||
EXECUTE 'ALTER TABLE ${t} ADD COLUMN IF NOT EXISTS name_primary text';
|
||||
EXECUTE 'UPDATE ${t} SET name_primary = names->>''primary''
|
||||
WHERE names IS NOT NULL AND name_primary IS NULL';
|
||||
EXECUTE 'CREATE INDEX IF NOT EXISTS idx_${t}_name_primary ON ${t} (name_primary)
|
||||
WHERE name_primary IS NOT NULL';
|
||||
END IF;
|
||||
END \$\$;
|
||||
"
|
||||
done
|
||||
|
||||
echo "🎉 اكتمل. البلاطات تُخدم عبر martin تلقائياً (WATCH_DB=true)."
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
set -e # Exit on error
|
||||
|
||||
echo "🚀 Starting 10-day map update (v2 — Jordan + Syria + Egypt + Delta)..."
|
||||
echo "🚀 Starting 10-day map update (v3 — Jordan + Syria + Egypt + Iraq + Delta)..."
|
||||
|
||||
APP_DIR="/home/hamzadoctor/app"
|
||||
DATA_DIR="${APP_DIR}/infrastructure/osm-data"
|
||||
@@ -21,27 +21,28 @@ DELTA_FILE="${DATA_DIR}/delta.osm"
|
||||
|
||||
cd "${APP_DIR}"
|
||||
|
||||
# ── Step 1: Download all three country PBFs ────────────────────────────────
|
||||
echo "🌍 Downloading Jordan, Syria & Egypt PBF data from Geofabrik..."
|
||||
# ── Step 1: Download all four country PBFs ─────────────────────────────────
|
||||
echo "🌍 Downloading Jordan, Syria, Egypt & Iraq PBF data from Geofabrik..."
|
||||
|
||||
wget -q --show-progress -O "${DATA_DIR}/jordan-latest.osm.pbf.new" \
|
||||
"https://download.geofabrik.de/asia/jordan-latest.osm.pbf"
|
||||
|
||||
wget -q --show-progress -O "${DATA_DIR}/syria-latest.osm.pbf.new" \
|
||||
"https://download.geofabrik.de/africa/egypt-latest.osm.pbf"
|
||||
# ✅ FIX: Syria URL was accidentally downloading Egypt above in old script — corrected
|
||||
wget -q --show-progress -O "${DATA_DIR}/egypt-latest.osm.pbf.new" \
|
||||
"https://download.geofabrik.de/africa/egypt-latest.osm.pbf"
|
||||
# ✅ FIX: كان يحمّل مصر إلى ملف سوريا ثم يعيد تحميل سوريا فوقها — هدر 177MB كل دورة
|
||||
wget -q --show-progress -O "${DATA_DIR}/syria-latest.osm.pbf.new" \
|
||||
"https://download.geofabrik.de/asia/syria-latest.osm.pbf"
|
||||
wget -q --show-progress -O "${DATA_DIR}/egypt-latest.osm.pbf.new" \
|
||||
"https://download.geofabrik.de/africa/egypt-latest.osm.pbf"
|
||||
wget -q --show-progress -O "${DATA_DIR}/iraq-latest.osm.pbf.new" \
|
||||
"https://download.geofabrik.de/asia/iraq-latest.osm.pbf"
|
||||
|
||||
mv "${DATA_DIR}/jordan-latest.osm.pbf.new" "${DATA_DIR}/jordan-latest.osm.pbf"
|
||||
mv "${DATA_DIR}/syria-latest.osm.pbf.new" "${DATA_DIR}/syria-latest.osm.pbf"
|
||||
mv "${DATA_DIR}/egypt-latest.osm.pbf.new" "${DATA_DIR}/egypt-latest.osm.pbf"
|
||||
mv "${DATA_DIR}/iraq-latest.osm.pbf.new" "${DATA_DIR}/iraq-latest.osm.pbf"
|
||||
|
||||
echo "✅ Downloads complete."
|
||||
|
||||
# ── Step 2: Import all three countries into PostGIS ───────────────────────
|
||||
# ── Step 2: Import all four countries into PostGIS ─────────────────────────
|
||||
echo "💾 Importing Jordan into PostGIS (--create resets planet_osm_* tables)..."
|
||||
docker compose --profile import run --rm osm-import \
|
||||
osm2pgsql --create --slim --cache 1000 \
|
||||
@@ -61,13 +62,30 @@ docker compose --profile import run --rm osm-import \
|
||||
--database mapdb --host db --user mapuser \
|
||||
/data/egypt-latest.osm.pbf
|
||||
|
||||
# ── Step 3: Merge all three PBFs into master_map.osm.pbf ─────────────────
|
||||
echo "💾 Importing Iraq into PostGIS (--append)..."
|
||||
docker compose --profile import run --rm osm-import \
|
||||
osm2pgsql --append --slim --cache 1000 \
|
||||
--database mapdb --host db --user mapuser \
|
||||
/data/iraq-latest.osm.pbf
|
||||
|
||||
# ── Step 2b: إسقاط جداول osm2pgsql الوسيطة ────────────────────────────────
|
||||
# planet_osm_nodes/ways/rels ينشئها --slim لدعم --append فقط، ولا يقرأها
|
||||
# التطبيق ولا martin إطلاقاً (جداول الإخراج هي line/polygon/point/roads).
|
||||
# قياس 30/07/2026: nodes 5031MB + ways 1400MB ≈ 6.4GB نائمة بين الدورات.
|
||||
# آمن لأن --create في الخطوة 2 يعيد إنشاءها من الصفر كل تشغيل.
|
||||
# ⚠️ لا تشغّل osm2pgsql --append بعد هذه النقطة حتى الدورة القادمة.
|
||||
echo "🧹 إسقاط جداول osm2pgsql الوسيطة (تُعاد في الدورة القادمة)..."
|
||||
docker compose exec -T db psql -U mapuser -d mapdb -c \
|
||||
"DROP TABLE IF EXISTS planet_osm_nodes, planet_osm_ways, planet_osm_rels;"
|
||||
|
||||
# ── Step 3: Merge all four PBFs into master_map.osm.pbf ────────────────────
|
||||
# ✅ FIX: Old script wrote to region.osm.pbf — GraphHopper reads master_map.osm.pbf
|
||||
echo "🗺️ Merging Jordan + Syria + Egypt → ${MASTER_FILE}..."
|
||||
echo "🗺️ Merging Jordan + Syria + Egypt + Iraq → ${MASTER_FILE}..."
|
||||
osmium merge \
|
||||
"${DATA_DIR}/jordan-latest.osm.pbf" \
|
||||
"${DATA_DIR}/syria-latest.osm.pbf" \
|
||||
"${DATA_DIR}/egypt-latest.osm.pbf" \
|
||||
"${DATA_DIR}/iraq-latest.osm.pbf" \
|
||||
-o "${MASTER_FILE}" --overwrite
|
||||
|
||||
echo "✅ Master PBF built: $(du -sh ${MASTER_FILE} | cut -f1)"
|
||||
@@ -92,16 +110,48 @@ docker compose exec -T db psql -U mapuser -d mapdb -t -c \
|
||||
echo "✅ Landmark sync done."
|
||||
|
||||
# ── Step 6: Rebuild GraphHopper routing index ─────────────────────────────
|
||||
# ذروة البناء لأربع دول ~3GB. على سيرفر بـ7.8GB الهامش ضيق، فنُخلي الذاكرة
|
||||
# للحاويات غير الضرورية أثناء البناء فقط ثم نعيدها. db و redis يبقيان لأن
|
||||
# الخطوات التالية تحتاجهما.
|
||||
echo "🚗 Rebuilding GraphHopper routing graph (may take 5-10 min)..."
|
||||
echo "⏸️ إيقاف مؤقت للحاويات غير الضرورية لتحرير الذاكرة أثناء البناء..."
|
||||
docker compose stop api web dashboard martin || true
|
||||
# نُعيدها مهما كانت نتيجة البناء — بما في ذلك الخروج المبكر عند الفشل
|
||||
trap 'echo "▶️ إعادة تشغيل الحاويات المؤقتة..."; docker compose up -d api web dashboard martin || true' EXIT
|
||||
|
||||
docker compose stop routing
|
||||
rm -rf "${DATA_DIR}/graph-cache" "${DATA_DIR}/default-gh"
|
||||
docker compose up -d routing
|
||||
echo "✅ GraphHopper restarting from ${MASTER_FILE}."
|
||||
|
||||
# ── Step 6b: بوابة تحقق — اتجاهات السير ──────────────────────────────────
|
||||
# انحدار 26/07/2026: سقوط قاعدة "!car_access → 0" جعل المحرك يتجاهل كل oneway
|
||||
# وكل دوار لأيام دون أن يلاحظ أحد. لا نُنهي التحديث دون إثبات أنها تعمل.
|
||||
# التفاصيل: docs/ROUTING_ONEWAY_AR.md
|
||||
echo "⏳ انتظار جهوزية المحرك قبل فحص الاتجاهات..."
|
||||
GH_READY=0
|
||||
for _ in $(seq 1 90); do
|
||||
if curl -fsS http://localhost:8989/health >/dev/null 2>&1; then GH_READY=1; break; fi
|
||||
sleep 10
|
||||
done
|
||||
|
||||
if [ "$GH_READY" = "0" ]; then
|
||||
echo "❌ المحرك لم يجهز خلال 15 دقيقة — راجع: docker compose logs -f routing"
|
||||
echo " (السبب المتكرر: exited with code 137 أي نفاد ذاكرة أثناء البناء)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! APP_DIR="${APP_DIR}" bash "${APP_DIR}/infrastructure/scripts/verify-oneway.sh"; then
|
||||
echo ""
|
||||
echo "❌❌ المحرك يتجاهل اتجاهات السير — التوجيه غير صالح للاستخدام."
|
||||
echo " اقرأ: docs/ROUTING_ONEWAY_AR.md"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── Step 7: Cache flush ───────────────────────────────────────────────────
|
||||
echo "🧹 Flushing Redis traffic cache..."
|
||||
docker compose exec -T redis redis-cli flushall
|
||||
|
||||
echo ""
|
||||
echo "✅ 10-day update complete — Jordan + Syria + Egypt + approved delta applied."
|
||||
echo "✅ 10-day update complete — Jordan + Syria + Egypt + Iraq + approved delta applied."
|
||||
echo " GraphHopper is rebuilding in the background. Allow 5-10 min for routing to be ready."
|
||||
|
||||
Executable
+82
@@ -0,0 +1,82 @@
|
||||
#!/bin/bash
|
||||
# --------------------------------------------------------------------------
|
||||
# verify-oneway.sh — يتحقق أن محرك التوجيه يحترم اتجاهات السير
|
||||
#
|
||||
# يختار طرقاً موسومة oneway=yes من البيانات نفسها، ويطلب مساراً على كل منها
|
||||
# في الاتجاهين. على طريق أحادي سليم يجب أن تختلف المسافتان (العودة تحتاج
|
||||
# التفافاً). التطابق التام = المحرك يتجاهل oneway.
|
||||
#
|
||||
# السبب المعتاد للفشل: سقوط قاعدة "!car_access → 0" من custom_model.
|
||||
# التفاصيل الكاملة: docs/ROUTING_ONEWAY_AR.md
|
||||
#
|
||||
# bash infrastructure/scripts/verify-oneway.sh
|
||||
#
|
||||
# متغيّرات: GH_URL (افتراضي http://localhost:8989)، BBOX، SAMPLES
|
||||
# --------------------------------------------------------------------------
|
||||
set -uo pipefail
|
||||
|
||||
APP_DIR="${APP_DIR:-/home/hamzadoctor/app}"
|
||||
GH_URL="${GH_URL:-http://localhost:8989}"
|
||||
PBF="${PBF:-${APP_DIR}/infrastructure/osm-data/master_map.osm.pbf}"
|
||||
BBOX="${BBOX:-36.055,32.100,36.072,32.116}" # lon1,lat1,lon2,lat2
|
||||
SAMPLES="${SAMPLES:-8}"
|
||||
|
||||
command -v osmium >/dev/null || { echo "❌ osmium غير مثبّت: apt-get install -y osmium-tool"; exit 2; }
|
||||
[ -f "$PBF" ] || { echo "❌ الملف غير موجود: $PBF"; exit 2; }
|
||||
curl -fsS "$GH_URL/health" >/dev/null || { echo "❌ المحرك لا يستجيب على $GH_URL"; exit 2; }
|
||||
|
||||
TMP=$(mktemp -d); trap 'rm -rf "$TMP"' EXIT
|
||||
|
||||
osmium extract -b "$BBOX" "$PBF" -o "$TMP/area.pbf" --overwrite >/dev/null 2>&1
|
||||
osmium tags-filter "$TMP/area.pbf" w/oneway=yes -o "$TMP/ow.pbf" --overwrite >/dev/null 2>&1
|
||||
osmium export -f geojson "$TMP/ow.pbf" -o "$TMP/ow.geojson" --overwrite >/dev/null 2>&1
|
||||
|
||||
GH_URL="$GH_URL" SAMPLES="$SAMPLES" python3 - "$TMP/ow.geojson" <<'PY'
|
||||
import json, os, sys, urllib.request
|
||||
|
||||
gh = os.environ["GH_URL"]
|
||||
want = int(os.environ["SAMPLES"])
|
||||
|
||||
def route(a, b):
|
||||
u = f"{gh}/route?point={a}&point={b}&profile=car"
|
||||
try:
|
||||
return round(json.load(urllib.request.urlopen(u, timeout=20))["paths"][0]["distance"])
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
feats = json.load(open(sys.argv[1]))["features"]
|
||||
print("═" * 60)
|
||||
print(" فحص احترام اتجاهات السير — " + gh)
|
||||
print("═" * 60)
|
||||
|
||||
tested = identical = 0
|
||||
for f in feats:
|
||||
if tested >= want:
|
||||
break
|
||||
g = f["geometry"]
|
||||
if g["type"] != "LineString" or len(g["coordinates"]) < 2:
|
||||
continue
|
||||
c = g["coordinates"]
|
||||
a = f"{c[0][1]:.7f},{c[0][0]:.7f}"
|
||||
b = f"{c[-1][1]:.7f},{c[-1][0]:.7f}"
|
||||
fwd, rev = route(a, b), route(b, a)
|
||||
if fwd is None or rev is None or fwd == 0:
|
||||
continue
|
||||
tested += 1
|
||||
name = (f["properties"].get("name") or "بلا اسم")[:24]
|
||||
# المعيار هو الاختلاف لا نسبته: الالتفاف قد يكون صغيراً نسبياً على مقطع طويل
|
||||
ok = fwd != rev
|
||||
identical += (not ok)
|
||||
print(f" {name:24} ذهاب:{fwd:6d}م عودة:{rev:6d}م {'✅' if ok else '❌ متطابق'}")
|
||||
|
||||
print()
|
||||
if tested == 0:
|
||||
print("⚠️ لم يُعثر على طرق أحادية صالحة للاختبار — راجع BBOX أو البيانات.")
|
||||
sys.exit(2)
|
||||
if identical:
|
||||
print(f"❌ فشل: {identical} من {tested} طريقاً أحادياً يُخترق — المحرك يتجاهل oneway.")
|
||||
print(" الأرجح: سقوط قاعدة '!car_access → 0' من custom_model في config.yml")
|
||||
print(" اقرأ: docs/ROUTING_ONEWAY_AR.md")
|
||||
sys.exit(1)
|
||||
print(f"✅ نجح: {tested} طريقاً أحادياً، كلها محترمة الاتجاه.")
|
||||
PY
|
||||
Reference in New Issue
Block a user