feat(presentation): wyszukiwarka lokalizacji + mapa (OSM/Leaflet)
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m39s
Testy / Build obrazu silnika B (swisseph) (pull_request) Failing after 28s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 21s
build / build (push) Successful in 57s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m47s
Testy / Build obrazu silnika B (swisseph) (push) Failing after 29s
Testy / Kontrola składni wszystkich warstw (push) Successful in 22s
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m39s
Testy / Build obrazu silnika B (swisseph) (pull_request) Failing after 28s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 21s
build / build (push) Successful in 57s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m47s
Testy / Build obrazu silnika B (swisseph) (push) Failing after 29s
Testy / Kontrola składni wszystkich warstw (push) Successful in 22s
QoL 2/2: pole „Szukaj miejsca" (nazwa/adres/POI) + interaktywna mapa na formularzach horoskopu / interpretacji / kalendarza. Bez klucza API. - geocode.py: proxy OSM/Nominatim PO STRONIE SERWERA (poprawny User-Agent, throttling ~1 req/s, cache TTL 1h) — endpointy /geocode i /reverse. Wolanie z serwera, nie z przegladarki: latwiej trzymac polityke Nominatim i dziala niezaleznie od secure-context (http://<ip>). - _location_picker.html: wspolny partial (search + wyniki + mapa + atrybucja), wpiety includem do 3 formularzy z polami lat/lon. - geo.js: Leaflet — wyszukiwanie (debounce), klik na wynik ustawia lat/lon i centruje mape, klik/drag pineski ustawia wspolrzedne + /reverse pokazuje nazwe; sync z „Tu i teraz" (now.js emituje event astrololo:coords). - Leaflet 1.9.4 vendorowany lokalnie (static/vendor/leaflet, BSD-2-Clause, permisywny) — niezaleznosc od CDN; kafelki mapy z OSM. Marker jako divIcon (bez plikow PNG). - styles.css: style pod ciemny motyw. Zweryfikowane w przegladarce: domyslny widok (pineska na Szpitalu Barlickiego), wyszukanie „Wawel Krakow" -> lista -> klik ustawia 50.0547/19.9361 i przesuwa mape, klik w mape ustawia wspolrzedne + reverse wypelnia nazwe. Zero bledow w konsoli. /geocode i /reverse zwracaja szpital; cache dziala. Uwaga wdrozeniowa: pod prezentacji potrzebuje egressu do nominatim.openstreetmap.org; przegladarki — do tile.openstreetmap.org. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit was merged in pull request #6.
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
"""Proxy geokodera (OpenStreetMap / Nominatim) dla wyszukiwarki lokalizacji w UI.
|
||||
|
||||
QoL: pozwala wpisać nazwę / adres / POI i dostać współrzędne (oraz odwrotnie —
|
||||
z punktu na mapie nazwę miejsca). Wołamy Nominatim **po stronie serwera**, nie
|
||||
z przeglądarki, bo:
|
||||
- polityka Nominatim wymaga sensownego User-Agent i max ~1 zapytanie/s — łatwiej
|
||||
to wyegzekwować i cache'ować centralnie niż z wielu klientów,
|
||||
- działa niezależnie od „secure context" przeglądarki (na http://<ip> też).
|
||||
|
||||
Atrybucja „© OpenStreetMap contributors" jest pokazywana w UI (mapa + wyniki).
|
||||
Zależność sieciowa jest OPCJONALNA: brak internetu = wyszukiwarka zwraca błąd,
|
||||
ale ręczne wpisanie lat/lon oraz mapa działają dalej.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import threading
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
|
||||
NOMINATIM = "https://nominatim.openstreetmap.org"
|
||||
# Nominatim wymaga identyfikującego User-Agent (inaczej blokuje). Kontakt = właściciel.
|
||||
USER_AGENT = "astrololo/1.0 (self-hosted astrology tool; +mtuszowski@gmail.com)"
|
||||
_MIN_INTERVAL = 1.1 # s — polityka Nominatim: max ~1 zapytanie/s
|
||||
_CACHE_TTL = 3600.0 # s — powtórki tego samego zapytania z pamięci przez godzinę
|
||||
_TIMEOUT = 6.0
|
||||
|
||||
_lock = threading.Lock() # serializuje wołania (throttling globalny w procesie)
|
||||
_last_call = 0.0
|
||||
_cache: dict[str, tuple[float, Any]] = {}
|
||||
|
||||
|
||||
def _throttle() -> None:
|
||||
global _last_call
|
||||
wait = _MIN_INTERVAL - (time.monotonic() - _last_call)
|
||||
if wait > 0:
|
||||
time.sleep(wait)
|
||||
_last_call = time.monotonic()
|
||||
|
||||
|
||||
def _cached(key: str) -> Any | None:
|
||||
hit = _cache.get(key)
|
||||
if hit and (time.monotonic() - hit[0]) < _CACHE_TTL:
|
||||
return hit[1]
|
||||
return None
|
||||
|
||||
|
||||
def _get(path: str, params: dict) -> Any:
|
||||
"""Wołanie Nominatim z throttlingiem; zakłada trzymany `_lock`."""
|
||||
_throttle()
|
||||
r = httpx.get(
|
||||
f"{NOMINATIM}{path}",
|
||||
params=params,
|
||||
headers={"User-Agent": USER_AGENT, "Accept-Language": "pl"},
|
||||
timeout=_TIMEOUT,
|
||||
)
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
|
||||
|
||||
def search(query: str, limit: int = 5) -> list[dict]:
|
||||
"""Nazwa/adres/POI → lista kandydatów [{name, lat, lon}] (najlepsze pierwsze)."""
|
||||
query = (query or "").strip()
|
||||
if not query:
|
||||
return []
|
||||
key = f"s:{limit}:{query.lower()}"
|
||||
with _lock:
|
||||
cached = _cached(key)
|
||||
if cached is not None:
|
||||
return cached
|
||||
data = _get("/search", {"q": query, "format": "jsonv2",
|
||||
"limit": limit, "addressdetails": 0})
|
||||
out = [
|
||||
{"name": item.get("display_name", ""),
|
||||
"lat": round(float(item["lat"]), 6),
|
||||
"lon": round(float(item["lon"]), 6)}
|
||||
for item in data if "lat" in item and "lon" in item
|
||||
]
|
||||
_cache[key] = (time.monotonic(), out)
|
||||
return out
|
||||
|
||||
|
||||
def reverse(lat: float, lon: float) -> dict:
|
||||
"""Punkt (lat, lon) → nazwa miejsca {name, lat, lon}."""
|
||||
key = f"r:{round(lat, 5)}:{round(lon, 5)}"
|
||||
with _lock:
|
||||
cached = _cached(key)
|
||||
if cached is not None:
|
||||
return cached
|
||||
data = _get("/reverse", {"lat": lat, "lon": lon, "format": "jsonv2"})
|
||||
out = {"name": data.get("display_name", ""),
|
||||
"lat": round(lat, 6), "lon": round(lon, 6)}
|
||||
_cache[key] = (time.monotonic(), out)
|
||||
return out
|
||||
Reference in New Issue
Block a user