# core/execution/spatial_index.py """ Index spatial par grille pour optimisation des requêtes géométriques UI. Auteur : Dom, Alice Kiro - 19 décembre 2024 """ from __future__ import annotations from dataclasses import dataclass, field from typing import Dict, Iterable, List, Optional, Set, Tuple from ..models.ui_element import UIElement def _right(b): return b[0] + b[2] def _bottom(b): return b[1] + b[3] def _intersects(a, b) -> bool: ax1, ay1, aw, ah = a bx1, by1, bw, bh = b ax2, ay2 = _right(a), _bottom(a) bx2, by2 = _right(b), _bottom(b) return not (ax2 <= bx1 or bx2 <= ax1 or ay2 <= by1 or by2 <= ay1) def _contains_point(b, x, y) -> bool: return (b[0] <= x <= _right(b)) and (b[1] <= y <= _bottom(b)) @dataclass class SpatialIndexGrid: """ Index spatial simple par grille (très efficace pour UI: rectangles). - build: O(n) - query_bbox / query_point: ~O(k) sur les cellules touchées Auteur : Dom, Alice Kiro - 19 décembre 2024 """ cell_size: int = 160 _cells: Dict[Tuple[int, int], List[UIElement]] = field(default_factory=dict) _by_id: Dict[str, UIElement] = field(default_factory=dict) _built: bool = False def build(self, elements: List[UIElement]) -> "SpatialIndexGrid": """Construit l'index à partir d'une liste d'éléments UI""" self._cells = {} self._by_id = {} for e in elements: self._by_id[e.element_id] = e for key in self._cells_for_bbox(e.bbox): self._cells.setdefault(key, []).append(e) self._built = True return self def _cells_for_bbox(self, bbox) -> Iterable[Tuple[int, int]]: """Retourne toutes les cellules touchées par une bbox""" x, y, w, h = bbox x2, y2 = x + w, y + h cs = self.cell_size cx1 = int(x // cs) cy1 = int(y // cs) cx2 = int(x2 // cs) cy2 = int(y2 // cs) for cy in range(cy1, cy2 + 1): for cx in range(cx1, cx2 + 1): yield (cx, cy) def query_bbox(self, bbox) -> List[UIElement]: """Trouve tous les éléments qui intersectent avec la bbox donnée""" if not self._built: return [] seen: Set[str] = set() out: List[UIElement] = [] for key in self._cells_for_bbox(bbox): for e in self._cells.get(key, []): if e.element_id in seen: continue seen.add(e.element_id) if _intersects(e.bbox, bbox): out.append(e) return out def query_point(self, x: int, y: int) -> List[UIElement]: """Trouve tous les éléments qui contiennent le point donné""" if not self._built: return [] cs = self.cell_size key = (int(x // cs), int(y // cs)) out = [] for e in self._cells.get(key, []): if _contains_point(e.bbox, x, y): out.append(e) return out