|
|
| import math
|
| from typing import Optional, Tuple
|
|
|
| import gradio as gr
|
|
|
| from config import get_style, DEFAULT_STYLE
|
| from core.geometry import control_point, sample_curve
|
| from models.city import DB
|
|
|
|
|
| def _extract_xy(evt: gr.SelectData) -> Tuple[Optional[float], Optional[float], Optional[any]]:
|
| """Extract (x, y, size) in base image pixels from Gradio click event."""
|
| if evt is None:
|
| return None, None, None
|
| x = getattr(evt, "x", None)
|
| y = getattr(evt, "y", None)
|
| size = getattr(evt, "size", None)
|
| if x is not None and y is not None:
|
| return float(x), float(y), size
|
| idx = getattr(evt, "index", None)
|
| if isinstance(idx, (list, tuple)) and len(idx) >= 2:
|
| return float(idx[0]), float(idx[1]), size
|
| val = getattr(evt, "value", None)
|
| if isinstance(val, dict) and ("x" in val) and ("y" in val):
|
| w = val.get("width")
|
| h = val.get("height")
|
| if (w is None or h is None) and isinstance(val.get("image"), dict):
|
| w = val["image"].get("width")
|
| h = val["image"].get("height")
|
| return float(val["x"]), float(val["y"]), (w, h)
|
| return None, None, None
|
|
|
|
|
| def _nearest_leg_on_click(x, y, legs, to_xy, bulge_overrides=None, n=220):
|
| best = None
|
| _default_bulge = get_style(DEFAULT_STYLE).curve_bulge
|
| for idx, e in enumerate(legs or []):
|
| a, b = e.get("a"), e.get("b")
|
| if a not in DB.by_name or b not in DB.by_name:
|
| continue
|
| bulge = float((bulge_overrides or {}).get((a, b), _default_bulge))
|
| p0 = to_xy(a)
|
| p2 = to_xy(b)
|
| pc = control_point(p0, p2, bulge)
|
| pts = sample_curve(p0, pc, p2, n=n)
|
| best_d = 1e18
|
| best_i = 0
|
| for i, (xx, yy) in enumerate(pts):
|
| d = (xx - x) ** 2 + (yy - y) ** 2
|
| if d < best_d:
|
| best_d = d
|
| best_i = i
|
| d_px = math.sqrt(best_d)
|
| t = best_i / (n - 1)
|
| if (best is None) or (d_px < best[0]):
|
| best = (d_px, idx, t)
|
| return best
|
|
|
|
|
| def _nearest_city_on_click(x, y, to_xy, city_names):
|
| best_nm, best_d = None, 1e18
|
| for nm in city_names:
|
| cx, cy = to_xy(nm)
|
| d = math.hypot(x - cx, y - cy)
|
| if d < best_d:
|
| best_d, best_nm = d, nm
|
| return best_nm, best_d
|
|
|