diff --git a/backend/app/services/vector_feature_service.py b/backend/app/services/vector_feature_service.py index 9c54717b..a731926e 100644 --- a/backend/app/services/vector_feature_service.py +++ b/backend/app/services/vector_feature_service.py @@ -11,7 +11,7 @@ from geoalchemy2.shape import to_shape from shapely.geometry import box, mapping, shape from shapely.ops import transform as transform_geometry from shapely.validation import make_valid -from sqlalchemy import Float, String, cast, func +from sqlalchemy import Float, String, case, cast, func from app.core.errors import AppError from app.models import Dataset, VectorFeature @@ -778,12 +778,18 @@ class VectorFeatureService: ) if method == "intersection_area": - measured_geometry = ( - VectorFeature.geometry - if full_dataset_area - else func.ST_Intersection(VectorFeature.geometry, selection_shape) - ) - area_expression = func.ST_Area(func.ST_Transform(measured_geometry, 31370)) + source_area = func.ST_Area(func.ST_Transform(VectorFeature.geometry, 31370)) + if full_dataset_area: + area_expression = source_area + else: + covered_by_selection = func.ST_CoveredBy(VectorFeature.geometry, selection_shape) + intersection_area = func.ST_Area( + func.ST_Transform(func.ST_Intersection(VectorFeature.geometry, selection_shape), 31370) + ) + area_expression = case( + (covered_by_selection, source_area), + else_=intersection_area, + ) area_m2 = db.query(func.coalesce(func.sum(area_expression), 0.0)).filter(*metric_filter).scalar() divisor = 10_000.0 if unit == "ha" else 1.0 metric_value = float(area_m2 or 0.0) / divisor @@ -808,12 +814,17 @@ class VectorFeatureService: ) numeric_value = cast(VectorFeature.properties_json.op("->>")(property_name), Float) value_expression = numeric_value + covered_by_selection = None if method == "area_weighted_sum" and not full_dataset_area: source_area = func.ST_Area(func.ST_Transform(VectorFeature.geometry, 31370)) intersection_area = func.ST_Area( func.ST_Transform(func.ST_Intersection(VectorFeature.geometry, selection_shape), 31370) ) - coverage_ratio = intersection_area / func.nullif(source_area, 0.0) + covered_by_selection = func.ST_CoveredBy(VectorFeature.geometry, selection_shape) + coverage_ratio = case( + (covered_by_selection, 1.0), + else_=intersection_area / func.nullif(source_area, 0.0), + ) value_expression = numeric_value * coverage_ratio aggregate_function = { "mean": func.avg, @@ -831,7 +842,7 @@ class VectorFeatureService: partial_feature_count = ( db.query(func.count(VectorFeature.id)) .filter(*metric_filter) - .filter(coverage_ratio < 0.999999) + .filter(~covered_by_selection) .scalar() ) is_estimate = bool(config.get("is_estimate", False)) or bool(partial_feature_count)