gendesign/backend/app/services/site_finder/okn_lookup.py
lekss361 50fcba1ca0
All checks were successful
Deploy / changes (push) Successful in 5s
Deploy / build-backend (push) Successful in 1m23s
Deploy / build-frontend (push) Has been skipped
Deploy / build-worker (push) Successful in 2m21s
Deploy / deploy (push) Successful in 1m15s
feat(sf): ingest ОКН-объектов ЕКБ из АИС ЕГРКН (точки+категория) → okn_objects (#1141)
Co-authored-by: lekss361 <lekss361@gendsgn.local>
Co-committed-by: lekss361 <lekss361@gendsgn.local>
2026-06-07 12:51:18 +00:00

91 lines
3.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""ОКН-lookup для analyze_parcel (#1066).
Читает ``okn_objects`` (м.141) и отдаёт объекты культурного наследия
в заданном радиусе от участка — [{source_id, category, distance_m}].
ST_DWithin по geography (метрическое расстояние в метрах) через GIST-индекс
idx_okn_objects_geom. Участок передаётся как WKT в EPSG:4326.
Graceful: если таблица ``okn_objects`` ещё не задеплоена (pre-migration)
или БД-ошибка — возвращает пустой список (analyze не падает).
Wiring в analyze — следующий PR (analyze-wiring, follow-up #1066).
"""
from __future__ import annotations
import logging
from typing import Any
from sqlalchemy import text
from sqlalchemy.exc import OperationalError, ProgrammingError
from sqlalchemy.orm import Session
logger = logging.getLogger(__name__)
# Пространственный запрос: ST_DWithin по geography даёт расстояние в метрах.
# CAST psycopg v3 (никогда :param::type — vault Pattern_CAST_AS_Type).
_OKN_DWITHIN_SQL = text("""
SELECT
source_id,
category,
ROUND(
ST_Distance(
geom::geography,
ST_GeomFromText(CAST(:parcel_wkt AS text), 4326)::geography
)
)::integer AS distance_m
FROM okn_objects
WHERE ST_DWithin(
geom::geography,
ST_GeomFromText(CAST(:parcel_wkt AS text), 4326)::geography,
CAST(:radius_m AS double precision)
)
ORDER BY distance_m ASC
""")
def parcel_okn_objects(
db: Session,
parcel_wkt: str | None,
radius_m: float = 100.0,
) -> list[dict[str, Any]]:
"""ОКН-объекты в радиусе radius_m от участка.
Args:
db: SQLAlchemy-сессия.
parcel_wkt: геометрия участка в WKT (EPSG:4326). None → пустой список.
radius_m: радиус поиска в метрах (по умолчанию 100 м).
Returns:
Список dict [{source_id, category, distance_m}], отсортированный
по расстоянию ASC. Пустой список при отсутствии участка / таблицы /
объектов в радиусе (analyze не падает).
"""
if not parcel_wkt:
return []
try:
rows = db.execute(
_OKN_DWITHIN_SQL,
{"parcel_wkt": parcel_wkt, "radius_m": radius_m},
).fetchall()
except (OperationalError, ProgrammingError) as exc:
logger.warning("parcel_okn_objects: okn_objects недоступна, skip: %s", exc)
return []
except Exception as exc:
logger.warning("parcel_okn_objects: ошибка запроса okn_objects: %s", exc)
return []
result: list[dict[str, Any]] = []
for row in rows:
result.append(
{
"source_id": row[0],
"category": row[1],
"distance_m": row[2],
}
)
return result
__all__ = ["parcel_okn_objects"]