from __future__ import annotations import asyncio from typing import Iterable from psycopg.rows import dict_row from psycopg_pool import ConnectionPool from app.config import settings class BuildingService: def __init__(self, database_url: str | None = None) -> None: self.database_url = database_url or settings.database_url self._pool: ConnectionPool | None = None self._available = False def connect(self) -> None: try: self._pool = ConnectionPool( conninfo=self.database_url, min_size=1, max_size=10, kwargs={"row_factory": dict_row}, open=True, ) with self._pool.connection() as conn: with conn.cursor() as cur: cur.execute("SELECT to_regclass('public.buildings') IS NOT NULL AS exists") row = cur.fetchone() self._available = bool(row and row["exists"]) except Exception: self._available = False if self._pool is not None: self._pool.close() self._pool = None def close(self) -> None: if self._pool is not None: self._pool.close() self._pool = None def is_available(self) -> bool: return self._available and self._pool is not None def _get_max_height_sync(self, lat: float, lng: float) -> float: if not self.is_available() or self._pool is None: return 0.0 with self._pool.connection() as conn: with conn.cursor() as cur: cur.execute( """ SELECT COALESCE(MAX(height_m), 0) AS height_m FROM buildings WHERE ST_Contains(geom, ST_SetSRID(ST_Point(%s, %s), 4326)) """, (lng, lat), ) row = cur.fetchone() return float(row["height_m"]) if row else 0.0 def _get_max_heights_sync(self, coordinates: Iterable[tuple[float, float]]) -> list[float]: coords = list(coordinates) if not coords: return [] if not self.is_available() or self._pool is None: return [0.0] * len(coords) values = [0.0] * len(coords) with self._pool.connection() as conn: with conn.cursor() as cur: for index, (lat, lng) in enumerate(coords): cur.execute( """ SELECT COALESCE(MAX(height_m), 0) AS height_m FROM buildings WHERE ST_Contains(geom, ST_SetSRID(ST_Point(%s, %s), 4326)) """, (lng, lat), ) row = cur.fetchone() values[index] = float(row["height_m"]) if row else 0.0 return values async def get_max_height(self, lat: float, lng: float) -> float: return await asyncio.to_thread(self._get_max_height_sync, lat, lng) async def get_max_heights(self, coordinates: list[tuple[float, float]]) -> list[float]: return await asyncio.to_thread(self._get_max_heights_sync, coordinates)