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

128 lines
7.1 KiB
Markdown
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.

# OSRM routing engine (self-hosted) — #39
Self-hosted [OSRM](https://project-osrm.org/) для 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 (`.gitignore``data/osrm/*`). Строится offline на VPS.
## Build / refresh графа
Скрипт: [`scripts/build_osrm.sh`](../scripts/build_osrm.sh). Запускается **вручную**
на VPS (НЕ часть `deploy.yml` — download+build тяжёлые).
```bash
cd /opt/gendesign
bash scripts/build_osrm.sh # car-профиль, Свердл-clip (default)
```
### Источник pbf и решение по региону
Geofabrik **не** публикует Свердловскую область отдельно — есть только
[Уральский ФО](https://download.geofabrik.de/russia/ural-fed-district-latest.osm.pbf)
(≈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*`):
```bash
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).
Поднять после сборки:
```bash
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 обновляется ежедневно. Граф освежать ~ежемесячно:
```cron
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)
```bash
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 свободного диска).