Feature: Rundweg-Vorschläge via OpenRouteService — 2/4/6 km, 3 Varianten, Navigation+Speichern — SW by-v478, APP_VER 455

This commit is contained in:
rene 2026-04-29 08:04:25 +02:00
parent b09a569689
commit 369eae5e5a
5 changed files with 396 additions and 19 deletions

View file

@ -2,6 +2,7 @@
import json, math, os, uuid
import httpx
import polyline as _polyline
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File
from pydantic import BaseModel
from typing import Optional, List
@ -14,6 +15,8 @@ from routes.push import send_push_to_user
router = APIRouter()
ORS_API_KEY = os.getenv("ORS_API_KEY")
_MAX_AVG_KMH = 15.0 # Über diesem Wert wird die Route nicht für Stats/Trophäen gewertet
def _check_speed(distanz_km, dauer_min) -> bool:
@ -172,6 +175,91 @@ async def create_route(data: RouteCreate, user=Depends(get_current_user)):
return result
# ------------------------------------------------------------------
# POST /api/routes/suggest — Rundweg-Vorschlag via OpenRouteService
# ------------------------------------------------------------------
class SuggestRequest(BaseModel):
lat: float
lon: float
distance_km: float # Zieldistanz in km (z.B. 2.0, 4.0, 6.0)
seed: int = 0 # 0-4: verschiedene Routenvarianten
@router.post("/suggest")
async def suggest_route(data: SuggestRequest, user=Depends(get_current_user)):
if not (0.5 <= data.distance_km <= 15):
raise HTTPException(400, "distance_km muss zwischen 0.5 und 15 liegen.")
if not ORS_API_KEY:
raise HTTPException(503, "ORS nicht konfiguriert")
payload = {
"coordinates": [[data.lon, data.lat]],
"options": {
"round_trip": {
"length": data.distance_km * 1000,
"points": 5,
"seed": data.seed,
},
"avoid_features": ["ferries", "steps"],
},
"units": "m",
"geometry": True,
"instructions": False,
}
try:
async with httpx.AsyncClient(timeout=10) as client:
resp = await client.post(
"https://api.openrouteservice.org/v2/directions/foot-walking",
headers={
"Authorization": f"Bearer {ORS_API_KEY}",
"Content-Type": "application/json",
},
json=payload,
)
except httpx.TimeoutException:
raise HTTPException(504, "ORS-Anfrage hat das Zeitlimit überschritten.")
if resp.status_code != 200:
try:
detail = resp.json()
except Exception:
detail = resp.text
raise HTTPException(502, f"ORS-Fehler: {detail}")
body = resp.json()
try:
route = body["routes"][0]
geometry = route["geometry"]
summary = route["summary"]
distanz_m = summary.get("distance", data.distance_km * 1000)
dauer_s = summary.get("duration", 0)
except (KeyError, IndexError) as exc:
raise HTTPException(502, f"Unerwartete ORS-Antwort: {exc}")
# encoded polyline → [[lat, lon], ...]
points = _polyline.decode(geometry)
gps_track = [{"lat": p[0], "lon": p[1]} for p in points]
distanz_km = round(distanz_m / 1000, 2)
dauer_min = max(1, round(dauer_s / 60))
if distanz_km < 3:
schwierigkeit = "leicht"
elif distanz_km <= 5:
schwierigkeit = "mittel"
else:
schwierigkeit = "anspruchsvoll"
return {
"name": f"Rundweg {distanz_km:.0f} km",
"gps_track": gps_track,
"distanz_km": distanz_km,
"dauer_min": dauer_min,
"schwierigkeit": schwierigkeit,
}
# ------------------------------------------------------------------
# GET /api/routes/{id} — Route mit vollem GPS-Track
# ------------------------------------------------------------------