fix: query persisted work area geometry
This commit is contained in:
@@ -237,19 +237,32 @@ def select_vector_features(
|
||||
raise HTTPException(status_code=404, detail="Dataset not found")
|
||||
if dataset.dataset_type not in {"vector", "geojson"}:
|
||||
raise AppError(code="DATASET_NOT_VECTOR", message="Area selection requires a vector dataset", status_code=400)
|
||||
result = VectorFeatureService.select_features_by_bbox(
|
||||
db,
|
||||
dataset_id=dataset_id,
|
||||
bbox=payload.bbox.model_dump(),
|
||||
limit=payload.limit,
|
||||
)
|
||||
if isinstance(dataset.source_metadata, dict) and dataset.source_metadata.get("selection_aggregation"):
|
||||
result["summary"] = VectorFeatureService.summarize_features_by_bbox(
|
||||
db,
|
||||
dataset=dataset,
|
||||
bbox=payload.bbox.model_dump(),
|
||||
total_feature_count=result.get("total_feature_count"),
|
||||
selection_area = None
|
||||
if payload.area_id is not None:
|
||||
selection_area = db.get(Area, payload.area_id)
|
||||
if selection_area is None or selection_area.project_id != project_id:
|
||||
raise AppError(code="AREA_NOT_FOUND", message="Area not found", status_code=404)
|
||||
|
||||
selection_kwargs = {
|
||||
"dataset_id": dataset_id,
|
||||
"bbox": payload.bbox.model_dump(),
|
||||
"limit": payload.limit,
|
||||
}
|
||||
if selection_area is not None:
|
||||
selection_kwargs.update(
|
||||
selection_geometry=selection_area.geometry,
|
||||
selection_area_id=selection_area.id,
|
||||
)
|
||||
result = VectorFeatureService.select_features_by_bbox(db, **selection_kwargs)
|
||||
if isinstance(dataset.source_metadata, dict) and dataset.source_metadata.get("selection_aggregation"):
|
||||
summary_kwargs = {
|
||||
"dataset": dataset,
|
||||
"bbox": payload.bbox.model_dump(),
|
||||
"total_feature_count": result.get("total_feature_count"),
|
||||
}
|
||||
if selection_area is not None:
|
||||
summary_kwargs["selection_geometry"] = selection_area.geometry
|
||||
result["summary"] = VectorFeatureService.summarize_features_by_bbox(db, **summary_kwargs)
|
||||
return envelope(VectorSelectionResponse(**result).model_dump(exclude_none=True))
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
|
||||
@@ -210,6 +212,7 @@ class VectorSelectionBBox(BaseModel):
|
||||
|
||||
class VectorSelectionRequest(BaseModel):
|
||||
bbox: VectorSelectionBBox
|
||||
area_id: UUID | None = None
|
||||
limit: int = Field(default=100, ge=1, le=1000)
|
||||
|
||||
|
||||
@@ -229,6 +232,7 @@ class VectorSelectionSummary(BaseModel):
|
||||
|
||||
class VectorSelectionResponse(BaseModel):
|
||||
selection_bbox: VectorSelectionBBox
|
||||
selection_area_id: UUID | None = None
|
||||
feature_count: int
|
||||
total_feature_count: int | None = None
|
||||
limit: int
|
||||
|
||||
@@ -124,25 +124,25 @@ class VectorFeatureService:
|
||||
bbox: dict[str, Any],
|
||||
limit: int = 100,
|
||||
dataset: Dataset | None = None,
|
||||
selection_geometry: Any | None = None,
|
||||
selection_area_id: UUID | None = None,
|
||||
) -> dict[str, Any]:
|
||||
normalized_bbox = VectorFeatureService._normalize_selection_bbox(bbox)
|
||||
safe_limit = max(1, min(int(limit), 1000))
|
||||
selection_shape = selection_geometry
|
||||
if selection_shape is None:
|
||||
selection_shape = ST_MakeEnvelope(
|
||||
normalized_bbox["min_x"],
|
||||
normalized_bbox["min_y"],
|
||||
normalized_bbox["max_x"],
|
||||
normalized_bbox["max_y"],
|
||||
4326,
|
||||
)
|
||||
|
||||
query = (
|
||||
db.query(VectorFeature)
|
||||
.filter(VectorFeature.dataset_id == dataset_id)
|
||||
.filter(
|
||||
ST_Intersects(
|
||||
VectorFeature.geometry,
|
||||
ST_MakeEnvelope(
|
||||
normalized_bbox["min_x"],
|
||||
normalized_bbox["min_y"],
|
||||
normalized_bbox["max_x"],
|
||||
normalized_bbox["max_y"],
|
||||
4326,
|
||||
),
|
||||
)
|
||||
)
|
||||
.filter(ST_Intersects(VectorFeature.geometry, selection_shape))
|
||||
)
|
||||
if hasattr(query, "count"):
|
||||
total_feature_count = int(query.count())
|
||||
@@ -164,9 +164,10 @@ class VectorFeatureService:
|
||||
dataset=dataset,
|
||||
bbox=normalized_bbox,
|
||||
total_feature_count=total_feature_count,
|
||||
selection_geometry=selection_geometry,
|
||||
)
|
||||
|
||||
return {
|
||||
result = {
|
||||
"selection_bbox": normalized_bbox,
|
||||
"feature_count": len(features),
|
||||
"total_feature_count": total_feature_count,
|
||||
@@ -178,6 +179,9 @@ class VectorFeatureService:
|
||||
},
|
||||
"summary": summary,
|
||||
}
|
||||
if selection_area_id is not None:
|
||||
result["selection_area_id"] = str(selection_area_id)
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def summarize_features_by_bbox(
|
||||
@@ -186,18 +190,21 @@ class VectorFeatureService:
|
||||
dataset: Dataset,
|
||||
bbox: dict[str, Any],
|
||||
total_feature_count: int | None = None,
|
||||
selection_geometry: Any | None = None,
|
||||
) -> dict[str, Any]:
|
||||
normalized_bbox = VectorFeatureService._normalize_selection_bbox(bbox)
|
||||
envelope = ST_MakeEnvelope(
|
||||
normalized_bbox["min_x"],
|
||||
normalized_bbox["min_y"],
|
||||
normalized_bbox["max_x"],
|
||||
normalized_bbox["max_y"],
|
||||
4326,
|
||||
)
|
||||
selection_shape = selection_geometry
|
||||
if selection_shape is None:
|
||||
selection_shape = ST_MakeEnvelope(
|
||||
normalized_bbox["min_x"],
|
||||
normalized_bbox["min_y"],
|
||||
normalized_bbox["max_x"],
|
||||
normalized_bbox["max_y"],
|
||||
4326,
|
||||
)
|
||||
selection_filter = (
|
||||
VectorFeature.dataset_id == dataset.id,
|
||||
ST_Intersects(VectorFeature.geometry, envelope),
|
||||
ST_Intersects(VectorFeature.geometry, selection_shape),
|
||||
)
|
||||
feature_count = total_feature_count
|
||||
if feature_count is None:
|
||||
@@ -215,13 +222,13 @@ class VectorFeatureService:
|
||||
|
||||
metric_value = float(feature_count)
|
||||
if method == "intersection_area":
|
||||
intersection = func.ST_Intersection(VectorFeature.geometry, envelope)
|
||||
intersection = func.ST_Intersection(VectorFeature.geometry, selection_shape)
|
||||
area_expression = func.ST_Area(func.ST_Transform(intersection, 31370))
|
||||
area_m2 = db.query(func.coalesce(func.sum(area_expression), 0.0)).filter(*selection_filter).scalar()
|
||||
divisor = 10_000.0 if unit == "ha" else 1.0
|
||||
metric_value = float(area_m2 or 0.0) / divisor
|
||||
elif method == "intersection_length":
|
||||
intersection = func.ST_Intersection(VectorFeature.geometry, envelope)
|
||||
intersection = func.ST_Intersection(VectorFeature.geometry, selection_shape)
|
||||
length_expression = func.ST_Length(func.ST_Transform(intersection, 31370))
|
||||
length_m = db.query(func.coalesce(func.sum(length_expression), 0.0)).filter(*selection_filter).scalar()
|
||||
divisor = 1_000.0 if unit == "km" else 1.0
|
||||
@@ -240,7 +247,7 @@ class VectorFeatureService:
|
||||
if method == "area_weighted_sum":
|
||||
source_area = func.ST_Area(func.ST_Transform(VectorFeature.geometry, 31370))
|
||||
intersection_area = func.ST_Area(
|
||||
func.ST_Transform(func.ST_Intersection(VectorFeature.geometry, envelope), 31370)
|
||||
func.ST_Transform(func.ST_Intersection(VectorFeature.geometry, selection_shape), 31370)
|
||||
)
|
||||
coverage_ratio = intersection_area / func.nullif(source_area, 0.0)
|
||||
value_expression = numeric_value * coverage_ratio
|
||||
|
||||
Reference in New Issue
Block a user