From 10bcb856aece3b8c4c43a55162bf3fcfd8045d6b Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 27 Oct 2023 18:34:43 +0200 Subject: [PATCH] fix(FeatureAmounts): Adds FeatureAmount calculation to feature shapefile upload time --- .../geo-features/geo-features.module.ts | 2 + .../geo-features/geo-features.service.ts | 43 ++++++++++++++++ .../compute-area.service.ts | 2 +- ...ture-amounts-per-planning-units.service.ts | 51 ++++++++++--------- 4 files changed, 73 insertions(+), 25 deletions(-) diff --git a/api/apps/api/src/modules/geo-features/geo-features.module.ts b/api/apps/api/src/modules/geo-features/geo-features.module.ts index e7a56fa610..6e53777506 100644 --- a/api/apps/api/src/modules/geo-features/geo-features.module.ts +++ b/api/apps/api/src/modules/geo-features/geo-features.module.ts @@ -24,6 +24,7 @@ import { UploadedFeatureAmount } from '@marxan-api/modules/geo-features/import/f import { FeatureAmountUploadService } from '@marxan-api/modules/geo-features/import/features-amounts-upload.service'; import { ApiEventsModule } from '@marxan-api/modules/api-events'; import { FeatureImportEventsService } from '@marxan-api/modules/geo-features/import/feature-import.events'; +import { FeatureAmountsPerPlanningUnitModule } from '@marxan/feature-amounts-per-planning-unit'; @Module({ imports: [ @@ -44,6 +45,7 @@ import { FeatureImportEventsService } from '@marxan-api/modules/geo-features/imp forwardRef(() => ScenarioFeaturesModule), ApiEventsModule, GeoFeatureTagsModule, + FeatureAmountsPerPlanningUnitModule.for(DbConnections.geoprocessingDB), ], providers: [ GeoFeaturesService, diff --git a/api/apps/api/src/modules/geo-features/geo-features.service.ts b/api/apps/api/src/modules/geo-features/geo-features.service.ts index c80ea2bcde..2868f55f09 100644 --- a/api/apps/api/src/modules/geo-features/geo-features.service.ts +++ b/api/apps/api/src/modules/geo-features/geo-features.service.ts @@ -48,6 +48,11 @@ import { } from '@marxan-api/modules/geo-feature-tags/geo-feature-tags.service'; import { FeatureAmountUploadService } from '@marxan-api/modules/geo-features/import/features-amounts-upload.service'; import { isNil } from 'lodash'; +import { + FeatureAmountsPerPlanningUnitEntity, + FeatureAmountsPerPlanningUnitService, +} from '@marxan/feature-amounts-per-planning-unit'; +import { ComputeFeatureAmountPerPlanningUnit } from '@marxan/feature-amounts-per-planning-unit/feature-amounts-per-planning-units.service'; const geoFeatureFilterKeyNames = [ 'featureClassName', @@ -119,6 +124,7 @@ export class GeoFeaturesService extends AppBaseService< private readonly scenarioFeaturesService: ScenarioFeaturesService, private readonly projectAclService: ProjectAclService, private readonly featureAmountUploads: FeatureAmountUploadService, + private readonly featureAmountsPerPlanningUnitService: FeatureAmountsPerPlanningUnitService, ) { super( geoFeaturesRepository, @@ -411,8 +417,23 @@ export class GeoFeaturesService extends AppBaseService< ); } + const computedFeatureAmounts = + await this.featureAmountsPerPlanningUnitService.computeMarxanAmountPerPlanningUnit( + geoFeature.id, + projectId, + geoQueryRunner.manager, + ); + + await this.saveFeatureAmountPerPlanningUnit( + geoQueryRunner.manager, + projectId, + computedFeatureAmounts, + ); + await apiQueryRunner.commitTransaction(); await geoQueryRunner.commitTransaction(); + + await this.saveAmountRangeForFeatures([geoFeature.id]); } catch (err) { await apiQueryRunner.rollbackTransaction(); await geoQueryRunner.rollbackTransaction(); @@ -431,6 +452,24 @@ export class GeoFeaturesService extends AppBaseService< return right(geoFeature); } + private async saveFeatureAmountPerPlanningUnit( + geoEntityManager: EntityManager, + projectId: string, + featureAmounts: ComputeFeatureAmountPerPlanningUnit[], + ): Promise { + const repo = geoEntityManager.getRepository( + FeatureAmountsPerPlanningUnitEntity, + ); + await repo.save( + featureAmounts.map(({ amount, projectPuId, featureId }) => ({ + projectId, + featureId, + amount, + projectPuId, + })), + ); + } + public async updateFeatureForProject( userId: string, featureId: string, @@ -854,6 +893,10 @@ export class GeoFeaturesService extends AppBaseService< .groupBy('fappu.feature_id') .getRawMany(); + if (minAndMaxAmountsForFeatures.length === 0) { + return; + } + const minMaxSqlValueStringForFeatures = minAndMaxAmountsForFeatures .map( (feature) => diff --git a/api/apps/api/src/modules/scenarios-features/compute-area.service.ts b/api/apps/api/src/modules/scenarios-features/compute-area.service.ts index d8b8f5f76b..a09123cc21 100644 --- a/api/apps/api/src/modules/scenarios-features/compute-area.service.ts +++ b/api/apps/api/src/modules/scenarios-features/compute-area.service.ts @@ -36,7 +36,7 @@ export class ComputeArea { const amountPerPlanningUnitOfFeature = await this.featureAmountsPerPlanningUnit.computeMarxanAmountPerPlanningUnit( featureId, - scenarioId, + projectId, ); return this.featureAmountsPerPlanningUnitRepo.saveAmountPerPlanningUnitAndFeature( diff --git a/api/libs/feature-amounts-per-planning-unit/src/feature-amounts-per-planning-units.service.ts b/api/libs/feature-amounts-per-planning-unit/src/feature-amounts-per-planning-units.service.ts index 1d6c436828..8a9e7d712a 100644 --- a/api/libs/feature-amounts-per-planning-unit/src/feature-amounts-per-planning-units.service.ts +++ b/api/libs/feature-amounts-per-planning-unit/src/feature-amounts-per-planning-units.service.ts @@ -41,7 +41,8 @@ export class FeatureAmountsPerPlanningUnitService { public async computeMarxanAmountPerPlanningUnit( featureId: string, - scenarioId: string, + projectId: string, + geoEntityManager?: EntityManager, ): Promise { /** * @TODO further performance savings: limiting scans to planning_units_geom @@ -49,40 +50,42 @@ export class FeatureAmountsPerPlanningUnitService { * && operator instead of st_intersects() for bbox-based calculation of * intersections. */ + geoEntityManager = geoEntityManager + ? geoEntityManager + : this.geoEntityManager; + const rows: { featureid: string; puid: number; projectpuid: string; amount: number; - }[] = await this.geoEntityManager.query( + }[] = await geoEntityManager.query( ` WITH all_amount_per_planning_unit as ( select - $2 as featureId, - pu.puid as puid, - pu.id as projectpuid, - ST_Area(ST_Transform(ST_Intersection(species.the_geom, pu.the_geom),3410)) as amount - from - ( - select - st_union(the_geom) as the_geom - from scenario_features_preparation sfp - inner join features_data fd on sfp.feature_class_id = fd.id where sfp.scenario_id = $1 - AND sfp.api_feature_id = $2 - group by sfp.api_feature_id - ) species, - ( - select the_geom, ppu.puid as puid, ppu.id as id, spd.scenario_id - from planning_units_geom pug - inner join projects_pu ppu on pug.id = ppu.geom_id - inner join scenarios_pu_data spd on ppu.id = spd.project_pu_id - where spd.scenario_id = $1 order by ppu.puid asc - ) pu - where species.the_geom && pu.the_geom + $2 as featureId, + pu.puid as puid, + pu.id as projectpuid, + ST_Area(ST_Transform(ST_Intersection(species.the_geom, pu.the_geom),3410)) as amount + from + ( + select st_union(the_geom) as the_geom + from features_data fd + where fd.feature_id = $2 + group by fd.feature_id + ) species, + ( + select the_geom, ppu.puid as puid, ppu.id as id + from planning_units_geom pug + inner join projects_pu ppu on pug.id = ppu.geom_id + where ppu.project_id = $1 + order by ppu.puid asc + ) pu + where species.the_geom && pu.the_geom ) select * from all_amount_per_planning_unit where amount > 0 order by puid; `, - [scenarioId, featureId], + [projectId, featureId], ); return rows.map(({ featureid, projectpuid, puid, amount }) => ({