This commit is contained in:
Nikidze
2025-10-31 22:08:55 +03:00
commit e3f8caf59f
78 changed files with 9249 additions and 0 deletions

126
engine-wrapper/main.py Normal file
View File

@@ -0,0 +1,126 @@
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Optional
import httpx
import os
from dotenv import load_dotenv
# Загрузка переменных окружения из .env файла
load_dotenv()
app = FastAPI(title="OSRM API Wrapper")
# --- Модели для запросов и ответов ---
# Входная модель для обоих эндпоинтов: массив точек
class PointsRequest(BaseModel):
points: List[List[float]] # Формат: [[lat, lon], [lat, lon], ...]
# Модель ответа для /table
class TableResponse(BaseModel):
distances: List[List[float]]
durations: List[List[float]]
# Модель для одного шага в маршруте (для /route)
class RouteStep(BaseModel):
location: List[float] # [lon, lat]
distance: float
type: str
modifier: Optional[str] = ""
name: str
# Модель ответа для /route
class RouteResponse(BaseModel):
steps: List[RouteStep]
# --- Конфигурация ---
# Чтение URL OSRM из переменной окружения
OSRM_BASE_URL = os.getenv("OSRM_URL")
if not OSRM_BASE_URL:
raise RuntimeError("Переменная окружения OSRM_URL не установлена.")
# --- Эндпоинты ---
@app.post("/table", response_model=TableResponse)
async def table(request: PointsRequest):
"""
Принимает список координат и возвращает матрицы расстояний и времени в пути между ними.
"""
if len(request.points) < 2:
raise HTTPException(status_code=400, detail="Нужно минимум 2 точки")
# OSRM ожидает формат lon,lat
coords = ";".join(f"{lon},{lat}" for lat, lon in request.points)
# Эндпоинт для Table service
url = f"{OSRM_BASE_URL}/table/v1/foot/{coords}?annotations=duration,distance"
print(f"Запрос к OSRM: {url}")
async with httpx.AsyncClient() as client:
try:
resp = await client.get(url, timeout=10)
resp.raise_for_status()
except httpx.RequestError as exc:
raise HTTPException(status_code=500, detail=f"Ошибка запроса к OSRM: {exc.request.url!r} - {exc}")
except httpx.HTTPStatusError as exc:
raise HTTPException(status_code=500, detail=f"OSRM вернул ошибку {exc.response.status_code}: {exc.response.text}")
data = resp.json()
distances = data.get("distances")
durations = data.get("durations")
if distances is None or durations is None:
raise HTTPException(status_code=500, detail="OSRM вернул некорректный ответ")
return TableResponse(distances=distances, durations=durations)
@app.post("/route", response_model=RouteResponse)
async def route(request: PointsRequest):
"""
Принимает две точки (начальную и конечную) и возвращает пошаговый маршрут.
"""
if len(request.points) != 2:
raise HTTPException(status_code=400, detail="Для маршрута нужно ровно 2 точки")
coords = ";".join(f"{lon},{lat}" for lat, lon in request.points)
# Эндпоинт для Route service с параметром steps=true
url = f"{OSRM_BASE_URL}/route/v1/foot/{coords}?steps=true"
print(f"Запрос к OSRM: {url}")
async with httpx.AsyncClient() as client:
try:
resp = await client.get(url, timeout=10)
resp.raise_for_status()
except httpx.RequestError as exc:
raise HTTPException(status_code=500, detail=f"Ошибка запроса к OSRM: {exc.request.url!r} - {exc}")
except httpx.HTTPStatusError as exc:
raise HTTPException(status_code=500, detail=f"OSRM вернул ошибку {exc.response.status_code}: {exc.response.text}")
data = resp.json()
if "routes" not in data or len(data["routes"]) == 0:
raise HTTPException(status_code=500, detail="OSRM не вернул маршруты")
# Извлекаем шаги из ответа
raw_steps = data["routes"][0]["legs"][0]["steps"]
formatted_steps = []
for step in raw_steps:
maneuver = step["maneuver"]
formatted_steps.append(
RouteStep(
location=[maneuver["location"][1], maneuver["location"][0]],
distance=step["distance"],
type=maneuver["type"],
modifier=maneuver.get("modifier"),
name=step.get("name", "")
)
)
return RouteResponse(steps=formatted_steps)