gendesign/scripts/build_osrm.sh
Light1YT 8a3bae0cda
All checks were successful
CI / changes (pull_request) Successful in 6s
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 1m46s
CI / backend-tests (pull_request) Successful in 11m55s
feat(analyze): per-category OSRM routing (foot vs driving) (#39 A3)
Walk-relevant POIs (school/shop/park/kindergarten/pharmacy/stops) now route
via a FOOT OSRM graph (osrm-walk service), car-relevant POIs (mall/hospital +
unknown) keep the DRIVING graph. Validation showed driving overstated
pedestrian-proximity distance — median 1.6-2.9x straight-line (#39).

- config: osrm_walk_local_url + osrm_walk_categories (frozenset, 9 walk cats)
- osrm_client_local: base_url override on get_road_distances_m (default unchanged)
- _apply_osrm_road_distances: split POIs by category, per-group OSRM call with
  independent graceful fallback (one server down -> its group keeps straight-line),
  in-place write-back by original index; never raises; flag-OFF byte-identical
- docker-compose: osrm-walk service (foot graph, internal, mem_limit 1.5g)
- build_osrm.sh: CAR=0 gate for foot-only refresh (doesn't touch live car graph)
- tests: per-category split, per-group fallback, asymmetric intra-group write-back

Still flag-gated (use_osrm_distances OFF) — enabling is a product decision.
Refs #39
2026-06-27 04:30:25 +05:00

167 lines
9.2 KiB
Bash
Executable file
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# Build / refresh OSRM routing graph для site-finder (#39).
#
# Готовит .osrm-файлы (MLD pipeline) из OpenStreetMap-экстракта Свердловской
# области и кладёт их в ./data/osrm/, откуда их монтирует docker-compose.prod.yml
# (service `osrm`, volume ./data/osrm:/data → osrm-routed читает /data/<region>.osrm).
#
# === Где запускать ===
# На VPS из /opt/gendesign: bash scripts/build_osrm.sh
# (требует docker + ~5GB свободного диска + ~3GB свободной RAM на время extract).
#
# === ВАЖНО: это offline-задача, НЕ часть deploy.yml ===
# Download ~600MB-1.5GB + build 30-50 мин. Запускается ВРУЧНУЮ оркестратором,
# НЕ на каждом push. deploy.yml только поднимает уже готовый osrm-контейнер.
#
# === Idempotent / re-runnable ===
# Повторный запуск перекачивает свежий pbf и пересобирает граф (overwrite).
# Безопасно гонять ежемесячно (osm.pbf на Geofabrik обновляется ежедневно).
# # monthly refresh: cron на VPS, напр. 0 4 1 * * bash /opt/gendesign/scripts/build_osrm.sh >> /var/log/osrm-build.log 2>&1
# После refresh: docker compose -p gendesign -f docker-compose.prod.yml up -d --force-recreate --no-deps osrm
#
# === Регион / источник pbf ===
# Geofabrik НЕ публикует Свердловскую область отдельным extract'ом — только
# «Уральский ФО» (ural-fed-district). Два режима (CLIP=1 по умолчанию):
#
# CLIP=1 (default, РЕКОМЕНДОВАНО): качаем Ural-FO pbf и обрезаем по bbox
# Свердл. обл. через `osmium extract`. Результат легче → меньше RAM/диск
# в build и в runtime (osrm-routed warm ~0.8-1.2GB вместо ~1.5GB+ на
# весь Ural-FO). Минус: нужен extra-шаг osmium (запускаем в docker).
#
# CLIP=0: используем весь Ural-FO pbf как есть (zero-clip, проще, но тяжелее —
# граф Курган/Тюмень/ХМАО/ЯНАО грузится в RAM без пользы для Свердл).
# Бери, если RAM не жмёт и нужен запас на соседние регионы.
#
# bbox Свердловской обл (грубый, с запасом): lon 57.2..66.2, lat 56.0..62.0.
# Можно сузить до ЕКБ-агломерации если RAM критична (lon 59.0..62.5, lat 56.2..58.2)
# через EKB_TIGHT=1.
#
# === Профили ===
# Car (driving) — основной, строится всегда → <region>.osrm*.
# Foot (walking) — опциональный Phase-2, строится при WALK=1 → <region>-foot.osrm*.
# Для walking нужен второй compose-service (osrm-walk) — см. docs/osrm-routing.md.
# Этот PR car-first; walk выключен по умолчанию (issue хочет оба eventually).
#
# === Проверка после build ===
# docker compose -p gendesign -f docker-compose.prod.yml up -d osrm
# docker compose -p gendesign -f docker-compose.prod.yml exec osrm \
# sh -c 'echo OK' # контейнер жив
# # route-probe С ХОСТА (внутри образа нет curl) — временно прокинуть порт:
# docker run --rm --network gendesign_default curlimages/curl:8 -fsS \
# 'http://osrm:5000/route/v1/driving/60.6,56.84;60.65,56.86'
# ↑ ожидаем JSON {"code":"Ok","routes":[...]}.
set -euo pipefail
# --- Config (override через env) -------------------------------------------
OSRM_IMAGE="${OSRM_IMAGE:-osrm/osrm-backend:latest}"
OSMIUM_IMAGE="${OSMIUM_IMAGE:-stefda/osmium-tool:latest}"
REGION="${OSRM_REGION:-sverdlovsk}" # имя выходных .osrm* (= OSRM_REGION в compose)
PBF_URL="${PBF_URL:-https://download.geofabrik.de/russia/ural-fed-district-latest.osm.pbf}"
CLIP="${CLIP:-1}" # 1 = обрезать по bbox Свердл, 0 = весь Ural-FO
EKB_TIGHT="${EKB_TIGHT:-0}" # 1 = узкий bbox только ЕКБ-агломерация
WALK="${WALK:-0}" # 1 = ещё и пеший профиль <region>-foot.osrm*
CAR="${CAR:-1}" # 0 = пропустить car-граф (foot-only refresh, #39 A3)
CAR_PROFILE="${CAR_PROFILE:-/opt/car.lua}" # встроенные профили в osrm-backend образе
FOOT_PROFILE="${FOOT_PROFILE:-/opt/foot.lua}"
# bbox: left,bottom,right,top (lon_min,lat_min,lon_max,lat_max)
if [ "$EKB_TIGHT" = "1" ]; then
BBOX="${BBOX:-59.0,56.2,62.5,58.2}" # ЕКБ-агломерация (минимум RAM)
else
BBOX="${BBOX:-57.2,56.0,66.2,62.0}" # вся Свердловская обл (с запасом)
fi
# Каталог host (= ./data/osrm в compose). Скрипт зовём из /opt/gendesign.
DATA_DIR="${DATA_DIR:-$(pwd)/data/osrm}"
mkdir -p "$DATA_DIR"
RAW_PBF="$DATA_DIR/${REGION}-raw.osm.pbf" # сырой Ural-FO (или clip-источник)
WORK_PBF="$DATA_DIR/${REGION}.osm.pbf" # то, что реально пойдёт в osrm-extract
echo "=== OSRM build ==="
echo " region : $REGION"
echo " pbf source : $PBF_URL"
echo " clip to bbox : CLIP=$CLIP BBOX=$BBOX (EKB_TIGHT=$EKB_TIGHT)"
echo " walking profile : WALK=$WALK"
echo " data dir (host) : $DATA_DIR"
echo ""
# --- 1. Download ------------------------------------------------------------
# -N: качаем только если на сервере новее (idempotent monthly refresh).
echo "=== 1. Download pbf (Ural-FO ≈600MB) ==="
if command -v wget >/dev/null 2>&1; then
wget -N -O "$RAW_PBF" "$PBF_URL"
else
curl -fSL --time-cond "$RAW_PBF" -o "$RAW_PBF" "$PBF_URL"
fi
# --- 2. Clip (optional) -----------------------------------------------------
if [ "$CLIP" = "1" ]; then
echo "=== 2. Clip to Свердл bbox через osmium ($BBOX) ==="
docker run --rm -v "$DATA_DIR:/data" "$OSMIUM_IMAGE" \
osmium extract -b "$BBOX" "/data/$(basename "$RAW_PBF")" \
-o "/data/$(basename "$WORK_PBF")" --overwrite
else
echo "=== 2. No clip (CLIP=0) — используем Ural-FO целиком ==="
cp -f "$RAW_PBF" "$WORK_PBF"
fi
# --- helper: запуск osrm-backend образа на /data --------------------------
osrm() {
docker run --rm -v "$DATA_DIR:/data" "$OSRM_IMAGE" "$@"
}
# --- 3. MLD pipeline (car) --------------------------------------------------
# MLD = extract → partition → customize. НЕ osrm-contract (то CH-алгоритм,
# несовместим с --algorithm mld в compose).
#
# osrm-extract пишет <pbf-без-.osm.pbf>.osrm. Имя входного pbf специально равно
# целевому базовому имени (<region>.osm.pbf → <region>.osrm; <region>-foot.osm.pbf
# → <region>-foot.osrm), поэтому partition/customize работают по тому же базовому
# имени и переименование не требуется.
build_profile() {
local profile="$1" src_pbf="$2"
local base extracted
base="$(basename "${src_pbf%.osm.pbf}")"
extracted="/data/${base}.osrm"
echo "--- extract ($profile${base}.osrm) ---"
osrm osrm-extract -p "$profile" "/data/$(basename "$src_pbf")"
echo "--- partition (${base}.osrm) ---"
osrm osrm-partition "$extracted"
echo "--- customize (${base}.osrm) ---"
osrm osrm-customize "$extracted"
}
if [ "$CAR" = "1" ]; then
echo "=== 3. Build car profile → ${REGION}.osrm* ==="
build_profile "$CAR_PROFILE" "$WORK_PBF"
else
echo "=== 3. Skip car profile (CAR=0) — foot-only refresh, не трогаем живой ${REGION}.osrm ==="
fi
# --- 4. MLD pipeline (foot, optional) --------------------------------------
if [ "$WALK" = "1" ]; then
echo "=== 4. Build foot profile → ${REGION}-foot.osrm* ==="
# Отдельная копия pbf под пеший граф (osrm-extract overwrite'ит базу).
WALK_PBF="$DATA_DIR/${REGION}-foot.osm.pbf"
cp -f "$WORK_PBF" "$WALK_PBF"
build_profile "$FOOT_PROFILE" "$WALK_PBF"
else
echo "=== 4. Skip foot profile (WALK=0) — car-first, см. docs/osrm-routing.md ==="
fi
# --- 5. Cleanup heavy intermediates ----------------------------------------
echo "=== 5. Cleanup промежуточных .pbf (граф .osrm* уже собран) ==="
rm -f "$RAW_PBF" "$WORK_PBF" "${DATA_DIR}/${REGION}-foot.osm.pbf" 2>/dev/null || true
echo ""
echo "=== DONE ==="
echo "Артефакты в $DATA_DIR:"
ls -lh "$DATA_DIR"/*.osrm* 2>/dev/null | awk '{print " "$5"\t"$9}' || true
echo ""
echo "Дальше:"
echo " docker compose -p gendesign -f docker-compose.prod.yml up -d osrm"
echo " # verify (curl с хоста — внутри образа curl нет):"
echo " docker run --rm --network gendesign_default curlimages/curl:8 -fsS \\"
echo " 'http://osrm:5000/route/v1/driving/60.6,56.84;60.65,56.86'"