diff --git a/app/Http/Controllers/V2/Terrafund/TerrafundClipGeometryController.php b/app/Http/Controllers/V2/Terrafund/TerrafundClipGeometryController.php index d13b63a3d..67d92f8fe 100644 --- a/app/Http/Controllers/V2/Terrafund/TerrafundClipGeometryController.php +++ b/app/Http/Controllers/V2/Terrafund/TerrafundClipGeometryController.php @@ -42,11 +42,46 @@ public function clipOverlappingPolygonsOfProjectBySite(string $uuid) $user = Auth::user(); $sitePolygon = Site::isUuid($uuid)->first(); $projectId = $sitePolygon->project_id ?? null; + + if (! $projectId) { + return response()->json(['error' => 'Project not found for the given site UUID.'], 404); + } + $polygonUuids = GeometryHelper::getProjectPolygonsUuids($projectId); + + if (empty($polygonUuids)) { + return response()->json(['message' => 'No polygons found for the project.'], 204); + } + + $allPolygonUuids = []; + foreach ($polygonUuids as $uuid) { + $polygonOverlappingExtraInfo = CriteriaSite::forCriteria(PolygonService::OVERLAPPING_CRITERIA_ID) + ->where('polygon_id', $uuid) + ->first() + ->extra_info ?? null; + + if ($polygonOverlappingExtraInfo) { + $decodedInfo = json_decode($polygonOverlappingExtraInfo, true); + $polygonUuidsOverlapping = array_map(function ($item) { + return $item['poly_uuid'] ?? null; + }, $decodedInfo); + $polygonUuidsFiltered = array_filter($polygonUuidsOverlapping); + + array_unshift($polygonUuidsFiltered, $uuid); + $allPolygonUuids = array_merge($allPolygonUuids, $polygonUuidsFiltered); + } + } + + $uniquePolygonUuids = array_unique($allPolygonUuids); + + if (empty($uniquePolygonUuids)) { + return response()->json(['message' => 'No overlapping polygons found for the project.'], 204); + } + $delayedJob = DelayedJobProgress::create([ - 'processed_content' => 0, - ]); - $job = new FixPolygonOverlapJob($delayedJob->id, $polygonUuids, $user->id); + 'processed_content' => 0, + ]); + $job = new FixPolygonOverlapJob($delayedJob->id, $uniquePolygonUuids, $user->id); dispatch($job); return new DelayedJobResource($delayedJob); diff --git a/app/Http/Resources/V2/Dashboard/ProjectProfileDetailsResource.php b/app/Http/Resources/V2/Dashboard/ProjectProfileDetailsResource.php index b20ca295e..c4e18b813 100644 --- a/app/Http/Resources/V2/Dashboard/ProjectProfileDetailsResource.php +++ b/app/Http/Resources/V2/Dashboard/ProjectProfileDetailsResource.php @@ -2,19 +2,18 @@ namespace App\Http\Resources\V2\Dashboard; +use App\Models\Traits\HasProjectCoverImage; use App\Models\V2\Forms\FormOptionListOption; use Illuminate\Http\Resources\Json\JsonResource; class ProjectProfileDetailsResource extends JsonResource { - /** - * Transform the resource into an array. - * - * @param \Illuminate\Http\Request $request - * @return array - */ + use HasProjectCoverImage; + public function toArray($request) { + $coverImage = $this->getProjectCoverImage($this->resource); + $data = [ 'name' => $this->name, 'descriptionObjetive' => $this->objectives, @@ -26,9 +25,16 @@ public function toArray($request) 'targetLandUse' => $this->land_use_types, 'landTenure' => $this->land_tenure_project_area, 'framework' => $this->framework_key, + 'cover_image' => $coverImage ? [ + 'id' => $coverImage->id, + 'url' => $coverImage->getUrl(), + 'thumbnail' => $coverImage->getUrl('thumbnail'), + 'is_cover' => $coverImage->is_cover, + 'mime_type' => $coverImage->mime_type, + ] : null, ]; - return $this->appendFilesToResource($data); + return $data; } public function getCountryLabel($slug) diff --git a/app/Models/Traits/HasProjectCoverImage.php b/app/Models/Traits/HasProjectCoverImage.php new file mode 100644 index 000000000..b6656e9f1 --- /dev/null +++ b/app/Models/Traits/HasProjectCoverImage.php @@ -0,0 +1,57 @@ + get_class($project), 'ids' => [$project->id]], + ['type' => Site::class, 'ids' => $project->sites->pluck('id')->toArray()], + ['type' => Nursery::class, 'ids' => $project->nurseries->pluck('id')->toArray()], + ['type' => ProjectReport::class, 'ids' => $project->reports->pluck('id')->toArray()], + ['type' => SiteReport::class, 'ids' => $project->siteReports->pluck('id')->toArray()], + ['type' => NurseryReport::class, 'ids' => $project->nurseryReports->pluck('id')->toArray()], + ]; + + $coverMedia = Media::where(function ($query) use ($models) { + foreach ($models as $model) { + $query->orWhere(function ($query) use ($model) { + $query->where('model_type', $model['type']) + ->whereIn('model_id', $model['ids']); + }); + } + }) + ->where('is_cover', true) + ->first(); + + if ($coverMedia) { + return $coverMedia; + } + + // If no cover image found, the latest image is sent + return Media::where(function ($query) use ($models) { + foreach ($models as $model) { + $query->orWhere(function ($query) use ($model) { + $query->where('model_type', $model['type']) + ->whereIn('model_id', $model['ids']); + }); + } + }) + ->where(function ($query) { + $query->where('mime_type', 'like', 'image/jpeg') + ->orWhere('mime_type', 'like', 'image/png'); + }) + ->latest() + ->first(); + } +} diff --git a/app/Validators/Extensions/Polygons/NotOverlapping.php b/app/Validators/Extensions/Polygons/NotOverlapping.php index d8d039696..de9862bcc 100644 --- a/app/Validators/Extensions/Polygons/NotOverlapping.php +++ b/app/Validators/Extensions/Polygons/NotOverlapping.php @@ -24,19 +24,21 @@ public static function passes($attribute, $value, $parameters, $validator): bool public static function getIntersectionData(string $polygonUuid): array { $sitePolygon = SitePolygon::forPolygonGeometry($polygonUuid)->first(); - if ($sitePolygon === null) { - return [ - 'valid' => false, - 'error' => 'Site polygon not found for the given polygon ID', - 'status' => 404, - ]; + if (! $sitePolygon) { + return ['valid' => false, 'error' => 'Site polygon not found', 'status' => 404]; } $relatedPolyIds = $sitePolygon->project->sitePolygons() ->where('poly_id', '!=', $polygonUuid) ->pluck('poly_id'); - $intersects = PolygonGeometry::join('site_polygon', 'polygon_geometry.uuid', '=', 'site_polygon.poly_id') + + $bboxFilteredPolyIds = PolygonGeometry::join('site_polygon', 'polygon_geometry.uuid', '=', 'site_polygon.poly_id') ->whereIn('polygon_geometry.uuid', $relatedPolyIds) + ->whereRaw('ST_Intersects(ST_Envelope(polygon_geometry.geom), (SELECT ST_Envelope(geom) FROM polygon_geometry WHERE uuid = ?))', [$polygonUuid]) + ->pluck('polygon_geometry.uuid'); + + $intersects = PolygonGeometry::join('site_polygon', 'polygon_geometry.uuid', '=', 'site_polygon.poly_id') + ->whereIn('polygon_geometry.uuid', $bboxFilteredPolyIds) ->select([ 'polygon_geometry.uuid', 'site_polygon.poly_name', @@ -50,28 +52,27 @@ public static function getIntersectionData(string $polygonUuid): array $mainPolygonArea = PolygonGeometry::where('uuid', $polygonUuid) ->value(DB::raw('ST_Area(geom)')); - $extra_info = []; - foreach ($intersects as $intersect) { - if ($intersect->intersects) { + + $extra_info = $intersects + ->filter(fn ($intersect) => $intersect->intersects) + ->map(function ($intersect) use ($mainPolygonArea) { $minArea = min($mainPolygonArea, $intersect->area); - if ($minArea > 0) { - $percentage = ($intersect->intersection_area / $minArea) * 100; - $percentage = round($percentage, 2); - } else { - $percentage = 100; - } - $extra_info[] = [ + $percentage = $minArea > 0 + ? round(($intersect->intersection_area / $minArea) * 100, 2) + : 100; + + return [ 'poly_uuid' => $intersect->uuid, 'poly_name' => $intersect->poly_name, 'percentage' => $percentage, 'intersectSmaller' => ($intersect->area < $mainPolygonArea), ]; - } - } - + }) + ->values() + ->toArray(); return [ - 'valid' => ! $intersects->contains('intersects', 1), + 'valid' => empty($extra_info), 'uuid' => $polygonUuid, 'project_id' => $sitePolygon->project_id, 'extra_info' => $extra_info, diff --git a/database/factories/V2/PolygonGeometryFactory.php b/database/factories/V2/PolygonGeometryFactory.php index bea9177a1..5c75a788c 100644 --- a/database/factories/V2/PolygonGeometryFactory.php +++ b/database/factories/V2/PolygonGeometryFactory.php @@ -16,11 +16,14 @@ public function definition() public function geojson(string|array $geojson) { - $geom = DB::raw("ST_GeomFromGeoJSON('$geojson')"); + if (is_array($geojson)) { + $geojson = json_encode($geojson); + } + $geomExpression = DB::raw("ST_GeomFromGeoJSON('$geojson')"); - return $this->state(function (array $attributes) use ($geom) { + return $this->state(function (array $attributes) use ($geomExpression) { return [ - 'geom' => $geom, + 'geom' => $geomExpression, ]; }); } diff --git a/database/factories/V2/Sites/SitePolygonFactory.php b/database/factories/V2/Sites/SitePolygonFactory.php index 1e8415ca5..13d595f0c 100644 --- a/database/factories/V2/Sites/SitePolygonFactory.php +++ b/database/factories/V2/Sites/SitePolygonFactory.php @@ -10,8 +10,24 @@ class SitePolygonFactory extends Factory { public function definition() { + $geojson = [ + 'type' => 'Polygon', + 'coordinates' => [ + [ + [0, 0], + [1, 0], + [1, 1], + [0, 1], + [0, 0], + ], + ], + ]; + return [ - 'poly_id' => PolygonGeometry::factory()->create()->uuid, + 'poly_id' => PolygonGeometry::factory() + ->geojson($geojson) + ->create() + ->uuid, 'site_id' => Site::factory()->create()->uuid, 'calc_area' => $this->faker->numberBetween(2.0, 50.0), ]; diff --git a/database/migrations/2024_11_26_154102_add_spatial_index_to_polygon_geometry.php b/database/migrations/2024_11_26_154102_add_spatial_index_to_polygon_geometry.php new file mode 100644 index 000000000..1a2e14336 --- /dev/null +++ b/database/migrations/2024_11_26_154102_add_spatial_index_to_polygon_geometry.php @@ -0,0 +1,30 @@ +index('poly_id', 'idx_site_polygon_poly_id'); + }); + } + + public function down(): void + { + Schema::table('site_polygon', function (Blueprint $table) { + $table->dropIndex('idx_site_polygon_poly_id'); + }); + } +};