#!/usr/bin/env bash # ============================================================================ # بناء حزمة التوجيه المحلية (Valhalla Routing Package) للأردن # ============================================================================ # English: # Builds an on-device Valhalla routing package for Jordan from the same OSM # data used by the server-side GraphHopper engine, with real SRTM elevation # baked into every edge (grade/ascend/descend), admin street context, and a # tile extract tar that the tactical app downloads and routes against 100% # offline via valhalla-mobile. # # Output: infrastructure/osm-data/routing-packages/ # - jordan-valhalla-.tar (valhalla_tiles.tar + admins.sqlite) # - jordan-routing-manifest.json (version, sha256, sizeBytes, builtAt) # # Runs in two modes: # 1. Host mode : ./infrastructure/scripts/build-valhalla-tiles.sh (needs docker) # 2. Container : docker compose --profile tiles run --rm valhalla-tiles # ============================================================================ set -euo pipefail VERSION="${JORDAN_ROUTING_VERSION:-jordan-1.0.0-$(date +%Y%m%d)}" VALHALLA_IMG="ghcr.io/valhalla/valhalla:latest" # Jordan bounding box with a small buffer so border corridors connect cleanly. S=29.05; W=34.75; N=32.80; E=39.45 SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" ROOT="$( cd "$SCRIPT_DIR/../.." && pwd )" IN_CONTAINER=false if [ -f /.dockerenv ] || [ "${VALHALLA_IN_CONTAINER:-0}" = "1" ]; then IN_CONTAINER=true fi if [ "$IN_CONTAINER" = true ]; then # Compose mounts ./infrastructure/osm-data at /data DATA_DIR="/data" else DATA_DIR="$ROOT/infrastructure/osm-data" fi OUT_DIR="$DATA_DIR/routing-packages" WORK_DIR="$DATA_DIR/valhalla-work" MASTER_PBF="$DATA_DIR/master_map.osm.pbf" log() { echo "🧭 $1"; } run_vh() { # Run a command inside the valhalla toolchain (locally or through docker). if [ "$IN_CONTAINER" = true ]; then bash -c "$1" else docker run --rm -v "$DATA_DIR":/data -w /data --entrypoint bash "$VALHALLA_IMG" -lc "$1" fi } if [ ! -f "$MASTER_PBF" ]; then echo "❌ master_map.osm.pbf not found at: $MASTER_PBF" >&2 echo " ضع ملف OSM المدمج في infrastructure/osm-data/master_map.osm.pbf أولاً." >&2 exit 1 fi mkdir -p "$OUT_DIR" "$WORK_DIR" log "[1/6] Cutting Jordan extract ($W,$S → $E,$N) ..." if run_vh 'command -v osmium >/dev/null 2>&1' 2>/dev/null; then run_vh "osmium extract --bbox $W,$S,$E,$N --strategy smart --overwrite \ /data/master_map.osm.pbf -o /data/valhalla-work/jordan_routing.osm.pbf" else log " osmium غير متوفر — جاري استخدام مستخرج Geofabrik للأردن (نفس مصدر OSM)." curl -fL --retry 3 "https://download.geofabrik.de/asia/jordan-latest.osm.pbf" \ -o "$WORK_DIR/jordan_routing.osm.pbf" fi log "[2/6] Generating config + downloading SRTM elevation for Jordan ..." run_vh ' set -euo pipefail cd /data/valhalla-work mkdir -p /data/valhalla-work/valhalla_tiles /data/valhalla-work/elevation valhalla_build_config \ --mjolnir-tile-dir /data/valhalla-work/valhalla_tiles \ --mjolnir-concurrency "$(nproc)" \ --mjolnir-tile-extract /data/valhalla-work/valhalla_tiles.tar \ --mjolnir-admin /data/valhalla-work/admins.sqlite \ --additional-data-elevation /data/valhalla-work/elevation \ > /data/valhalla-work/valhalla.json # SRTM 30m elevation for Jordan bbox valhalla_build_elevation -c /data/valhalla-work/valhalla.json -b 34.75,29.05,39.45,32.80 || true ' log "[3/6] Building admin database (governorate/street context) ..." run_vh 'valhalla_build_admins --config /data/valhalla-work/valhalla.json /data/valhalla-work/jordan_routing.osm.pbf' log "[4/6] Building routing graph tiles (roads + oneway + elevation grades) ..." run_vh 'valhalla_build_tiles --config /data/valhalla-work/valhalla.json -j "$(nproc)" /data/valhalla-work/jordan_routing.osm.pbf' log "[5/6] Creating tile extract (tar) and verifying a test route ..." run_vh ' set -euo pipefail cd /data/valhalla-work rm -f /data/valhalla-work/valhalla_tiles.tar valhalla_build_extract --config /data/valhalla-work/valhalla.json -v --overwrite # فحص سلامة: عمان ← العقبة يجب أن ينجح بالكامل على الغراف المحلي. valhalla_service /data/valhalla-work/valhalla.json route \ "{\"locations\":[{\"lat\":31.9539,\"lon\":35.9106},{\"lat\":29.5320,\"lon\":35.0060}],\"costing\":\"auto\",\"units\":\"kilometers\",\"directions_options\":{\"language\":\"ar\"}}" \ > /data/valhalla-work/test_route.json 2>/dev/null || true python3 - <<'PY' import json, sys try: with open("/data/valhalla-work/test_route.json") as f: d = json.load(f) t = d.get("trip", {}) status = t.get("status") msg = t.get("status_message", "unknown error") if status == 0: dist = round(t.get("summary", {}).get("length", 0), 1) maneuvers = sum(len(l.get("maneuvers", [])) for l in t.get("legs", [])) print(f" ✅ test route OK: {dist} km | {maneuvers} maneuvers") else: print(f" ⚠️ test route status: {status} ({msg})") except Exception as e: print(f" ℹ️ test route check note: {e}") PY ' log "[5.5/6] Extracting road graph (jordan_roads.db) for on-device SQLite routing ..." PY_SCRIPT="$SCRIPT_DIR/extract_roads_graph.py" if [ ! -f "$PY_SCRIPT" ] && [ -f "$DATA_DIR/../infrastructure/scripts/extract_roads_graph.py" ]; then PY_SCRIPT="$DATA_DIR/../infrastructure/scripts/extract_roads_graph.py" fi if [ -f "$PY_SCRIPT" ]; then python3 "$PY_SCRIPT" "$WORK_DIR/jordan_routing.osm.pbf" "$WORK_DIR/jordan_roads.db" || true fi log "[6/6] Packaging + manifest ..." FINAL_TAR="$OUT_DIR/jordan-valhalla-$VERSION.tar" rm -f "$FINAL_TAR" PACK_ITEMS="valhalla_tiles.tar admins.sqlite" if [ -f "$WORK_DIR/jordan_roads.db" ]; then PACK_ITEMS="$PACK_ITEMS jordan_roads.db" log " Included jordan_roads.db in package." fi tar -cf "$FINAL_TAR" -C "$WORK_DIR" $PACK_ITEMS if [ "$IN_CONTAINER" = false ]; then SIZE=$(stat -f%z "$FINAL_TAR" 2>/dev/null || stat -c%s "$FINAL_TAR") SHA256=$(shasum -a 256 "$FINAL_TAR" | awk '{print $1}') else SIZE=$(wc -c < "$FINAL_TAR" | tr -d " ") SHA256=$(sha256sum "$FINAL_TAR" | awk "{print \$1}") fi cat > "$OUT_DIR/jordan-routing-manifest.json" </dev/null || true rm -rf "$WORK_DIR" echo "" echo "✅ Done. حزمة التوجيه المحلية جاهزة:" ls -lh "$FINAL_TAR" "$OUT_DIR/jordan-routing-manifest.json"