| """Session-scoped app configuration with project defaults.""" |
|
|
| from math import gcd |
| import streamlit as st |
|
|
| MAPBOX_TOKEN = "pk.eyJ1Ijoibm90aG90dHJ5aGFyZCIsImEiOiJjbW54bWg5aWMwM2FxMnFyOHlkeTJ1ZG5pIn0.AiLtFFbOXt3MIouqO-cUag" |
|
|
| DEFAULT_CONFIG = { |
| "map_style": "mapbox://styles/mapbox/light-v11", |
| "neighbor_level": 3, |
| "risk_scenario": "summer", |
| } |
|
|
| MAP_STYLE_OPTIONS = [ |
| "mapbox://styles/mapbox/light-v11", |
| "mapbox://styles/mapbox/dark-v11", |
| "mapbox://styles/mapbox/streets-v12", |
| "mapbox://styles/mapbox/outdoors-v12", |
| "mapbox://styles/mapbox/satellite-v9", |
| "mapbox://styles/mapbox/satellite-streets-v12", |
| "mapbox://styles/mapbox/navigation-day-v1", |
| "mapbox://styles/mapbox/navigation-night-v1", |
| ] |
|
|
|
|
| def ensure_config() -> dict: |
| """Initialize and return mutable session config.""" |
| if "app_config" not in st.session_state: |
| st.session_state["app_config"] = DEFAULT_CONFIG.copy() |
| cfg = st.session_state["app_config"] |
| for key, value in DEFAULT_CONFIG.items(): |
| cfg.setdefault(key, value) |
|
|
| level = int(cfg.get("neighbor_level", 1)) |
| cfg["neighbor_level"] = _normalize_neighbor_level(level) |
|
|
| |
| |
| if "neighbor_offsets" not in cfg: |
| cfg["neighbor_offsets"] = recommended_offset_keys(cfg["neighbor_level"]) |
| return cfg |
|
|
|
|
| def get_config_value(key: str): |
| """Get one config value with fallback to default.""" |
| cfg = ensure_config() |
| return cfg.get(key, DEFAULT_CONFIG.get(key)) |
|
|
|
|
| def _normalize_neighbor_level(level: int) -> int: |
| level = max(1, int(level)) |
| return level if level % 2 == 1 else level + 1 |
|
|
|
|
| def offset_key(dx: int, dy: int) -> str: |
| return f"{dx},{dy}" |
|
|
|
|
| def parse_offset_key(key: str) -> tuple[int, int]: |
| a, b = key.split(",") |
| return int(a), int(b) |
|
|
|
|
| def base_offset_candidates(level: int) -> list[tuple[int, int]]: |
| """Offsets for one octant: 0 <= dy <= dx <= level, excluding (0, 0).""" |
| level = _normalize_neighbor_level(level) |
| out = [] |
| for dx in range(1, level + 1): |
| for dy in range(0, dx + 1): |
| out.append((dx, dy)) |
| return out |
|
|
|
|
| def primitive_offset(dx: int, dy: int) -> bool: |
| return gcd(dx, dy) == 1 if dy != 0 else dx == 1 |
|
|
|
|
| def recommended_offset_keys(level: int) -> list[str]: |
| return [ |
| offset_key(dx, dy) |
| for dx, dy in base_offset_candidates(level) |
| if primitive_offset(dx, dy) |
| ] |
|
|
|
|
| def all_offset_keys(level: int) -> list[str]: |
| return [offset_key(dx, dy) for dx, dy in base_offset_candidates(level)] |
|
|
|
|
| def expanded_directions(base_offsets: list[tuple[int, int]]) -> list[tuple[int, int]]: |
| """Expand first-octant offsets to all 8 directions.""" |
| out = set() |
| for dx, dy in base_offsets: |
| variants = {(dx, dy), (dy, dx)} |
| for a, b in variants: |
| for sx in (-1, 1): |
| for sy in (-1, 1): |
| out.add((sx * a, sy * b)) |
| return sorted(out, key=lambda v: (v[0] * v[0] + v[1] * v[1], abs(v[0]), abs(v[1]), v)) |
|
|
|
|
| def get_neighbor_offsets() -> list[tuple[int, int]]: |
| """Return expanded directions selected for current session config.""" |
| cfg = ensure_config() |
| level = _normalize_neighbor_level(int(cfg.get("neighbor_level", 1))) |
| cfg["neighbor_level"] = level |
|
|
| valid_keys = set(all_offset_keys(level)) |
| selected_keys = cfg.get("neighbor_offsets") |
| if not isinstance(selected_keys, list): |
| selected_keys = recommended_offset_keys(level) |
| selected_keys = [k for k in selected_keys if k in valid_keys] |
| if not selected_keys: |
| selected_keys = recommended_offset_keys(level) |
| cfg["neighbor_offsets"] = selected_keys |
|
|
| base = [parse_offset_key(k) for k in selected_keys] |
| return expanded_directions(base) |
|
|