gendesign/docs/osrm-routing.md
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

7.1 KiB
Raw Permalink Blame History

OSRM routing engine (self-hosted) — #39

Self-hosted OSRM для site-finder /analyze: реальные дорожные / пешие расстояния до POI вместо straight-line ST_Distance(geography).

Scope этого PR — INFRA ONLY. Здесь стоит контейнер + build-скрипт + healthcheck. Интеграция в /analyze (замена ST_Distance на OSRM route/table) — отдельный follow-up (A2 изохроны, A3 per-category routing-decay). Backend пока к osrm НЕ ходит.

Архитектура

backend ──http://osrm:5000──▶ osrm (osrm-routed --algorithm mld)
                                 │ volume ./data/osrm:/data
                                 └─ /data/sverdlovsk.osrm*  (MLD-граф, ~ХХХ MB)
  • Compose service osrm в docker-compose.prod.yml, сеть default (project gendesign).
  • Наружу НЕ публикуется — нет ports:. Достижим только из backend по http://osrm:5000. Caddy его не проксирует (routing — internal API, не должен быть публичным).
  • mem_limit: 1.5g, restart: unless-stopped.
  • Граф (/data/*.osrm*) НЕ в git (.gitignoredata/osrm/*). Строится offline на VPS.

Build / refresh графа

Скрипт: scripts/build_osrm.sh. Запускается вручную на VPS (НЕ часть deploy.yml — download+build тяжёлые).

cd /opt/gendesign
bash scripts/build_osrm.sh            # car-профиль, Свердл-clip (default)

Источник pbf и решение по региону

Geofabrik не публикует Свердловскую область отдельно — есть только Уральский ФО (≈600 MB). Скрипт по умолчанию (CLIP=1) скачивает Ural-FO и обрезает по bbox Свердл. обл. через osmium extract → легче граф, меньше RAM в runtime.

Режим Команда pbf Граф в RAM (warm) Когда
Свердл-clip (default) bash scripts/build_osrm.sh clip из Ural-FO ~0.81.2 GB рекоменд., extensibility на обл
ЕКБ-tight EKB_TIGHT=1 bash scripts/build_osrm.sh узкий bbox ЕКБ ~0.40.6 GB если RAM на VPS критична
Ural-FO zero-clip CLIP=0 bash scripts/build_osrm.sh весь Ural-FO ~1.5 GB+ если нужен запас на соседние регионы

bbox Свердл (с запасом): lon 57.2..66.2, lat 56.0..62.0. ЕКБ-tight: lon 59.0..62.5, lat 56.2..58.2.

Pipeline (MLD, не CH)

--algorithm mld в compose требует pipeline extract → partition → customize (скрипт делает это через тот же osrm/osrm-backend образ). osrm-contract — это CH-алгоритм, он несовместим с --algorithm mld, поэтому НЕ используется.

Walking-профиль + per-category routing (#39 A3 — LIVE)

Issue хочет оба профиля (car + foot). A3 их свёл в per-category routing: пешеходные POI (школа, д/сад, магазины, аптека, парк, остановки) считаются по пешему графу (osrm-walk), авто-POI (ТЦ, больница) — по car (osrm). Это убирает завышение дистанций driving-профилем до близких-пешком POI (валидация #39: driving-медиана 1.62.9× straight-line). Маппинг категория→профиль: settings.osrm_walk_categories.

Сборка пешего графа (sverdlovsk-foot.osrm*):

CAR=0 WALK=1 bash scripts/build_osrm.sh   # только пеший граф, НЕ трогает живой car-граф
# (полный refresh обоих: WALK=1 bash scripts/build_osrm.sh)

CAR=0 пропускает car-этап (foot-only refresh не перезаписывает работающий sverdlovsk.osrm, который mmap'нут живым контейнером osrm).

Compose-service osrm-walk добавлен в docker-compose.prod.yml (рядом с osrm, internal-only, mem_limit 1.5g). Backend ходит к нему по http://osrm-walk:5000 (settings.osrm_walk_local_url). До сборки графа контейнер crash-loop'ит — безвредно (backend не depends_on, флаг OFF).

Поднять после сборки:

docker compose -p gendesign -f docker-compose.prod.yml up -d --no-deps osrm-walk
docker run --rm --network gendesign_default curlimages/curl:8 -fsS \
  'http://osrm-walk:5000/route/v1/foot/60.6,56.84;60.65,56.86'   # ожидаем code:Ok

Monthly refresh

osm.pbf на Geofabrik обновляется ежедневно. Граф освежать ~ежемесячно:

0 4 1 * * cd /opt/gendesign && bash scripts/build_osrm.sh >> /var/log/osrm-build.log 2>&1 \
  && docker compose -p gendesign -f docker-compose.prod.yml up -d --force-recreate --no-deps osrm

Скрипт idempotent: wget -N качает только если на сервере новее, граф перестраивается overwrite.

Развёртывание (что выполняет оркестратор на VPS)

cd /opt/gendesign
git fetch origin main && git reset --hard origin/main     # подтянуть compose + скрипт

# 1. Build графа (~3050 мин, ~600MB download + ~3GB peak RAM на extract)
bash scripts/build_osrm.sh

# 2. Поднять контейнер
docker compose -p gendesign -f docker-compose.prod.yml up -d osrm

# 3. Verify (curl ВНУТРИ образа нет — используем sidecar 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":[{...}], ...}

Healthcheck

Compose-healthcheck — TCP-probe порта 5000 через bash /dev/tcp (образ osrm/osrm-backend НЕ содержит curl/wget, HTTP-probe падал бы на missing binary). Полный route-probe — verify-команда выше (sidecar curl).

Риски / open

  • RAM на Beget VPS: +~1 GB warm для Свердл-clip. Перед up -d osrm проверить свободную RAM (free -h). Если жмёт — EKB_TIGHT=1 (~0.40.6 GB) или upgrade plan.
  • Build на VPS медленный (50+ мин). Альтернатива: собрать локально и scp data/osrm/*.osrm* в /opt/gendesign/data/osrm/.
  • Размер pbf неточен: Ural-FO ≈600 MB на скачку; «1.5GB» из issue — оценка по всему ФО + intermediate-файлы на диске во время build (нужно ~5 GB свободного диска).