feat: AR-House initial commit
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
"""Sub-agente: Estilo de vida náutico.
|
||||
|
||||
Fuentes: Overpass API (marinas, boat ramps, playas, acceso al agua).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
import time
|
||||
import requests
|
||||
from data_fetchers.base import USER_AGENT
|
||||
|
||||
OVERPASS_URL = "https://overpass-api.de/api/interpreter"
|
||||
|
||||
|
||||
def run(lat: float, lon: float, address: str) -> dict:
|
||||
result = {
|
||||
"marinas": [],
|
||||
"boat_ramps": [],
|
||||
"beaches": [],
|
||||
"nearest_marina": None,
|
||||
"nearest_beach": None,
|
||||
"nearest_boat_ramp": None,
|
||||
"ocean_access": False,
|
||||
"waterway_nearby": False,
|
||||
"sources": ["OpenStreetMap/Overpass"],
|
||||
"errors": [],
|
||||
}
|
||||
|
||||
try:
|
||||
data = _overpass_nautical(lat, lon)
|
||||
result.update(data)
|
||||
except Exception as e:
|
||||
result["errors"].append(f"Overpass lifestyle: {e}")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _overpass_nautical(lat: float, lon: float, radius_m: int = 16000) -> dict:
|
||||
"""Consulta amenidades náuticas en radio de ~10 millas."""
|
||||
query = f"""
|
||||
[out:json][timeout:35];
|
||||
(
|
||||
node["leisure"="marina"](around:{radius_m},{lat},{lon});
|
||||
way["leisure"="marina"](around:{radius_m},{lat},{lon});
|
||||
node["leisure"="slipway"](around:{radius_m},{lat},{lon});
|
||||
way["leisure"="slipway"](around:{radius_m},{lat},{lon});
|
||||
node["natural"="beach"](around:{radius_m},{lat},{lon});
|
||||
way["natural"="beach"](around:{radius_m},{lat},{lon});
|
||||
node["waterway"="river"](around:3200,{lat},{lon});
|
||||
node["natural"="water"](around:3200,{lat},{lon});
|
||||
way["natural"="coastline"](around:8000,{lat},{lon});
|
||||
);
|
||||
out body center;
|
||||
"""
|
||||
time.sleep(1)
|
||||
r = requests.post(OVERPASS_URL, data={"data": query},
|
||||
headers={"User-Agent": USER_AGENT}, timeout=40)
|
||||
r.raise_for_status()
|
||||
elements = r.json().get("elements", [])
|
||||
|
||||
marinas, boat_ramps, beaches = [], [], []
|
||||
waterway_nearby = False
|
||||
ocean_access = False
|
||||
|
||||
for el in elements:
|
||||
tags = el.get("tags", {})
|
||||
name = tags.get("name", "Sin nombre")
|
||||
|
||||
# Obtener coords
|
||||
if "center" in el:
|
||||
el_lat = el["center"]["lat"]
|
||||
el_lon = el["center"]["lon"]
|
||||
else:
|
||||
el_lat = el.get("lat", lat)
|
||||
el_lon = el.get("lon", lon)
|
||||
|
||||
dist = _haversine(lat, lon, el_lat, el_lon)
|
||||
|
||||
leisure = tags.get("leisure", "")
|
||||
natural = tags.get("natural", "")
|
||||
waterway = tags.get("waterway", "")
|
||||
|
||||
if leisure == "marina":
|
||||
entry = {
|
||||
"name": name,
|
||||
"dist_miles": round(dist, 2),
|
||||
"fuel": tags.get("fuel", "unknown"),
|
||||
"pump_out": tags.get("pump_out", "unknown"),
|
||||
"depth": tags.get("maxdraught", tags.get("depth", "unknown")),
|
||||
}
|
||||
marinas.append(entry)
|
||||
elif leisure == "slipway":
|
||||
boat_ramps.append({"name": name, "dist_miles": round(dist, 2)})
|
||||
elif natural == "beach":
|
||||
beaches.append({"name": name, "dist_miles": round(dist, 2)})
|
||||
elif natural == "coastline":
|
||||
ocean_access = True
|
||||
elif waterway in ("river", "canal") or natural in ("water", "bay"):
|
||||
waterway_nearby = True
|
||||
|
||||
marinas.sort(key=lambda x: x["dist_miles"])
|
||||
boat_ramps.sort(key=lambda x: x["dist_miles"])
|
||||
beaches.sort(key=lambda x: x["dist_miles"])
|
||||
|
||||
return {
|
||||
"marinas": marinas[:10],
|
||||
"boat_ramps": boat_ramps[:10],
|
||||
"beaches": beaches[:10],
|
||||
"nearest_marina": marinas[0] if marinas else None,
|
||||
"nearest_beach": beaches[0] if beaches else None,
|
||||
"nearest_boat_ramp": boat_ramps[0] if boat_ramps else None,
|
||||
"ocean_access": ocean_access,
|
||||
"waterway_nearby": waterway_nearby,
|
||||
}
|
||||
|
||||
|
||||
def _haversine(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
|
||||
R = 3958.8
|
||||
dlat = math.radians(lat2 - lat1)
|
||||
dlon = math.radians(lon2 - lon1)
|
||||
a = math.sin(dlat/2)**2 + math.cos(math.radians(lat1)) * math.cos(math.radians(lat2)) * math.sin(dlon/2)**2
|
||||
return R * 2 * math.asin(math.sqrt(a))
|
||||
|
||||
|
||||
def score(data: dict) -> int:
|
||||
"""Score 0-100 para lifestyle náutico."""
|
||||
s = 30 # base
|
||||
|
||||
nearest_marina = data.get("nearest_marina")
|
||||
if nearest_marina:
|
||||
d = nearest_marina["dist_miles"]
|
||||
if d <= 1:
|
||||
s += 30
|
||||
elif d <= 3:
|
||||
s += 20
|
||||
elif d <= 5:
|
||||
s += 12
|
||||
elif d <= 10:
|
||||
s += 6
|
||||
|
||||
nearest_beach = data.get("nearest_beach")
|
||||
if nearest_beach:
|
||||
d = nearest_beach["dist_miles"]
|
||||
if d <= 1:
|
||||
s += 20
|
||||
elif d <= 3:
|
||||
s += 12
|
||||
elif d <= 5:
|
||||
s += 6
|
||||
|
||||
if data.get("ocean_access"):
|
||||
s += 10
|
||||
if data.get("waterway_nearby"):
|
||||
s += 5
|
||||
|
||||
boat_ramps = len(data.get("boat_ramps", []))
|
||||
s += min(10, boat_ramps * 2)
|
||||
|
||||
return min(100, max(0, s))
|
||||
Reference in New Issue
Block a user