added bootstrap buildings
This commit is contained in:
@@ -1,5 +1,164 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.geo import PathPoint
|
||||
from app.models.buildings import BuildingsQueryRequest, BuildingsQueryResponse
|
||||
|
||||
|
||||
def query_buildings(request: BuildingsQueryRequest) -> BuildingsQueryResponse:
|
||||
raise NotImplementedError("PostGIS building queries are implemented in a later stage")
|
||||
def _feature_from_value(value: Any) -> dict[str, Any]:
|
||||
if isinstance(value, str):
|
||||
return json.loads(value)
|
||||
return dict(value)
|
||||
|
||||
|
||||
def _geometry_from_geojson(geojson: dict[str, Any]) -> dict[str, Any]:
|
||||
if geojson.get("type") == "Feature":
|
||||
geometry = geojson.get("geometry")
|
||||
else:
|
||||
geometry = geojson
|
||||
|
||||
if not isinstance(geometry, dict) or geometry.get("type") not in {
|
||||
"LineString",
|
||||
"MultiLineString",
|
||||
"Polygon",
|
||||
"MultiPolygon",
|
||||
}:
|
||||
raise ValueError("path must be a GeoJSON geometry or Feature")
|
||||
return geometry
|
||||
|
||||
|
||||
def query_buildings(
|
||||
request: BuildingsQueryRequest,
|
||||
db: Session,
|
||||
limit: int = 5000,
|
||||
) -> BuildingsQueryResponse:
|
||||
try:
|
||||
if request.bbox is not None:
|
||||
rows = _query_by_bbox(db, request.bbox, limit)
|
||||
else:
|
||||
rows = _query_by_path(db, request.path or {}, request.buffer_m, limit)
|
||||
except SQLAlchemyError as exc:
|
||||
raise NotImplementedError(
|
||||
"PostGIS buildings table is not ready. Run scripts/load_buildings.sh first."
|
||||
) from exc
|
||||
|
||||
return BuildingsQueryResponse(features=[_feature_from_value(row["feature"]) for row in rows])
|
||||
|
||||
|
||||
def building_heights_along(points: list[PathPoint], db: Session) -> list[float]:
|
||||
if not points:
|
||||
return []
|
||||
|
||||
points_json = json.dumps(
|
||||
[{"i": index, "lon": point.lon, "lat": point.lat} for index, point in enumerate(points)]
|
||||
)
|
||||
statement = text(
|
||||
"""
|
||||
WITH points AS (
|
||||
SELECT *
|
||||
FROM jsonb_to_recordset(CAST(:points_json AS jsonb))
|
||||
AS p(i integer, lon double precision, lat double precision)
|
||||
)
|
||||
SELECT p.i, COALESCE(MAX(b.height_m), 0) AS height_m
|
||||
FROM points p
|
||||
LEFT JOIN buildings b
|
||||
ON ST_Intersects(b.geom, ST_SetSRID(ST_MakePoint(p.lon, p.lat), 4326))
|
||||
GROUP BY p.i
|
||||
ORDER BY p.i
|
||||
"""
|
||||
)
|
||||
rows = db.execute(statement, {"points_json": points_json}).mappings().all()
|
||||
heights = [0.0] * len(points)
|
||||
for row in rows:
|
||||
heights[int(row["i"])] = float(row["height_m"] or 0.0)
|
||||
return heights
|
||||
|
||||
|
||||
def _query_by_bbox(
|
||||
db: Session,
|
||||
bbox: tuple[float, float, float, float],
|
||||
limit: int,
|
||||
) -> list[dict[str, Any]]:
|
||||
minlon, minlat, maxlon, maxlat = bbox
|
||||
statement = text(
|
||||
"""
|
||||
SELECT jsonb_build_object(
|
||||
'type', 'Feature',
|
||||
'id', osm_id,
|
||||
'geometry', ST_AsGeoJSON(geom)::jsonb,
|
||||
'properties', jsonb_build_object(
|
||||
'height_m', height_m,
|
||||
'levels', levels,
|
||||
'building_type', building_type,
|
||||
'source', source
|
||||
)
|
||||
) AS feature
|
||||
FROM buildings
|
||||
WHERE ST_Intersects(geom, ST_MakeEnvelope(:minlon, :minlat, :maxlon, :maxlat, 4326))
|
||||
LIMIT :limit
|
||||
"""
|
||||
)
|
||||
return list(
|
||||
db.execute(
|
||||
statement,
|
||||
{
|
||||
"minlon": minlon,
|
||||
"minlat": minlat,
|
||||
"maxlon": maxlon,
|
||||
"maxlat": maxlat,
|
||||
"limit": limit,
|
||||
},
|
||||
)
|
||||
.mappings()
|
||||
.all()
|
||||
)
|
||||
|
||||
|
||||
def _query_by_path(
|
||||
db: Session,
|
||||
path_geojson: dict[str, Any],
|
||||
buffer_m: float,
|
||||
limit: int,
|
||||
) -> list[dict[str, Any]]:
|
||||
geometry_json = json.dumps(_geometry_from_geojson(path_geojson))
|
||||
statement = text(
|
||||
"""
|
||||
WITH query_geom AS (
|
||||
SELECT ST_Transform(
|
||||
ST_Buffer(
|
||||
ST_Transform(ST_SetSRID(ST_GeomFromGeoJSON(:geometry_json), 4326), 3857),
|
||||
:buffer_m
|
||||
),
|
||||
4326
|
||||
) AS geom
|
||||
)
|
||||
SELECT jsonb_build_object(
|
||||
'type', 'Feature',
|
||||
'id', b.osm_id,
|
||||
'geometry', ST_AsGeoJSON(b.geom)::jsonb,
|
||||
'properties', jsonb_build_object(
|
||||
'height_m', b.height_m,
|
||||
'levels', b.levels,
|
||||
'building_type', b.building_type,
|
||||
'source', b.source
|
||||
)
|
||||
) AS feature
|
||||
FROM buildings b, query_geom q
|
||||
WHERE ST_Intersects(b.geom, q.geom)
|
||||
LIMIT :limit
|
||||
"""
|
||||
)
|
||||
return list(
|
||||
db.execute(
|
||||
statement,
|
||||
{"geometry_json": geometry_json, "buffer_m": buffer_m, "limit": limit},
|
||||
)
|
||||
.mappings()
|
||||
.all()
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user