diff --git a/api/apps/api/src/modules/clone/export/adapters/export-resource-pieces.adapter.spec.ts b/api/apps/api/src/modules/clone/export/adapters/export-resource-pieces.adapter.spec.ts index 640f572620..23e14b2fa9 100644 --- a/api/apps/api/src/modules/clone/export/adapters/export-resource-pieces.adapter.spec.ts +++ b/api/apps/api/src/modules/clone/export/adapters/export-resource-pieces.adapter.spec.ts @@ -83,6 +83,7 @@ const getFixtures = async () => { projectWithCustomPlanningArea ? ClonePiece.PlanningAreaCustom : ClonePiece.PlanningAreaGAdm, + ClonePiece.ProjectCustomProtectedAreas, ]; const expectedScenarioPieces = (projectExport: boolean) => { diff --git a/api/apps/api/src/modules/clone/export/adapters/export-resource-pieces.adapter.ts b/api/apps/api/src/modules/clone/export/adapters/export-resource-pieces.adapter.ts index ff0880c7c5..bd4b109892 100644 --- a/api/apps/api/src/modules/clone/export/adapters/export-resource-pieces.adapter.ts +++ b/api/apps/api/src/modules/clone/export/adapters/export-resource-pieces.adapter.ts @@ -58,6 +58,7 @@ export class ExportResourcePiecesAdapter implements ExportResourcePieces { customPlanningArea ? ExportComponent.newOne(id, ClonePiece.PlanningAreaCustom) : ExportComponent.newOne(id, ClonePiece.PlanningAreaGAdm), + ExportComponent.newOne(id, ClonePiece.ProjectCustomProtectedAreas), ...scenarioPieces.flat(), ]; diff --git a/api/apps/geoprocessing/src/export/pieces-exporters/pieces-exporters.module.ts b/api/apps/geoprocessing/src/export/pieces-exporters/pieces-exporters.module.ts index 674fd41c95..38a051c678 100644 --- a/api/apps/geoprocessing/src/export/pieces-exporters/pieces-exporters.module.ts +++ b/api/apps/geoprocessing/src/export/pieces-exporters/pieces-exporters.module.ts @@ -9,6 +9,7 @@ import { PlanningAreaCustomPieceExporter } from './planning-area-custom.piece-ex import { PlanningAreaGadmPieceExporter } from './planning-area-gadm.piece-exporter'; import { PlanningUnitsGridGeojsonPieceExporter } from './planning-units-grid-geojson.piece-exporter'; import { PlanningUnitsGridPieceExporter } from './planning-units-grid.piece-exporter'; +import { ProjectCustomProtectedAreasPieceExporter } from './project-custom-protected-areas.piece-exporter'; import { ProjectMetadataPieceExporter } from './project-metadata.piece-exporter'; import { ScenarioMetadataPieceExporter } from './scenario-metadata.piece-exporter'; @@ -27,6 +28,7 @@ import { ScenarioMetadataPieceExporter } from './scenario-metadata.piece-exporte PlanningAreaCustomGeojsonPieceExporter, PlanningUnitsGridPieceExporter, PlanningUnitsGridGeojsonPieceExporter, + ProjectCustomProtectedAreasPieceExporter, ScenarioMetadataPieceExporter, Logger, ], diff --git a/api/apps/geoprocessing/src/export/pieces-exporters/project-custom-protected-areas.piece-exporter.ts b/api/apps/geoprocessing/src/export/pieces-exporters/project-custom-protected-areas.piece-exporter.ts new file mode 100644 index 0000000000..361f7b2d77 --- /dev/null +++ b/api/apps/geoprocessing/src/export/pieces-exporters/project-custom-protected-areas.piece-exporter.ts @@ -0,0 +1,80 @@ +import { geoprocessingConnections } from '@marxan-geoprocessing/ormconfig'; +import { ClonePiece, ExportJobInput, ExportJobOutput } from '@marxan/cloning'; +import { ResourceKind } from '@marxan/cloning/domain'; +import { FileRepository } from '@marxan/files-repository'; +import { Injectable, Logger } from '@nestjs/common'; +import { InjectEntityManager } from '@nestjs/typeorm'; +import { EntityManager } from 'typeorm'; +import { + ExportPieceProcessor, + PieceExportProvider, +} from '../pieces/export-piece-processor'; +import { Readable } from 'stream'; +import { isLeft } from 'fp-ts/lib/Either'; +import { ClonePieceUrisResolver } from '@marxan/cloning/infrastructure/clone-piece-data'; +import { ProjectCustomProtectedAreasContent } from '@marxan/cloning/infrastructure/clone-piece-data/project-custom-protected-areas'; +import { ProtectedArea } from '@marxan/protected-areas'; + +interface ProjectCustomProtectedAreasSelectResult { + fullName: string; + ewkb: Buffer; +} + +@Injectable() +@PieceExportProvider() +export class ProjectCustomProtectedAreasPieceExporter + implements ExportPieceProcessor { + constructor( + private readonly fileRepository: FileRepository, + @InjectEntityManager(geoprocessingConnections.default) + private readonly geoprocessingEntityManager: EntityManager, + private readonly logger: Logger, + ) { + this.logger.setContext(ProjectCustomProtectedAreasPieceExporter.name); + } + + isSupported(piece: ClonePiece, kind: ResourceKind): boolean { + return ( + piece === ClonePiece.ProjectCustomProtectedAreas && + kind === ResourceKind.Project + ); + } + + async run(input: ExportJobInput): Promise { + const customProtectedAreas: ProjectCustomProtectedAreasSelectResult[] = await this.geoprocessingEntityManager + .createQueryBuilder() + .select('ST_AsEWKB(wdpa.the_geom)', 'ewkb') + .addSelect('full_name', 'fullName') + .from(ProtectedArea, 'wdpa') + .where('project_id = :projectId', { projectId: input.resourceId }) + .execute(); + + const content = customProtectedAreas.map( + (protectedArea) => { + return { + fullName: protectedArea.fullName, + ewkb: protectedArea.ewkb.toJSON().data, + }; + }, + ); + + const outputFile = await this.fileRepository.save( + Readable.from(JSON.stringify(content)), + `json`, + ); + + if (isLeft(outputFile)) { + const errorMessage = `${ProjectCustomProtectedAreasPieceExporter.name} - Project Custom Protected Areas - couldn't save file - ${outputFile.left.description}`; + this.logger.error(errorMessage); + throw new Error(errorMessage); + } + + return { + ...input, + uris: ClonePieceUrisResolver.resolveFor( + ClonePiece.ProjectCustomProtectedAreas, + outputFile.right, + ), + }; + } +} diff --git a/api/libs/cloning/src/infrastructure/clone-piece-data/project-custom-protected-areas.ts b/api/libs/cloning/src/infrastructure/clone-piece-data/project-custom-protected-areas.ts index d7297d8c79..553fd0d8a7 100644 --- a/api/libs/cloning/src/infrastructure/clone-piece-data/project-custom-protected-areas.ts +++ b/api/libs/cloning/src/infrastructure/clone-piece-data/project-custom-protected-areas.ts @@ -1,2 +1,7 @@ export const projectCustomProtectedAreasRelativePath = 'project-custom-protected-areas.json'; + +export interface ProjectCustomProtectedAreasContent { + fullName: string; + ewkb: number[]; +}