Feature: Gassi-Treffen — Orts-Autocomplete, Modal-UX, Teilnehmerliste, Karten-Fix
- Orts-/POI-Suche mit GPS und Vorschlägen (wie Tagebuch) + Mini-Karte im Formular
- Stornieren/Austreten als Zwei-Klick-Pattern (kein UI.modal.confirm in Modals)
- Teilnehmerliste im Detail-Modal mit User-Namen und Hunden
- Leaflet invalidateSize auf 150ms (Memory-Regel), _loadLeaflet robuster
- /api/walks/nearby Backend-Endpunkt (vor /{walk_id} Route)
- SW by-v203, APP_VER 169
This commit is contained in:
parent
80e3f0dc0d
commit
e3230237a2
4 changed files with 379 additions and 75 deletions
|
|
@ -1,6 +1,7 @@
|
|||
"""BAN YARO — Gassi-Treffen"""
|
||||
|
||||
import math
|
||||
import httpx
|
||||
from datetime import date
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import BaseModel
|
||||
|
|
@ -20,6 +21,10 @@ def _haversine(lat1, lon1, lat2, lon2):
|
|||
return 2 * R * math.asin(math.sqrt(a))
|
||||
|
||||
|
||||
def _haversine_km(lat1, lon1, lat2, lon2):
|
||||
return _haversine(lat1, lon1, lat2, lon2) / 1000
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Schemas
|
||||
# ------------------------------------------------------------------
|
||||
|
|
@ -103,6 +108,79 @@ async def create_walk(data: WalkCreate, user=Depends(get_current_user)):
|
|||
return {**dict(row), 'teilnehmer_count': 0}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# GET /api/walks/nearby — POI-Suche für Treffpunkt-Autocomplete
|
||||
# WICHTIG: Muss VOR /{walk_id} stehen (FastAPI Route-Reihenfolge)
|
||||
# ------------------------------------------------------------------
|
||||
@router.get("/nearby")
|
||||
async def nearby_places(lat: float, lon: float, user=Depends(get_current_user)):
|
||||
results = []
|
||||
|
||||
with db() as conn:
|
||||
# 1. User-eigene Places
|
||||
places = conn.execute(
|
||||
"SELECT name, typ, lat, lon FROM places WHERE lat IS NOT NULL",
|
||||
).fetchall()
|
||||
for p in places:
|
||||
km = _haversine_km(lat, lon, p["lat"], p["lon"])
|
||||
if km <= 5:
|
||||
results.append({"name": p["name"], "type": p["typ"] or "place",
|
||||
"lat": p["lat"], "lon": p["lon"],
|
||||
"distance_m": int(km * 1000), "source": "places"})
|
||||
|
||||
# 2. Gecachte OSM-POIs
|
||||
osm = conn.execute(
|
||||
"SELECT name, type, lat, lon FROM osm_pois WHERE name IS NOT NULL AND name != ''"
|
||||
).fetchall()
|
||||
for p in osm:
|
||||
km = _haversine_km(lat, lon, p["lat"], p["lon"])
|
||||
if km <= 2:
|
||||
results.append({"name": p["name"], "type": p["type"],
|
||||
"lat": p["lat"], "lon": p["lon"],
|
||||
"distance_m": int(km * 1000), "source": "osm"})
|
||||
|
||||
# 3. Overpass: benannte POIs in 1000m
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=6) as client:
|
||||
q = (
|
||||
f'[out:json][timeout:6];'
|
||||
f'(node["name"]["leisure"](around:1000,{lat},{lon});'
|
||||
f' node["name"]["amenity"](around:1000,{lat},{lon});'
|
||||
f' node["name"]["tourism"](around:1000,{lat},{lon});'
|
||||
f' way["name"]["leisure"](around:1000,{lat},{lon});'
|
||||
f');out center;'
|
||||
)
|
||||
r = await client.post("https://overpass-api.de/api/interpreter",
|
||||
data={"data": q})
|
||||
if r.status_code == 200:
|
||||
for el in r.json().get("elements", []):
|
||||
name = el.get("tags", {}).get("name")
|
||||
if not name:
|
||||
continue
|
||||
elat = el.get("lat") or el.get("center", {}).get("lat")
|
||||
elon = el.get("lon") or el.get("center", {}).get("lon")
|
||||
if elat is None or elon is None:
|
||||
continue
|
||||
km = _haversine_km(lat, lon, elat, elon)
|
||||
if km <= 1:
|
||||
results.append({"name": name, "type": "osm",
|
||||
"lat": elat, "lon": elon,
|
||||
"distance_m": int(km * 1000), "source": "osm"})
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Deduplizieren nach Name + Sortieren nach Distanz
|
||||
seen = set()
|
||||
unique = []
|
||||
for r in sorted(results, key=lambda x: x["distance_m"]):
|
||||
key = r["name"].lower()
|
||||
if key not in seen:
|
||||
seen.add(key)
|
||||
unique.append(r)
|
||||
|
||||
return unique[:20]
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# GET /api/walks/{id} — Detail mit Teilnehmerliste
|
||||
# ------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue