127 lines
4.7 KiB
Python
127 lines
4.7 KiB
Python
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)
|
||
|