diff --git a/backend/requirements.txt b/backend/requirements.txt index 25c2274..7b268fa 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -12,3 +12,4 @@ anthropic==0.49.0 pywebpush==2.0.0 apscheduler==3.10.4 odfpy==1.4.1 +polyline==2.0.2 diff --git a/backend/routes/routen.py b/backend/routes/routen.py index 8205af7..5d27e04 100644 --- a/backend/routes/routen.py +++ b/backend/routes/routen.py @@ -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 # ------------------------------------------------------------------ diff --git a/backend/static/js/app.js b/backend/static/js/app.js index 74c8701..104bfba 100644 --- a/backend/static/js/app.js +++ b/backend/static/js/app.js @@ -3,7 +3,7 @@ Router, State-Management, Navigation, Initialisierung. ============================================================ */ -const APP_VER = '454'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen +const APP_VER = '455'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen const App = (() => { diff --git a/backend/static/js/pages/routes.js b/backend/static/js/pages/routes.js index b3591d9..7f4f6d9 100644 --- a/backend/static/js/pages/routes.js +++ b/backend/static/js/pages/routes.js @@ -40,9 +40,15 @@ window.Page_routes = (() => { let _recPolyline = null, _recLocMarker = null; let _recWakeLock = null, _recInactTimer = null, _recDimmed = false; - // 'mine' | 'discover' + // 'mine' | 'discover' | 'suggest' let _browseMode = 'mine'; + // Vorschläge-Tab state + let _suggestKm = 4; // gewählte Distanz: 2, 4 oder 6 + let _suggestSeed = 0; // Variante: 0, 1, 2 + let _suggestResult = null; // letzte API-Antwort + let _suggestMap = null; // Leaflet-Instanz der Vorschau-Karte + // Ansichts-Modus: 'list' | 'map' let _viewMode = 'list'; let _searchMap = null; // L.map Instanz der Suchkarte @@ -121,9 +127,10 @@ window.Page_routes = (() => {
+ Standort wird benötigt. Bitte erlaube den Zugriff in den Browser-Einstellungen. +
+