91 lines
2.8 KiB
Python
91 lines
2.8 KiB
Python
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)
|