From d101857e80c52e123719c33cec35a31a243eb832 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Tue, 23 Apr 2024 15:18:24 -0700 Subject: [PATCH 01/64] [TM-877] Checkpoint commit; the bulk of the real work here is the migration script with its current verification output. --- .../Commands/OneOff/MigrateWorkdayData.php | 152 ++++++++++++++++++ .../Commands/ReportWorkdayDiscrepancies.php | 93 +++++++++++ app/Models/V2/Workdays/Workday.php | 19 +++ app/Models/V2/Workdays/WorkdayDemographic.php | 31 ++++ ..._19_225021_create_workday_demographics.php | 34 ++++ 5 files changed, 329 insertions(+) create mode 100644 app/Console/Commands/OneOff/MigrateWorkdayData.php create mode 100644 app/Console/Commands/ReportWorkdayDiscrepancies.php create mode 100644 app/Models/V2/Workdays/WorkdayDemographic.php create mode 100644 database/migrations/2024_04_19_225021_create_workday_demographics.php diff --git a/app/Console/Commands/OneOff/MigrateWorkdayData.php b/app/Console/Commands/OneOff/MigrateWorkdayData.php new file mode 100644 index 000000000..b8a939839 --- /dev/null +++ b/app/Console/Commands/OneOff/MigrateWorkdayData.php @@ -0,0 +1,152 @@ +testCase(\App\Models\V2\Projects\ProjectReport::class, 783), + $this->testCase(\App\Models\V2\Sites\SiteReport::class, 15109), + $this->testCase(\App\Models\V2\Projects\ProjectReport::class, 784), + $this->testCase(\App\Models\V2\Sites\SiteReport::class, 13965), + $this->testCase(\App\Models\V2\Projects\ProjectReport::class, 763), + $this->testCase(\App\Models\V2\Sites\SiteReport::class, 15170), + $this->testCase(\App\Models\V2\Sites\SiteReport::class, 15400), + $this->testCase(\App\Models\V2\Sites\SiteReport::class, 13805), + $this->testCase(\App\Models\V2\Projects\ProjectReport::class, 778), + ]; + echo json_encode($result, JSON_PRETTY_PRINT); + + + // TODO (NJC): The code below will be needed when this has been updated to perform the actual migration instead + // of just testing a few isolated cases and dumping a JSON result. + // $entityTypes = Workday::select('workdayable_type')->distinct()->pluck('workdayable_type'); + // foreach ($entityTypes as $entityType) { + // $entityIds = Workday::where('workdayable_type', $entityType) + // ->select('workdayable_id') + // ->distinct() + // ->pluck('workdayable_id'); + // $count = $entityIds->count(); + // $shortName = explode_pop('\\', $entityType); + // echo "Processing $shortName: $count records\n"; + // + // foreach ($entityIds as $entityId) { + // $this->updateEntity($entityType, $entityId); + // } + // } + } + + private function testCase(string $entityType, int $entityId): array + { + return [ + 'type' => $entityType, + 'id' => $entityId, + 'uuid' => $entityType::find($entityId)->uuid, + 'mapping' => $this->updateEntityWorkdays($entityType, $entityId), + ]; + } + + private function updateEntityWorkdays(string $entityType, int $entityId): array + { + $workdayCollections = Workday::where(['workdayable_type' => $entityType, 'workdayable_id' => $entityId]) + ->get() + ->reduce(function (array $carry, Workday $workday) { + $carry[$workday['collection']][] = $workday; + + return $carry; + }, []); + + $results = []; + foreach ($workdayCollections as $collection => $workdays) { + $results[$collection] = $this->mapWorkdayCollection($workdays); + } + + return $results; + } + + private function mapWorkdayCollection(array $workdays): array + { + $results = ['original' => collect($workdays)->map(function ($workday) { + return [ + 'amount' => $workday->amount, + 'gender' => $workday->gender, + 'age' => $workday->age, + 'ethnicity' => $workday->ethnicity, + 'indigeneity' => $workday->indigeneity, + ]; + })]; + + $demographics = []; + foreach (self::DEMOGRAPHICS as $demographic) { + foreach ($workdays as $workday) { + $subType = $this->getSubtype($demographic, $workday); + $value = match ($workday[$demographic]) { + null, 'gender-undefined', 'age-undefined' => 'unknown', + default => $workday[$demographic], + }; + + $current = data_get($demographics, "$demographic.$subType.$value.amount"); + data_set($demographics, "$demographic.$subType.$value.amount", $current + $workday->amount); + } + } + + $results['new-workday-demographics'] = $demographics; + + return $results; + } + + private function getSubtype(string $demographic, Workday $workday): string + { + if ($demographic != WorkdayDemographic::ETHNICITY) { + return self::SUBTYPE_NULL; + } + + if ($workday->indigeneity != null) { + // TODO (NJC): Waiting to hear back on what to do with `decline to specify` and `unknown` rows + return $workday->indigeneity; + } + + if (Str::startsWith($workday->ethnicity, 'indigenous')) { + return 'indigenous'; + } elseif (Str::startsWith($workday->ethnicity, 'other')) { + return 'other'; + } + + // TODO (NJC): Based on what's in the DB, this can only be `decline-to-specify` or `unknown`, see TODO above + // Note that when it appears in the indigeneity column, it's `decline to specify`, but when it's in the + // ethnicity column, it's `decline-to-specify`. + return $workday->ethnicity; + } +} diff --git a/app/Console/Commands/ReportWorkdayDiscrepancies.php b/app/Console/Commands/ReportWorkdayDiscrepancies.php new file mode 100644 index 000000000..56237974b --- /dev/null +++ b/app/Console/Commands/ReportWorkdayDiscrepancies.php @@ -0,0 +1,93 @@ + [ + 'paid' => [ + Workday::COLLECTION_PROJECT_PAID_NURSERY_OPRERATIONS, + Workday::COLLECTION_PROJECT_PAID_PROJECT_MANAGEMENT, + Workday::COLLECTION_PROJECT_PAID_OTHER, + ], + 'volunteer' => [ + Workday::COLLECTION_PROJECT_VOLUNTEER_NURSERY_OPRERATIONS, + Workday::COLLECTION_PROJECT_VOLUNTEER_PROJECT_MANAGEMENT, + Workday::COLLECTION_PROJECT_VOLUNTEER_OTHER, + ], + ], + SiteReport::class => [ + 'paid' => [ + Workday::COLLECTION_SITE_PAID_SITE_ESTABLISHMENT, + Workday::COLLECTION_SITE_PAID_PLANTING, + Workday::COLLECTION_SITE_PAID_SITE_MAINTENANCE, + Workday::COLLECTION_SITE_PAID_SITE_MONITORING, + Workday::COLLECTION_SITE_PAID_OTHER, + ], + 'volunteer' => [ + Workday::COLLECTION_SITE_VOLUNTEER_SITE_ESTABLISHMENT, + Workday::COLLECTION_SITE_VOLUNTEER_PLANTING, + Workday::COLLECTION_SITE_VOLUNTEER_SITE_MAINTENANCE, + Workday::COLLECTION_SITE_VOLUNTEER_SITE_MONITORING, + Workday::COLLECTION_SITE_VOLUNTEER_OTHER, + ], + ], + ]; + + /** + * Execute the console command. + */ + public function handle() + { + echo "Model Type,Model UUID,Aggregate Paid Total,Disaggregate Paid Total,Aggregate Volunteer Total,Disaggregate Volunteer Total\n"; + foreach (self::PROPERTIES as $model => $propertySets) { + $model::where('status', 'approved')->chunkById( + 100, + function ($reports) use ($propertySets) { + foreach ($reports as $report) { + $aggregate_paid = (int)$report->workdays_paid; + $aggregate_volunteer = (int)$report->workdays_volunteer; + + $modelType = get_class($report); + $query = Workday::where([ + 'workdayable_type' => $modelType, + 'workdayable_id' => $report->id, + ]); + if ($query->count() == 0) { + // Skip reports that have no associated workday rows. + continue; + } + + $disaggregate_paid = (int)(clone $query)->whereIn('collection', $propertySets['paid'])->sum('amount'); + $disaggregate_volunteer = (int)(clone $query)->whereIn('collection', $propertySets['volunteer'])->sum('amount'); + + if ($aggregate_paid != $disaggregate_paid || $aggregate_volunteer != $disaggregate_volunteer) { + $shortType = explode_pop('\\', $modelType); + echo "$shortType,$report->uuid,$aggregate_paid,$disaggregate_paid,$aggregate_volunteer,$disaggregate_volunteer\n"; + } + } + } + ); + } + } +} diff --git a/app/Models/V2/Workdays/Workday.php b/app/Models/V2/Workdays/Workday.php index bb1c3e0b1..f48f900c7 100644 --- a/app/Models/V2/Workdays/Workday.php +++ b/app/Models/V2/Workdays/Workday.php @@ -6,6 +6,7 @@ use App\Models\Traits\HasUuid; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; class Workday extends Model @@ -83,6 +84,24 @@ public function getRouteKeyName() return 'uuid'; } + public function genderDemographics(): HasMany + { + return $this->hasMany(WorkdayDemographic::class) + ->where('demographic_type', WorkdayDemographic::GENDER); + } + + public function ageDemographics(): HasMany + { + return $this->hasMany(WorkdayDemographic::class) + ->where('demographic_type', WorkdayDemographic::AGE); + } + + public function ethnicityDemographics(): HasMany + { + return $this->hasMany(WorkdayDemographic::class) + ->where('demographic_type', WorkdayDemographic::ETHNICITY); + } + public function getReadableCollectionAttribute(): ?string { if (empty($this->collection)) { diff --git a/app/Models/V2/Workdays/WorkdayDemographic.php b/app/Models/V2/Workdays/WorkdayDemographic.php new file mode 100644 index 000000000..e32a6ec06 --- /dev/null +++ b/app/Models/V2/Workdays/WorkdayDemographic.php @@ -0,0 +1,31 @@ +belongsTo(Workday::class); + } +} diff --git a/database/migrations/2024_04_19_225021_create_workday_demographics.php b/database/migrations/2024_04_19_225021_create_workday_demographics.php new file mode 100644 index 000000000..3f7421093 --- /dev/null +++ b/database/migrations/2024_04_19_225021_create_workday_demographics.php @@ -0,0 +1,34 @@ +id(); + $table->foreignIdFor(Workday::class); + $table->string('demographic_type'); + $table->string('demographic_subtype')->nullable(); + $table->string('demographic_value'); + $table->integer('amount'); + + $table->softDeletes(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('workday_demographics'); + } +}; From 68cd9193463864951d1c313b643f89c6a30829d7 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Thu, 25 Apr 2024 14:52:21 -0700 Subject: [PATCH 02/64] [TM-877] A couple of tweaks based on feedback from CI/PPC. --- app/Console/Commands/OneOff/MigrateWorkdayData.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/app/Console/Commands/OneOff/MigrateWorkdayData.php b/app/Console/Commands/OneOff/MigrateWorkdayData.php index b8a939839..f9f13c7c8 100644 --- a/app/Console/Commands/OneOff/MigrateWorkdayData.php +++ b/app/Console/Commands/OneOff/MigrateWorkdayData.php @@ -30,6 +30,7 @@ class MigrateWorkdayData extends Command ]; private const SUBTYPE_NULL = 'subtype-null'; + private const VALUE_NULL = 'value-null'; /** * Execute the console command. @@ -116,6 +117,11 @@ private function mapWorkdayCollection(array $workdays): array null, 'gender-undefined', 'age-undefined' => 'unknown', default => $workday[$demographic], }; + if ($subType == 'unknown' && strcasecmp($value, 'unknown') == 0) { + // We only get an unknown subtype when we're working on ethnicity. If the value is also unknown in + // this case, we want to leave it null. + $value = self::VALUE_NULL; + } $current = data_get($demographics, "$demographic.$subType.$value.amount"); data_set($demographics, "$demographic.$subType.$value.amount", $current + $workday->amount); @@ -133,8 +139,7 @@ private function getSubtype(string $demographic, Workday $workday): string return self::SUBTYPE_NULL; } - if ($workday->indigeneity != null) { - // TODO (NJC): Waiting to hear back on what to do with `decline to specify` and `unknown` rows + if ($workday->indigeneity != null && $workday->indigeneity != 'decline to specify') { return $workday->indigeneity; } @@ -144,9 +149,6 @@ private function getSubtype(string $demographic, Workday $workday): string return 'other'; } - // TODO (NJC): Based on what's in the DB, this can only be `decline-to-specify` or `unknown`, see TODO above - // Note that when it appears in the indigeneity column, it's `decline to specify`, but when it's in the - // ethnicity column, it's `decline-to-specify`. - return $workday->ethnicity; + return 'unknown'; } } From d2207df1f0609862eec1fa8be0e906fb42da10b1 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Thu, 25 Apr 2024 15:55:23 -0700 Subject: [PATCH 03/64] [TM-877] Workdays migration ready. --- .../Commands/OneOff/MigrateWorkdayData.php | 114 ++++++++---------- app/Models/V2/Workdays/Workday.php | 7 +- app/Models/V2/Workdays/WorkdayDemographic.php | 5 +- ..._19_225021_create_workday_demographics.php | 6 +- ...4_04_25_222046_add_migrated_to_workday.php | 27 +++++ 5 files changed, 86 insertions(+), 73 deletions(-) create mode 100644 database/migrations/2024_04_25_222046_add_migrated_to_workday.php diff --git a/app/Console/Commands/OneOff/MigrateWorkdayData.php b/app/Console/Commands/OneOff/MigrateWorkdayData.php index f9f13c7c8..51b6089a6 100644 --- a/app/Console/Commands/OneOff/MigrateWorkdayData.php +++ b/app/Console/Commands/OneOff/MigrateWorkdayData.php @@ -30,56 +30,30 @@ class MigrateWorkdayData extends Command ]; private const SUBTYPE_NULL = 'subtype-null'; - private const VALUE_NULL = 'value-null'; + private const NAME_NULL = 'name-null'; /** * Execute the console command. */ public function handle() { - $result = [ - $this->testCase(\App\Models\V2\Projects\ProjectReport::class, 783), - $this->testCase(\App\Models\V2\Sites\SiteReport::class, 15109), - $this->testCase(\App\Models\V2\Projects\ProjectReport::class, 784), - $this->testCase(\App\Models\V2\Sites\SiteReport::class, 13965), - $this->testCase(\App\Models\V2\Projects\ProjectReport::class, 763), - $this->testCase(\App\Models\V2\Sites\SiteReport::class, 15170), - $this->testCase(\App\Models\V2\Sites\SiteReport::class, 15400), - $this->testCase(\App\Models\V2\Sites\SiteReport::class, 13805), - $this->testCase(\App\Models\V2\Projects\ProjectReport::class, 778), - ]; - echo json_encode($result, JSON_PRETTY_PRINT); - - - // TODO (NJC): The code below will be needed when this has been updated to perform the actual migration instead - // of just testing a few isolated cases and dumping a JSON result. - // $entityTypes = Workday::select('workdayable_type')->distinct()->pluck('workdayable_type'); - // foreach ($entityTypes as $entityType) { - // $entityIds = Workday::where('workdayable_type', $entityType) - // ->select('workdayable_id') - // ->distinct() - // ->pluck('workdayable_id'); - // $count = $entityIds->count(); - // $shortName = explode_pop('\\', $entityType); - // echo "Processing $shortName: $count records\n"; - // - // foreach ($entityIds as $entityId) { - // $this->updateEntity($entityType, $entityId); - // } - // } - } - - private function testCase(string $entityType, int $entityId): array - { - return [ - 'type' => $entityType, - 'id' => $entityId, - 'uuid' => $entityType::find($entityId)->uuid, - 'mapping' => $this->updateEntityWorkdays($entityType, $entityId), - ]; + $entityTypes = Workday::select('workdayable_type')->distinct()->pluck('workdayable_type'); + foreach ($entityTypes as $entityType) { + $entityIds = Workday::where('workdayable_type', $entityType) + ->select('workdayable_id') + ->distinct() + ->pluck('workdayable_id'); + $count = $entityIds->count(); + $shortName = explode_pop('\\', $entityType); + echo "Processing $shortName: $count records\n"; + + foreach ($entityIds as $entityId) { + $this->updateEntityWorkdays($entityType, $entityId); + } + } } - private function updateEntityWorkdays(string $entityType, int $entityId): array + private function updateEntityWorkdays(string $entityType, int $entityId): void { $workdayCollections = Workday::where(['workdayable_type' => $entityType, 'workdayable_id' => $entityId]) ->get() @@ -89,48 +63,58 @@ private function updateEntityWorkdays(string $entityType, int $entityId): array return $carry; }, []); - $results = []; foreach ($workdayCollections as $collection => $workdays) { - $results[$collection] = $this->mapWorkdayCollection($workdays); - } + $mapping = $this->mapWorkdayCollection($workdays); + $framework_key = $workdays[0]->framework_key; + + $workday = Workday::create([ + 'workdayable_type' => $entityType, + 'workdayable_id' => $entityId, + 'framework_key' => $framework_key, + 'collection' => $collection, + ]); + foreach ($mapping as $demographic => $subTypes) { + foreach ($subTypes as $subType => $names) { + foreach ($names as $name => $amount) { + WorkdayDemographic::create([ + 'workday_id' => $workday->id, + 'type' => $demographic, + 'subtype' => $subType == self::SUBTYPE_NULL ? null : $subType, + 'name' => $name == self::NAME_NULL ? null : $name, + 'amount' => $amount, + ]); + } + } + } - return $results; + $workdayIds = collect($workdays)->map(fn ($workday) => $workday->id)->all(); + Workday::whereIn('id', $workdayIds)->update(['migrated_to_demographics' => true]); + Workday::whereIn('id', $workdayIds)->delete(); + } } private function mapWorkdayCollection(array $workdays): array { - $results = ['original' => collect($workdays)->map(function ($workday) { - return [ - 'amount' => $workday->amount, - 'gender' => $workday->gender, - 'age' => $workday->age, - 'ethnicity' => $workday->ethnicity, - 'indigeneity' => $workday->indigeneity, - ]; - })]; - $demographics = []; foreach (self::DEMOGRAPHICS as $demographic) { foreach ($workdays as $workday) { $subType = $this->getSubtype($demographic, $workday); - $value = match ($workday[$demographic]) { - null, 'gender-undefined', 'age-undefined' => 'unknown', + $name = match ($workday[$demographic]) { + null, 'gender-undefined', 'age-undefined', 'decline-to-specify' => 'unknown', default => $workday[$demographic], }; - if ($subType == 'unknown' && strcasecmp($value, 'unknown') == 0) { + if ($subType == 'unknown' && strcasecmp($name, 'unknown') == 0) { // We only get an unknown subtype when we're working on ethnicity. If the value is also unknown in // this case, we want to leave it null. - $value = self::VALUE_NULL; + $name = self::NAME_NULL; } - $current = data_get($demographics, "$demographic.$subType.$value.amount"); - data_set($demographics, "$demographic.$subType.$value.amount", $current + $workday->amount); + $current = data_get($demographics, "$demographic.$subType.$name"); + data_set($demographics, "$demographic.$subType.$name", $current + $workday->amount); } } - $results['new-workday-demographics'] = $demographics; - - return $results; + return $demographics; } private function getSubtype(string $demographic, Workday $workday): string diff --git a/app/Models/V2/Workdays/Workday.php b/app/Models/V2/Workdays/Workday.php index f48f900c7..1f5e0103d 100644 --- a/app/Models/V2/Workdays/Workday.php +++ b/app/Models/V2/Workdays/Workday.php @@ -32,6 +32,7 @@ class Workday extends Model 'age', 'ethnicity', 'indigeneity', + 'migrated_to_demographics', ]; public const COLLECTION_PROJECT_PAID_NURSERY_OPRERATIONS = 'paid-nursery-operations'; @@ -87,19 +88,19 @@ public function getRouteKeyName() public function genderDemographics(): HasMany { return $this->hasMany(WorkdayDemographic::class) - ->where('demographic_type', WorkdayDemographic::GENDER); + ->where('type', WorkdayDemographic::GENDER); } public function ageDemographics(): HasMany { return $this->hasMany(WorkdayDemographic::class) - ->where('demographic_type', WorkdayDemographic::AGE); + ->where('type', WorkdayDemographic::AGE); } public function ethnicityDemographics(): HasMany { return $this->hasMany(WorkdayDemographic::class) - ->where('demographic_type', WorkdayDemographic::ETHNICITY); + ->where('type', WorkdayDemographic::ETHNICITY); } public function getReadableCollectionAttribute(): ?string diff --git a/app/Models/V2/Workdays/WorkdayDemographic.php b/app/Models/V2/Workdays/WorkdayDemographic.php index e32a6ec06..2d6308544 100644 --- a/app/Models/V2/Workdays/WorkdayDemographic.php +++ b/app/Models/V2/Workdays/WorkdayDemographic.php @@ -14,8 +14,9 @@ class WorkdayDemographic extends Model protected $fillable = [ 'workday_id', - 'demographic_type', - 'demographic_value', + 'type', + 'subtype', + 'name', 'amount', ]; diff --git a/database/migrations/2024_04_19_225021_create_workday_demographics.php b/database/migrations/2024_04_19_225021_create_workday_demographics.php index 3f7421093..e3d742fb5 100644 --- a/database/migrations/2024_04_19_225021_create_workday_demographics.php +++ b/database/migrations/2024_04_19_225021_create_workday_demographics.php @@ -14,9 +14,9 @@ public function up(): void Schema::create('workday_demographics', function (Blueprint $table) { $table->id(); $table->foreignIdFor(Workday::class); - $table->string('demographic_type'); - $table->string('demographic_subtype')->nullable(); - $table->string('demographic_value'); + $table->string('type'); + $table->string('subtype')->nullable(); + $table->string('name')->nullable(); $table->integer('amount'); $table->softDeletes(); diff --git a/database/migrations/2024_04_25_222046_add_migrated_to_workday.php b/database/migrations/2024_04_25_222046_add_migrated_to_workday.php new file mode 100644 index 000000000..5c3b0a230 --- /dev/null +++ b/database/migrations/2024_04_25_222046_add_migrated_to_workday.php @@ -0,0 +1,27 @@ +boolean('migrated_to_demographics')->default(false); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('v2_workdays', function (Blueprint $table) { + $table->dropColumn('migrated_to_demographics'); + }); + } +}; From d07dfb827d580937fa762876c9c7b5769112ebad Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Mon, 29 Apr 2024 13:52:17 -0700 Subject: [PATCH 04/64] [TM-799] Convert bounds validation to a laravel validator. --- .../TerrafundCreateGeometryController.php | 40 ++++------------ app/Validators/Extensions/FeatureBounds.php | 47 +++++++++++++++++++ app/Validators/SitePolygonValidator.php | 11 +++++ 3 files changed, 67 insertions(+), 31 deletions(-) create mode 100644 app/Validators/Extensions/FeatureBounds.php create mode 100644 app/Validators/SitePolygonValidator.php diff --git a/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php b/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php index c346905f5..c855ddcfd 100644 --- a/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php +++ b/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php @@ -8,11 +8,13 @@ use App\Models\V2\Sites\CriteriaSite; use App\Models\V2\Sites\SitePolygon; use App\Models\V2\WorldCountryGeneralized; +use App\Validators\SitePolygonValidator; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; +use Illuminate\Validation\ValidationException; use Symfony\Component\Process\Process; class TerrafundCreateGeometryController extends Controller @@ -35,7 +37,7 @@ public function processGeometry(string $uuid) public function storeGeometry(Request $request) { $request->validate([ - 'geometry' => 'required|json', + 'geometry' => 'required|json|geo_json', ]); $geometry = json_decode($request->input('geometry')); @@ -48,26 +50,6 @@ public function storeGeometry(Request $request) return response()->json(['uuid' => $polygonGeometry->uuid], 200); } - private function validatePolygonBounds(array $geometry): bool - { - if ($geometry['type'] !== 'Polygon') { - return false; - } - $coordinates = $geometry['coordinates'][0]; - foreach ($coordinates as $coordinate) { - $latitude = $coordinate[1]; - $longitude = $coordinate[0]; - if ($latitude < -90 || $latitude > 90) { - return false; - } - if ($longitude < -180 || $longitude > 180) { - return false; - } - - return true; - } - } - private function insertSinglePolygon(array $geometry, int $srid) { try { @@ -96,32 +78,28 @@ private function insertSinglePolygon(array $geometry, int $srid) } } + /** + * @throws ValidationException + */ public function insertGeojsonToDB(string $geojsonFilename) { $srid = 4326; $geojsonData = Storage::get("public/geojson_files/{$geojsonFilename}"); $geojson = json_decode($geojsonData, true); - if (! isset($geojson['features'])) { - return ['error' => 'GeoJSON file does not contain features']; - } + SitePolygonValidator::validate('FEATURE_BOUNDS', $geojson); + $uuids = []; foreach ($geojson['features'] as $feature) { if ($feature['geometry']['type'] === 'Polygon') { - if (! $this->validatePolygonBounds($feature['geometry'])) { - return ['error' => 'Invalid polygon bounds']; - } $data = $this->insertSinglePolygon($feature['geometry'], $srid); $uuids[] = $data['uuid']; $returnSite = $this->insertSitePolygon($data['uuid'], $feature['properties'], $data['area']); if ($returnSite) { - Log::info($returnSite) ; + Log::info($returnSite); } } elseif ($feature['geometry']['type'] === 'MultiPolygon') { foreach ($feature['geometry']['coordinates'] as $polygon) { $singlePolygon = ['type' => 'Polygon', 'coordinates' => $polygon]; - if (! $this->validatePolygonBounds($singlePolygon)) { - return ['error' => 'Invalid polygon bounds']; - } $data = $this->insertSinglePolygon($singlePolygon, $srid); $uuids[] = $data['uuid']; $returnSite = $this->insertSitePolygon($data['uuid'], $feature['properties'], $data['area']); diff --git a/app/Validators/Extensions/FeatureBounds.php b/app/Validators/Extensions/FeatureBounds.php new file mode 100644 index 000000000..cb8b40d03 --- /dev/null +++ b/app/Validators/Extensions/FeatureBounds.php @@ -0,0 +1,47 @@ + ':attribute'], + 'The :attribute field must have valid feature polygon bounds.', + ]; + + public static function passes($attribute, $value, $parameters, $validator): bool + { + $type = data_get($value, 'geometry.type'); + if ($type === 'Polygon') { + return self::hasValidPolygonBounds(data_get($value, 'geometry.coordinates')); + } else if ($type === 'MultiPolygon') { + foreach (data_get($value, 'geometry.coordinates') as $coordinates) { + if (! self::hasValidPolygonBounds($coordinates)) { + return false; + } + } + } + + return true; + } + + private static function hasValidPolygonBounds($coordinates): bool + { + foreach ($coordinates as $coordinate) { + $latitude = $coordinate[1]; + $longitude = $coordinate[0]; + if ($latitude < -90 || $latitude > 90) { + return false; + } + if ($longitude < -180 || $longitude > 180) { + return false; + } + } + + return true; + } +} diff --git a/app/Validators/SitePolygonValidator.php b/app/Validators/SitePolygonValidator.php new file mode 100644 index 000000000..7a1681eb3 --- /dev/null +++ b/app/Validators/SitePolygonValidator.php @@ -0,0 +1,11 @@ + 'required|array', + 'features.*' => 'feature_bounds' + ]; +} From 4750c00cae378ace51ea11874c25d3471956ea50 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Mon, 29 Apr 2024 13:53:09 -0700 Subject: [PATCH 05/64] [TM-799] Start up a controller for GH polygon upload. --- .../Sites/StoreBulkSitePolygonsController.php | 18 ++++++++++++++++++ app/Policies/V2/Sites/SitePolicy.php | 5 +++++ routes/api_v2.php | 16 +++++++++------- 3 files changed, 32 insertions(+), 7 deletions(-) create mode 100644 app/Http/Controllers/V2/Sites/StoreBulkSitePolygonsController.php diff --git a/app/Http/Controllers/V2/Sites/StoreBulkSitePolygonsController.php b/app/Http/Controllers/V2/Sites/StoreBulkSitePolygonsController.php new file mode 100644 index 000000000..a6abd1754 --- /dev/null +++ b/app/Http/Controllers/V2/Sites/StoreBulkSitePolygonsController.php @@ -0,0 +1,18 @@ +authorize('uploadPolygons', $site); + + + } +} diff --git a/app/Policies/V2/Sites/SitePolicy.php b/app/Policies/V2/Sites/SitePolicy.php index 9f8370a3c..153f6c065 100644 --- a/app/Policies/V2/Sites/SitePolicy.php +++ b/app/Policies/V2/Sites/SitePolicy.php @@ -102,6 +102,11 @@ public function uploadFiles(?User $user, ?Site $site = null): bool return false; } + public function uploadPolygons(?User $user, ?Site $site): bool + { + return $site != null && $user->can('polygons-manage'); + } + public function export(?User $user, ?Form $form = null, ?Project $project = null): bool { return $user->can('framework-' . $form->framework_key) or diff --git a/routes/api_v2.php b/routes/api_v2.php index 426143a3e..a97fa0deb 100644 --- a/routes/api_v2.php +++ b/routes/api_v2.php @@ -156,6 +156,7 @@ use App\Http\Controllers\V2\Sites\Monitoring\AdminUpdateSiteMonitoringController; use App\Http\Controllers\V2\Sites\Monitoring\ViewSiteMonitoringController; use App\Http\Controllers\V2\Sites\SoftDeleteSiteController; +use App\Http\Controllers\V2\Sites\StoreBulkSitePolygonsController; use App\Http\Controllers\V2\Sites\ViewASitesMonitoringsController; use App\Http\Controllers\V2\Stages\DeleteStageController; use App\Http\Controllers\V2\Stages\IndexStageController; @@ -547,13 +548,14 @@ Route::get('/{projectReport}/image/locations', ProjectReportImageLocationsController::class); }); -Route::prefix('sites')->group(function () { - Route::get('/{site}/files', ViewSiteGalleryController::class); - Route::get('/{site}/reports', SiteReportsViaSiteController::class); - Route::get('/{site}/monitorings', ViewASitesMonitoringsController::class); - Route::get('/{site}/image/locations', SiteImageLocationsController::class); - Route::delete('/{site}', SoftDeleteSiteController::class); - Route::get('/{site}/export', ExportAllSiteDataAsProjectDeveloperController::class); +Route::prefix('sites/{site}')->group(function () { + Route::get('/files', ViewSiteGalleryController::class); + Route::get('/reports', SiteReportsViaSiteController::class); + Route::get('/monitorings', ViewASitesMonitoringsController::class); + Route::get('/image/locations', SiteImageLocationsController::class); + Route::delete('/', SoftDeleteSiteController::class); + Route::get('/export', ExportAllSiteDataAsProjectDeveloperController::class); + Route::post('/polygons', StoreBulkSitePolygonsController::class); }); Route::prefix('project-monitorings')->group(function () { From daf12b77dd3e8112f1701910273bc50a727e07d1 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Tue, 30 Apr 2024 15:10:53 -0700 Subject: [PATCH 06/64] [TM-799] Offload creation of geojson and criteria models to a service. --- .../TerrafundCreateGeometryController.php | 172 ++---------------- app/Services/PolygonService.php | 136 ++++++++++++++ app/Validators/Extensions/FeatureBounds.php | 2 +- app/Validators/SitePolygonValidator.php | 22 ++- app/Validators/Validator.php | 23 ++- 5 files changed, 198 insertions(+), 157 deletions(-) create mode 100644 app/Services/PolygonService.php diff --git a/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php b/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php index c855ddcfd..b0259ca9b 100644 --- a/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php +++ b/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php @@ -8,8 +8,9 @@ use App\Models\V2\Sites\CriteriaSite; use App\Models\V2\Sites\SitePolygon; use App\Models\V2\WorldCountryGeneralized; -use App\Validators\SitePolygonValidator; +use App\Services\PolygonService; use Illuminate\Http\Request; +use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Storage; @@ -50,85 +51,21 @@ public function storeGeometry(Request $request) return response()->json(['uuid' => $polygonGeometry->uuid], 200); } - private function insertSinglePolygon(array $geometry, int $srid) - { - try { - // Convert geometry to GeoJSON string with specified SRID - $geojson = json_encode(['type' => 'Feature', 'geometry' => $geometry, 'crs' => ['type' => 'name', 'properties' => ['name' => "EPSG:$srid"]]]); - - // Insert GeoJSON data into the database - $geom = DB::raw("ST_GeomFromGeoJSON('$geojson')"); - $areaSqDegrees = DB::selectOne("SELECT ST_Area(ST_GeomFromGeoJSON('$geojson')) AS area")->area; - $latitude = DB::selectOne("SELECT ST_Y(ST_Centroid(ST_GeomFromGeoJSON('$geojson'))) AS latitude")->latitude; - // 111320 is the length of one degree of latitude in meters at the equator - $unitLatitude = 111320; - $areaSqMeters = $areaSqDegrees * pow($unitLatitude * cos(deg2rad($latitude)), 2); - - $areaHectares = $areaSqMeters / 10000; - - $polygonGeometry = PolygonGeometry::create([ - 'geom' => $geom, - ]); - - return ['uuid' => $polygonGeometry->uuid, 'id' => $polygonGeometry->id, 'area' => $areaHectares]; - } catch (\Exception $e) { - echo $e; - - return $e->getMessage(); - } - } - /** * @throws ValidationException */ public function insertGeojsonToDB(string $geojsonFilename) { - $srid = 4326; $geojsonData = Storage::get("public/geojson_files/{$geojsonFilename}"); $geojson = json_decode($geojsonData, true); - SitePolygonValidator::validate('FEATURE_BOUNDS', $geojson); - - $uuids = []; - foreach ($geojson['features'] as $feature) { - if ($feature['geometry']['type'] === 'Polygon') { - $data = $this->insertSinglePolygon($feature['geometry'], $srid); - $uuids[] = $data['uuid']; - $returnSite = $this->insertSitePolygon($data['uuid'], $feature['properties'], $data['area']); - if ($returnSite) { - Log::info($returnSite); - } - } elseif ($feature['geometry']['type'] === 'MultiPolygon') { - foreach ($feature['geometry']['coordinates'] as $polygon) { - $singlePolygon = ['type' => 'Polygon', 'coordinates' => $polygon]; - $data = $this->insertSinglePolygon($singlePolygon, $srid); - $uuids[] = $data['uuid']; - $returnSite = $this->insertSitePolygon($data['uuid'], $feature['properties'], $data['area']); - if ($returnSite) { - Log::info($returnSite) ; - } - } - } - } - - return $uuids; - } - - private function validateSchema(array $properties, array $fields): bool - { - foreach ($fields as $field) { - if (! array_key_exists($field, $properties)) { - return false; - } - } - return true; + return App::make(PolygonService::class)->createGeojsonModels($geojson); } public function validateDataInDB(Request $request) { $polygonUuid = $request->input('uuid'); $fieldsToValidate = ['poly_name', 'plantstart', 'plantend', 'practice', 'target_sys', 'distr', 'num_trees']; - $DATA_CRITERIA_ID = 14; // Check if the polygon with the specified poly_id exists $polygonExists = SitePolygon::where('poly_id', $polygonUuid) ->exists(); @@ -150,71 +87,18 @@ public function validateDataInDB(Request $request) } }) ->first(); - $this->insertCriteriaSite($polygonUuid, $DATA_CRITERIA_ID, false); + $service = App::make(PolygonService::class); + $service->createCriteriaSite($polygonUuid, PolygonService::DATA_CRITERIA_ID, false); if ($sitePolygonData) { return response()->json(['valid' => false, 'message' => 'Some attributes of the site polygon are invalid.']); } $valid = true; - $this->insertCriteriaSite($polygonUuid, $DATA_CRITERIA_ID, $valid); + $service->createCriteriaSite($polygonUuid, PolygonService::DATA_CRITERIA_ID, $valid); return response()->json(['valid' => true]); } - private function validateData(array $properties, array $fields): bool - { - foreach ($fields as $field) { - $value = $properties[$field]; - if ($value === null || strtoupper($value) === 'NULL' || $value === '') { - return false; - } - } - - return true; - } - - private function insertSitePolygon(string $polygonUuid, array $properties, float $area) - { - try { - $fieldsToValidate = ['poly_name', 'plantstart', 'plantend', 'practice', 'target_sys', 'distr', 'num_trees']; - $SCHEMA_CRITERIA_ID = 13; - $validSchema = true; - $DATA_CRITERIA_ID = 14; - $validData = true; - if (! $this->validateSchema($properties, $fieldsToValidate)) { - $validSchema = false; - $validData = false; - } elseif (! $this->validateData($properties, $fieldsToValidate)) { - $validData = false; - } - $insertionSchemaSuccess = $this->insertCriteriaSite($polygonUuid, $SCHEMA_CRITERIA_ID, $validSchema); - $insertionDataSuccess = $this->insertCriteriaSite($polygonUuid, $DATA_CRITERIA_ID, $validData); - - $sitePolygon = new SitePolygon(); - $sitePolygon->project_id = $properties['project_id'] ?? null; - $sitePolygon->proj_name = $properties['proj_name'] ?? null; - $sitePolygon->org_name = $properties['org_name'] ?? null; - $sitePolygon->country = $properties['country'] ?? null; - $sitePolygon->poly_id = $polygonUuid ?? null; - $sitePolygon->poly_name = $properties['poly_name'] ?? null; - $sitePolygon->site_id = $properties['site_id'] ?? null; - $sitePolygon->site_name = $properties['site_name'] ?? null; - $sitePolygon->poly_label = $properties['poly_label'] ?? null; - $sitePolygon->plantstart = ! empty($properties['plantstart']) ? $properties['plantstart'] : null; - $sitePolygon->plantend = ! empty($properties['plantend']) ? $properties['plantend'] : null; - $sitePolygon->practice = $properties['practice'] ?? null; - $sitePolygon->target_sys = $properties['target_sys'] ?? null; - $sitePolygon->distr = $properties['distr'] ?? null; - $sitePolygon->num_trees = $properties['num_trees'] ?? null; - $sitePolygon->est_area = $area ?? null; - $sitePolygon->save(); - - return null; - } catch (\Exception $e) { - return $e->getMessage(); - } - } - public function getGeometryProperties(string $geojsonFilename) { $geojsonData = Storage::get("public/geojson_files/{$geojsonFilename}"); @@ -338,9 +222,9 @@ public function checkSelfIntersection(Request $request) } $isSimple = DB::selectOne('SELECT ST_IsSimple(geom) AS is_simple FROM polygon_geometry WHERE uuid = :uuid', ['uuid' => $uuid])->is_simple; - $SELF_CRITERIA_ID = 4; $message = $isSimple ? 'The geometry is valid' : 'The geometry has self-intersections'; - $insertionSuccess = $this->insertCriteriaSite($uuid, $SELF_CRITERIA_ID, $isSimple); + $insertionSuccess = App::make(PolygonService::class) + ->createCriteriaSite($uuid, PolygonService::SELF_CRITERIA_ID, $isSimple); return response()->json(['selfintersects' => $message, 'geometry_id' => $geometry->id, 'insertion_success' => $insertionSuccess, 'valid' => $isSimple ? true : false], 200); } @@ -389,22 +273,6 @@ public function detectSpikes($geometry) return $spikes; } - public function insertCriteriaSite($polygonId, $criteriaId, $valid) - { - $criteriaSite = new CriteriaSite(); - $criteriaSite->polygon_id = $polygonId; - $criteriaSite->criteria_id = $criteriaId; - $criteriaSite->valid = $valid; - - try { - $criteriaSite->save(); - - return true; - } catch (\Exception $e) { - return $e->getMessage(); - } - } - public function checkBoundarySegments(Request $request) { $uuid = $request->query('uuid'); @@ -416,9 +284,9 @@ public function checkBoundarySegments(Request $request) $geojson = DB::selectOne('SELECT ST_AsGeoJSON(geom) AS geojson FROM polygon_geometry WHERE uuid = :uuid', ['uuid' => $uuid])->geojson; $geojsonArray = json_decode($geojson, true); $spikes = $this->detectSpikes($geojsonArray); - $SPIKE_CRITERIA_ID = 8; $valid = count($spikes) === 0; - $insertionSuccess = $this->insertCriteriaSite($uuid, $SPIKE_CRITERIA_ID, $valid); + $insertionSuccess = App::make(PolygonService::class) + ->createCriteriaSite($uuid, PolygonService::SPIKE_CRITERIA_ID, $valid); return response()->json(['spikes' => $spikes, 'geometry_id' => $uuid, 'insertion_success' => $insertionSuccess, 'valid' => $valid], 200); } @@ -436,9 +304,9 @@ public function validatePolygonSize(Request $request) $areaSqDegrees = $areaAndLatitude->area; $latitude = $areaAndLatitude->latitude; $areaSqMeters = $areaSqDegrees * pow(111320 * cos(deg2rad($latitude)), 2); - $SIZE_CRITERIA_ID = 6; $valid = $areaSqMeters <= 10000000; - $insertionSuccess = $this->insertCriteriaSite($uuid, $SIZE_CRITERIA_ID, $valid); + $insertionSuccess = App::make(PolygonService::class) + ->createCriteriaSite($uuid, PolygonService::SIZE_CRITERIA_ID, $valid); return response()->json([ 'area_hectares' => $areaSqMeters / 10000, // Convert to hectares @@ -492,8 +360,8 @@ public function checkWithinCountry(Request $request) $insideThreshold = 75; $insideViolation = $insidePercentage < $insideThreshold; - $WITHIN_COUNTRY_CRITERIA_ID = 7; - $insertionSuccess = $this->insertCriteriaSite($polygonUuid, $WITHIN_COUNTRY_CRITERIA_ID, ! $insideViolation); + $insertionSuccess = App::make(PolygonService::class) + ->createCriteraSite($polygonUuid, PolygonService::WITHIN_COUNTRY_CRITERIA_ID, ! $insideViolation); return response()->json([ 'country_name' => $countryName, @@ -516,8 +384,8 @@ public function getGeometryType(Request $request) if ($result) { $geometryType = $result->geometry_type; $valid = $geometryType === 'POLYGON'; - $GEOMETRY_TYPE_CRITERIA_ID = 10; - $insertionSuccess = $this->insertCriteriaSite($uuid, $GEOMETRY_TYPE_CRITERIA_ID, $valid); + $insertionSuccess = App::make(PolygonService::class) + ->createCriteriaSite($uuid, PolygonService::GEOMETRY_TYPE_CRITERIA_ID, $valid); return response()->json(['uuid' => $uuid, 'geometry_type' => $geometryType, 'valid' => $valid, 'insertion_success' => $insertionSuccess]); } else { @@ -610,8 +478,8 @@ public function validateOverlapping(Request $request) $intersects = in_array(1, $intersects->toArray()); $valid = ! $intersects; - $OVERLAPPING_CRITERIA_ID = 3; - $insertionSuccess = $this->insertCriteriaSite($uuid, $OVERLAPPING_CRITERIA_ID, $valid); + $insertionSuccess = App::make(PolygonService::class) + ->createCriteriaSite($uuid, PolygonService::OVERLAPPING_CRITERIA_ID, $valid); return response()->json(['intersects' => $intersects, 'project_id' => $projectId, 'uuid' => $uuid, 'valid' => $valid, 'creteria_succes' => $insertionSuccess], 200); } @@ -648,8 +516,8 @@ public function validateEstimatedArea(Request $request) if ($sumEstArea >= $lowerBound && $sumEstArea <= $upperBound) { $valid = true; } - $ESTIMATED_AREA_CRITERIA_ID = 12; - $insertionSuccess = $this->insertCriteriaSite($uuid, $ESTIMATED_AREA_CRITERIA_ID, $valid); + $insertionSuccess = App::make(PolygonService::class) + ->createCriteriaSite($uuid, PolygonService::ESTIMATED_AREA_CRITERIA_ID, $valid); return response()->json(['valid' => $valid, 'sum_area_project' => $sumEstArea, 'total_area_project' => $totalHectaresRestoredGoal, 'insertionSuccess' => $insertionSuccess], 200); } diff --git a/app/Services/PolygonService.php b/app/Services/PolygonService.php new file mode 100644 index 000000000..f13bfcb89 --- /dev/null +++ b/app/Services/PolygonService.php @@ -0,0 +1,136 @@ +insertSinglePolygon($feature['geometry']); + $uuids[] = $data['uuid']; + $sitePolygonProperties['area'] = $data['area']; + $returnSite = $this->insertSitePolygon( + $data['uuid'], + array_merge($sitePolygonProperties, $feature['properties']), + $data['area'] + ); + if ($returnSite) { + Log::info($returnSite); + } + } elseif ($feature['geometry']['type'] === 'MultiPolygon') { + foreach ($feature['geometry']['coordinates'] as $polygon) { + $singlePolygon = ['type' => 'Polygon', 'coordinates' => $polygon]; + $data = $this->insertSinglePolygon($singlePolygon); + $uuids[] = $data['uuid']; + $returnSite = $this->insertSitePolygon( + $data['uuid'], + array_merge($sitePolygonProperties, $feature['properties']), + $data['area'] + ); + if ($returnSite) { + Log::info($returnSite); + } + } + } + } + + return $uuids; + } + + public function createCriteriaSite($polygonId, $criteriaId, $valid): bool|string + { + $criteriaSite = new CriteriaSite(); + $criteriaSite->polygon_id = $polygonId; + $criteriaSite->criteria_id = $criteriaId; + $criteriaSite->valid = $valid; + + try { + $criteriaSite->save(); + + return true; + } catch (\Exception $e) { + return $e->getMessage(); + } + } + + private function insertSinglePolygon(array $geometry): array + { + // Convert geometry to GeoJSON string + $geojson = json_encode(['type' => 'Feature', 'geometry' => $geometry, 'crs' => ['type' => 'name', 'properties' => ['name' => 'EPSG:4326']]]); + + // Insert GeoJSON data into the database + $geom = DB::raw("ST_GeomFromGeoJSON('$geojson')"); + $areaSqDegrees = DB::selectOne("SELECT ST_Area(ST_GeomFromGeoJSON('$geojson')) AS area")->area; + $latitude = DB::selectOne("SELECT ST_Y(ST_Centroid(ST_GeomFromGeoJSON('$geojson'))) AS latitude")->latitude; + // 111320 is the length of one degree of latitude in meters at the equator + $unitLatitude = 111320; + $areaSqMeters = $areaSqDegrees * pow($unitLatitude * cos(deg2rad($latitude)), 2); + + $areaHectares = $areaSqMeters / 10000; + + $polygonGeometry = PolygonGeometry::create([ + 'geom' => $geom, + ]); + + return ['uuid' => $polygonGeometry->uuid, 'area' => $areaHectares]; + } + + private function insertSitePolygon(string $polygonUuid, array $properties, float $area) + { + try { + $validSchema = SitePolygonValidator::isValid('SCHEMA', $properties); + $validData = SitePolygonValidator::isValid('DATA', $properties); + $this->createCriteriaSite($polygonUuid, self::SCHEMA_CRITERIA_ID, $validSchema); + $this->createCriteriaSite($polygonUuid, self::DATA_CRITERIA_ID, $validData); + + $sitePolygon = new SitePolygon(); + $sitePolygon->project_id = $properties['project_id'] ?? null; + $sitePolygon->proj_name = $properties['proj_name'] ?? null; + $sitePolygon->org_name = $properties['org_name'] ?? null; + $sitePolygon->country = $properties['country'] ?? null; + $sitePolygon->poly_id = $polygonUuid ?? null; + $sitePolygon->poly_name = $properties['poly_name'] ?? null; + $sitePolygon->site_id = $properties['site_id'] ?? null; + $sitePolygon->site_name = $properties['site_name'] ?? null; + $sitePolygon->poly_label = $properties['poly_label'] ?? null; + $sitePolygon->plantstart = ! empty($properties['plantstart']) ? $properties['plantstart'] : null; + $sitePolygon->plantend = ! empty($properties['plantend']) ? $properties['plantend'] : null; + $sitePolygon->practice = $properties['practice'] ?? null; + $sitePolygon->target_sys = $properties['target_sys'] ?? null; + $sitePolygon->distr = $properties['distr'] ?? null; + $sitePolygon->num_trees = $properties['num_trees'] ?? null; + $sitePolygon->est_area = $area ?? null; + $sitePolygon->save(); + + return null; + } catch (\Exception $e) { + return $e->getMessage(); + } + } +} diff --git a/app/Validators/Extensions/FeatureBounds.php b/app/Validators/Extensions/FeatureBounds.php index cb8b40d03..42afc4393 100644 --- a/app/Validators/Extensions/FeatureBounds.php +++ b/app/Validators/Extensions/FeatureBounds.php @@ -18,7 +18,7 @@ public static function passes($attribute, $value, $parameters, $validator): bool $type = data_get($value, 'geometry.type'); if ($type === 'Polygon') { return self::hasValidPolygonBounds(data_get($value, 'geometry.coordinates')); - } else if ($type === 'MultiPolygon') { + } elseif ($type === 'MultiPolygon') { foreach (data_get($value, 'geometry.coordinates') as $coordinates) { if (! self::hasValidPolygonBounds($coordinates)) { return false; diff --git a/app/Validators/SitePolygonValidator.php b/app/Validators/SitePolygonValidator.php index 7a1681eb3..36a65875f 100644 --- a/app/Validators/SitePolygonValidator.php +++ b/app/Validators/SitePolygonValidator.php @@ -6,6 +6,26 @@ class SitePolygonValidator extends Validator { public const FEATURE_BOUNDS = [ 'features' => 'required|array', - 'features.*' => 'feature_bounds' + 'features.*' => 'feature_bounds', + ]; + + public const SCHEMA = [ + 'poly_name' => 'required', + 'plantstart' => 'required', + 'plantend' => 'required', + 'practice' => 'required', + 'target_sys' => 'required', + 'distr' => 'required', + 'num_trees' => 'required', + ]; + + public const DATA = [ + 'poly_name' => 'required|string|not_in:null,NULL', + 'plantstart' => 'required|date|', + 'plantend' => 'required|date|', + 'practice' => 'required|string|not_in:null,NULL', + 'target_sys' => 'required|string|not_in:null,NULL', + 'distr' => 'required|string|not_in:null,NULL', + 'num_trees' => 'required|integer|', ]; } diff --git a/app/Validators/Validator.php b/app/Validators/Validator.php index 07161aa4a..c7e2a7a60 100644 --- a/app/Validators/Validator.php +++ b/app/Validators/Validator.php @@ -66,13 +66,32 @@ class Validator \App\Validators\Extensions\TerrafundDisturbance::class, \App\Validators\Extensions\CompletePercentage::class, \App\Validators\Extensions\OrganisationFileType::class, + \App\Validators\Extensions\FeatureBounds::class, ]; private function __construct() { } + /** + * @throws ValidationException + */ public static function validate(string $name, array $data, bool $checkExtraFields = true): void + { + $validator = self::createValidator($name, $data, $checkExtraFields); + if ($validator->fails()) { + throw new ValidationException($validator); + } + } + + public static function isValid(string $name, array $data, bool $checkExtraFields = true): bool + { + $validator = self::createValidator($name, $data, $checkExtraFields); + + return ! $validator->fails(); + } + + private static function createValidator(string $name, array $data, bool $checkExtraFields = true): \Illuminate\Validation\Validator { $target = get_called_class() . '::' . $name; if (! defined($target) || ! is_array(constant($target))) { @@ -109,8 +128,6 @@ public static function validate(string $name, array $data, bool $checkExtraField }); } - if ($validator->fails()) { - throw new ValidationException($validator); - } + return $validator; } } From 87d6743f9e7b4c8f2ae136b7c455601c4fe1f82f Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Wed, 1 May 2024 15:50:47 -0700 Subject: [PATCH 07/64] [TM-799] Convert validations that don't require site context to laravel validators. --- .../TerrafundCreateGeometryController.php | 119 +++++------------- app/Models/V2/PolygonGeometry.php | 50 ++++++-- .../{ => Polygons}/FeatureBounds.php | 6 +- .../Extensions/Polygons/PolygonSize.php | 62 +++++++++ .../Extensions/Polygons/PolygonType.php | 49 ++++++++ .../Extensions/Polygons/SelfIntersection.php | 49 ++++++++ app/Validators/Extensions/Polygons/Spikes.php | 83 ++++++++++++ app/Validators/SitePolygonValidator.php | 2 +- app/Validators/Validator.php | 2 +- 9 files changed, 318 insertions(+), 104 deletions(-) rename app/Validators/Extensions/{ => Polygons}/FeatureBounds.php (90%) create mode 100644 app/Validators/Extensions/Polygons/PolygonSize.php create mode 100644 app/Validators/Extensions/Polygons/PolygonType.php create mode 100644 app/Validators/Extensions/Polygons/SelfIntersection.php create mode 100644 app/Validators/Extensions/Polygons/Spikes.php diff --git a/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php b/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php index b0259ca9b..c261015ef 100644 --- a/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php +++ b/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php @@ -9,6 +9,10 @@ use App\Models\V2\Sites\SitePolygon; use App\Models\V2\WorldCountryGeneralized; use App\Services\PolygonService; +use App\Validators\Extensions\Polygons\PolygonSize; +use App\Validators\Extensions\Polygons\PolygonType; +use App\Validators\Extensions\Polygons\SelfIntersection; +use App\Validators\Extensions\Polygons\Spikes; use Illuminate\Http\Request; use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\DB; @@ -22,14 +26,10 @@ class TerrafundCreateGeometryController extends Controller { public function processGeometry(string $uuid) { - $geometry = PolygonGeometry::where('uuid', $uuid) - ->select(DB::raw('ST_AsGeoJSON(geom) AS geojson')) - ->first(); - - $geojson = $geometry->geojson; + $geometry = PolygonGeometry::isUuid($uuid)->first(); - if ($geojson) { - return response()->json(['geometry' => $geojson], 200); + if ($geometry) { + return response()->json(['geometry' => $geometry->geo_json], 200); } else { return response()->json(['error' => 'Geometry not found'], 404); } @@ -87,16 +87,16 @@ public function validateDataInDB(Request $request) } }) ->first(); - $service = App::make(PolygonService::class); - $service->createCriteriaSite($polygonUuid, PolygonService::DATA_CRITERIA_ID, false); - if ($sitePolygonData) { - return response()->json(['valid' => false, 'message' => 'Some attributes of the site polygon are invalid.']); + $valid = $sitePolygonData == null; + $responseData = ['valid' => $valid]; + if (! $valid) { + $responseData['message'] = 'Some attributes of the site polygon are invalid.'; } - $valid = true; - $service->createCriteriaSite($polygonUuid, PolygonService::DATA_CRITERIA_ID, $valid); + App::make(PolygonService::class) + ->createCriteriaSite($polygonUuid, PolygonService::DATA_CRITERIA_ID, $valid); - return response()->json(['valid' => true]); + return response()->json($responseData); } public function getGeometryProperties(string $geojsonFilename) @@ -221,7 +221,7 @@ public function checkSelfIntersection(Request $request) return response()->json(['error' => 'Geometry not found'], 404); } - $isSimple = DB::selectOne('SELECT ST_IsSimple(geom) AS is_simple FROM polygon_geometry WHERE uuid = :uuid', ['uuid' => $uuid])->is_simple; + $isSimple = SelfIntersection::uuidValid($uuid); $message = $isSimple ? 'The geometry is valid' : 'The geometry has self-intersections'; $insertionSuccess = App::make(PolygonService::class) ->createCriteriaSite($uuid, PolygonService::SELF_CRITERIA_ID, $isSimple); @@ -229,61 +229,15 @@ public function checkSelfIntersection(Request $request) return response()->json(['selfintersects' => $message, 'geometry_id' => $geometry->id, 'insertion_success' => $insertionSuccess, 'valid' => $isSimple ? true : false], 200); } - public function calculateDistance($point1, $point2) - { - $lat1 = $point1[1]; - $lon1 = $point1[0]; - $lat2 = $point2[1]; - $lon2 = $point2[0]; - - $theta = $lon1 - $lon2; - $dist = sin(deg2rad($lat1)) * sin(deg2rad($lat2)) + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * cos(deg2rad($theta)); - $dist = acos($dist); - $dist = rad2deg($dist); - $miles = $dist * 60 * 1.1515; - - return $miles * 1.609344; - } - - public function detectSpikes($geometry) - { - $spikes = []; - - if ($geometry['type'] === 'Polygon' || $geometry['type'] === 'MultiPolygon') { - $coordinates = $geometry['type'] === 'Polygon' ? $geometry['coordinates'][0] : $geometry['coordinates'][0][0]; // First ring of the polygon or the first polygon in the MultiPolygon - $numVertices = count($coordinates); - $totalDistance = 0; - - for ($i = 0; $i < $numVertices - 1; $i++) { - $totalDistance += $this->calculateDistance($coordinates[$i], $coordinates[$i + 1]); - } - - for ($i = 0; $i < $numVertices - 1; $i++) { - $distance1 = $this->calculateDistance($coordinates[$i], $coordinates[($i + 1) % $numVertices]); - $distance2 = $this->calculateDistance($coordinates[($i + 1) % $numVertices], $coordinates[($i + 2) % $numVertices]); - $combinedDistance = $distance1 + $distance2; - - if ($combinedDistance > 0.6 * $totalDistance) { - // Vertex and its adjacent vertices contribute more than 25% of the total boundary path distance - $spikes[] = $coordinates[($i + 1) % $numVertices]; - } - } - } - - return $spikes; - } - public function checkBoundarySegments(Request $request) { $uuid = $request->query('uuid'); - $geometry = PolygonGeometry::where('uuid', $uuid)->first(); + $geometry = PolygonGeometry::isUuid($uuid)->first(); if (! $geometry) { return response()->json(['error' => 'Geometry not found'], 404); } - $geojson = DB::selectOne('SELECT ST_AsGeoJSON(geom) AS geojson FROM polygon_geometry WHERE uuid = :uuid', ['uuid' => $uuid])->geojson; - $geojsonArray = json_decode($geojson, true); - $spikes = $this->detectSpikes($geojsonArray); + $spikes = Spikes::detectSpikes($geometry->geo_json); $valid = count($spikes) === 0; $insertionSuccess = App::make(PolygonService::class) ->createCriteriaSite($uuid, PolygonService::SPIKE_CRITERIA_ID, $valid); @@ -294,17 +248,14 @@ public function checkBoundarySegments(Request $request) public function validatePolygonSize(Request $request) { $uuid = $request->query('uuid'); - $geometry = PolygonGeometry::where('uuid', $uuid)->first(); + $geometry = PolygonGeometry::isUuid($uuid)->first(); if (! $geometry) { return response()->json(['error' => 'Geometry not found'], 404); } - $areaAndLatitude = $geometry->getDbGeometryAttribute(); - $areaSqDegrees = $areaAndLatitude->area; - $latitude = $areaAndLatitude->latitude; - $areaSqMeters = $areaSqDegrees * pow(111320 * cos(deg2rad($latitude)), 2); - $valid = $areaSqMeters <= 10000000; + $areaSqMeters = PolygonSize::calculateSqMeters($geometry->db_geometry); + $valid = $areaSqMeters <= PolygonSize::SIZE_LIMIT; $insertionSuccess = App::make(PolygonService::class) ->createCriteriaSite($uuid, PolygonService::SIZE_CRITERIA_ID, $valid); @@ -325,15 +276,13 @@ public function checkWithinCountry(Request $request) return response()->json(['error' => 'UUID not provided'], 200); } - $geometry = PolygonGeometry::where('uuid', $polygonUuid)->first(); + $geometry = PolygonGeometry::isUuid($polygonUuid)->first(); if (! $geometry) { return response()->json(['error' => 'Geometry not found'], 404); } - $totalArea = PolygonGeometry::where('uuid', $polygonUuid) - ->selectRaw('ST_Area(geom) AS area') - ->first()->area; + $totalArea = $geometry->db_geometry->area; // Find site_polygon_id and project_id using the polygonUuid $sitePolygonData = SitePolygon::where('poly_id', $polygonUuid) @@ -377,13 +326,9 @@ public function getGeometryType(Request $request) { $uuid = $request->input('uuid'); - // Fetch the geometry type based on the UUID using SQL query - $query = 'SELECT ST_GeometryType(geom) AS geometry_type FROM polygon_geometry WHERE uuid = ?'; - $result = DB::selectOne($query, [$uuid]); - - if ($result) { - $geometryType = $result->geometry_type; - $valid = $geometryType === 'POLYGON'; + $geometryType = PolygonGeometry::getGeometryType($uuid); + if ($geometryType) { + $valid = $geometryType === PolygonType::VALID_TYPE; $insertionSuccess = App::make(PolygonService::class) ->createCriteriaSite($uuid, PolygonService::GEOMETRY_TYPE_CRITERIA_ID, $valid); @@ -397,11 +342,8 @@ public function getCriteriaData(Request $request) { $uuid = $request->input('uuid'); - // Find the ID of the polygon based on the UUID - $polygonIdQuery = 'SELECT id FROM polygon_geometry WHERE uuid = ?'; - $polygonIdResult = DB::selectOne($polygonIdQuery, [$uuid]); - - if (! $polygonIdResult) { + $geometry = PolygonGeometry::isUuid($uuid)->first(); + if ($geometry === null) { return response()->json(['error' => 'Polygon not found for the given UUID'], 404); } @@ -512,10 +454,7 @@ public function validateEstimatedArea(Request $request) } $lowerBound = 0.75 * $totalHectaresRestoredGoal; $upperBound = 1.25 * $totalHectaresRestoredGoal; - $valid = false; - if ($sumEstArea >= $lowerBound && $sumEstArea <= $upperBound) { - $valid = true; - } + $valid = $sumEstArea >= $lowerBound && $sumEstArea <= $upperBound; $insertionSuccess = App::make(PolygonService::class) ->createCriteriaSite($uuid, PolygonService::ESTIMATED_AREA_CRITERIA_ID, $valid); @@ -525,7 +464,7 @@ public function validateEstimatedArea(Request $request) public function getPolygonsAsGeoJSON() { $limit = 2; - $polygons = PolygonGeometry::select(DB::raw('ST_AsGeoJSON(geom) AS geojson')) + $polygons = PolygonGeometry::selectRaw('ST_AsGeoJSON(geom) AS geojson') ->orderBy('created_at', 'desc') ->whereNotNull('geom') ->limit($limit) diff --git a/app/Models/V2/PolygonGeometry.php b/app/Models/V2/PolygonGeometry.php index 2e2dc8763..7843e01a9 100644 --- a/app/Models/V2/PolygonGeometry.php +++ b/app/Models/V2/PolygonGeometry.php @@ -6,8 +6,11 @@ use App\Models\V2\Sites\CriteriaSite; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; -use Illuminate\Support\Facades\DB; +/** + * @method static isUuid($uuid) + * @property mixed $uuid + */ class PolygonGeometry extends Model { use HasUuid; @@ -24,16 +27,43 @@ public function criteriaSite() return $this->hasMany(CriteriaSite::class, 'polygon_id', 'polygon_id'); } + public static function getGeoJson(string $uuid): ?array + { + $geojson = PolygonGeometry::isUuid($uuid) + ->selectRaw('ST_AsGeoJSON(geom) as geojson') + ->first() + ?->geojson; + + return $geojson == null ? null : json_decode($geojson, true); + } + + public function getGeoJsonAttribute(): array + { + return self::getGeoJson($this->uuid); + } + + public static function getGeometryType(string $uuid): ?string + { + return PolygonGeometry::isUuid($uuid) + ->selectRaw('ST_GeometryType(geom) as geometry_type') + ->first() + ?->geometry_type; + } + + public function getGeometryTypeAttribute(): string + { + return self::getGeometryType($this->uuid); + } + + public static function getDbGeometry(string $uuid) + { + return PolygonGeometry::isUuid($uuid) + ->selectRaw('ST_Area(geom) AS area, ST_Y(ST_Centroid(geom)) AS latitude') + ->first(); + } + public function getDbGeometryAttribute() { - $result = DB::selectOne( - ' - SELECT ST_Area(geom) AS area, ST_Y(ST_Centroid(geom)) AS latitude - FROM polygon_geometry - WHERE uuid = :uuid', - ['uuid' => $this->uuid] - ); - - return $result; + return self::getDbGeometry($this->uuid); } } diff --git a/app/Validators/Extensions/FeatureBounds.php b/app/Validators/Extensions/Polygons/FeatureBounds.php similarity index 90% rename from app/Validators/Extensions/FeatureBounds.php rename to app/Validators/Extensions/Polygons/FeatureBounds.php index 42afc4393..050863997 100644 --- a/app/Validators/Extensions/FeatureBounds.php +++ b/app/Validators/Extensions/Polygons/FeatureBounds.php @@ -1,10 +1,12 @@ ':attribute'], + 'The :attribute field must not represent a polygon that is too large.', + ]; + + public const SIZE_LIMIT = 10000000; + + public static function passes($attribute, $value, $parameters, $validator): bool + { + if (is_string($value)) { + // assume we have a DB UUID + return self::uuidValid($value); + } + + // assume we have GeoJSON + return self::geoJsonValid($value); + } + + public static function uuidValid($uuid): bool + { + $areaSqMeters = self::calculateSqMeters(PolygonGeometry::isUuid($uuid)->first()->db_geometry); + + return $areaSqMeters <= self::SIZE_LIMIT; + } + + public static function geoJsonValid($geojson): bool + { + $areaSqMeters = self::calculateSqMeters(DB::selectOne( + 'SELECT + ST_Area(params.geom) AS area, + ST_Y(ST_Centroid(params.geom)) AS latitude + FROM ( + SELECT ST_GeomFromGeoJSON(:geojson) AS geom + ) as params', + ['geojson' => json_encode($geojson)] + )); + + return $areaSqMeters <= self::SIZE_LIMIT; + } + + public static function calculateSqMeters($dbGeometry): bool + { + $areaSqDegrees = $dbGeometry->area; + $latitude = $dbGeometry->latitude; + + return $areaSqDegrees * pow(111320 * cos(deg2rad($latitude)), 2); + } +} diff --git a/app/Validators/Extensions/Polygons/PolygonType.php b/app/Validators/Extensions/Polygons/PolygonType.php new file mode 100644 index 000000000..cf91601aa --- /dev/null +++ b/app/Validators/Extensions/Polygons/PolygonType.php @@ -0,0 +1,49 @@ + ':attribute'], + 'The :attribute field must not represent geojson that is not polygon geometry', + ]; + + public const VALID_TYPE = 'POLYGON'; + + public static function passes($attribute, $value, $parameters, $validator): bool + { + if (is_string($value)) { + // assume we have a DB UUID + return self::uuidValid($value); + } + + // assume we have GeoJSON + return self::geoJsonValid($value); + } + + public static function uuidValid($uuid): bool + { + $geometryType = PolygonGeometry::getGeometryType($uuid); + + return $geometryType === self::VALID_TYPE; + } + + public static function geoJsonValid($geojson): bool + { + $result = DB::selectOne( + 'SELECT ST_GeometryType(ST_GeomFromGeoJSON(:geojson)) AS geometry_type', + ['geojson' => json_encode($geojson)] + ); + + return $result->geometry_type === self::VALID_TYPE; + } +} diff --git a/app/Validators/Extensions/Polygons/SelfIntersection.php b/app/Validators/Extensions/Polygons/SelfIntersection.php new file mode 100644 index 000000000..a7db606e6 --- /dev/null +++ b/app/Validators/Extensions/Polygons/SelfIntersection.php @@ -0,0 +1,49 @@ + ':attribute'], + 'The :attribute geometry field must not self intersect.', + ]; + + public static function passes($attribute, $value, $parameters, $validator): bool + { + if (is_string($value)) { + // assume we have a DB UUID + return self::uuidValid($value); + } + + // assume we have GeoJSON + return self::geoJsonValid($value); + } + + public static function uuidValid($uuid): bool + { + $result = DB::selectOne( + 'SELECT ST_IsSimple(geom) AS is_simple FROM polygon_geometry WHERE uuid = :uuid', + ['uuid' => $uuid] + ); + + return $result?->is_simple === 1; + } + + public static function geoJsonValid($geojson): bool + { + $result = DB::selectOne( + 'SELECT ST_IsSimple(ST_GeomFromGeoJSON(:geojson)) AS is_simple', + ['geojson' => json_encode($geojson)] + ); + + return $result?->is_simple === 1; + } +} diff --git a/app/Validators/Extensions/Polygons/Spikes.php b/app/Validators/Extensions/Polygons/Spikes.php new file mode 100644 index 000000000..65be09afb --- /dev/null +++ b/app/Validators/Extensions/Polygons/Spikes.php @@ -0,0 +1,83 @@ + ':attribute'], + 'The :attribute field must not represent a polygon with spikes', + ]; + + public static function passes($attribute, $value, $parameters, $validator): bool + { + if (is_string($value)) { + // assume we have a DB UUID + return self::uuidValid($value); + } + + // assume we have GeoJSON + return self::geoJsonValid($value); + } + + public static function uuidValid($uuid): bool + { + return self::geoJsonValid(PolygonGeometry::isUuid($uuid)->first()->geo_json); + } + + public static function geoJsonValid($geojson): bool + { + return count(self::detectSpikes($geojson)) === 0; + } + + public static function detectSpikes($geometry): array + { + $spikes = []; + + if ($geometry['type'] === 'Polygon' || $geometry['type'] === 'MultiPolygon') { + $coordinates = $geometry['type'] === 'Polygon' ? $geometry['coordinates'][0] : $geometry['coordinates'][0][0]; // First ring of the polygon or the first polygon in the MultiPolygon + $numVertices = count($coordinates); + $totalDistance = 0; + + for ($i = 0; $i < $numVertices - 1; $i++) { + $totalDistance += self::calculateDistance($coordinates[$i], $coordinates[$i + 1]); + } + + for ($i = 0; $i < $numVertices - 1; $i++) { + $distance1 = self::calculateDistance($coordinates[$i], $coordinates[($i + 1) % $numVertices]); + $distance2 = self::calculateDistance($coordinates[($i + 1) % $numVertices], $coordinates[($i + 2) % $numVertices]); + $combinedDistance = $distance1 + $distance2; + + if ($combinedDistance > 0.6 * $totalDistance) { + // Vertex and its adjacent vertices contribute more than 25% of the total boundary path distance + $spikes[] = $coordinates[($i + 1) % $numVertices]; + } + } + } + + return $spikes; + } + + private static function calculateDistance($point1, $point2): float + { + $lat1 = $point1[1]; + $lon1 = $point1[0]; + $lat2 = $point2[1]; + $lon2 = $point2[0]; + + $theta = $lon1 - $lon2; + $dist = sin(deg2rad($lat1)) * sin(deg2rad($lat2)) + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * cos(deg2rad($theta)); + $dist = acos($dist); + $dist = rad2deg($dist); + $miles = $dist * 60 * 1.1515; + + return $miles * 1.609344; + } +} diff --git a/app/Validators/SitePolygonValidator.php b/app/Validators/SitePolygonValidator.php index 36a65875f..e1fc901b4 100644 --- a/app/Validators/SitePolygonValidator.php +++ b/app/Validators/SitePolygonValidator.php @@ -6,7 +6,7 @@ class SitePolygonValidator extends Validator { public const FEATURE_BOUNDS = [ 'features' => 'required|array', - 'features.*' => 'feature_bounds', + 'features.*' => 'polygon_feature_bounds', ]; public const SCHEMA = [ diff --git a/app/Validators/Validator.php b/app/Validators/Validator.php index c7e2a7a60..b46e8d276 100644 --- a/app/Validators/Validator.php +++ b/app/Validators/Validator.php @@ -66,7 +66,7 @@ class Validator \App\Validators\Extensions\TerrafundDisturbance::class, \App\Validators\Extensions\CompletePercentage::class, \App\Validators\Extensions\OrganisationFileType::class, - \App\Validators\Extensions\FeatureBounds::class, + Extensions\Polygons\FeatureBounds::class, ]; private function __construct() From aa938e040280644a4577dc2fd2505046db18d68e Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Wed, 1 May 2024 16:19:25 -0700 Subject: [PATCH 08/64] [TM-799] Convert within country to a laravel validator. --- .../TerrafundCreateGeometryController.php | 53 ++++------------ app/Models/V2/Sites/SitePolygon.php | 9 +++ app/Models/V2/WorldCountryGeneralized.php | 9 +++ .../Extensions/Polygons/HasPolygonSite.php | 23 +++++++ .../Extensions/Polygons/WithinCountry.php | 63 +++++++++++++++++++ app/Validators/SitePolygonValidator.php | 4 ++ app/Validators/Validator.php | 8 ++- 7 files changed, 127 insertions(+), 42 deletions(-) create mode 100644 app/Validators/Extensions/Polygons/HasPolygonSite.php create mode 100644 app/Validators/Extensions/Polygons/WithinCountry.php diff --git a/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php b/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php index c261015ef..07f1c017c 100644 --- a/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php +++ b/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php @@ -13,6 +13,7 @@ use App\Validators\Extensions\Polygons\PolygonType; use App\Validators\Extensions\Polygons\SelfIntersection; use App\Validators\Extensions\Polygons\Spikes; +use App\Validators\Extensions\Polygons\WithinCountry; use Illuminate\Http\Request; use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\DB; @@ -67,8 +68,7 @@ public function validateDataInDB(Request $request) $polygonUuid = $request->input('uuid'); $fieldsToValidate = ['poly_name', 'plantstart', 'plantend', 'practice', 'target_sys', 'distr', 'num_trees']; // Check if the polygon with the specified poly_id exists - $polygonExists = SitePolygon::where('poly_id', $polygonUuid) - ->exists(); + $polygonExists = SitePolygon::forPolygonGeometry($polygonUuid)->exists(); if (! $polygonExists) { return response()->json(['valid' => false, 'message' => 'No site polygon found with the specified poly_id.']); @@ -80,7 +80,7 @@ public function validateDataInDB(Request $request) $whereConditions[] = "(IFNULL($field, '') = '' OR $field IS NULL)"; } - $sitePolygonData = SitePolygon::where('poly_id', $polygonUuid) + $sitePolygonData = SitePolygon::forPolygonGeometry($polygonUuid) ->where(function ($query) use ($whereConditions) { foreach ($whereConditions as $condition) { $query->orWhereRaw($condition); @@ -282,44 +282,17 @@ public function checkWithinCountry(Request $request) return response()->json(['error' => 'Geometry not found'], 404); } - $totalArea = $geometry->db_geometry->area; - - // Find site_polygon_id and project_id using the polygonUuid - $sitePolygonData = SitePolygon::where('poly_id', $polygonUuid) - ->select('id', 'project_id') - ->first(); - - if (! $sitePolygonData) { - return response()->json(['error' => 'Site polygon data not found for the specified polygonUuid'], 404); + $response = WithinCountry::getIntersectionData($polygonUuid); + if ($response == null) { + return response()->json(['error' => 'Data is missing for within country calculation'], 404); } - $countryIso = $sitePolygonData->project->country; - if (! $countryIso) { - return response()->json(['error' => 'Country ISO not found for the specified project_id'], 404); - } - - $intersectionData = WorldCountryGeneralized::where('iso', $countryIso) - ->selectRaw('world_countries_generalized.country AS country, ST_Area(ST_Intersection(world_countries_generalized.geometry, (SELECT geom FROM polygon_geometry WHERE uuid = ?))) AS area', [$polygonUuid]) - ->first(); - - $intersectionArea = $intersectionData->area; - $countryName = $intersectionData->country; - - $insidePercentage = $intersectionArea / $totalArea * 100; - - $insideThreshold = 75; - $insideViolation = $insidePercentage < $insideThreshold; $insertionSuccess = App::make(PolygonService::class) - ->createCriteraSite($polygonUuid, PolygonService::WITHIN_COUNTRY_CRITERIA_ID, ! $insideViolation); - - return response()->json([ - 'country_name' => $countryName, - 'inside_percentage' => $insidePercentage, - 'valid' => ! $insideViolation, - 'geometry_id' => $geometry->id, - 'insertion_success' => $insertionSuccess, - ]); + ->createCriteraSite($polygonUuid, PolygonService::WITHIN_COUNTRY_CRITERIA_ID, $response['valid']); + $response['geometry_id'] = $geometry->id; + $response['insertion_success'] = $insertionSuccess; + return response()->json($response); } public function getGeometryType(Request $request) @@ -398,8 +371,7 @@ public function uploadGeoJSONFile(Request $request) public function validateOverlapping(Request $request) { $uuid = $request->input('uuid'); - $sitePolygon = SitePolygon::where('poly_id', $uuid) - ->first(); + $sitePolygon = SitePolygon::forPolygonGeometry($uuid)->first(); if (! $sitePolygon) { return response()->json(['error' => 'Site polygon not found for the given polygon ID'], 200); @@ -429,8 +401,7 @@ public function validateOverlapping(Request $request) public function validateEstimatedArea(Request $request) { $uuid = $request->input('uuid'); - $sitePolygon = SitePolygon::where('poly_id', $uuid) - ->first(); + $sitePolygon = SitePolygon::forPolygonGeometry($uuid)->first(); if (! $sitePolygon) { return response()->json(['error' => 'Site polygon not found for the given polygon ID'], 200); diff --git a/app/Models/V2/Sites/SitePolygon.php b/app/Models/V2/Sites/SitePolygon.php index b57290ca4..d4311bb78 100644 --- a/app/Models/V2/Sites/SitePolygon.php +++ b/app/Models/V2/Sites/SitePolygon.php @@ -5,9 +5,13 @@ use App\Models\Traits\HasUuid; use App\Models\V2\PolygonGeometry; use App\Models\V2\Projects\Project; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; +/** + * @method static forPolygonGeometry($value): Builder + */ class SitePolygon extends Model { use HasUuid; @@ -39,6 +43,11 @@ public function polygonGeometry() return $this->belongsTo(PolygonGeometry::class, 'poly_id', 'uuid'); } + public function scopeForPolygonGeometry($query, $uuid): Builder + { + return $query->where('poly_id', $uuid); + } + public function project() { return $this->belongsTo(Project::class, 'project_id', 'uuid'); diff --git a/app/Models/V2/WorldCountryGeneralized.php b/app/Models/V2/WorldCountryGeneralized.php index 13670a0ab..f68ca40c6 100644 --- a/app/Models/V2/WorldCountryGeneralized.php +++ b/app/Models/V2/WorldCountryGeneralized.php @@ -2,8 +2,12 @@ namespace App\Models\V2; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; +/** + * @method static forIso($countryIso): Builder + */ class WorldCountryGeneralized extends Model { protected $table = 'world_countries_generalized'; @@ -11,4 +15,9 @@ class WorldCountryGeneralized extends Model protected $fillable = [ 'countryaff', 'country', 'iso', 'country_aff', 'aff_iso', 'geometry', 'OGR_FID', ]; + + public function scopeForIso($query, string $iso): Builder + { + return $query->where('iso', $iso); + } } diff --git a/app/Validators/Extensions/Polygons/HasPolygonSite.php b/app/Validators/Extensions/Polygons/HasPolygonSite.php new file mode 100644 index 000000000..f68dd74bf --- /dev/null +++ b/app/Validators/Extensions/Polygons/HasPolygonSite.php @@ -0,0 +1,23 @@ + ':attribute'], + 'The :attribute field must represent a polygon with an attached site' + ]; + + public static function passes($attribute, $value, $parameters, $validator): bool + { + return SitePolygon::where('poly_id', $value)->exists(); + } +} diff --git a/app/Validators/Extensions/Polygons/WithinCountry.php b/app/Validators/Extensions/Polygons/WithinCountry.php new file mode 100644 index 000000000..101bd8759 --- /dev/null +++ b/app/Validators/Extensions/Polygons/WithinCountry.php @@ -0,0 +1,63 @@ + ':attribute'], + 'The :attribute field must represent a polygon with an attached site' + ]; + + public const THRESHOLD_PERCENTAGE = 75; + + public static function passes($attribute, $value, $parameters, $validator): bool + { + $result = self::getIntersectionData($value); + return $result != null && $result['valid']; + } + + public static function getIntersectionData(string $polygonUuid): ?array + { + $geometry = PolygonGeometry::isUuid($polygonUuid)->first(); + $sitePolygonData = SitePolygon::forPolygonGeometry($polygonUuid)->select('id', 'project_id')->first(); + if ($geometry == null || $sitePolygonData == null) { + return null; + } + + $countryIso = $sitePolygonData->project->country; + if ($countryIso == null) { + return null; + } + + $intersectionData = WorldCountryGeneralized::forIso($countryIso) + ->selectRaw( + 'world_countries_generalized.country AS country, + ST_Area( + ST_Intersection( + world_countries_generalized.geometry, + (SELECT geom FROM polygon_geometry WHERE uuid = ?) + ) + ) AS area', + [$polygonUuid] + ) + ->first(); + + $totalArea = $geometry->db_geometry->area; + $insidePercentage = $intersectionData->area / $totalArea * 100; + return [ + 'valid' => $insidePercentage >= self::THRESHOLD_PERCENTAGE, + 'inside_percentage' => $insidePercentage, + 'country_name' => $intersectionData->country + ]; + } +} diff --git a/app/Validators/SitePolygonValidator.php b/app/Validators/SitePolygonValidator.php index e1fc901b4..e83a9b9bd 100644 --- a/app/Validators/SitePolygonValidator.php +++ b/app/Validators/SitePolygonValidator.php @@ -9,6 +9,10 @@ class SitePolygonValidator extends Validator 'features.*' => 'polygon_feature_bounds', ]; + public const WITHIN_COUNTRY = [ + '*' => 'required|string|uuid|has_polygon_site|within_country' + ]; + public const SCHEMA = [ 'poly_name' => 'required', 'plantstart' => 'required', diff --git a/app/Validators/Validator.php b/app/Validators/Validator.php index b46e8d276..f769cc243 100644 --- a/app/Validators/Validator.php +++ b/app/Validators/Validator.php @@ -66,7 +66,13 @@ class Validator \App\Validators\Extensions\TerrafundDisturbance::class, \App\Validators\Extensions\CompletePercentage::class, \App\Validators\Extensions\OrganisationFileType::class, - Extensions\Polygons\FeatureBounds::class, + \App\validators\Extensions\Polygons\FeatureBounds::class, + \App\validators\Extensions\Polygons\HasPolygonSite::class, + \App\validators\Extensions\Polygons\PolygonSize::class, + \App\validators\Extensions\Polygons\PolygonType::class, + \App\validators\Extensions\Polygons\SelfIntersection::class, + \App\validators\Extensions\Polygons\Spikes::class, + \App\validators\Extensions\Polygons\WithinCountry::class, ]; private function __construct() From 65539677bfaf4e9ff07f754218f239733e7d867c Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Wed, 1 May 2024 16:36:04 -0700 Subject: [PATCH 09/64] [TM-799] Convert overlapping polygon validation to a validator. --- .../TerrafundCreateGeometryController.php | 31 ++++------- .../Extensions/Polygons/HasPolygonSite.php | 2 +- .../Extensions/Polygons/NotOverlapping.php | 54 +++++++++++++++++++ .../Extensions/Polygons/WithinCountry.php | 12 +++-- app/Validators/SitePolygonValidator.php | 6 ++- app/Validators/Validator.php | 1 + 6 files changed, 78 insertions(+), 28 deletions(-) create mode 100644 app/Validators/Extensions/Polygons/NotOverlapping.php diff --git a/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php b/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php index 07f1c017c..58a5b358a 100644 --- a/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php +++ b/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php @@ -9,6 +9,7 @@ use App\Models\V2\Sites\SitePolygon; use App\Models\V2\WorldCountryGeneralized; use App\Services\PolygonService; +use App\Validators\Extensions\Polygons\NotOverlapping; use App\Validators\Extensions\Polygons\PolygonSize; use App\Validators\Extensions\Polygons\PolygonType; use App\Validators\Extensions\Polygons\SelfIntersection; @@ -292,6 +293,7 @@ public function checkWithinCountry(Request $request) $response['geometry_id'] = $geometry->id; $response['insertion_success'] = $insertionSuccess; + return response()->json($response); } @@ -371,31 +373,18 @@ public function uploadGeoJSONFile(Request $request) public function validateOverlapping(Request $request) { $uuid = $request->input('uuid'); - $sitePolygon = SitePolygon::forPolygonGeometry($uuid)->first(); - - if (! $sitePolygon) { - return response()->json(['error' => 'Site polygon not found for the given polygon ID'], 200); - } - - $projectId = $sitePolygon->project_id; - if(! $projectId) { - return response()->json(['error' => 'Project ID not found for the given polygon ID'], 200); + $response = NotOverlapping::getIntersectionData($uuid); + if ($response == null) { + return response()->json(['error' => 'Data is missing for overlapping calculation'], 404); } - $relatedPolyIds = SitePolygon::where('project_id', $projectId) - ->where('poly_id', '!=', $uuid) - ->pluck('poly_id'); - $intersects = PolygonGeometry::whereIn('uuid', $relatedPolyIds) - ->selectRaw('ST_Intersects(geom, (SELECT geom FROM polygon_geometry WHERE uuid = ?)) as intersects', [$uuid]) - ->get() - ->pluck('intersects'); - - $intersects = in_array(1, $intersects->toArray()); - $valid = ! $intersects; $insertionSuccess = App::make(PolygonService::class) - ->createCriteriaSite($uuid, PolygonService::OVERLAPPING_CRITERIA_ID, $valid); + ->createCriteriaSite($uuid, PolygonService::OVERLAPPING_CRITERIA_ID, $response['valid']); + + $response['uuid'] = $uuid; + $response['criteria_success'] = $insertionSuccess; - return response()->json(['intersects' => $intersects, 'project_id' => $projectId, 'uuid' => $uuid, 'valid' => $valid, 'creteria_succes' => $insertionSuccess], 200); + return response()->json($response); } public function validateEstimatedArea(Request $request) diff --git a/app/Validators/Extensions/Polygons/HasPolygonSite.php b/app/Validators/Extensions/Polygons/HasPolygonSite.php index f68dd74bf..1c6c7132a 100644 --- a/app/Validators/Extensions/Polygons/HasPolygonSite.php +++ b/app/Validators/Extensions/Polygons/HasPolygonSite.php @@ -13,7 +13,7 @@ class HasPolygonSite extends Extension 'HAS_POLYGON_SITE', 'The {{attribute}} field must represent a polygon with an attached site', ['attribute' => ':attribute'], - 'The :attribute field must represent a polygon with an attached site' + 'The :attribute field must represent a polygon with an attached site', ]; public static function passes($attribute, $value, $parameters, $validator): bool diff --git a/app/Validators/Extensions/Polygons/NotOverlapping.php b/app/Validators/Extensions/Polygons/NotOverlapping.php new file mode 100644 index 000000000..597a15986 --- /dev/null +++ b/app/Validators/Extensions/Polygons/NotOverlapping.php @@ -0,0 +1,54 @@ + ':attribute'], + 'The :attribute field must represent a polygon that does not overlap with other site polygons', + ]; + + public static function passes($attribute, $value, $parameters, $validator): bool + { + $result = self::getIntersectionData($value); + + return $result != null && $result['valid']; + } + + public static function getIntersectionData(string $polygonUuid): ?array + { + $sitePolygon = SitePolygon::forPolygonGeometry($polygonUuid)->first(); + if ($sitePolygon == null) { + return null; + } + + $relatedPolyIds = SitePolygon::where('project_id', $sitePolygon->project_id) + ->where('poly_id', '!=', $polygonUuid) + ->pluck('poly_id'); + + $intersects = PolygonGeometry::whereIn('uuid', $relatedPolyIds) + ->selectRaw( + 'ST_Intersects( + geom, + (SELECT geom FROM polygon_geometry WHERE uuid = ?) + ) as intersects', + [$polygonUuid] + ) + ->get() + ->pluck('intersects'); + + return [ + 'valid' => ! in_array(1, $intersects->toArray()), + 'project_id' => $sitePolygon->project_id, + ]; + } +} diff --git a/app/Validators/Extensions/Polygons/WithinCountry.php b/app/Validators/Extensions/Polygons/WithinCountry.php index 101bd8759..efeb94169 100644 --- a/app/Validators/Extensions/Polygons/WithinCountry.php +++ b/app/Validators/Extensions/Polygons/WithinCountry.php @@ -9,13 +9,13 @@ class WithinCountry extends Extension { - public static $name = 'has_polygon_site'; + public static $name = 'within_country'; public static $message = [ - 'HAS_POLYGON_SITE', - 'The {{attribute}} field must represent a polygon with an attached site', + 'WITHIN_COUNTRY', + 'The {{attribute}} field must represent a polygon that is within its assigned country', ['attribute' => ':attribute'], - 'The :attribute field must represent a polygon with an attached site' + 'The :attribute field must represent a polygon that is within its assigned country', ]; public const THRESHOLD_PERCENTAGE = 75; @@ -23,6 +23,7 @@ class WithinCountry extends Extension public static function passes($attribute, $value, $parameters, $validator): bool { $result = self::getIntersectionData($value); + return $result != null && $result['valid']; } @@ -54,10 +55,11 @@ public static function getIntersectionData(string $polygonUuid): ?array $totalArea = $geometry->db_geometry->area; $insidePercentage = $intersectionData->area / $totalArea * 100; + return [ 'valid' => $insidePercentage >= self::THRESHOLD_PERCENTAGE, 'inside_percentage' => $insidePercentage, - 'country_name' => $intersectionData->country + 'country_name' => $intersectionData->country, ]; } } diff --git a/app/Validators/SitePolygonValidator.php b/app/Validators/SitePolygonValidator.php index e83a9b9bd..7f1adc840 100644 --- a/app/Validators/SitePolygonValidator.php +++ b/app/Validators/SitePolygonValidator.php @@ -10,7 +10,11 @@ class SitePolygonValidator extends Validator ]; public const WITHIN_COUNTRY = [ - '*' => 'required|string|uuid|has_polygon_site|within_country' + '*' => 'required|string|uuid|has_polygon_site|within_country', + ]; + + public const OVERLAPPING = [ + '*' => 'required|string|uuid|has_polygon_site|not_overlapping', ]; public const SCHEMA = [ diff --git a/app/Validators/Validator.php b/app/Validators/Validator.php index f769cc243..87eb4722e 100644 --- a/app/Validators/Validator.php +++ b/app/Validators/Validator.php @@ -68,6 +68,7 @@ class Validator \App\Validators\Extensions\OrganisationFileType::class, \App\validators\Extensions\Polygons\FeatureBounds::class, \App\validators\Extensions\Polygons\HasPolygonSite::class, + \App\validators\Extensions\Polygons\NotOverlapping::class, \App\validators\Extensions\Polygons\PolygonSize::class, \App\validators\Extensions\Polygons\PolygonType::class, \App\validators\Extensions\Polygons\SelfIntersection::class, From 88db5eb5ec1d2e20dfa4411f257fbf4b16d9fd08 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Thu, 2 May 2024 10:19:33 -0700 Subject: [PATCH 10/64] [TM-799] Convert valid estimated area to a validator. --- .../TerrafundCreateGeometryController.php | 33 +++-------- .../Extensions/Polygons/EstimatedArea.php | 58 +++++++++++++++++++ .../Extensions/Polygons/NotOverlapping.php | 2 +- app/Validators/Validator.php | 1 + 4 files changed, 68 insertions(+), 26 deletions(-) create mode 100644 app/Validators/Extensions/Polygons/EstimatedArea.php diff --git a/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php b/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php index 58a5b358a..dd1113ea4 100644 --- a/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php +++ b/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php @@ -4,11 +4,11 @@ use App\Http\Controllers\Controller; use App\Models\V2\PolygonGeometry; -use App\Models\V2\Projects\Project; use App\Models\V2\Sites\CriteriaSite; use App\Models\V2\Sites\SitePolygon; use App\Models\V2\WorldCountryGeneralized; use App\Services\PolygonService; +use App\Validators\Extensions\Polygons\EstimatedArea; use App\Validators\Extensions\Polygons\NotOverlapping; use App\Validators\Extensions\Polygons\PolygonSize; use App\Validators\Extensions\Polygons\PolygonType; @@ -390,35 +390,18 @@ public function validateOverlapping(Request $request) public function validateEstimatedArea(Request $request) { $uuid = $request->input('uuid'); - $sitePolygon = SitePolygon::forPolygonGeometry($uuid)->first(); + $response = EstimatedArea::getAreaData($uuid); + if ($response['error'] != null) { + unset($response['valid']); - if (! $sitePolygon) { - return response()->json(['error' => 'Site polygon not found for the given polygon ID'], 200); + return response()->json($response); } - $projectId = $sitePolygon->project_id; - - $sumEstArea = SitePolygon::where('project_id', $projectId) - ->sum('est_area'); - - $project = Project::where('uuid', $projectId) - ->first(); - - if (! $project) { - return response()->json(['error' => 'Project not found for the given project ID', 'projectId' => $projectId], 200); - } - - $totalHectaresRestoredGoal = $project->total_hectares_restored_goal; - if ($totalHectaresRestoredGoal === null || $totalHectaresRestoredGoal === 0) { - return response()->json(['error' => 'Total hectares restored goal not set for the project'], 400); - } - $lowerBound = 0.75 * $totalHectaresRestoredGoal; - $upperBound = 1.25 * $totalHectaresRestoredGoal; - $valid = $sumEstArea >= $lowerBound && $sumEstArea <= $upperBound; $insertionSuccess = App::make(PolygonService::class) - ->createCriteriaSite($uuid, PolygonService::ESTIMATED_AREA_CRITERIA_ID, $valid); + ->createCriteriaSite($uuid, PolygonService::ESTIMATED_AREA_CRITERIA_ID, $response['valid']); + $response['insertion_success'] = $insertionSuccess; - return response()->json(['valid' => $valid, 'sum_area_project' => $sumEstArea, 'total_area_project' => $totalHectaresRestoredGoal, 'insertionSuccess' => $insertionSuccess], 200); + return response()->json($response); } public function getPolygonsAsGeoJSON() diff --git a/app/Validators/Extensions/Polygons/EstimatedArea.php b/app/Validators/Extensions/Polygons/EstimatedArea.php new file mode 100644 index 000000000..adf0fee12 --- /dev/null +++ b/app/Validators/Extensions/Polygons/EstimatedArea.php @@ -0,0 +1,58 @@ + ':attribute'], + 'The :attribute field must represent a polygon that matches the site\'s estimated area', + ]; + + public static function passes($attribute, $value, $parameters, $validator): bool + { + return self::getAreaData($value)['valid']; + } + + public const LOWER_BOUND_MULTIPLIER = 0.75; + public const UPPER_BOUND_MULTIPLIER = 1.25; + + public static function getAreaData(string $polygonUuid): ?array + { + $sitePolygon = SitePolygon::forPolygonGeometry($polygonUuid)->first(); + if ($sitePolygon == null) { + return ['valid' => false, 'error' => 'Site polygon not found for the given polygon ID']; + } + + $project = Project::isUuid($sitePolygon->project_uuid)->first(); + if ($project == null) { + return [ + 'valid' => false, + 'error' => 'Project not found for the given Project ID', 'projectId' => $sitePolygon->project_id, + ]; + } + + if (empty($project->total_hectares_restored_goal)) { + return ['valid' => false, 'error' => 'Total hectares restored goal not set for the project']; + } + + $sumEstArea = SitePolygon::where('project_id', $sitePolygon->project_id)->sum('est_area'); + $lowerBound = self::LOWER_BOUND_MULTIPLIER * $project->total_hectares_restored_goal; + $upperBound = self::UPPER_BOUND_MULTIPLIER * $project->total_hectares_restored_goal; + $valid = $sumEstArea >= $lowerBound && $sumEstArea <= $upperBound; + + return [ + 'valid' => $valid, + 'sum_area_project' => $sumEstArea, + 'total_area_project' => $project->total_hectares_restored_goal, + ]; + } +} diff --git a/app/Validators/Extensions/Polygons/NotOverlapping.php b/app/Validators/Extensions/Polygons/NotOverlapping.php index 597a15986..0c350dc16 100644 --- a/app/Validators/Extensions/Polygons/NotOverlapping.php +++ b/app/Validators/Extensions/Polygons/NotOverlapping.php @@ -11,7 +11,7 @@ class NotOverlapping extends Extension public static $name = 'not_overlapping'; public static $message = [ - 'HAS_POLYGON_SITE', + 'NOT_OVERLAPPING', 'The {{attribute}} field must represent a polygon that does not overlap with other site polygons', ['attribute' => ':attribute'], 'The :attribute field must represent a polygon that does not overlap with other site polygons', diff --git a/app/Validators/Validator.php b/app/Validators/Validator.php index 87eb4722e..a0cdcfaca 100644 --- a/app/Validators/Validator.php +++ b/app/Validators/Validator.php @@ -66,6 +66,7 @@ class Validator \App\Validators\Extensions\TerrafundDisturbance::class, \App\Validators\Extensions\CompletePercentage::class, \App\Validators\Extensions\OrganisationFileType::class, + \App\validators\Extensions\Polygons\EstimatedArea::class, \App\validators\Extensions\Polygons\FeatureBounds::class, \App\validators\Extensions\Polygons\HasPolygonSite::class, \App\validators\Extensions\Polygons\NotOverlapping::class, From 5e45980787bb94b7d17a351965b764b5add12315 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Thu, 2 May 2024 10:37:46 -0700 Subject: [PATCH 11/64] [TM-799] Improve error message handling for polygon validation. --- .../TerrafundCreateGeometryController.php | 76 ++++++++----------- .../Extensions/Polygons/EstimatedArea.php | 7 +- .../Extensions/Polygons/NotOverlapping.php | 9 +-- .../Extensions/Polygons/WithinCountry.php | 21 +++-- 4 files changed, 54 insertions(+), 59 deletions(-) diff --git a/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php b/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php index dd1113ea4..02ce9688f 100644 --- a/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php +++ b/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php @@ -15,6 +15,7 @@ use App\Validators\Extensions\Polygons\SelfIntersection; use App\Validators\Extensions\Polygons\Spikes; use App\Validators\Extensions\Polygons\WithinCountry; +use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\DB; @@ -273,28 +274,11 @@ public function checkWithinCountry(Request $request) { $polygonUuid = $request->input('uuid'); - if ($polygonUuid === null || $polygonUuid === '') { - return response()->json(['error' => 'UUID not provided'], 200); - } - - $geometry = PolygonGeometry::isUuid($polygonUuid)->first(); - - if (! $geometry) { - return response()->json(['error' => 'Geometry not found'], 404); - } - - $response = WithinCountry::getIntersectionData($polygonUuid); - if ($response == null) { - return response()->json(['error' => 'Data is missing for within country calculation'], 404); - } - - $insertionSuccess = App::make(PolygonService::class) - ->createCriteraSite($polygonUuid, PolygonService::WITHIN_COUNTRY_CRITERIA_ID, $response['valid']); - - $response['geometry_id'] = $geometry->id; - $response['insertion_success'] = $insertionSuccess; - - return response()->json($response); + return $this->handlePolygonValidation( + $polygonUuid, + WithinCountry::getIntersectionData($polygonUuid), + PolygonService::WITHIN_COUNTRY_CRITERIA_ID + ); } public function getGeometryType(Request $request) @@ -373,35 +357,23 @@ public function uploadGeoJSONFile(Request $request) public function validateOverlapping(Request $request) { $uuid = $request->input('uuid'); - $response = NotOverlapping::getIntersectionData($uuid); - if ($response == null) { - return response()->json(['error' => 'Data is missing for overlapping calculation'], 404); - } - $insertionSuccess = App::make(PolygonService::class) - ->createCriteriaSite($uuid, PolygonService::OVERLAPPING_CRITERIA_ID, $response['valid']); - - $response['uuid'] = $uuid; - $response['criteria_success'] = $insertionSuccess; - - return response()->json($response); + return $this->handlePolygonValidation( + $uuid, + NotOverlapping::getIntersectionData($uuid), + PolygonService::OVERLAPPING_CRITERIA_ID + ); } public function validateEstimatedArea(Request $request) { $uuid = $request->input('uuid'); - $response = EstimatedArea::getAreaData($uuid); - if ($response['error'] != null) { - unset($response['valid']); - return response()->json($response); - } - - $insertionSuccess = App::make(PolygonService::class) - ->createCriteriaSite($uuid, PolygonService::ESTIMATED_AREA_CRITERIA_ID, $response['valid']); - $response['insertion_success'] = $insertionSuccess; - - return response()->json($response); + return $this->handlePolygonValidation( + $uuid, + EstimatedArea::getAreaData($uuid), + PolygonService::ESTIMATED_AREA_CRITERIA_ID + ); } public function getPolygonsAsGeoJSON() @@ -444,4 +416,20 @@ public function getAllCountryNames() return response()->json(['countries' => $countries]); } + + private function handlePolygonValidation($polygonUuid, $response, $criteriaId): JsonResponse + { + if ($response['error'] != null) { + $status = $response['status']; + unset($response['valid']); + unset($response['status']); + + return response()->json($response, $status); + } + + $response['insertion_success'] = App::make(PolygonService::class) + ->createCriteraSite($polygonUuid, $criteriaId, $response['valid']); + + return response()->json($response); + } } diff --git a/app/Validators/Extensions/Polygons/EstimatedArea.php b/app/Validators/Extensions/Polygons/EstimatedArea.php index adf0fee12..13f636dff 100644 --- a/app/Validators/Extensions/Polygons/EstimatedArea.php +++ b/app/Validators/Extensions/Polygons/EstimatedArea.php @@ -25,11 +25,11 @@ public static function passes($attribute, $value, $parameters, $validator): bool public const LOWER_BOUND_MULTIPLIER = 0.75; public const UPPER_BOUND_MULTIPLIER = 1.25; - public static function getAreaData(string $polygonUuid): ?array + public static function getAreaData(string $polygonUuid): array { $sitePolygon = SitePolygon::forPolygonGeometry($polygonUuid)->first(); if ($sitePolygon == null) { - return ['valid' => false, 'error' => 'Site polygon not found for the given polygon ID']; + return ['valid' => false, 'error' => 'Site polygon not found for the given polygon ID', 'status' => 404]; } $project = Project::isUuid($sitePolygon->project_uuid)->first(); @@ -37,11 +37,12 @@ public static function getAreaData(string $polygonUuid): ?array return [ 'valid' => false, 'error' => 'Project not found for the given Project ID', 'projectId' => $sitePolygon->project_id, + 'status' => 404, ]; } if (empty($project->total_hectares_restored_goal)) { - return ['valid' => false, 'error' => 'Total hectares restored goal not set for the project']; + return ['valid' => false, 'error' => 'Total hectares restored goal not set for the project', 'status' => 500]; } $sumEstArea = SitePolygon::where('project_id', $sitePolygon->project_id)->sum('est_area'); diff --git a/app/Validators/Extensions/Polygons/NotOverlapping.php b/app/Validators/Extensions/Polygons/NotOverlapping.php index 0c350dc16..136982f7c 100644 --- a/app/Validators/Extensions/Polygons/NotOverlapping.php +++ b/app/Validators/Extensions/Polygons/NotOverlapping.php @@ -19,16 +19,14 @@ class NotOverlapping extends Extension public static function passes($attribute, $value, $parameters, $validator): bool { - $result = self::getIntersectionData($value); - - return $result != null && $result['valid']; + return self::getIntersectionData($value)['valid']; } - public static function getIntersectionData(string $polygonUuid): ?array + public static function getIntersectionData(string $polygonUuid): array { $sitePolygon = SitePolygon::forPolygonGeometry($polygonUuid)->first(); if ($sitePolygon == null) { - return null; + return ['valid' => false, 'error' => 'Site polygon not found for the given polygon ID', 'status' => 404]; } $relatedPolyIds = SitePolygon::where('project_id', $sitePolygon->project_id) @@ -48,6 +46,7 @@ public static function getIntersectionData(string $polygonUuid): ?array return [ 'valid' => ! in_array(1, $intersects->toArray()), + 'uuid' => $polygonUuid, 'project_id' => $sitePolygon->project_id, ]; } diff --git a/app/Validators/Extensions/Polygons/WithinCountry.php b/app/Validators/Extensions/Polygons/WithinCountry.php index efeb94169..129f398e9 100644 --- a/app/Validators/Extensions/Polygons/WithinCountry.php +++ b/app/Validators/Extensions/Polygons/WithinCountry.php @@ -22,22 +22,28 @@ class WithinCountry extends Extension public static function passes($attribute, $value, $parameters, $validator): bool { - $result = self::getIntersectionData($value); - - return $result != null && $result['valid']; + return self::getIntersectionData($value)['valid']; } - public static function getIntersectionData(string $polygonUuid): ?array + public static function getIntersectionData(string $polygonUuid): array { + if (empty($polygonUuid)) { + return ['valid' => false, 'status' => 404, 'error' => 'UUID not provided']; + } + $geometry = PolygonGeometry::isUuid($polygonUuid)->first(); + if ($geometry === null) { + return ['valid' => false, 'status' => 404, 'error' => 'Geometry not found']; + } + $sitePolygonData = SitePolygon::forPolygonGeometry($polygonUuid)->select('id', 'project_id')->first(); - if ($geometry == null || $sitePolygonData == null) { - return null; + if ($sitePolygonData == null) { + return ['valid' => false, 'status' => 404, 'error' => 'Site polygon data not found for the specified polygonUuid']; } $countryIso = $sitePolygonData->project->country; if ($countryIso == null) { - return null; + return ['valid' => false, 'status' => 404, 'error' => 'Country ISO not found for the specified project_id']; } $intersectionData = WorldCountryGeneralized::forIso($countryIso) @@ -58,6 +64,7 @@ public static function getIntersectionData(string $polygonUuid): ?array return [ 'valid' => $insidePercentage >= self::THRESHOLD_PERCENTAGE, + 'geometry_id' => $geometry->id, 'inside_percentage' => $insidePercentage, 'country_name' => $intersectionData->country, ]; From 7f907ea615ae1058d31a2f8c38867770c0275272 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Thu, 2 May 2024 15:12:00 -0700 Subject: [PATCH 12/64] [TM-799] Move validation to the controller. --- .../V2/Terrafund/TerrafundCreateGeometryController.php | 3 +++ app/Services/PolygonService.php | 5 ----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php b/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php index 02ce9688f..3299e6194 100644 --- a/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php +++ b/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php @@ -15,6 +15,7 @@ use App\Validators\Extensions\Polygons\SelfIntersection; use App\Validators\Extensions\Polygons\Spikes; use App\Validators\Extensions\Polygons\WithinCountry; +use App\Validators\SitePolygonValidator; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\App; @@ -62,6 +63,8 @@ public function insertGeojsonToDB(string $geojsonFilename) $geojsonData = Storage::get("public/geojson_files/{$geojsonFilename}"); $geojson = json_decode($geojsonData, true); + SitePolygonValidator::validate('FEATURE_BOUNDS', $geojson); + return App::make(PolygonService::class)->createGeojsonModels($geojson); } diff --git a/app/Services/PolygonService.php b/app/Services/PolygonService.php index f13bfcb89..ed4218e73 100644 --- a/app/Services/PolygonService.php +++ b/app/Services/PolygonService.php @@ -22,13 +22,8 @@ class PolygonService public const SCHEMA_CRITERIA_ID = 13; public const DATA_CRITERIA_ID = 14; - /** - * @throws ValidationException - */ public function createGeojsonModels($geojson, $sitePolygonProperties = []): array { - SitePolygonValidator::validate('FEATURE_BOUNDS', $geojson); - $uuids = []; foreach ($geojson['features'] as $feature) { if ($feature['geometry']['type'] === 'Polygon') { From a9ff6b2ba4377d6f70d4ba24536a4f1946f17dc2 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Fri, 3 May 2024 11:40:03 -0700 Subject: [PATCH 13/64] [TM-878] Drive-by fix: use the builder pattern for nothing to report controller routing. --- .../ModelInterfaceBindingMiddleware.php | 23 +++++++++++++++---- routes/api_v2.php | 9 +++----- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/app/Http/Middleware/ModelInterfaceBindingMiddleware.php b/app/Http/Middleware/ModelInterfaceBindingMiddleware.php index 87118f1f4..53ab7f0ab 100644 --- a/app/Http/Middleware/ModelInterfaceBindingMiddleware.php +++ b/app/Http/Middleware/ModelInterfaceBindingMiddleware.php @@ -53,8 +53,12 @@ class ModelInterfaceBindingMiddleware private static array $typeSlugsCache = []; - public static function with(string $interface, callable $routeGroup, string $prefix = null, string $modelParameter = null): RouteRegistrar - { + public static function with( + string $interface, + callable $routeGroup, + string $prefix = null, + string $modelParameter = null, + ): RouteRegistrar { $typeSlugs = self::$typeSlugsCache[$interface] ?? []; if (empty($typeSlugs)) { foreach (self::CONCRETE_MODELS as $slug => $concrete) { @@ -66,11 +70,22 @@ public static function with(string $interface, callable $routeGroup, string $pre self::$typeSlugsCache[$interface] = $typeSlugs; } - $middleware = $modelParameter == null ? 'modelInterface' : "modelInterface:$modelParameter"; + return self::forSlugs($typeSlugs, $routeGroup, $prefix, $modelParameter); + } + /** + * @param array $typeSlugs The type slugs in use must be defined in CONCRETE_MODELS for the middleware + * to function. + */ + public static function forSlugs( + array $typeSlugs, + callable $routeGroup, + string $prefix = null, + string $modelParameter = null, + ): RouteRegistrar { return Route::prefix("$prefix/{modelSlug}") ->whereIn('modelSlug', $typeSlugs) - ->middleware($middleware) + ->middleware($modelParameter == null ? 'modelInterface' : "modelInterface:$modelParameter") ->group($routeGroup); } diff --git a/routes/api_v2.php b/routes/api_v2.php index 426143a3e..5303dd0e0 100644 --- a/routes/api_v2.php +++ b/routes/api_v2.php @@ -531,12 +531,9 @@ Route::put('/{task}/submit', SubmitProjectTasksController::class); }); -Route::prefix('{modelSlug}') - ->whereIn('modelSlug', ['site-reports', 'nursery-reports']) - ->middleware('modelInterface') - ->group(function () { - Route::put('/{report}/nothing-to-report', NothingToReportReportController::class); - }); +ModelInterfaceBindingMiddleware::forSlugs(['site-reports', 'nursery-reports'], function () { + Route::put('/{report}/nothing-to-report', NothingToReportReportController::class); +}); ModelInterfaceBindingMiddleware::with(EntityModel::class, function () { Route::get('/{entity}', ViewEntityController::class); From 132ea9ed827a8cd41e46cd82bcfb6df608e7791c Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Fri, 3 May 2024 11:56:07 -0700 Subject: [PATCH 14/64] [TM-878] Use the interface middleware for workdays and tree species. --- .../GetTreeSpeciesForEntityController.php | 62 ++----------------- .../GetWorkdaysForEntityController.php | 40 ++---------- routes/api_v2.php | 12 ++-- 3 files changed, 16 insertions(+), 98 deletions(-) diff --git a/app/Http/Controllers/V2/TreeSpecies/GetTreeSpeciesForEntityController.php b/app/Http/Controllers/V2/TreeSpecies/GetTreeSpeciesForEntityController.php index 45fa259f8..26f621b49 100644 --- a/app/Http/Controllers/V2/TreeSpecies/GetTreeSpeciesForEntityController.php +++ b/app/Http/Controllers/V2/TreeSpecies/GetTreeSpeciesForEntityController.php @@ -4,72 +4,20 @@ use App\Http\Controllers\Controller; use App\Http\Resources\V2\TreeSpecies\TreeSpeciesCollection; -use App\Models\V2\Nurseries\Nursery; -use App\Models\V2\Nurseries\NurseryReport; -use App\Models\V2\Projects\Project; -use App\Models\V2\Projects\ProjectReport; -use App\Models\V2\Sites\Site; -use App\Models\V2\Sites\SiteReport; +use App\Models\V2\EntityModel; use App\Models\V2\TreeSpecies\TreeSpecies; -use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; class GetTreeSpeciesForEntityController extends Controller { - public function __invoke(Request $request, string $entity, string $uuid) + public function __invoke(Request $request, EntityModel $entity) { - $model = $this->getModel($entity); - - if (is_null($model)) { - return new JsonResponse($entity . ' is not a valid entity key', 422); - } - - $object = $model::isUuid($uuid)->first(); - - $this->authorize('read', $object); - - if (is_null($object)) { - return new JsonResponse($entity . ' record not found', 404); - } + $this->authorize('read', $entity); $query = TreeSpecies::query() - ->where('speciesable_type', $model) - ->where('speciesable_id', $object->id); + ->where('speciesable_type', get_class($entity)) + ->where('speciesable_id', $entity->id); return new TreeSpeciesCollection($query->paginate()); } - - private function getModel(string $entity) - { - $model = null; - - switch ($entity) { - case 'project': - $model = Project::class; - - break; - case 'site': - $model = Site::class; - - break; - case 'nursery': - $model = Nursery::class; - - break; - case 'project-report': - $model = ProjectReport::class; - - break; - case 'site-report': - $model = SiteReport::class; - - break; - case 'nursery-report': - $model = NurseryReport::class; - - break; - } - - return $model; - } } diff --git a/app/Http/Controllers/V2/Workdays/GetWorkdaysForEntityController.php b/app/Http/Controllers/V2/Workdays/GetWorkdaysForEntityController.php index ade27985f..4c686fa01 100644 --- a/app/Http/Controllers/V2/Workdays/GetWorkdaysForEntityController.php +++ b/app/Http/Controllers/V2/Workdays/GetWorkdaysForEntityController.php @@ -4,36 +4,22 @@ use App\Http\Controllers\Controller; use App\Http\Resources\V2\Workdays\WorkdaysCollection; -use App\Models\V2\Projects\ProjectReport; -use App\Models\V2\Sites\SiteReport; +use App\Models\V2\EntityModel; use App\Models\V2\Workdays\Workday; -use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Spatie\QueryBuilder\AllowedFilter; use Spatie\QueryBuilder\QueryBuilder; class GetWorkdaysForEntityController extends Controller { - public function __invoke(Request $request, string $entity, string $uuid) + public function __invoke(Request $request, EntityModel $entity) { - $model = $this->getModel($entity); $perPage = $request->query('per_page') ?? config('app.pagination_default', 15); - - if (is_null($model)) { - return new JsonResponse($entity . ' is not a valid entity key', 422); - } - - $object = $model::isUuid($uuid)->first(); - - $this->authorize('update', $object); - - if (is_null($object)) { - return new JsonResponse($entity . ' record not found', 404); - } + $this->authorize('update', $entity); $qry = QueryBuilder::for(Workday::class) - ->where('workdayable_type', $model) - ->where('workdayable_id', $object->id) + ->where('workdayable_type', get_class($entity)) + ->where('workdayable_id', $entity->id) ->allowedFilters([ AllowedFilter::exact('collection'), ]); @@ -45,20 +31,4 @@ public function __invoke(Request $request, string $entity, string $uuid) return (new WorkdaysCollection($collection))->params(['count_total' => $totalAmount ]); } - - private function getModel(string $entity) - { - $model = null; - - switch ($entity) { - case 'project-report': - $model = ProjectReport::class; - - break; - case 'site-report': - $model = SiteReport::class; - } - - return $model; - } } diff --git a/routes/api_v2.php b/routes/api_v2.php index 5303dd0e0..41d4b6921 100644 --- a/routes/api_v2.php +++ b/routes/api_v2.php @@ -451,13 +451,13 @@ Route::put('/submit/{projectPitch}', SubmitProjectPitchController::class); }); -Route::prefix('tree-species')->group(function () { - Route::get('/{entity}/{uuid}', GetTreeSpeciesForEntityController::class); -}); +ModelInterfaceBindingMiddleware::with(EntityModel::class, function () { + Route::get('/{entity}', GetTreeSpeciesForEntityController::class); +}, prefix: 'tree-species'); -Route::prefix('workdays')->group(function () { - Route::get('/{entity}/{uuid}', GetWorkdaysForEntityController::class); -}); +ModelInterfaceBindingMiddleware::forSlugs(['project-report', 'site-report'], function () { + Route::get('/{entity}', GetWorkdaysForEntityController::class); +}, prefix: 'workdays'); Route::prefix('stratas')->group(function () { Route::post('/', StoreStrataController::class); From 09bc5fdb22aa2fddf374b8228de115e51940954b Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Fri, 3 May 2024 12:15:21 -0700 Subject: [PATCH 15/64] [TM-878] Remove pagination from the workdays endpoint. --- .../GetWorkdaysForEntityController.php | 8 +- .../V2/Workdays/WorkdaysCollection.php | 16 ---- .../V2/definitions/V2WorkdaysPaginated.yml | 30 -------- openapi-src/V2/definitions/_index.yml | 2 - .../Workdays/get-v2-workdays-entity-uuid.yml | 9 ++- resources/docs/swagger-v2.yml | 73 +------------------ .../GetWorkdaysForEntityControllerTest.php | 2 +- 7 files changed, 10 insertions(+), 130 deletions(-) delete mode 100644 openapi-src/V2/definitions/V2WorkdaysPaginated.yml diff --git a/app/Http/Controllers/V2/Workdays/GetWorkdaysForEntityController.php b/app/Http/Controllers/V2/Workdays/GetWorkdaysForEntityController.php index 4c686fa01..0e4061413 100644 --- a/app/Http/Controllers/V2/Workdays/GetWorkdaysForEntityController.php +++ b/app/Http/Controllers/V2/Workdays/GetWorkdaysForEntityController.php @@ -14,7 +14,6 @@ class GetWorkdaysForEntityController extends Controller { public function __invoke(Request $request, EntityModel $entity) { - $perPage = $request->query('per_page') ?? config('app.pagination_default', 15); $this->authorize('update', $entity); $qry = QueryBuilder::for(Workday::class) @@ -24,11 +23,6 @@ public function __invoke(Request $request, EntityModel $entity) AllowedFilter::exact('collection'), ]); - $totalAmount = $qry->sum('amount'); - - $collection = $qry->paginate($perPage) - ->appends(request()->query()); - - return (new WorkdaysCollection($collection))->params(['count_total' => $totalAmount ]); + return new WorkdaysCollection($qry->get()); } } diff --git a/app/Http/Resources/V2/Workdays/WorkdaysCollection.php b/app/Http/Resources/V2/Workdays/WorkdaysCollection.php index 2960f3a1a..afd68bcde 100644 --- a/app/Http/Resources/V2/Workdays/WorkdaysCollection.php +++ b/app/Http/Resources/V2/Workdays/WorkdaysCollection.php @@ -6,24 +6,8 @@ class WorkdaysCollection extends ResourceCollection { - protected $params; - - public function params(array $params = null) - { - $this->params = $params; - - return $this; - } - public function toArray($request) { return ['data' => WorkdayResource::collection($this->collection)]; } - - public function paginationInformation($request, $paginated, $default) - { - $default['meta']['count_total'] = data_get($this->params, 'count_total'); - - return $default; - } } diff --git a/openapi-src/V2/definitions/V2WorkdaysPaginated.yml b/openapi-src/V2/definitions/V2WorkdaysPaginated.yml deleted file mode 100644 index 1265f48c2..000000000 --- a/openapi-src/V2/definitions/V2WorkdaysPaginated.yml +++ /dev/null @@ -1,30 +0,0 @@ -type: object -properties: - data: - type: array - items: - $ref: './_index.yml#/V2WorkdayRead' - links: - type: object - properties: - first: - type: string - last: - type: string - prev: - type: string - next: - type: string - meta: - type: object - properties: - current_page: - type: integer - from: - type: integer - last_page: - type: integer - next: - type: integer - unfiltered_total: - type: integer diff --git a/openapi-src/V2/definitions/_index.yml b/openapi-src/V2/definitions/_index.yml index 5ebd90ac2..89a66ff9f 100644 --- a/openapi-src/V2/definitions/_index.yml +++ b/openapi-src/V2/definitions/_index.yml @@ -152,8 +152,6 @@ V2SeedingPaginated: $ref: './V2SeedingPaginated.yml' V2WorkdayRead: $ref: './V2WorkdayRead.yml' -V2WorkdaysPaginated: - $ref: './V2WorkdaysPaginated.yml' V2DisturbanceRead: $ref: './V2DisturbanceRead.yml' V2DisturbanceCreate: diff --git a/openapi-src/V2/paths/Workdays/get-v2-workdays-entity-uuid.yml b/openapi-src/V2/paths/Workdays/get-v2-workdays-entity-uuid.yml index c4d5f1fd9..b88b41b92 100644 --- a/openapi-src/V2/paths/Workdays/get-v2-workdays-entity-uuid.yml +++ b/openapi-src/V2/paths/Workdays/get-v2-workdays-entity-uuid.yml @@ -7,7 +7,7 @@ parameters: name: ENTITY in: path required: true - description: allowed values project/site/nursery/project-reports/site-reports/nursery-reports + description: allowed values project-report/site-report - type: string name: UUID in: path @@ -16,4 +16,9 @@ responses: '200': description: OK schema: - $ref: '../../definitions/_index.yml#/V2WorkdaysPaginated' + type: object + properties: + data: + type: array + items: + $ref: '../../definitions/_index.yml#/V2WorkdayRead' diff --git a/resources/docs/swagger-v2.yml b/resources/docs/swagger-v2.yml index 74a86ac98..23f18c2a7 100644 --- a/resources/docs/swagger-v2.yml +++ b/resources/docs/swagger-v2.yml @@ -9266,53 +9266,6 @@ definitions: type: string indigeneity: type: string - V2WorkdaysPaginated: - type: object - properties: - data: - type: array - items: - title: V2WorkdayRead - type: object - properties: - uuid: - type: string - amount: - type: integer - collection: - type: string - gender: - type: string - age: - type: string - ethnicity: - type: string - indigeneity: - type: string - links: - type: object - properties: - first: - type: string - last: - type: string - prev: - type: string - next: - type: string - meta: - type: object - properties: - current_page: - type: integer - from: - type: integer - last_page: - type: integer - next: - type: integer - unfiltered_total: - type: integer V2DisturbanceRead: title: V2DisturbanceRead type: object @@ -57577,7 +57530,7 @@ paths: name: ENTITY in: path required: true - description: allowed values project/site/nursery/project-reports/site-reports/nursery-reports + description: allowed values project-report/site-report - type: string name: UUID in: path @@ -57608,30 +57561,6 @@ paths: type: string indigeneity: type: string - links: - type: object - properties: - first: - type: string - last: - type: string - prev: - type: string - next: - type: string - meta: - type: object - properties: - current_page: - type: integer - from: - type: integer - last_page: - type: integer - next: - type: integer - unfiltered_total: - type: integer /v2/stratas: post: operationId: post-v2-stratas diff --git a/tests/V2/Workdays/GetWorkdaysForEntityControllerTest.php b/tests/V2/Workdays/GetWorkdaysForEntityControllerTest.php index f6d94db26..b56abc683 100644 --- a/tests/V2/Workdays/GetWorkdaysForEntityControllerTest.php +++ b/tests/V2/Workdays/GetWorkdaysForEntityControllerTest.php @@ -43,7 +43,7 @@ public function test_invoke_action() 'status' => EntityStatusStateMachine::STARTED, ]); - $workday = Workday::factory()->create([ + Workday::factory()->create([ 'workdayable_type' => SiteReport::class, 'workdayable_id' => $report->id, ]); From 4f45fb3ab2b452089fa35c920507a8dfddc5766c Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Fri, 3 May 2024 12:43:27 -0700 Subject: [PATCH 16/64] [TM-878] Update workday API shape to use demographics. --- .../Workdays/WorkdayDemographicResource.php | 18 +++++ .../Resources/V2/Workdays/WorkdayResource.php | 10 +-- app/Models/V2/Workdays/Workday.php | 21 ++---- openapi-src/V2/definitions/V2WorkdayRead.yml | 14 ++-- .../V2/definitions/WorkdayDemographic.yml | 12 ++++ openapi-src/V2/definitions/_index.yml | 2 + resources/docs/swagger-v2.yml | 72 +++++++++++++------ 7 files changed, 95 insertions(+), 54 deletions(-) create mode 100644 app/Http/Resources/V2/Workdays/WorkdayDemographicResource.php create mode 100644 openapi-src/V2/definitions/WorkdayDemographic.yml diff --git a/app/Http/Resources/V2/Workdays/WorkdayDemographicResource.php b/app/Http/Resources/V2/Workdays/WorkdayDemographicResource.php new file mode 100644 index 000000000..6a95f0a91 --- /dev/null +++ b/app/Http/Resources/V2/Workdays/WorkdayDemographicResource.php @@ -0,0 +1,18 @@ + $this->type, + 'subtype' => $this->subtype, + 'name' => $this->name, + 'amount' => $this->amount, + ]; + } +} diff --git a/app/Http/Resources/V2/Workdays/WorkdayResource.php b/app/Http/Resources/V2/Workdays/WorkdayResource.php index b81b7734e..0c5280a33 100644 --- a/app/Http/Resources/V2/Workdays/WorkdayResource.php +++ b/app/Http/Resources/V2/Workdays/WorkdayResource.php @@ -6,21 +6,13 @@ class WorkdayResource extends JsonResource { - /** - * @param Request $request - * @return array - */ public function toArray($request) { return [ 'uuid' => $this->uuid, 'collection' => $this->collection, 'readable_collection' => $this->readable_collection, - 'amount' => $this->amount, - 'gender' => $this->gender, - 'age' => $this->age, - 'ethnicity' => $this->ethnicity, - 'indigeneity' => $this->indigeneity, + 'demographics' => WorkdayDemographicResource::collection($this->demographics), ]; } } diff --git a/app/Models/V2/Workdays/Workday.php b/app/Models/V2/Workdays/Workday.php index 1f5e0103d..cc1a75c34 100644 --- a/app/Models/V2/Workdays/Workday.php +++ b/app/Models/V2/Workdays/Workday.php @@ -8,7 +8,11 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; +use Illuminate\Support\Collection; +/** + * @property Collection $demographics + */ class Workday extends Model { use HasFactory; @@ -85,22 +89,9 @@ public function getRouteKeyName() return 'uuid'; } - public function genderDemographics(): HasMany + public function demographics(): HasMany { - return $this->hasMany(WorkdayDemographic::class) - ->where('type', WorkdayDemographic::GENDER); - } - - public function ageDemographics(): HasMany - { - return $this->hasMany(WorkdayDemographic::class) - ->where('type', WorkdayDemographic::AGE); - } - - public function ethnicityDemographics(): HasMany - { - return $this->hasMany(WorkdayDemographic::class) - ->where('type', WorkdayDemographic::ETHNICITY); + return $this->hasMany(WorkdayDemographic::class); } public function getReadableCollectionAttribute(): ?string diff --git a/openapi-src/V2/definitions/V2WorkdayRead.yml b/openapi-src/V2/definitions/V2WorkdayRead.yml index 0e551805c..72d547c2c 100644 --- a/openapi-src/V2/definitions/V2WorkdayRead.yml +++ b/openapi-src/V2/definitions/V2WorkdayRead.yml @@ -3,15 +3,9 @@ type: object properties: uuid: type: string - amount: - type: integer collection: type: string - gender: - type: string - age: - type: string - ethnicity: - type: string - indigeneity: - type: string \ No newline at end of file + demographics: + type: array + items: + $ref: './_index.yml#/WorkdayDemographic' diff --git a/openapi-src/V2/definitions/WorkdayDemographic.yml b/openapi-src/V2/definitions/WorkdayDemographic.yml new file mode 100644 index 000000000..9e3f7af9f --- /dev/null +++ b/openapi-src/V2/definitions/WorkdayDemographic.yml @@ -0,0 +1,12 @@ +title: WorkdayDemographic +type: object +properties: + type: + type: string + enum: [gender, age, ethnicity] + subtype: + type: string + name: + type: string + amount: + type: integer diff --git a/openapi-src/V2/definitions/_index.yml b/openapi-src/V2/definitions/_index.yml index 89a66ff9f..f0638013f 100644 --- a/openapi-src/V2/definitions/_index.yml +++ b/openapi-src/V2/definitions/_index.yml @@ -270,3 +270,5 @@ V2ProjectInviteRead: $ref: './V2ProjectInviteRead.yml' V2ProjectInviteCreate: $ref: './V2ProjectInviteCreate.yml' +WorkdayDemographic: + $ref: './WorkdayDemographic.yml' diff --git a/resources/docs/swagger-v2.yml b/resources/docs/swagger-v2.yml index 23f18c2a7..e2c5d5b25 100644 --- a/resources/docs/swagger-v2.yml +++ b/resources/docs/swagger-v2.yml @@ -9254,18 +9254,26 @@ definitions: properties: uuid: type: string - amount: - type: integer collection: type: string - gender: - type: string - age: - type: string - ethnicity: - type: string - indigeneity: - type: string + demographics: + type: array + items: + title: WorkdayDemographic + type: object + properties: + type: + type: string + enum: + - gender + - age + - ethnicity + subtype: + type: string + name: + type: string + amount: + type: integer V2DisturbanceRead: title: V2DisturbanceRead type: object @@ -43950,6 +43958,22 @@ definitions: properties: email_address: type: string + WorkdayDemographic: + title: WorkdayDemographic + type: object + properties: + type: + type: string + enum: + - gender + - age + - ethnicity + subtype: + type: string + name: + type: string + amount: + type: integer paths: '/v2/tree-species/{entity}/{UUID}': get: @@ -57549,18 +57573,26 @@ paths: properties: uuid: type: string - amount: - type: integer collection: type: string - gender: - type: string - age: - type: string - ethnicity: - type: string - indigeneity: - type: string + demographics: + type: array + items: + title: WorkdayDemographic + type: object + properties: + type: + type: string + enum: + - gender + - age + - ethnicity + subtype: + type: string + name: + type: string + amount: + type: integer /v2/stratas: post: operationId: post-v2-stratas From 756bf7cb6bd3e3b1bcd74bce2a0b22baf0998baa Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Fri, 3 May 2024 16:19:41 -0700 Subject: [PATCH 17/64] [TM-878] Handle syncing workday demographics. --- .../Interfaces/HandlesLinkedFieldSync.php | 10 +++++ app/Models/Traits/UsesLinkedFields.php | 8 ++++ app/Models/V2/Workdays/Workday.php | 45 ++++++++++++++++++- 3 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 app/Models/Interfaces/HandlesLinkedFieldSync.php diff --git a/app/Models/Interfaces/HandlesLinkedFieldSync.php b/app/Models/Interfaces/HandlesLinkedFieldSync.php new file mode 100644 index 000000000..781cc9822 --- /dev/null +++ b/app/Models/Interfaces/HandlesLinkedFieldSync.php @@ -0,0 +1,10 @@ +$property()->getMorphClass(); + if (is_a($class, HandlesLinkedFieldSync::class, true)) { + $class::syncRelation($this, $property, $data); + + return; + } + $this->$property()->whereNotIn('uuid', $data->pluck('uuid')->filter())->delete(); // This would be better as a bulk operation, but too much processing is required to make that feasible diff --git a/app/Models/V2/Workdays/Workday.php b/app/Models/V2/Workdays/Workday.php index cc1a75c34..264bb0e74 100644 --- a/app/Models/V2/Workdays/Workday.php +++ b/app/Models/V2/Workdays/Workday.php @@ -2,8 +2,11 @@ namespace App\Models\V2\Workdays; +use App\Models\Interfaces\HandlesLinkedFieldSync; use App\Models\Traits\HasTypes; use App\Models\Traits\HasUuid; +use App\Models\V2\EntityModel; +use http\Exception\InvalidArgumentException; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -13,7 +16,7 @@ /** * @property Collection $demographics */ -class Workday extends Model +class Workday extends Model implements HandlesLinkedFieldSync { use HasFactory; use SoftDeletes; @@ -79,6 +82,46 @@ class Workday extends Model self::COLLECTION_SITE_VOLUNTEER_OTHER => 'Volunteer Other Activities', ]; + public static function syncRelation(EntityModel $entity, string $property, $data): void + { + // Workdays only have one instance per collection + $workday = $entity->$property()->first(); + if ($workday != null && $workday->collection != $data['collection']) { + throw new InvalidArgumentException( + 'Workday collection does not match entity property [' . + 'property collection: ' . $workday->collection . ', ' . + 'submitted collection: ' . $data['collection'] . ']' + ); + } + + if ($workday == null) { + $workday = Workday::create([ + 'workdayable_type' => get_class($entity), + 'workdayable_id' => $entity->id, + 'collection' => $data['collection'], + ]); + } + + foreach ($data['demographics'] as $demographicData) { + $demographic = $workday->demographics()->where([ + 'type' => $demographicData['type'], + 'subtype' => $demographicData['subtype'], + 'name' => $demographicData['name'], + ]); + + if ($demographic == null) { + $workday->demographics()->create([ + 'type' => $demographicData['type'], + 'subtype' => $demographicData['subtype'], + 'name' => $demographicData['name'], + 'amount' => $demographicData['amount'], + ]); + } else { + $demographic->update(['amount' => $demographicData['amount']]); + } + } + } + public function workdayable() { return $this->morphTo(); From 9d7ac712b0b308b7b444c273c544d5d94ad9c41e Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Sun, 5 May 2024 13:57:18 -0700 Subject: [PATCH 18/64] [TM-880] Update the workdays get endpoint to include demographics. --- .../GetWorkdaysForEntityController.php | 37 ++++++++++++++----- .../Resources/V2/Workdays/WorkdayResource.php | 2 +- .../V2/Workdays/WorkdaysCollection.php | 13 ------- app/Models/V2/Workdays/Workday.php | 24 +++++++----- openapi-src/V2/definitions/V2WorkdayRead.yml | 2 + resources/docs/swagger-v2.yml | 4 ++ 6 files changed, 49 insertions(+), 33 deletions(-) delete mode 100644 app/Http/Resources/V2/Workdays/WorkdaysCollection.php diff --git a/app/Http/Controllers/V2/Workdays/GetWorkdaysForEntityController.php b/app/Http/Controllers/V2/Workdays/GetWorkdaysForEntityController.php index 0e4061413..aa0ad899c 100644 --- a/app/Http/Controllers/V2/Workdays/GetWorkdaysForEntityController.php +++ b/app/Http/Controllers/V2/Workdays/GetWorkdaysForEntityController.php @@ -3,26 +3,43 @@ namespace App\Http\Controllers\V2\Workdays; use App\Http\Controllers\Controller; -use App\Http\Resources\V2\Workdays\WorkdaysCollection; +use App\Http\Resources\V2\Workdays\WorkdayResource; use App\Models\V2\EntityModel; use App\Models\V2\Workdays\Workday; +use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Http\Request; -use Spatie\QueryBuilder\AllowedFilter; -use Spatie\QueryBuilder\QueryBuilder; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; class GetWorkdaysForEntityController extends Controller { + /** + * @throws AuthorizationException + * @throws \JsonException + */ public function __invoke(Request $request, EntityModel $entity) { $this->authorize('update', $entity); - $qry = QueryBuilder::for(Workday::class) - ->where('workdayable_type', get_class($entity)) - ->where('workdayable_id', $entity->id) - ->allowedFilters([ - AllowedFilter::exact('collection'), - ]); + $workdays = Workday::where([ + 'workdayable_type' => get_class($entity), + 'workdayable_id' => $entity->id, + ])->get(); - return new WorkdaysCollection($qry->get()); + $collections = match ($entity->shortName) { + 'site-report' => array_keys(Workday::$siteCollections), + 'project-report' => array_keys(Workday::$projectCollections), + default => throw new NotFoundHttpException(), + }; + foreach ($collections as $collection) { + if (!$workdays->keys()->contains($collection)) { + $workday = new Workday(); + // Allows the resource to return an API response with no demographics, but still containing + // the collection and readable collection name. + $workday['collection'] = $collection; + $workdays->push($workday); + } + } + + return WorkdayResource::collection($workdays); } } diff --git a/app/Http/Resources/V2/Workdays/WorkdayResource.php b/app/Http/Resources/V2/Workdays/WorkdayResource.php index 0c5280a33..aaaceaec0 100644 --- a/app/Http/Resources/V2/Workdays/WorkdayResource.php +++ b/app/Http/Resources/V2/Workdays/WorkdayResource.php @@ -12,7 +12,7 @@ public function toArray($request) 'uuid' => $this->uuid, 'collection' => $this->collection, 'readable_collection' => $this->readable_collection, - 'demographics' => WorkdayDemographicResource::collection($this->demographics), + 'demographics' => empty($this->demographics) ? [] : WorkdayDemographicResource::collection($this->demographics), ]; } } diff --git a/app/Http/Resources/V2/Workdays/WorkdaysCollection.php b/app/Http/Resources/V2/Workdays/WorkdaysCollection.php deleted file mode 100644 index afd68bcde..000000000 --- a/app/Http/Resources/V2/Workdays/WorkdaysCollection.php +++ /dev/null @@ -1,13 +0,0 @@ - WorkdayResource::collection($this->collection)]; - } -} diff --git a/app/Models/V2/Workdays/Workday.php b/app/Models/V2/Workdays/Workday.php index 264bb0e74..4150f5ac6 100644 --- a/app/Models/V2/Workdays/Workday.php +++ b/app/Models/V2/Workdays/Workday.php @@ -6,7 +6,6 @@ use App\Models\Traits\HasTypes; use App\Models\Traits\HasUuid; use App\Models\V2\EntityModel; -use http\Exception\InvalidArgumentException; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -82,12 +81,15 @@ class Workday extends Model implements HandlesLinkedFieldSync self::COLLECTION_SITE_VOLUNTEER_OTHER => 'Volunteer Other Activities', ]; + /** + * @throws \Exception + */ public static function syncRelation(EntityModel $entity, string $property, $data): void { // Workdays only have one instance per collection $workday = $entity->$property()->first(); if ($workday != null && $workday->collection != $data['collection']) { - throw new InvalidArgumentException( + throw new \Exception( 'Workday collection does not match entity property [' . 'property collection: ' . $workday->collection . ', ' . 'submitted collection: ' . $data['collection'] . ']' @@ -102,24 +104,28 @@ public static function syncRelation(EntityModel $entity, string $property, $data ]); } + $demographics = $workday->demographics; + $represented = collect(); foreach ($data['demographics'] as $demographicData) { - $demographic = $workday->demographics()->where([ + $demographic = $demographics->firstWhere([ 'type' => $demographicData['type'], 'subtype' => $demographicData['subtype'], 'name' => $demographicData['name'], ]); if ($demographic == null) { - $workday->demographics()->create([ - 'type' => $demographicData['type'], - 'subtype' => $demographicData['subtype'], - 'name' => $demographicData['name'], - 'amount' => $demographicData['amount'], - ]); + $workday->demographics()->create($demographicData); } else { + $represented->push($demographic->id); $demographic->update(['amount' => $demographicData['amount']]); } } + // Remove any existing demographic that wasn't in the submitted set. + foreach ($demographics as $demographic) { + if (!$represented->contains($demographic->id)) { + $demographic->delete(); + } + } } public function workdayable() diff --git a/openapi-src/V2/definitions/V2WorkdayRead.yml b/openapi-src/V2/definitions/V2WorkdayRead.yml index 72d547c2c..582cfc5a6 100644 --- a/openapi-src/V2/definitions/V2WorkdayRead.yml +++ b/openapi-src/V2/definitions/V2WorkdayRead.yml @@ -5,6 +5,8 @@ properties: type: string collection: type: string + readable_collection: + type: string demographics: type: array items: diff --git a/resources/docs/swagger-v2.yml b/resources/docs/swagger-v2.yml index e2c5d5b25..00b08d64f 100644 --- a/resources/docs/swagger-v2.yml +++ b/resources/docs/swagger-v2.yml @@ -9256,6 +9256,8 @@ definitions: type: string collection: type: string + readable_collection: + type: string demographics: type: array items: @@ -57575,6 +57577,8 @@ paths: type: string collection: type: string + readable_collection: + type: string demographics: type: array items: From c764bd2d9efc9a5b6ab92e91ee2c410062f4a9d7 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Mon, 6 May 2024 16:06:48 -0700 Subject: [PATCH 19/64] [TM-880] Fixes workday sync to DB from form data. --- app/Models/Traits/UsesLinkedFields.php | 2 +- app/Models/V2/Workdays/Workday.php | 22 ++++++++++++++-------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/app/Models/Traits/UsesLinkedFields.php b/app/Models/Traits/UsesLinkedFields.php index b98437515..fe30e485b 100644 --- a/app/Models/Traits/UsesLinkedFields.php +++ b/app/Models/Traits/UsesLinkedFields.php @@ -251,7 +251,7 @@ private function syncRelation(string $property, string $inputType, $data): void return; } - $class = $this->$property()->getMorphClass(); + $class = get_class($this->$property()->make()); if (is_a($class, HandlesLinkedFieldSync::class, true)) { $class::syncRelation($this, $property, $data); diff --git a/app/Models/V2/Workdays/Workday.php b/app/Models/V2/Workdays/Workday.php index 4150f5ac6..2bc4d0ad9 100644 --- a/app/Models/V2/Workdays/Workday.php +++ b/app/Models/V2/Workdays/Workday.php @@ -86,13 +86,19 @@ class Workday extends Model implements HandlesLinkedFieldSync */ public static function syncRelation(EntityModel $entity, string $property, $data): void { + if (count($data) == 0) { + $entity->$property()->delete(); + return; + } + // Workdays only have one instance per collection + $workdayData = $data[0]; $workday = $entity->$property()->first(); - if ($workday != null && $workday->collection != $data['collection']) { + if ($workday != null && $workday->collection != $workdayData['collection']) { throw new \Exception( 'Workday collection does not match entity property [' . 'property collection: ' . $workday->collection . ', ' . - 'submitted collection: ' . $data['collection'] . ']' + 'submitted collection: ' . $workdayData['collection'] . ']' ); } @@ -100,24 +106,24 @@ public static function syncRelation(EntityModel $entity, string $property, $data $workday = Workday::create([ 'workdayable_type' => get_class($entity), 'workdayable_id' => $entity->id, - 'collection' => $data['collection'], + 'collection' => $workdayData['collection'], ]); } $demographics = $workday->demographics; $represented = collect(); - foreach ($data['demographics'] as $demographicData) { + foreach ($workdayData['demographics'] as $demographicData) { $demographic = $demographics->firstWhere([ - 'type' => $demographicData['type'], - 'subtype' => $demographicData['subtype'], - 'name' => $demographicData['name'], + 'type' => data_get($demographicData,'type'), + 'subtype' => data_get($demographicData, 'subtype'), + 'name' => data_get($demographicData, 'name'), ]); if ($demographic == null) { $workday->demographics()->create($demographicData); } else { $represented->push($demographic->id); - $demographic->update(['amount' => $demographicData['amount']]); + $demographic->update(['amount' => data_get($demographicData, 'amount')]); } } // Remove any existing demographic that wasn't in the submitted set. From cc01eea10f9f2e2dc83a8d1a68ee12593a563dba Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Tue, 7 May 2024 15:25:26 -0700 Subject: [PATCH 20/64] [TM-878] Unit tests for the get workdays endpoint. --- .../GetWorkdaysForEntityController.php | 7 +- app/Models/V2/Workdays/Workday.php | 5 +- .../V2/Workdays/WorkdayDemographicFactory.php | 60 +++++++++++ .../factories/V2/Workdays/WorkdayFactory.php | 12 +-- .../GetWorkdaysForEntityControllerTest.php | 100 ++++++++++++++++-- 5 files changed, 161 insertions(+), 23 deletions(-) create mode 100644 database/factories/V2/Workdays/WorkdayDemographicFactory.php diff --git a/app/Http/Controllers/V2/Workdays/GetWorkdaysForEntityController.php b/app/Http/Controllers/V2/Workdays/GetWorkdaysForEntityController.php index aa0ad899c..087cdb144 100644 --- a/app/Http/Controllers/V2/Workdays/GetWorkdaysForEntityController.php +++ b/app/Http/Controllers/V2/Workdays/GetWorkdaysForEntityController.php @@ -25,13 +25,14 @@ public function __invoke(Request $request, EntityModel $entity) 'workdayable_id' => $entity->id, ])->get(); - $collections = match ($entity->shortName) { + $expectedCollections = match ($entity->shortName) { 'site-report' => array_keys(Workday::$siteCollections), 'project-report' => array_keys(Workday::$projectCollections), default => throw new NotFoundHttpException(), }; - foreach ($collections as $collection) { - if (!$workdays->keys()->contains($collection)) { + $collections = $workdays->pluck('collection'); + foreach ($expectedCollections as $collection) { + if (! $collections->contains($collection)) { $workday = new Workday(); // Allows the resource to return an API response with no demographics, but still containing // the collection and readable collection name. diff --git a/app/Models/V2/Workdays/Workday.php b/app/Models/V2/Workdays/Workday.php index 2bc4d0ad9..39b62a48e 100644 --- a/app/Models/V2/Workdays/Workday.php +++ b/app/Models/V2/Workdays/Workday.php @@ -88,6 +88,7 @@ public static function syncRelation(EntityModel $entity, string $property, $data { if (count($data) == 0) { $entity->$property()->delete(); + return; } @@ -114,7 +115,7 @@ public static function syncRelation(EntityModel $entity, string $property, $data $represented = collect(); foreach ($workdayData['demographics'] as $demographicData) { $demographic = $demographics->firstWhere([ - 'type' => data_get($demographicData,'type'), + 'type' => data_get($demographicData, 'type'), 'subtype' => data_get($demographicData, 'subtype'), 'name' => data_get($demographicData, 'name'), ]); @@ -128,7 +129,7 @@ public static function syncRelation(EntityModel $entity, string $property, $data } // Remove any existing demographic that wasn't in the submitted set. foreach ($demographics as $demographic) { - if (!$represented->contains($demographic->id)) { + if (! $represented->contains($demographic->id)) { $demographic->delete(); } } diff --git a/database/factories/V2/Workdays/WorkdayDemographicFactory.php b/database/factories/V2/Workdays/WorkdayDemographicFactory.php new file mode 100644 index 000000000..685528506 --- /dev/null +++ b/database/factories/V2/Workdays/WorkdayDemographicFactory.php @@ -0,0 +1,60 @@ + Workday::factory()->create()->id, + 'type' => 'gender', + 'subtype' => null, + 'name' => $this->faker->randomElement(self::GENDERS), + 'amount' => $this->faker->randomNumber([0, 5000]), + ]; + } + + public function gender() + { + return $this->state(function (array $attributes) { + return [ + 'type' => WorkdayDemographic::GENDER, + 'name' => $this->faker->randomElement(self::GENDERS), + ]; + }); + } + + public function age() + { + return $this->state(function (array $attributes) { + return [ + 'type' => WorkdayDemographic::AGE, + 'name' => $this->faker->randomElement(self::AGES), + ]; + }); + } + + public function ethnicity() + { + return $this->state(function (array $attributes) { + return [ + 'type' => WorkdayDemographic::ETHNICITY, + 'subtype' => $this->faker->randomElement(self::ETHNICITIES), + ]; + }); + } +} diff --git a/database/factories/V2/Workdays/WorkdayFactory.php b/database/factories/V2/Workdays/WorkdayFactory.php index 843904fa1..dfe7cf23a 100644 --- a/database/factories/V2/Workdays/WorkdayFactory.php +++ b/database/factories/V2/Workdays/WorkdayFactory.php @@ -15,19 +15,11 @@ class WorkdayFactory extends Factory */ public function definition() { - $gender = ['female', 'male', 'gender-undefined']; - $age = ['youth-15-24', 'adult-24-65', 'elder-65+', 'age-undefined']; - $ethnicity = ['middle-eastern', 'hispanic', 'irish', 'native-american', 'Jewish', 'pacific-islander', 'ethnicity-undefined']; - - return [ + 'uuid' => $this->faker->uuid(), 'workdayable_type' => SiteReport::class, 'workdayable_id' => SiteReport::factory()->create(), - 'amount' => $this->faker->numberBetween(0, 5000), - 'collection' => $this->faker->randomElement(Workday::$siteCollections), - 'gender' => $this->faker->randomElement($gender), - 'age' => $this->faker->randomElement($age), - 'ethnicity' => $this->faker->randomElement($ethnicity), + 'collection' => $this->faker->randomElement(array_keys(Workday::$siteCollections)), ]; } } diff --git a/tests/V2/Workdays/GetWorkdaysForEntityControllerTest.php b/tests/V2/Workdays/GetWorkdaysForEntityControllerTest.php index b56abc683..4b0393d10 100644 --- a/tests/V2/Workdays/GetWorkdaysForEntityControllerTest.php +++ b/tests/V2/Workdays/GetWorkdaysForEntityControllerTest.php @@ -8,6 +8,7 @@ use App\Models\V2\Sites\Site; use App\Models\V2\Sites\SiteReport; use App\Models\V2\Workdays\Workday; +use App\Models\V2\Workdays\WorkdayDemographic; use App\StateMachines\EntityStatusStateMachine; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Artisan; @@ -17,7 +18,7 @@ class GetWorkdaysForEntityControllerTest extends TestCase { use RefreshDatabase; - public function test_invoke_action() + public function test_empty_workdays_response() { Artisan::call('v2migration:roles'); $organisation = Organisation::factory()->create(); @@ -43,19 +44,102 @@ public function test_invoke_action() 'status' => EntityStatusStateMachine::STARTED, ]); - Workday::factory()->create([ - 'workdayable_type' => SiteReport::class, - 'workdayable_id' => $report->id, - ]); - $uri = '/api/v2/workdays/site-report/' . $report->uuid; $this->actingAs($user) ->getJson($uri) ->assertStatus(403); - $this->actingAs($owner) + // The endpoint should return a workday for each collection with empty demographics for each + $response = $this->actingAs($owner) + ->getJson($uri) + ->assertSuccessful() + ->assertJsonCount(count(Workday::$siteCollections), 'data') + ->decodeResponseJson(); + foreach ($response['data'] as $workday) { + $this->assertCount(0, $workday['demographics']); + } + } + + public function test_populated_workdays() + { + Artisan::call('v2migration:roles'); + $organisation = Organisation::factory()->create(); + $owner = User::factory()->create(['organisation_id' => $organisation->id]); + $owner->givePermissionTo('manage-own'); + + $project = Project::factory()->create([ + 'organisation_id' => $organisation->id, + 'framework_key' => 'ppc', + ]); + + $site = Site::factory()->create([ + 'project_id' => $project->id, + 'framework_key' => 'ppc', + 'status' => EntityStatusStateMachine::STARTED, + ]); + + $report = SiteReport::factory()->create([ + 'site_id' => $site->id, + 'framework_key' => 'ppc', + 'status' => EntityStatusStateMachine::STARTED, + ]); + + $workday = Workday::factory()->create([ + 'workdayable_id' => $report->id, + ]); + $femaleCount = WorkdayDemographic::factory()->gender()->create([ + 'workday_id' => $workday->id, + 'name' => 'female', + ])->amount; + $nonBinaryCount = WorkdayDemographic::factory()->gender()->create([ + 'workday_id' => $workday->id, + 'name' => 'non-binary', + ])->amount; + $youthCount = WorkdayDemographic::factory()->age()->create([ + 'workday_id' => $workday->id, + 'name' => 'youth', + ])->amount; + $otherAgeCount = WorkdayDemographic::factory()->age()->create([ + 'workday_id' => $workday->id, + 'name' => 'other', + ])->amount; + $indigenousCount = WorkdayDemographic::factory()->ethnicity()->create([ + 'workday_id' => $workday->id, + 'subtype' => 'indigenous', + 'name' => 'Ohlone', + ])->amount; + + $uri = '/api/v2/workdays/site-report/' . $report->uuid; + + $response = $this->actingAs($owner) ->getJson($uri) - ->assertSuccessful(); + ->assertSuccessful() + ->assertJsonCount(count(Workday::$siteCollections), 'data') + ->decodeResponseJson(); + $foundCollection = false; + foreach ($response['data'] as $workdayData) { + $demographics = $workdayData['demographics']; + if ($workdayData['collection'] != $workday->collection) { + $this->assertCount(0, $demographics); + + continue; + } + + $foundCollection = true; + $this->assertCount(5, $demographics); + + // They should be in creation order + $expected = [ + ['type' => 'gender', 'subtype' => null, 'name' => 'female', 'amount' => $femaleCount], + ['type' => 'gender', 'subtype' => null, 'name' => 'non-binary', 'amount' => $nonBinaryCount], + ['type' => 'age', 'subtype' => null, 'name' => 'youth', 'amount' => $youthCount], + ['type' => 'age', 'subtype' => null, 'name' => 'other', 'amount' => $otherAgeCount], + ['type' => 'ethnicity', 'subtype' => 'indigenous', 'name' => 'Ohlone', 'amount' => $indigenousCount], + ]; + $this->assertEquals($expected, $demographics); + } + + $this->assertTrue($foundCollection); } } From cbead4bb652e92b601fcce9a333719e9a50873b3 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Tue, 7 May 2024 15:49:33 -0700 Subject: [PATCH 21/64] [TM-878] Unit tests syncing workday demographics. --- app/Models/V2/Workdays/WorkdayDemographic.php | 31 +++++++++++ tests/Unit/Models/V2/Workdays/WorkdayTest.php | 55 +++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 tests/Unit/Models/V2/Workdays/WorkdayTest.php diff --git a/app/Models/V2/Workdays/WorkdayDemographic.php b/app/Models/V2/Workdays/WorkdayDemographic.php index 2d6308544..7acd120c5 100644 --- a/app/Models/V2/Workdays/WorkdayDemographic.php +++ b/app/Models/V2/Workdays/WorkdayDemographic.php @@ -2,6 +2,7 @@ namespace App\Models\V2\Workdays; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -29,4 +30,34 @@ public function workday(): BelongsTo { return $this->belongsTo(Workday::class); } + + public function scopeGender(Builder $query): Builder + { + return $query->where('type', self::GENDER); + } + + public function scopeIsGender(Builder $query, string $gender): Builder + { + return $query->where(['type' => self::GENDER, 'name' => $gender]); + } + + public function scopeAge(Builder $query): Builder + { + return $query->where('type', self::AGE); + } + + public function scopeIsAge(Builder $query, string $age): Builder + { + return $query->where(['type' => self::AGE, 'name' => $age]); + } + + public function scopeEthnicity(Builder $query): Builder + { + return $query->where('type', self::ETHNICITY); + } + + public function scopeIsEthnicity(Builder $query, string $ethnicity, string $name = null): Builder + { + return $query->where(['type' => self::ETHNICITY, 'subtype' => $ethnicity, 'name' => $name]); + } } diff --git a/tests/Unit/Models/V2/Workdays/WorkdayTest.php b/tests/Unit/Models/V2/Workdays/WorkdayTest.php new file mode 100644 index 000000000..46b2c7815 --- /dev/null +++ b/tests/Unit/Models/V2/Workdays/WorkdayTest.php @@ -0,0 +1,55 @@ +create(); + + // First, test adding workdays to an empty set + $data = [ + [ + 'collection' => Workday::COLLECTION_SITE_VOLUNTEER_PLANTING, + 'demographics' => [ + ['type' => 'age', 'name' => 'youth', 'amount' => 20], + ['type' => 'gender', 'name' => 'non-binary', 'amount' => 20], + ['type' => 'ethnicity', 'subtype' => 'other', 'amount' => 20], + ], + ], + ]; + Workday::syncRelation($siteReport, 'workdaysVolunteerPlanting', $data); + + $workday = $siteReport->workdaysVolunteerPlanting()->first(); + $this->assertEquals(3, $workday->demographics()->count()); + $this->assertEquals(20, $workday->demographics()->isAge('youth')->first()->amount); + $this->assertEquals(20, $workday->demographics()->isGender('non-binary')->first()->amount); + $this->assertEquals(20, $workday->demographics()->isEthnicity('other')->first()->amount); + + // Test modifying an existing demographic collection + $data[0]['demographics'] = [ + ['type' => 'age', 'name' => 'youth', 'amount' => 40], + ['type' => 'gender', 'name' => 'non-binary', 'amount' => 20], + ['type' => 'gender', 'name' => 'female', 'amount' => 20], + ['type' => 'ethnicity', 'subtype' => 'indigenous', 'name' => 'Ohlone', 'amount' => 40], + ]; + Workday::syncRelation($siteReport->fresh(), 'workdaysVolunteerPlanting', $data); + $workday->refresh(); + $this->assertEquals(4, $workday->demographics()->count()); + $this->assertEquals(40, $workday->demographics()->isAge('youth')->first()->amount); + $this->assertEquals(20, $workday->demographics()->isGender('non-binary')->first()->amount); + $this->assertEquals(20, $workday->demographics()->isGender('female')->first()->amount); + $this->assertEquals(40, $workday->demographics()->isEthnicity('indigenous', 'Ohlone')->first()->amount); + + // Test remove demographics + $data[0]['demographics'] = []; + Workday::syncRelation($siteReport->fresh(), 'workdaysVolunteerPlanting', $data); + $workday->refresh(); + $this->assertEquals(0, $workday->demographics()->count()); + } +} From a765b37a0a7632b066f8e3d02e549aa795a78257 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Wed, 8 May 2024 09:29:42 -0700 Subject: [PATCH 22/64] [TM-799] Fix geo json attribute getters. --- .../V2/Terrafund/TerrafundEditGeometryController.php | 3 +-- app/Models/V2/PolygonGeometry.php | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/Http/Controllers/V2/Terrafund/TerrafundEditGeometryController.php b/app/Http/Controllers/V2/Terrafund/TerrafundEditGeometryController.php index b9c57ecf5..a5693c537 100644 --- a/app/Http/Controllers/V2/Terrafund/TerrafundEditGeometryController.php +++ b/app/Http/Controllers/V2/Terrafund/TerrafundEditGeometryController.php @@ -46,10 +46,9 @@ public function getPolygonGeojson(string $uuid) if (! $geometryQuery->exists()) { return response()->json(['message' => 'No polygon geometry found for the given UUID.'], 404); } - $geojsonData = json_decode($geometryQuery->select(DB::raw('ST_AsGeoJSON(geom) as geojson'))->first()->geojson, true); return response()->json([ - 'geojson' => $geojsonData, + 'geojson' => $geometryQuery->first()->geojson, ]); } diff --git a/app/Models/V2/PolygonGeometry.php b/app/Models/V2/PolygonGeometry.php index 7843e01a9..d082498a8 100644 --- a/app/Models/V2/PolygonGeometry.php +++ b/app/Models/V2/PolygonGeometry.php @@ -29,12 +29,12 @@ public function criteriaSite() public static function getGeoJson(string $uuid): ?array { - $geojson = PolygonGeometry::isUuid($uuid) - ->selectRaw('ST_AsGeoJSON(geom) as geojson') + $geojson_string = PolygonGeometry::isUuid($uuid) + ->selectRaw('ST_AsGeoJSON(geom) as geojson_string') ->first() - ?->geojson; + ?->geojson_string; - return $geojson == null ? null : json_decode($geojson, true); + return $geojson_string == null ? null : json_decode($geojson_string, true); } public function getGeoJsonAttribute(): array From 2bf5afc4189ba46fddb36e56a2a5e9257b7e6430 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Wed, 8 May 2024 16:46:36 -0700 Subject: [PATCH 23/64] [TM-799] Set up tests for all validators that don't require site/project context. --- .../TerrafundCreateGeometryController.php | 2 +- app/Services/PolygonService.php | 8 +-- .../Extensions/Polygons/FeatureBounds.php | 2 +- .../Extensions/Polygons/PolygonSize.php | 2 +- app/Validators/SitePolygonValidator.php | 45 +++++++++++----- .../Extensions/PolygonValidatorsTest.php | 53 +++++++++++++++++++ .../Polygons/TestFiles/data_fail.geojson | 1 + .../Polygons/TestFiles/data_pass.geojson | 1 + .../TestFiles/feature_bounds_fail.geojson | 1 + .../TestFiles/feature_bounds_pass.geojson | 1 + .../TestFiles/polygon_size_fail.geojson | 1 + .../TestFiles/polygon_size_pass.geojson | 1 + .../Polygons/TestFiles/schema_fail.geojson | 1 + .../Polygons/TestFiles/schema_pass.geojson | 1 + .../TestFiles/self_intersection_fail.geojson | 1 + .../TestFiles/self_intersection_pass.geojson | 1 + .../Polygons/TestFiles/spikes_fail.geojson | 1 + .../Polygons/TestFiles/spikes_pass.geojson | 1 + 18 files changed, 104 insertions(+), 20 deletions(-) create mode 100644 tests/Unit/Validators/Extensions/PolygonValidatorsTest.php create mode 100644 tests/Unit/Validators/Extensions/Polygons/TestFiles/data_fail.geojson create mode 100644 tests/Unit/Validators/Extensions/Polygons/TestFiles/data_pass.geojson create mode 100644 tests/Unit/Validators/Extensions/Polygons/TestFiles/feature_bounds_fail.geojson create mode 100644 tests/Unit/Validators/Extensions/Polygons/TestFiles/feature_bounds_pass.geojson create mode 100644 tests/Unit/Validators/Extensions/Polygons/TestFiles/polygon_size_fail.geojson create mode 100644 tests/Unit/Validators/Extensions/Polygons/TestFiles/polygon_size_pass.geojson create mode 100644 tests/Unit/Validators/Extensions/Polygons/TestFiles/schema_fail.geojson create mode 100644 tests/Unit/Validators/Extensions/Polygons/TestFiles/schema_pass.geojson create mode 100644 tests/Unit/Validators/Extensions/Polygons/TestFiles/self_intersection_fail.geojson create mode 100644 tests/Unit/Validators/Extensions/Polygons/TestFiles/self_intersection_pass.geojson create mode 100644 tests/Unit/Validators/Extensions/Polygons/TestFiles/spikes_fail.geojson create mode 100644 tests/Unit/Validators/Extensions/Polygons/TestFiles/spikes_pass.geojson diff --git a/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php b/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php index 3299e6194..4e0a02969 100644 --- a/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php +++ b/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php @@ -63,7 +63,7 @@ public function insertGeojsonToDB(string $geojsonFilename) $geojsonData = Storage::get("public/geojson_files/{$geojsonFilename}"); $geojson = json_decode($geojsonData, true); - SitePolygonValidator::validate('FEATURE_BOUNDS', $geojson); + SitePolygonValidator::validate('FEATURE_BOUNDS', $geojson, false); return App::make(PolygonService::class)->createGeojsonModels($geojson); } diff --git a/app/Services/PolygonService.php b/app/Services/PolygonService.php index ed4218e73..b71699b78 100644 --- a/app/Services/PolygonService.php +++ b/app/Services/PolygonService.php @@ -8,7 +8,6 @@ use App\Validators\SitePolygonValidator; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; -use Illuminate\Validation\ValidationException; class PolygonService { @@ -99,8 +98,11 @@ private function insertSinglePolygon(array $geometry): array private function insertSitePolygon(string $polygonUuid, array $properties, float $area) { try { - $validSchema = SitePolygonValidator::isValid('SCHEMA', $properties); - $validData = SitePolygonValidator::isValid('DATA', $properties); + $validationGeojson = ['features' => [ + 'feature' => ['properties' => $properties] + ]]; + $validSchema = SitePolygonValidator::isValid('SCHEMA', $validationGeojson); + $validData = SitePolygonValidator::isValid('DATA', $validationGeojson); $this->createCriteriaSite($polygonUuid, self::SCHEMA_CRITERIA_ID, $validSchema); $this->createCriteriaSite($polygonUuid, self::DATA_CRITERIA_ID, $validData); diff --git a/app/Validators/Extensions/Polygons/FeatureBounds.php b/app/Validators/Extensions/Polygons/FeatureBounds.php index 050863997..ff41ab868 100644 --- a/app/Validators/Extensions/Polygons/FeatureBounds.php +++ b/app/Validators/Extensions/Polygons/FeatureBounds.php @@ -19,7 +19,7 @@ public static function passes($attribute, $value, $parameters, $validator): bool { $type = data_get($value, 'geometry.type'); if ($type === 'Polygon') { - return self::hasValidPolygonBounds(data_get($value, 'geometry.coordinates')); + return self::hasValidPolygonBounds(data_get($value, 'geometry.coordinates.0')); } elseif ($type === 'MultiPolygon') { foreach (data_get($value, 'geometry.coordinates') as $coordinates) { if (! self::hasValidPolygonBounds($coordinates)) { diff --git a/app/Validators/Extensions/Polygons/PolygonSize.php b/app/Validators/Extensions/Polygons/PolygonSize.php index b018e49f0..8f500706f 100644 --- a/app/Validators/Extensions/Polygons/PolygonSize.php +++ b/app/Validators/Extensions/Polygons/PolygonSize.php @@ -52,7 +52,7 @@ public static function geoJsonValid($geojson): bool return $areaSqMeters <= self::SIZE_LIMIT; } - public static function calculateSqMeters($dbGeometry): bool + public static function calculateSqMeters($dbGeometry): float { $areaSqDegrees = $dbGeometry->area; $latitude = $dbGeometry->latitude; diff --git a/app/Validators/SitePolygonValidator.php b/app/Validators/SitePolygonValidator.php index 7f1adc840..a5c25e7d2 100644 --- a/app/Validators/SitePolygonValidator.php +++ b/app/Validators/SitePolygonValidator.php @@ -9,6 +9,21 @@ class SitePolygonValidator extends Validator 'features.*' => 'polygon_feature_bounds', ]; + public const SPIKES = [ + 'features' => 'required|array', + 'features.*.geometry' => 'polygon_spikes', + ]; + + public const POLYGON_SIZE = [ + 'features' => 'required|array', + 'features.*' => 'polygon_size', + ]; + + public const SELF_INTERSECTION = [ + 'features' => 'required|array', + 'features.*' => 'polygon_self_intersection', + ]; + public const WITHIN_COUNTRY = [ '*' => 'required|string|uuid|has_polygon_site|within_country', ]; @@ -18,22 +33,24 @@ class SitePolygonValidator extends Validator ]; public const SCHEMA = [ - 'poly_name' => 'required', - 'plantstart' => 'required', - 'plantend' => 'required', - 'practice' => 'required', - 'target_sys' => 'required', - 'distr' => 'required', - 'num_trees' => 'required', + 'features' => 'required|array', + 'features.*.properties.poly_name' => 'required', + 'features.*.properties.plantstart' => 'required', + 'features.*.properties.plantend' => 'required', + 'features.*.properties.practice' => 'required', + 'features.*.properties.target_sys' => 'required', + 'features.*.properties.distr' => 'required', + 'features.*.properties.num_trees' => 'required', ]; public const DATA = [ - 'poly_name' => 'required|string|not_in:null,NULL', - 'plantstart' => 'required|date|', - 'plantend' => 'required|date|', - 'practice' => 'required|string|not_in:null,NULL', - 'target_sys' => 'required|string|not_in:null,NULL', - 'distr' => 'required|string|not_in:null,NULL', - 'num_trees' => 'required|integer|', + 'features' => 'required|array', + 'features.*.properties.poly_name' => 'required|string|not_in:null,NULL', + 'features.*.properties.plantstart' => 'required|date|', + 'features.*.properties.plantend' => 'required|date|', + 'features.*.properties.practice' => 'required|string|not_in:null,NULL', + 'features.*.properties.target_sys' => 'required|string|not_in:null,NULL', + 'features.*.properties.distr' => 'required|string|not_in:null,NULL', + 'features.*.properties.num_trees' => 'required|integer|', ]; } diff --git a/tests/Unit/Validators/Extensions/PolygonValidatorsTest.php b/tests/Unit/Validators/Extensions/PolygonValidatorsTest.php new file mode 100644 index 000000000..8260dcf9e --- /dev/null +++ b/tests/Unit/Validators/Extensions/PolygonValidatorsTest.php @@ -0,0 +1,53 @@ +runValidationTest('FEATURE_BOUNDS'); + } + + public function test_self_intersection() + { + $this->runValidationTest('SELF_INTERSECTION'); + } + + public function test_size_limit() + { + $this->runValidationTest('POLYGON_SIZE'); + } + + public function test_detect_spikes() + { + $this->runValidationTest('SPIKES'); + } + + public function test_schema() + { + $this->runValidationTest('SCHEMA'); + } + + public function test_data() + { + $this->runValidationTest('DATA'); + } + + protected function runValidationTest(string $validationName): void + { + $passFile = self::FILES_DIR . Str::lower($validationName) . '_pass.geojson'; + $passGeojson = json_decode(file_get_contents($passFile), true); + $failFile = self::FILES_DIR . Str::lower($validationName) . '_fail.geojson'; + $failGeojson = json_decode(file_get_contents($failFile), true); + + $this->assertTrue(SitePolygonValidator::isValid($validationName, $passGeojson, false)); + $this->assertFalse(SitePolygonValidator::isValid($validationName, $failGeojson, false)); + } +} diff --git a/tests/Unit/Validators/Extensions/Polygons/TestFiles/data_fail.geojson b/tests/Unit/Validators/Extensions/Polygons/TestFiles/data_fail.geojson new file mode 100644 index 000000000..b34b1ac92 --- /dev/null +++ b/tests/Unit/Validators/Extensions/Polygons/TestFiles/data_fail.geojson @@ -0,0 +1 @@ +{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[40.405701461490054,-12.96724571876176],[40.40517180334834,-12.965903759897898],[40.403529908559165,-12.964716647487307],[40.400669841338356,-12.964819870022112],[40.3990459167255,-12.966312776735407],[40.39862217229356,-12.968102907699532],[40.398939980922535,-12.970322277411398],[40.40010520549225,-12.972438397330677],[40.40258316565041,-12.973669721688609],[40.40473735406755,-12.973136499056011],[40.40688554181705,-12.972162411592251],[40.40704443522537,-12.969891409784722],[40.4044491492121,-12.970355942218049],[40.403972454664455,-12.971852704880703],[40.40275426315375,-12.970923669720435],[40.40333688067838,-12.96911721161139],[40.40503177248303,-12.969272054897885],[40.40614403547883,-12.969478504071148],[40.40677960559336,-12.968394601830951],[40.404978804273696,-12.968033308262562],[40.406832566272016,-12.967620388148362],[40.4064617987822,-12.966175188510476],[40.405701461490054,-12.96724571876176]]]},"properties":{"poly_name":"siteNameMz","plantstart":"2022-09-29","plantend":"2023-10-10","practice":"","target_sys":"","distr":"","num_trees":"","project_id":"652ba56f-2e75-4735-a0d1-aafebbd940c1","site_id":""},"id":0}]} \ No newline at end of file diff --git a/tests/Unit/Validators/Extensions/Polygons/TestFiles/data_pass.geojson b/tests/Unit/Validators/Extensions/Polygons/TestFiles/data_pass.geojson new file mode 100644 index 000000000..2291b9d2a --- /dev/null +++ b/tests/Unit/Validators/Extensions/Polygons/TestFiles/data_pass.geojson @@ -0,0 +1 @@ +{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[40.405701461490054,-12.96724571876176],[40.40517180334834,-12.965903759897898],[40.403529908559165,-12.964716647487307],[40.400669841338356,-12.964819870022112],[40.3990459167255,-12.966312776735407],[40.39862217229356,-12.968102907699532],[40.398939980922535,-12.970322277411398],[40.40010520549225,-12.972438397330677],[40.40258316565041,-12.973669721688609],[40.40473735406755,-12.973136499056011],[40.40688554181705,-12.972162411592251],[40.40704443522537,-12.969891409784722],[40.4044491492121,-12.970355942218049],[40.403972454664455,-12.971852704880703],[40.40275426315375,-12.970923669720435],[40.40333688067838,-12.96911721161139],[40.40503177248303,-12.969272054897885],[40.40614403547883,-12.969478504071148],[40.40677960559336,-12.968394601830951],[40.404978804273696,-12.968033308262562],[40.406832566272016,-12.967620388148362],[40.4064617987822,-12.966175188510476],[40.405701461490054,-12.96724571876176]]]},"properties":{"poly_name":"siteNameMz","plantstart":"2022-09-29","plantend":"2023-10-10","practice":"tree-planting","target_sys":"peatland","distr":"partial","num_trees":200,"project_id":"652ba56f-2e75-4735-a0d1-aafebbd940c1","site_id":"bd530b53-43c2-4a04-b759-85588c65f5b3"},"id":0}]} \ No newline at end of file diff --git a/tests/Unit/Validators/Extensions/Polygons/TestFiles/feature_bounds_fail.geojson b/tests/Unit/Validators/Extensions/Polygons/TestFiles/feature_bounds_fail.geojson new file mode 100644 index 000000000..18496c4f4 --- /dev/null +++ b/tests/Unit/Validators/Extensions/Polygons/TestFiles/feature_bounds_fail.geojson @@ -0,0 +1 @@ +{"type":"FeatureCollection","features":[{"type":"Feature","properties":{},"geometry":{"coordinates":[[[33.76231487924011,-1.6084293333869937],[33.762257206638196,-1.6368721761542844],[33.79312931123508,-1.6368984045277557],[33.799542061681336,-1.6084308633374889],[33.785024077589924,-1.5933659004352876],[33.76231487924011,-91.6084293333869937]]],"type":"Polygon"}}]} diff --git a/tests/Unit/Validators/Extensions/Polygons/TestFiles/feature_bounds_pass.geojson b/tests/Unit/Validators/Extensions/Polygons/TestFiles/feature_bounds_pass.geojson new file mode 100644 index 000000000..bfd241634 --- /dev/null +++ b/tests/Unit/Validators/Extensions/Polygons/TestFiles/feature_bounds_pass.geojson @@ -0,0 +1 @@ +{"type":"FeatureCollection","features":[{"type":"Feature","properties":{},"geometry":{"coordinates":[[[33.76231487924011,-1.6084293333869937],[33.762257206638196,-1.6368721761542844],[33.79312931123508,-1.6368984045277557],[33.799542061681336,-1.6084308633374889],[33.785024077589924,-1.5933659004352876],[33.76231487924011,-1.6084293333869937]]],"type":"Polygon"}}]} \ No newline at end of file diff --git a/tests/Unit/Validators/Extensions/Polygons/TestFiles/polygon_size_fail.geojson b/tests/Unit/Validators/Extensions/Polygons/TestFiles/polygon_size_fail.geojson new file mode 100644 index 000000000..b45f5a73a --- /dev/null +++ b/tests/Unit/Validators/Extensions/Polygons/TestFiles/polygon_size_fail.geojson @@ -0,0 +1 @@ +{"type":"FeatureCollection","features":[{"type":"Feature","properties":{},"geometry":{"coordinates":[[[33.59378037935954,-1.8834388665191284],[33.59376301461322,-2.0317842432603754],[34.22966064314821,-2.0652331235720567],[34.26143065981151,-1.6829135983939665],[33.825078905983844,-1.6277472733105185],[33.59378037935954,-1.8834388665191284]]],"type":"Polygon"}}]} \ No newline at end of file diff --git a/tests/Unit/Validators/Extensions/Polygons/TestFiles/polygon_size_pass.geojson b/tests/Unit/Validators/Extensions/Polygons/TestFiles/polygon_size_pass.geojson new file mode 100644 index 000000000..7a88cf636 --- /dev/null +++ b/tests/Unit/Validators/Extensions/Polygons/TestFiles/polygon_size_pass.geojson @@ -0,0 +1 @@ +{"type":"FeatureCollection","features":[{"type":"Feature","properties":{},"geometry":{"coordinates":[[[33.14306107010836,-2.1194571381479648],[33.14235292903325,-2.1191435707724082],[33.142269768933716,-2.119570549468648],[33.142633899108006,-2.119880175576199],[33.143276777500205,-2.1198329426218123],[33.14306107010836,-2.1194571381479648]]],"type":"Polygon"}}]} \ No newline at end of file diff --git a/tests/Unit/Validators/Extensions/Polygons/TestFiles/schema_fail.geojson b/tests/Unit/Validators/Extensions/Polygons/TestFiles/schema_fail.geojson new file mode 100644 index 000000000..4e0f333f0 --- /dev/null +++ b/tests/Unit/Validators/Extensions/Polygons/TestFiles/schema_fail.geojson @@ -0,0 +1 @@ +{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[40.405701461490054,-12.96724571876176],[40.40517180334834,-12.965903759897898],[40.403529908559165,-12.964716647487307],[40.400669841338356,-12.964819870022112],[40.3990459167255,-12.966312776735407],[40.39862217229356,-12.968102907699532],[40.398939980922535,-12.970322277411398],[40.40010520549225,-12.972438397330677],[40.40258316565041,-12.973669721688609],[40.40473735406755,-12.973136499056011],[40.40688554181705,-12.972162411592251],[40.40704443522537,-12.969891409784722],[40.4044491492121,-12.970355942218049],[40.403972454664455,-12.971852704880703],[40.40275426315375,-12.970923669720435],[40.40333688067838,-12.96911721161139],[40.40503177248303,-12.969272054897885],[40.40614403547883,-12.969478504071148],[40.40677960559336,-12.968394601830951],[40.404978804273696,-12.968033308262562],[40.406832566272016,-12.967620388148362],[40.4064617987822,-12.966175188510476],[40.405701461490054,-12.96724571876176]]]},"properties":{"poly_name":"siteNameMz","practice":"","target_sys":"","distr":"","num_trees":"","project_id":"","site_id":"bd530b53-43c2-4a04-b759-85588c65f5b3","big-trees":""},"id":0}]} \ No newline at end of file diff --git a/tests/Unit/Validators/Extensions/Polygons/TestFiles/schema_pass.geojson b/tests/Unit/Validators/Extensions/Polygons/TestFiles/schema_pass.geojson new file mode 100644 index 000000000..2291b9d2a --- /dev/null +++ b/tests/Unit/Validators/Extensions/Polygons/TestFiles/schema_pass.geojson @@ -0,0 +1 @@ +{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[40.405701461490054,-12.96724571876176],[40.40517180334834,-12.965903759897898],[40.403529908559165,-12.964716647487307],[40.400669841338356,-12.964819870022112],[40.3990459167255,-12.966312776735407],[40.39862217229356,-12.968102907699532],[40.398939980922535,-12.970322277411398],[40.40010520549225,-12.972438397330677],[40.40258316565041,-12.973669721688609],[40.40473735406755,-12.973136499056011],[40.40688554181705,-12.972162411592251],[40.40704443522537,-12.969891409784722],[40.4044491492121,-12.970355942218049],[40.403972454664455,-12.971852704880703],[40.40275426315375,-12.970923669720435],[40.40333688067838,-12.96911721161139],[40.40503177248303,-12.969272054897885],[40.40614403547883,-12.969478504071148],[40.40677960559336,-12.968394601830951],[40.404978804273696,-12.968033308262562],[40.406832566272016,-12.967620388148362],[40.4064617987822,-12.966175188510476],[40.405701461490054,-12.96724571876176]]]},"properties":{"poly_name":"siteNameMz","plantstart":"2022-09-29","plantend":"2023-10-10","practice":"tree-planting","target_sys":"peatland","distr":"partial","num_trees":200,"project_id":"652ba56f-2e75-4735-a0d1-aafebbd940c1","site_id":"bd530b53-43c2-4a04-b759-85588c65f5b3"},"id":0}]} \ No newline at end of file diff --git a/tests/Unit/Validators/Extensions/Polygons/TestFiles/self_intersection_fail.geojson b/tests/Unit/Validators/Extensions/Polygons/TestFiles/self_intersection_fail.geojson new file mode 100644 index 000000000..90d9119fe --- /dev/null +++ b/tests/Unit/Validators/Extensions/Polygons/TestFiles/self_intersection_fail.geojson @@ -0,0 +1 @@ +{"type":"FeatureCollection","features":[{"type":"Feature","properties":{},"geometry":{"coordinates":[[[33.03873351756249,-2.0430255122929424],[33.040856449501575,-2.0647156071144366],[33.06952640130697,-2.0632417104665848],[33.02323596781497,-2.0496831394453636],[33.04731987928977,-2.0477520777156],[33.0501451837699,-2.073555409697647],[33.03873351756249,-2.0430255122929424]]],"type":"Polygon"}}]} \ No newline at end of file diff --git a/tests/Unit/Validators/Extensions/Polygons/TestFiles/self_intersection_pass.geojson b/tests/Unit/Validators/Extensions/Polygons/TestFiles/self_intersection_pass.geojson new file mode 100644 index 000000000..1a44b34d4 --- /dev/null +++ b/tests/Unit/Validators/Extensions/Polygons/TestFiles/self_intersection_pass.geojson @@ -0,0 +1 @@ +{"type":"FeatureCollection","features":[{"type":"Feature","properties":{},"geometry":{"coordinates":[[[32.98790342060309,-2.0425428020699883],[32.98790812940635,-2.048908036281489],[32.9968703696056,-2.04890837910024],[32.99781331634469,-2.0439575527450273],[32.99285096063568,-2.0371099340211885],[32.98790342060309,-2.0425428020699883]]],"type":"Polygon"}}]} \ No newline at end of file diff --git a/tests/Unit/Validators/Extensions/Polygons/TestFiles/spikes_fail.geojson b/tests/Unit/Validators/Extensions/Polygons/TestFiles/spikes_fail.geojson new file mode 100644 index 000000000..57f9cf7d9 --- /dev/null +++ b/tests/Unit/Validators/Extensions/Polygons/TestFiles/spikes_fail.geojson @@ -0,0 +1 @@ +{"type":"FeatureCollection","features":[{"type":"Feature","properties":{},"geometry":{"coordinates":[[[33.0532174455731,-2.0235234982835237],[33.0532174455731,-2.023961881360833],[33.05375358151258,-2.0241323636379036],[33.05426534763643,-2.0238888175230443],[33.05426534763643,-2.0235722075195923],[33.05421660800633,-2.02308511508636],[33.05377795132836,-2.02303640583429],[33.05338803428191,-2.0229389873271515],[32.96613893328197,-1.85418173381602],[33.0533135885056,-2.0230763147354196],[33.0532174455731,-2.0235234982835237]]],"type":"Polygon"},"id":0}]} \ No newline at end of file diff --git a/tests/Unit/Validators/Extensions/Polygons/TestFiles/spikes_pass.geojson b/tests/Unit/Validators/Extensions/Polygons/TestFiles/spikes_pass.geojson new file mode 100644 index 000000000..e470c11c3 --- /dev/null +++ b/tests/Unit/Validators/Extensions/Polygons/TestFiles/spikes_pass.geojson @@ -0,0 +1 @@ +{"type":"FeatureCollection","features":[{"type":"Feature","properties":{},"geometry":{"coordinates":[[[33.08793397091034,-2.0110453510043698],[33.08793108280574,-2.014462908508264],[33.09279555773685,-2.0156659101236016],[33.093202358829984,-2.012849711983364],[33.093200176401695,-2.0096319653234502],[33.08793397091034,-2.0110453510043698]]],"type":"Polygon"},"id":0}]} \ No newline at end of file From a963380394348dc815e09347c30433b0ce48dca9 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Thu, 9 May 2024 15:04:01 -0700 Subject: [PATCH 24/64] [TM-799] Create a table migration and seeder for would countries generalized. --- ...143_create_world_countries_generalized.php | 35 + .../WorldCountriesGeneralizedTableSeeder.php | 2032 +++++++++++++++++ 2 files changed, 2067 insertions(+) create mode 100644 database/migrations/2024_05_09_200143_create_world_countries_generalized.php create mode 100644 database/seeders/WorldCountriesGeneralizedTableSeeder.php diff --git a/database/migrations/2024_05_09_200143_create_world_countries_generalized.php b/database/migrations/2024_05_09_200143_create_world_countries_generalized.php new file mode 100644 index 000000000..6f48ce9b1 --- /dev/null +++ b/database/migrations/2024_05_09_200143_create_world_countries_generalized.php @@ -0,0 +1,35 @@ +integer('OGR_FID')->autoIncrement(); + $table->geometry('geometry')->spatialIndex('geometry'); + $table->string('country', 50)->nullable(); + $table->string('iso', 2)->nullable(); + $table->string('countryaff', 50)->nullable(); + $table->string('aff_iso', 2)->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('world_countries_generalized'); + } +}; diff --git a/database/seeders/WorldCountriesGeneralizedTableSeeder.php b/database/seeders/WorldCountriesGeneralizedTableSeeder.php new file mode 100644 index 000000000..3ceeb7270 --- /dev/null +++ b/database/seeders/WorldCountriesGeneralizedTableSeeder.php @@ -0,0 +1,2032 @@ +delete(); + + $geometry = json_decode(file_get_contents('database/seeders/world_country_geometry.json'), true); + + $countries = [ + [ + 'OGR_FID' => 252, + 'geometry' => '', + 'country' => 'Afghanistan', + 'iso' => 'AF', + 'countryaff' => 'Afghanistan', + 'aff_iso' => 'AF', + ], + [ + 'OGR_FID' => 253, + 'geometry' => '', + 'country' => 'Albania', + 'iso' => 'AL', + 'countryaff' => 'Albania', + 'aff_iso' => 'AL', + ], + [ + 'OGR_FID' => 254, + 'geometry' => '', + 'country' => 'Algeria', + 'iso' => 'DZ', + 'countryaff' => 'Algeria', + 'aff_iso' => 'DZ', + ], + [ + 'OGR_FID' => 255, + 'geometry' => '', + 'country' => 'American Samoa', + 'iso' => 'AS', + 'countryaff' => 'United States', + 'aff_iso' => 'US', + ], + [ + 'OGR_FID' => 256, + 'geometry' => '', + 'country' => 'Andorra', + 'iso' => 'AD', + 'countryaff' => 'Andorra', + 'aff_iso' => 'AD', + ], + [ + 'OGR_FID' => 257, + 'geometry' => '', + 'country' => 'Angola', + 'iso' => 'AO', + 'countryaff' => 'Angola', + 'aff_iso' => 'AO', + ], + [ + 'OGR_FID' => 258, + 'geometry' => '', + 'country' => 'Anguilla', + 'iso' => 'AI', + 'countryaff' => 'United Kingdom', + 'aff_iso' => 'GB', + ], + [ + 'OGR_FID' => 259, + 'geometry' => '', + 'country' => 'Antarctica', + 'iso' => 'AQ', + 'countryaff' => null, + 'aff_iso' => null, + ], + [ + 'OGR_FID' => 260, + 'geometry' => '', + 'country' => 'Antigua and Barbuda', + 'iso' => 'AG', + 'countryaff' => 'Antigua and Barbuda', + 'aff_iso' => 'AG', + ], + [ + 'OGR_FID' => 261, + 'geometry' => '', + 'country' => 'Argentina', + 'iso' => 'AR', + 'countryaff' => 'Argentina', + 'aff_iso' => 'AR', + ], + [ + 'OGR_FID' => 262, + 'geometry' => '', 'country' => 'Armenia', + 'iso' => 'AM', + 'countryaff' => 'Armenia', + 'aff_iso' => 'AM', + ], + [ + 'OGR_FID' => 263, + 'geometry' => '', + 'country' => 'Aruba', + 'iso' => 'AW', + 'countryaff' => 'Netherlands', + 'aff_iso' => 'NL', + ], + [ + 'OGR_FID' => 264, + 'geometry' => '', + 'country' => 'Australia', + 'iso' => 'AU', + 'countryaff' => 'Australia', + 'aff_iso' => 'AU', + ], + [ + 'OGR_FID' => 265, + 'geometry' => '', + 'country' => 'Austria', + 'iso' => 'AT', + 'countryaff' => 'Austria', + 'aff_iso' => 'AT', + ], + [ + 'OGR_FID' => 266, + 'geometry' => '', + 'country' => 'Azerbaijan', + 'iso' => 'AZ', + 'countryaff' => 'Azerbaijan', + 'aff_iso' => 'AZ', + ], + [ + 'OGR_FID' => 267, + 'geometry' => '', + 'country' => 'Azores', + 'iso' => 'PT', + 'countryaff' => 'Portugal', + 'aff_iso' => 'PT', + ], + [ + 'OGR_FID' => 268, + 'geometry' => '', + 'country' => 'Bahamas', + 'iso' => 'BS', + 'countryaff' => 'Bahamas', + 'aff_iso' => 'BS', + ], + [ + 'OGR_FID' => 269, + 'geometry' => '', + 'country' => 'Bahrain', + 'iso' => 'BH', + 'countryaff' => 'Bahrain', + 'aff_iso' => 'BH', + ], + [ + 'OGR_FID' => 270, + 'geometry' => '', + 'country' => 'Bangladesh', + 'iso' => 'BD', + 'countryaff' => 'Bangladesh', + 'aff_iso' => 'BD', + ], + [ + 'OGR_FID' => 271, + 'geometry' => '', + 'country' => 'Barbados', + 'iso' => 'BB', + 'countryaff' => 'Barbados', + 'aff_iso' => 'BB', + ], + [ + 'OGR_FID' => 272, + 'geometry' => '', + 'country' => 'Belarus', + 'iso' => 'BY', + 'countryaff' => 'Belarus', + 'aff_iso' => 'BY', + ], + [ + 'OGR_FID' => 273, + 'geometry' => '', + 'country' => 'Belgium', + 'iso' => 'BE', + 'countryaff' => 'Belgium', + 'aff_iso' => 'BE', + ], + [ + 'OGR_FID' => 274, + 'geometry' => '', + 'country' => 'Belize', + 'iso' => 'BZ', + 'countryaff' => 'Belize', + 'aff_iso' => 'BZ', + ], + [ + 'OGR_FID' => 275, + 'geometry' => '', + 'country' => 'Benin', + 'iso' => 'BJ', + 'countryaff' => 'Benin', + 'aff_iso' => 'BJ', + ], + [ + 'OGR_FID' => 276, + 'geometry' => '', + 'country' => 'Bermuda', + 'iso' => 'BM', + 'countryaff' => 'United Kingdom', + 'aff_iso' => 'GB', + ], + [ + 'OGR_FID' => 277, + 'geometry' => '', + 'country' => 'Bhutan', + 'iso' => 'BT', + 'countryaff' => 'Bhutan', + 'aff_iso' => 'BT', + ], + [ + 'OGR_FID' => 278, + 'geometry' => '', + 'country' => 'Bolivia', + 'iso' => 'BO', + 'countryaff' => 'Bolivia, Plurinational State of', + 'aff_iso' => 'BO', + ], + [ + 'OGR_FID' => 279, + 'geometry' => '', + 'country' => 'Bonaire', + 'iso' => 'BQ', + 'countryaff' => 'Netherlands', + 'aff_iso' => 'NL', + ], + [ + 'OGR_FID' => 280, + 'geometry' => '', + 'country' => 'Bosnia and Herzegovina', + 'iso' => 'BA', + 'countryaff' => 'Bosnia and Herzegovina', + 'aff_iso' => 'BA', + ], + [ + 'OGR_FID' => 281, + 'geometry' => '', + 'country' => 'Botswana', + 'iso' => 'BW', + 'countryaff' => 'Botswana', + 'aff_iso' => 'BW', + ], + [ + 'OGR_FID' => 282, + 'geometry' => '', + 'country' => 'Bouvet Island', + 'iso' => 'BV', + 'countryaff' => 'Norway', + 'aff_iso' => 'NO', + ], + [ + 'OGR_FID' => 283, + 'geometry' => '', + 'country' => 'Brazil', + 'iso' => 'BR', + 'countryaff' => 'Brazil', + 'aff_iso' => 'BR', + ], + [ + 'OGR_FID' => 284, + 'geometry' => '', + 'country' => 'British Indian Ocean Territory', + 'iso' => 'IO', + 'countryaff' => 'United Kingdom', + 'aff_iso' => 'GB', + ], + [ + 'OGR_FID' => 285, + 'geometry' => '', + 'country' => 'British Virgin Islands', + 'iso' => 'VG', + 'countryaff' => 'United Kingdom', + 'aff_iso' => 'GB', + ], + [ + 'OGR_FID' => 286, + 'geometry' => '', + 'country' => 'Brunei Darussalam', + 'iso' => 'BN', + 'countryaff' => 'Brunei Darussalam', + 'aff_iso' => 'BN', + ], + [ + 'OGR_FID' => 287, + 'geometry' => '', + 'country' => 'Bulgaria', + 'iso' => 'BG', + 'countryaff' => 'Bulgaria', + 'aff_iso' => 'BG', + ], + [ + 'OGR_FID' => 288, + 'geometry' => '', + 'country' => 'Burkina Faso', + 'iso' => 'BF', + 'countryaff' => 'Burkina Faso', + 'aff_iso' => 'BF', + ], + [ + 'OGR_FID' => 289, + 'geometry' => '', + 'country' => 'Burundi', + 'iso' => 'BI', + 'countryaff' => 'Burundi', + 'aff_iso' => 'BI', + ], + [ + 'OGR_FID' => 290, + 'geometry' => '', + 'country' => 'Cabo Verde', + 'iso' => 'CV', + 'countryaff' => 'Cabo Verde', + 'aff_iso' => 'CV', + ], + [ + 'OGR_FID' => 291, + 'geometry' => '', + 'country' => 'Cambodia', + 'iso' => 'KH', + 'countryaff' => 'Cambodia', + 'aff_iso' => 'KH', + ], + [ + 'OGR_FID' => 292, + 'geometry' => '', + 'country' => 'Cameroon', + 'iso' => 'CM', + 'countryaff' => 'Cameroon', + 'aff_iso' => 'CM', + ], + [ + 'OGR_FID' => 293, + 'geometry' => '', + 'country' => 'Canada', + 'iso' => 'CA', + 'countryaff' => 'Canada', + 'aff_iso' => 'CA', + ], + [ + 'OGR_FID' => 294, + 'geometry' => '', + 'country' => 'Canarias', + 'iso' => 'ES', + 'countryaff' => 'Spain', + 'aff_iso' => 'ES', + ], + [ + 'OGR_FID' => 295, + 'geometry' => '', + 'country' => 'Cayman Islands', + 'iso' => 'KY', + 'countryaff' => 'United Kingdom', + 'aff_iso' => 'GB', + ], + [ + 'OGR_FID' => 296, + 'geometry' => '', + 'country' => 'Central African Republic', + 'iso' => 'CF', + 'countryaff' => 'Central African Republic', + 'aff_iso' => 'CF', + ], + [ + 'OGR_FID' => 297, + 'geometry' => '', + 'country' => 'Chad', + 'iso' => 'TD', + 'countryaff' => 'Chad', + 'aff_iso' => 'TD', + ], + [ + 'OGR_FID' => 298, + 'geometry' => '', + 'country' => 'Chile', + 'iso' => 'CL', + 'countryaff' => 'Chile', + 'aff_iso' => 'CL', + ], + [ + 'OGR_FID' => 299, + 'geometry' => '', + 'country' => 'China', + 'iso' => 'CN', + 'countryaff' => 'China', + 'aff_iso' => 'CN', + ], + [ + 'OGR_FID' => 300, + 'geometry' => '', + 'country' => 'Christmas Island', + 'iso' => 'CX', + 'countryaff' => 'Australia', + 'aff_iso' => 'AU', + ], + [ + 'OGR_FID' => 301, + 'geometry' => '', + 'country' => 'Cocos Islands', + 'iso' => 'CC', + 'countryaff' => 'Australia', + 'aff_iso' => 'AU', + ], + [ + 'OGR_FID' => 302, + 'geometry' => '', + 'country' => 'Colombia', + 'iso' => 'CO', + 'countryaff' => 'Colombia', + 'aff_iso' => 'CO', + ], + [ + 'OGR_FID' => 303, + 'geometry' => '', + 'country' => 'Comoros', + 'iso' => 'KM', + 'countryaff' => 'Comoros', + 'aff_iso' => 'KM', + ], + [ + 'OGR_FID' => 304, + 'geometry' => '', + 'country' => 'Congo', + 'iso' => 'CG', + 'countryaff' => 'Congo', + 'aff_iso' => 'CG', + ], + [ + 'OGR_FID' => 305, + 'geometry' => '', + 'country' => 'Congo DRC', + 'iso' => 'CD', + 'countryaff' => 'Congo, The Democratic Republic of the', + 'aff_iso' => 'CD', + ], + [ + 'OGR_FID' => 306, + 'geometry' => '', + 'country' => 'Cook Islands', + 'iso' => 'CK', + 'countryaff' => 'New Zealand', + 'aff_iso' => 'NZ', + ], + [ + 'OGR_FID' => 307, + 'geometry' => '', + 'country' => 'Costa Rica', + 'iso' => 'CR', + 'countryaff' => 'Costa Rica', + 'aff_iso' => 'CR', + ], + [ + 'OGR_FID' => 308, + 'geometry' => '', + 'country' => 'Côte d\'Ivoire', + 'iso' => 'CI', + 'countryaff' => 'Côte d\'Ivoire', + 'aff_iso' => 'CI', + ], + [ + 'OGR_FID' => 309, + 'geometry' => '', + 'country' => 'Croatia', + 'iso' => 'HR', + 'countryaff' => 'Croatia', + 'aff_iso' => 'HR', + ], + [ + 'OGR_FID' => 310, + 'geometry' => '', + 'country' => 'Cuba', + 'iso' => 'CU', + 'countryaff' => 'Cuba', + 'aff_iso' => 'CU', + ], + [ + 'OGR_FID' => 311, + 'geometry' => '', + 'country' => 'Curacao', + 'iso' => 'CW', + 'countryaff' => 'Netherlands', + 'aff_iso' => 'NL', + ], + [ + 'OGR_FID' => 312, + 'geometry' => '', + 'country' => 'Cyprus', + 'iso' => 'CY', + 'countryaff' => 'Cyprus', + 'aff_iso' => 'CY', + ], + [ + 'OGR_FID' => 313, + 'geometry' => '', + 'country' => 'Czech Republic', + 'iso' => 'CZ', + 'countryaff' => 'Czechia', + 'aff_iso' => 'CZ', + ], + [ + 'OGR_FID' => 314, + 'geometry' => '', + 'country' => 'Denmark', + 'iso' => 'DK', + 'countryaff' => 'Denmark', + 'aff_iso' => 'DK', + ], + [ + 'OGR_FID' => 315, + 'geometry' => '', + 'country' => 'Djibouti', + 'iso' => 'DJ', + 'countryaff' => 'Djibouti', + 'aff_iso' => 'DJ', + ], + [ + 'OGR_FID' => 316, + 'geometry' => '', + 'country' => 'Dominica', + 'iso' => 'DM', + 'countryaff' => 'Dominica', + 'aff_iso' => 'DM', + ], + [ + 'OGR_FID' => 317, + 'geometry' => '', + 'country' => 'Dominican Republic', + 'iso' => 'DO', + 'countryaff' => 'Dominican Republic', + 'aff_iso' => 'DO', + ], + [ + 'OGR_FID' => 318, + 'geometry' => '', + 'country' => 'Ecuador', + 'iso' => 'EC', + 'countryaff' => 'Ecuador', + 'aff_iso' => 'EC', + ], + [ + 'OGR_FID' => 319, + 'geometry' => '', + 'country' => 'Egypt', + 'iso' => 'EG', + 'countryaff' => 'Egypt', + 'aff_iso' => 'EG', + ], + [ + 'OGR_FID' => 320, + 'geometry' => '', + 'country' => 'El Salvador', + 'iso' => 'SV', + 'countryaff' => 'El Salvador', + 'aff_iso' => 'SV', + ], + [ + 'OGR_FID' => 321, + 'geometry' => '', + 'country' => 'Equatorial Guinea', + 'iso' => 'GQ', + 'countryaff' => 'Equatorial Guinea', + 'aff_iso' => 'GQ', + ], + [ + 'OGR_FID' => 322, + 'geometry' => '', + 'country' => 'Eritrea', + 'iso' => 'ER', + 'countryaff' => 'Eritrea', + 'aff_iso' => 'ER', + ], + [ + 'OGR_FID' => 323, + 'geometry' => '', + 'country' => 'Estonia', + 'iso' => 'EE', + 'countryaff' => 'Estonia', + 'aff_iso' => 'EE', + ], + [ + 'OGR_FID' => 324, + 'geometry' => '', + 'country' => 'Eswatini', + 'iso' => 'SZ', + 'countryaff' => 'Eswatini', + 'aff_iso' => 'SZ', + ], + [ + 'OGR_FID' => 325, + 'geometry' => '', + 'country' => 'Ethiopia', + 'iso' => 'ET', + 'countryaff' => 'Ethiopia', + 'aff_iso' => 'ET', + ], + [ + 'OGR_FID' => 326, + 'geometry' => '', + 'country' => 'Falkland Islands', + 'iso' => 'FK', + 'countryaff' => 'United Kingdom', + 'aff_iso' => 'GB', + ], + [ + 'OGR_FID' => 327, + 'geometry' => '', + 'country' => 'Faroe Islands', + 'iso' => 'FO', + 'countryaff' => 'Denmark', + 'aff_iso' => 'DK', + ], + [ + 'OGR_FID' => 328, + 'geometry' => '', + 'country' => 'Fiji', + 'iso' => 'FJ', + 'countryaff' => 'Fiji', + 'aff_iso' => 'FJ', + ], + [ + 'OGR_FID' => 329, + 'geometry' => '', + 'country' => 'Finland', + 'iso' => 'FI', + 'countryaff' => 'Finland', + 'aff_iso' => 'FI', + ], + [ + 'OGR_FID' => 330, + 'geometry' => '', + 'country' => 'France', + 'iso' => 'FR', + 'countryaff' => 'France', + 'aff_iso' => 'FR', + ], + [ + 'OGR_FID' => 331, + 'geometry' => '', + 'country' => 'French Guiana', + 'iso' => 'GF', + 'countryaff' => 'France', + 'aff_iso' => 'FR', + ], + [ + 'OGR_FID' => 332, + 'geometry' => '', + 'country' => 'French Polynesia', + 'iso' => 'PF', + 'countryaff' => 'France', + 'aff_iso' => 'FR', + ], + [ + 'OGR_FID' => 333, + 'geometry' => '', + 'country' => 'French Southern Territories', + 'iso' => 'TF', + 'countryaff' => 'France', + 'aff_iso' => 'FR', + ], + [ + 'OGR_FID' => 334, + 'geometry' => '', + 'country' => 'Gabon', + 'iso' => 'GA', + 'countryaff' => 'Gabon', + 'aff_iso' => 'GA', + ], + [ + 'OGR_FID' => 335, + 'geometry' => '', + 'country' => 'Gambia', + 'iso' => 'GM', + 'countryaff' => 'Gambia', + 'aff_iso' => 'GM', + ], + [ + 'OGR_FID' => 336, + 'geometry' => '', + 'country' => 'Georgia', + 'iso' => 'GE', + 'countryaff' => 'Georgia', + 'aff_iso' => 'GE', + ], + [ + 'OGR_FID' => 337, + 'geometry' => '', + 'country' => 'Germany', + 'iso' => 'DE', + 'countryaff' => 'Germany', + 'aff_iso' => 'DE', + ], + [ + 'OGR_FID' => 338, + 'geometry' => '', + 'country' => 'Ghana', + 'iso' => 'GH', + 'countryaff' => 'Ghana', + 'aff_iso' => 'GH', + ], + [ + 'OGR_FID' => 339, + 'geometry' => '', + 'country' => 'Gibraltar', + 'iso' => 'GI', + 'countryaff' => 'United Kingdom', + 'aff_iso' => 'GB', + ], + [ + 'OGR_FID' => 340, + 'geometry' => '', + 'country' => 'Glorioso Islands', + 'iso' => 'TF', + 'countryaff' => 'France', + 'aff_iso' => 'FR', + ], + [ + 'OGR_FID' => 341, + 'geometry' => '', + 'country' => 'Greece', + 'iso' => 'GR', + 'countryaff' => 'Greece', + 'aff_iso' => 'GR', + ], + [ + 'OGR_FID' => 342, + 'geometry' => '', + 'country' => 'Greenland', + 'iso' => 'GL', + 'countryaff' => 'Denmark', + 'aff_iso' => 'DK', + ], + [ + 'OGR_FID' => 343, + 'geometry' => '', + 'country' => 'Grenada', + 'iso' => 'GD', + 'countryaff' => 'Grenada', + 'aff_iso' => 'GD', + ], + [ + 'OGR_FID' => 344, + 'geometry' => '', + 'country' => 'Guadeloupe', + 'iso' => 'GP', + 'countryaff' => 'France', + 'aff_iso' => 'FR', + ], + [ + 'OGR_FID' => 345, + 'geometry' => '', + 'country' => 'Guam', + 'iso' => 'GU', + 'countryaff' => 'United States', + 'aff_iso' => 'US', + ], + [ + 'OGR_FID' => 346, + 'geometry' => '', + 'country' => 'Guatemala', + 'iso' => 'GT', + 'countryaff' => 'Guatemala', + 'aff_iso' => 'GT', + ], + [ + 'OGR_FID' => 347, + 'geometry' => '', + 'country' => 'Guernsey', + 'iso' => 'GG', + 'countryaff' => 'United Kingdom', + 'aff_iso' => 'GB', + ], + [ + 'OGR_FID' => 348, + 'geometry' => '', + 'country' => 'Guinea', + 'iso' => 'GN', + 'countryaff' => 'Guinea', + 'aff_iso' => 'GN', + ], + [ + 'OGR_FID' => 349, + 'geometry' => '', + 'country' => 'Guinea-Bissau', + 'iso' => 'GW', + 'countryaff' => 'Guinea-Bissau', + 'aff_iso' => 'GW', + ], + [ + 'OGR_FID' => 350, + 'geometry' => '', + 'country' => 'Guyana', + 'iso' => 'GY', + 'countryaff' => 'Guyana', + 'aff_iso' => 'GY', + ], + [ + 'OGR_FID' => 351, + 'geometry' => '', + 'country' => 'Haiti', + 'iso' => 'HT', + 'countryaff' => 'Haiti', + 'aff_iso' => 'HT', + ], + [ + 'OGR_FID' => 352, + 'geometry' => '', + 'country' => 'Heard Island and McDonald Islands', + 'iso' => 'HM', + 'countryaff' => 'Australia', + 'aff_iso' => 'AU', + ], + [ + 'OGR_FID' => 353, + 'geometry' => '', + 'country' => 'Honduras', + 'iso' => 'HN', + 'countryaff' => 'Honduras', + 'aff_iso' => 'HN', + ], + [ + 'OGR_FID' => 354, + 'geometry' => '', + 'country' => 'Hungary', + 'iso' => 'HU', + 'countryaff' => 'Hungary', + 'aff_iso' => 'HU', + ], + [ + 'OGR_FID' => 355, + 'geometry' => '', + 'country' => 'Iceland', + 'iso' => 'IS', + 'countryaff' => 'Iceland', + 'aff_iso' => 'IS', + ], + [ + 'OGR_FID' => 356, + 'geometry' => '', + 'country' => 'India', + 'iso' => 'IN', + 'countryaff' => 'India', + 'aff_iso' => 'IN', + ], + [ + 'OGR_FID' => 357, + 'geometry' => '', + 'country' => 'Indonesia', + 'iso' => 'ID', + 'countryaff' => 'Indonesia', + 'aff_iso' => 'ID', + ], + [ + 'OGR_FID' => 358, + 'geometry' => '', + 'country' => 'Iran', + 'iso' => 'IR', + 'countryaff' => 'Iran, Islamic Republic of', + 'aff_iso' => 'IR', + ], + [ + 'OGR_FID' => 359, + 'geometry' => '', + 'country' => 'Iraq', + 'iso' => 'IQ', + 'countryaff' => 'Iraq', + 'aff_iso' => 'IQ', + ], + [ + 'OGR_FID' => 360, + 'geometry' => '', + 'country' => 'Ireland', + 'iso' => 'IE', + 'countryaff' => 'Ireland', + 'aff_iso' => 'IE', + ], + [ + 'OGR_FID' => 361, + 'geometry' => '', + 'country' => 'Isle of Man', + 'iso' => 'IM', + 'countryaff' => 'United Kingdom', + 'aff_iso' => 'GB', + ], + [ + 'OGR_FID' => 362, + 'geometry' => '', + 'country' => 'Israel', + 'iso' => 'IL', + 'countryaff' => 'Israel', + 'aff_iso' => 'IL', + ], + [ + 'OGR_FID' => 363, + 'geometry' => '', + 'country' => 'Italy', + 'iso' => 'IT', + 'countryaff' => 'Italy', + 'aff_iso' => 'IT', + ], + [ + 'OGR_FID' => 364, + 'geometry' => '', + 'country' => 'Jamaica', + 'iso' => 'JM', + 'countryaff' => 'Jamaica', + 'aff_iso' => 'JM', + ], + [ + 'OGR_FID' => 365, + 'geometry' => '', + 'country' => 'Japan', + 'iso' => 'JP', + 'countryaff' => 'Japan', + 'aff_iso' => 'JP', + ], + [ + 'OGR_FID' => 366, + 'geometry' => '', + 'country' => 'Jersey', + 'iso' => 'JE', + 'countryaff' => 'United Kingdom', + 'aff_iso' => 'GB', + ], + [ + 'OGR_FID' => 367, + 'geometry' => '', + 'country' => 'Jordan', + 'iso' => 'JO', + 'countryaff' => 'Jordan', + 'aff_iso' => 'JO', + ], + [ + 'OGR_FID' => 368, + 'geometry' => '', + 'country' => 'Juan De Nova Island', + 'iso' => 'TF', + 'countryaff' => 'France', + 'aff_iso' => 'FR', + ], + [ + 'OGR_FID' => 369, + 'geometry' => '', + 'country' => 'Kazakhstan', + 'iso' => 'KZ', + 'countryaff' => 'Kazakhstan', + 'aff_iso' => 'KZ', + ], + [ + 'OGR_FID' => 370, + 'geometry' => '', + 'country' => 'Kenya', + 'iso' => 'KE', + 'countryaff' => 'Kenya', + 'aff_iso' => 'KE', + ], + [ + 'OGR_FID' => 371, + 'geometry' => '', + 'country' => 'Kiribati', + + 'iso' => 'KI', + 'countryaff' => 'Kiribati', + 'aff_iso' => 'KI', + ], + [ + 'OGR_FID' => 372, + 'geometry' => '', + 'country' => 'Kuwait', + 'iso' => 'KW', + 'countryaff' => 'Kuwait', + 'aff_iso' => 'KW', + ], + [ + 'OGR_FID' => 373, + 'geometry' => '', + 'country' => 'Kyrgyzstan', + 'iso' => 'KG', + 'countryaff' => 'Kyrgyzstan', + 'aff_iso' => 'KG', + ], + [ + 'OGR_FID' => 374, + 'geometry' => '', + 'country' => 'Laos', + 'iso' => 'LA', + 'countryaff' => 'Lao People\'s Democratic Republic', + 'aff_iso' => 'LA', + ], + [ + 'OGR_FID' => 375, + 'geometry' => '', + 'country' => 'Latvia', + 'iso' => 'LV', + 'countryaff' => 'Latvia', + 'aff_iso' => 'LV', + ], + [ + 'OGR_FID' => 376, + 'geometry' => '', + 'country' => 'Lebanon', + 'iso' => 'LB', + 'countryaff' => 'Lebanon', + 'aff_iso' => 'LB', + ], + [ + 'OGR_FID' => 377, + 'geometry' => '', + 'country' => 'Lesotho', + 'iso' => 'LS', + 'countryaff' => 'Lesotho', + 'aff_iso' => 'LS', + ], + [ + 'OGR_FID' => 378, + 'geometry' => '', + 'country' => 'Liberia', + 'iso' => 'LR', + 'countryaff' => 'Liberia', + 'aff_iso' => 'LR', + ], + [ + 'OGR_FID' => 379, + 'geometry' => '', + 'country' => 'Libya', + 'iso' => 'LY', + 'countryaff' => 'Libya', + 'aff_iso' => 'LY', + ], + [ + 'OGR_FID' => 380, + 'geometry' => '', + 'country' => 'Liechtenstein', + 'iso' => 'LI', + 'countryaff' => 'Liechtenstein', + 'aff_iso' => 'LI', + ], + [ + 'OGR_FID' => 381, + 'geometry' => '', + 'country' => 'Lithuania', + 'iso' => 'LT', + 'countryaff' => 'Lithuania', + 'aff_iso' => 'LT', + ], + [ + 'OGR_FID' => 382, + 'geometry' => '', + 'country' => 'Luxembourg', + 'iso' => 'LU', + 'countryaff' => 'Luxembourg', + 'aff_iso' => 'LU', + ], + [ + 'OGR_FID' => 383, + 'geometry' => '', + 'country' => 'Madagascar', + 'iso' => 'MG', + 'countryaff' => 'Madagascar', + 'aff_iso' => 'MG', + ], + [ + 'OGR_FID' => 384, + 'geometry' => '', + 'country' => 'Madeira', + 'iso' => 'PT', + 'countryaff' => 'Portugal', + 'aff_iso' => 'PT', + ], + [ + 'OGR_FID' => 385, + 'geometry' => '', + 'country' => 'Malawi', + 'iso' => 'MW', + 'countryaff' => 'Malawi', + 'aff_iso' => 'MW', + ], + [ + 'OGR_FID' => 386, + 'geometry' => '', + 'country' => 'Malaysia', + 'iso' => 'MY', + 'countryaff' => 'Malaysia', + 'aff_iso' => 'MY', + ], + [ + 'OGR_FID' => 387, + 'geometry' => '', + 'country' => 'Maldives', + 'iso' => 'MV', + 'countryaff' => 'Maldives', + 'aff_iso' => 'MV', + ], + [ + 'OGR_FID' => 388, + 'geometry' => '', + 'country' => 'Mali', + 'iso' => 'ML', + 'countryaff' => 'Mali', + 'aff_iso' => 'ML', + ], + [ + 'OGR_FID' => 389, + 'geometry' => '', + 'country' => 'Malta', + 'iso' => 'MT', + 'countryaff' => 'Malta', + 'aff_iso' => 'MT', + ], + [ + 'OGR_FID' => 390, + 'geometry' => '', + 'country' => 'Marshall Islands', + 'iso' => 'MH', + 'countryaff' => 'Marshall Islands', + 'aff_iso' => 'MH', + ], + [ + 'OGR_FID' => 391, + 'geometry' => '', + 'country' => 'Martinique', + 'iso' => 'MQ', + 'countryaff' => 'France', + 'aff_iso' => 'FR', + ], + [ + 'OGR_FID' => 392, + 'geometry' => '', + 'country' => 'Mauritania', + 'iso' => 'MR', + 'countryaff' => 'Mauritania', + 'aff_iso' => 'MR', + ], + [ + 'OGR_FID' => 393, + 'geometry' => '', + 'country' => 'Mauritius', + 'iso' => 'MU', + 'countryaff' => 'Mauritius', + 'aff_iso' => 'MU', + ], + [ + 'OGR_FID' => 394, + 'geometry' => '', + 'country' => 'Mayotte', + 'iso' => 'YT', + 'countryaff' => 'France', + 'aff_iso' => 'FR', + ], + [ + 'OGR_FID' => 395, + 'geometry' => '', + 'country' => 'Mexico', + 'iso' => 'MX', + 'countryaff' => 'Mexico', + 'aff_iso' => 'MX', + ], + [ + 'OGR_FID' => 396, + 'geometry' => '', + 'country' => 'Micronesia', + 'iso' => 'FM', + 'countryaff' => 'Micronesia, Federated States of', + 'aff_iso' => 'FM', + ], + [ + 'OGR_FID' => 397, + 'geometry' => '', + 'country' => 'Moldova', + 'iso' => 'MD', + 'countryaff' => 'Moldova, Republic of', + 'aff_iso' => 'MD', + ], + [ + 'OGR_FID' => 398, + 'geometry' => '', + 'country' => 'Monaco', + 'iso' => 'MC', + 'countryaff' => 'Monaco', + 'aff_iso' => 'MC', + ], + [ + 'OGR_FID' => 399, + 'geometry' => '', + 'country' => 'Mongolia', + 'iso' => 'MN', + 'countryaff' => 'Mongolia', + 'aff_iso' => 'MN', + ], + [ + 'OGR_FID' => 400, + 'geometry' => '', + 'country' => 'Montenegro', + 'iso' => 'ME', + 'countryaff' => 'Montenegro', + 'aff_iso' => 'ME', + ], + [ + 'OGR_FID' => 401, + 'geometry' => '', + 'country' => 'Montserrat', + 'iso' => 'MS', + 'countryaff' => 'United Kingdom', + 'aff_iso' => 'GB', + ], + [ + 'OGR_FID' => 402, + 'geometry' => '', + 'country' => 'Morocco', + 'iso' => 'MA', + 'countryaff' => 'Morocco', + 'aff_iso' => 'MA', + ], + [ + 'OGR_FID' => 403, + 'geometry' => '', + 'country' => 'Mozambique', + 'iso' => 'MZ', + 'countryaff' => 'Mozambique', + 'aff_iso' => 'MZ', + ], + [ + 'OGR_FID' => 404, + 'geometry' => '', + 'country' => 'Myanmar', + 'iso' => 'MM', + 'countryaff' => 'Myanmar', + 'aff_iso' => 'MM', + ], + [ + 'OGR_FID' => 405, + 'geometry' => '', + 'country' => 'Namibia', + 'iso' => 'NA', + 'countryaff' => 'Namibia', + 'aff_iso' => 'NA', + ], + [ + 'OGR_FID' => 406, + 'geometry' => '', + 'country' => 'Nauru', + 'iso' => 'NR', + 'countryaff' => 'Nauru', + 'aff_iso' => 'NR', + ], + [ + 'OGR_FID' => 407, + 'geometry' => '', + 'country' => 'Nepal', + 'iso' => 'NP', + 'countryaff' => 'Nepal', + 'aff_iso' => 'NP', + ], + [ + 'OGR_FID' => 408, + 'geometry' => '', + 'country' => 'Netherlands', + 'iso' => 'NL', + 'countryaff' => 'Netherlands', + 'aff_iso' => 'NL', + ], + [ + 'OGR_FID' => 409, + 'geometry' => '', + 'country' => 'New Caledonia', + 'iso' => 'NC', + 'countryaff' => 'France', + 'aff_iso' => 'FR', + ], + [ + 'OGR_FID' => 410, + 'geometry' => '', + 'country' => 'New Zealand', + 'iso' => 'NZ', + 'countryaff' => 'New Zealand', + 'aff_iso' => 'NZ', + ], + [ + 'OGR_FID' => 411, + 'geometry' => '', + 'country' => 'Nicaragua', + 'iso' => 'NI', + 'countryaff' => 'Nicaragua', + 'aff_iso' => 'NI', + ], + [ + 'OGR_FID' => 412, + 'geometry' => '', + 'country' => 'Niger', + 'iso' => 'NE', + 'countryaff' => 'Niger', + 'aff_iso' => 'NE', + ], + [ + 'OGR_FID' => 413, + 'geometry' => '', + 'country' => 'Nigeria', + 'iso' => 'NG', + 'countryaff' => 'Nigeria', + 'aff_iso' => 'NG', + ], + [ + 'OGR_FID' => 414, + 'geometry' => '', + 'country' => 'Niue', + 'iso' => 'NU', + 'countryaff' => 'New Zealand', + 'aff_iso' => 'NZ', + ], + [ + 'OGR_FID' => 415, + 'geometry' => '', + 'country' => 'Norfolk Island', + 'iso' => 'NF', + 'countryaff' => 'Australia', + 'aff_iso' => 'AU', + ], + [ + 'OGR_FID' => 416, + 'geometry' => '', + 'country' => 'North Korea', + 'iso' => 'KP', + 'countryaff' => 'Korea, Democratic People\'s Republic of', + 'aff_iso' => 'KP', + ], + [ + 'OGR_FID' => 417, + 'geometry' => '', + 'country' => 'North Macedonia', + 'iso' => 'MK', + 'countryaff' => 'North Macedonia', + 'aff_iso' => 'MK', + ], + [ + 'OGR_FID' => 418, + 'geometry' => '', + 'country' => 'Northern Mariana Islands', + 'iso' => 'MP', + 'countryaff' => 'United States', + 'aff_iso' => 'US', + ], + [ + 'OGR_FID' => 419, + 'geometry' => '', + 'country' => 'Norway', + 'iso' => 'NO', + 'countryaff' => 'Norway', + 'aff_iso' => 'NO', + ], + [ + 'OGR_FID' => 420, + 'geometry' => '', + 'country' => 'Oman', + 'iso' => 'OM', + 'countryaff' => 'Oman', + 'aff_iso' => 'OM', + ], + [ + 'OGR_FID' => 421, + 'geometry' => '', + 'country' => 'Pakistan', + 'iso' => 'PK', + 'countryaff' => 'Pakistan', + 'aff_iso' => 'PK', + ], + [ + 'OGR_FID' => 422, + 'geometry' => '', + 'country' => 'Palau', + 'iso' => 'PW', + 'countryaff' => 'Palau', + 'aff_iso' => 'PW', + ], + [ + 'OGR_FID' => 423, + 'geometry' => '', + 'country' => 'Palestinian Territory', + 'iso' => 'PS', + 'countryaff' => 'Palestine, State of', + 'aff_iso' => 'PS', + ], + [ + 'OGR_FID' => 424, + 'geometry' => '', + 'country' => 'Panama', + 'iso' => 'PA', + 'countryaff' => 'Panama', + 'aff_iso' => 'PA', + ], + [ + 'OGR_FID' => 425, + 'geometry' => '', + 'country' => 'Papua New Guinea', + 'iso' => 'PG', + 'countryaff' => 'Papua New Guinea', + 'aff_iso' => 'PG', + ], + [ + 'OGR_FID' => 426, + 'geometry' => '', + 'country' => 'Paraguay', + 'iso' => 'PY', + 'countryaff' => 'Paraguay', + 'aff_iso' => 'PY', + ], + [ + 'OGR_FID' => 427, + 'geometry' => '', + 'country' => 'Peru', + 'iso' => 'PE', + 'countryaff' => 'Peru', + 'aff_iso' => 'PE', + ], + [ + 'OGR_FID' => 428, + 'geometry' => '', + 'country' => 'Philippines', + 'iso' => 'PH', + 'countryaff' => 'Philippines', + 'aff_iso' => 'PH', + ], + [ + 'OGR_FID' => 429, + 'geometry' => '', + 'country' => 'Pitcairn', + 'iso' => 'PN', + 'countryaff' => 'United Kingdom', + 'aff_iso' => 'GB', + ], + [ + 'OGR_FID' => 430, + 'geometry' => '', + 'country' => 'Poland', + 'iso' => 'PL', + 'countryaff' => 'Poland', + 'aff_iso' => 'PL', + ], + [ + 'OGR_FID' => 431, + 'geometry' => '', + 'country' => 'Portugal', + 'iso' => 'PT', + 'countryaff' => 'Portugal', + 'aff_iso' => 'PT', + ], + [ + 'OGR_FID' => 432, + 'geometry' => '', + 'country' => 'Puerto Rico', + 'iso' => 'PR', + 'countryaff' => 'United States', + 'aff_iso' => 'US', + ], + [ + 'OGR_FID' => 433, + 'geometry' => '', + 'country' => 'Qatar', + 'iso' => 'QA', + 'countryaff' => 'Qatar', + 'aff_iso' => 'QA', + ], + [ + 'OGR_FID' => 434, + 'geometry' => '', + 'country' => 'Réunion', + 'iso' => 'RE', + 'countryaff' => 'France', + 'aff_iso' => 'FR', + ], + [ + 'OGR_FID' => 435, + 'geometry' => '', + 'country' => 'Romania', + 'iso' => 'RO', + 'countryaff' => 'Romania', + 'aff_iso' => 'RO', + ], + [ + 'OGR_FID' => 436, + 'geometry' => '', + 'country' => 'Russian Federation', + 'iso' => 'RU', + 'countryaff' => 'Russian Federation', + 'aff_iso' => 'RU', + ], + [ + 'OGR_FID' => 437, + 'geometry' => '', + 'country' => 'Rwanda', + 'iso' => 'RW', + 'countryaff' => 'Rwanda', + 'aff_iso' => 'RW', + ], + [ + 'OGR_FID' => 438, + 'geometry' => '', + 'country' => 'Saba', + 'iso' => 'BQ', + 'countryaff' => 'Netherlands', + 'aff_iso' => 'NL', + ], + [ + 'OGR_FID' => 439, + 'geometry' => '', + 'country' => 'Saint Barthelemy', + 'iso' => 'BL', + 'countryaff' => 'France', + 'aff_iso' => 'FR', + ], + [ + 'OGR_FID' => 440, + 'geometry' => '', + 'country' => 'Saint Eustatius', + 'iso' => 'BQ', + 'countryaff' => 'Netherlands', + 'aff_iso' => 'NL', + ], + [ + 'OGR_FID' => 441, + 'geometry' => '', + 'country' => 'Saint Helena', + 'iso' => 'SH', + 'countryaff' => 'United Kingdom', + 'aff_iso' => 'GB', + ], + [ + 'OGR_FID' => 442, + 'geometry' => '', + 'country' => 'Saint Kitts and Nevis', + 'iso' => 'KN', + 'countryaff' => 'Saint Kitts and Nevis', + 'aff_iso' => 'KN', + ], + [ + 'OGR_FID' => 443, + 'geometry' => '', + 'country' => 'Saint Lucia', + 'iso' => 'LC', + 'countryaff' => 'Saint Lucia', + 'aff_iso' => 'LC', + ], + [ + 'OGR_FID' => 444, + 'geometry' => '', + 'country' => 'Saint Martin', + 'iso' => 'MF', + 'countryaff' => 'France', + 'aff_iso' => 'FR', + ], + [ + 'OGR_FID' => 445, + 'geometry' => '', + 'country' => 'Saint Pierre and Miquelon', + 'iso' => 'PM', + 'countryaff' => 'France', + 'aff_iso' => 'FR', + ], + [ + 'OGR_FID' => 446, + 'geometry' => '', + 'country' => 'Saint Vincent and the Grenadines', + 'iso' => 'VC', + 'countryaff' => 'Saint Vincent and the Grenadines', + 'aff_iso' => 'VC', + ], + [ + 'OGR_FID' => 447, + 'geometry' => '', + 'country' => 'Samoa', + 'iso' => 'WS', + 'countryaff' => 'Samoa', + 'aff_iso' => 'WS', + ], + [ + 'OGR_FID' => 448, + 'geometry' => '', + 'country' => 'San Marino', + 'iso' => 'SM', + 'countryaff' => 'San Marino', + 'aff_iso' => 'SM', + ], + [ + 'OGR_FID' => 449, + 'geometry' => '', + 'country' => 'Sao Tome and Principe', + 'iso' => 'ST', + 'countryaff' => 'São Tomé and Príncipe', + 'aff_iso' => 'ST', + ], + [ + 'OGR_FID' => 450, + 'geometry' => '', + 'country' => 'Saudi Arabia', + 'iso' => 'SA', + 'countryaff' => 'Saudi Arabia', + 'aff_iso' => 'SA', + ], + [ + 'OGR_FID' => 451, + 'geometry' => '', + 'country' => 'Senegal', + 'iso' => 'SN', + 'countryaff' => 'Senegal', + 'aff_iso' => 'SN', + ], + [ + 'OGR_FID' => 452, + 'geometry' => '', + 'country' => 'Serbia', + 'iso' => 'RS', + 'countryaff' => 'Serbia', + 'aff_iso' => 'RS', + ], + [ + 'OGR_FID' => 453, + 'geometry' => '', + 'country' => 'Seychelles', + 'iso' => 'SC', + 'countryaff' => 'Seychelles', + 'aff_iso' => 'SC', + ], + [ + 'OGR_FID' => 454, + 'geometry' => '', + 'country' => 'Sierra Leone', + 'iso' => 'SL', + 'countryaff' => 'Sierra Leone', + 'aff_iso' => 'SL', + ], + [ + 'OGR_FID' => 455, + 'geometry' => '', + 'country' => 'Singapore', + 'iso' => 'SG', + 'countryaff' => 'Singapore', + 'aff_iso' => 'SG', + ], + [ + 'OGR_FID' => 456, + 'geometry' => '', + 'country' => 'Sint Maarten', + 'iso' => 'SX', + 'countryaff' => 'Netherlands', + 'aff_iso' => 'NL', + ], + [ + 'OGR_FID' => 457, + 'geometry' => '', + 'country' => 'Slovakia', + 'iso' => 'SK', + 'countryaff' => 'Slovakia', + 'aff_iso' => 'SK', + ], + [ + 'OGR_FID' => 458, + 'geometry' => '', + 'country' => 'Slovenia', + 'iso' => 'SI', + 'countryaff' => 'Slovenia', + 'aff_iso' => 'SI', + ], + [ + 'OGR_FID' => 459, + 'geometry' => '', + 'country' => 'Solomon Islands', + 'iso' => 'SB', + 'countryaff' => 'Solomon Islands', + 'aff_iso' => 'SB', + ], + [ + 'OGR_FID' => 460, + 'geometry' => '', + 'country' => 'Somalia', + 'iso' => 'SO', + 'countryaff' => 'Somalia', + 'aff_iso' => 'SO', + ], + [ + 'OGR_FID' => 461, + 'geometry' => '', + 'country' => 'South Africa', + 'iso' => 'ZA', + 'countryaff' => 'South Africa', + 'aff_iso' => 'ZA', + ], + [ + 'OGR_FID' => 462, + 'geometry' => '', + 'country' => 'South Georgia and South Sandwich Islands', + 'iso' => 'GS', + 'countryaff' => 'United Kingdom', + 'aff_iso' => 'GB', + ], + [ + 'OGR_FID' => 463, + 'geometry' => '', + 'country' => 'South Korea', + 'iso' => 'KR', + 'countryaff' => 'Korea, Republic of', + 'aff_iso' => 'KR', + ], + [ + 'OGR_FID' => 464, + 'geometry' => '', + 'country' => 'South Sudan', + 'iso' => 'SS', + 'countryaff' => 'South Sudan', + 'aff_iso' => 'SS', + ], + [ + 'OGR_FID' => 465, + 'geometry' => '', + 'country' => 'Spain', + 'iso' => 'ES', + 'countryaff' => 'Spain', + 'aff_iso' => 'ES', + ], + [ + 'OGR_FID' => 466, + 'geometry' => '', + 'country' => 'Sri Lanka', + 'iso' => 'LK', + 'countryaff' => 'Sri Lanka', + 'aff_iso' => 'LK', + ], + [ + 'OGR_FID' => 467, + 'geometry' => '', + 'country' => 'Sudan', + 'iso' => 'SD', + 'countryaff' => 'Sudan', + 'aff_iso' => 'SD', + ], + [ + 'OGR_FID' => 468, + 'geometry' => '', + 'country' => 'Suriname', + 'iso' => 'SR', + 'countryaff' => 'Suriname', + 'aff_iso' => 'SR', + ], + [ + 'OGR_FID' => 469, + 'geometry' => '', + 'country' => 'Svalbard', + 'iso' => 'SJ', + 'countryaff' => 'Norway', + 'aff_iso' => 'NO', + ], + [ + 'OGR_FID' => 470, + 'geometry' => '', + 'country' => 'Sweden', + 'iso' => 'SE', + 'countryaff' => 'Sweden', + 'aff_iso' => 'SE', + ], + [ + 'OGR_FID' => 471, + 'geometry' => '', + 'country' => 'Switzerland', + 'iso' => 'CH', + 'countryaff' => 'Switzerland', + 'aff_iso' => 'CH', + ], + [ + 'OGR_FID' => 472, + 'geometry' => '', + 'country' => 'Syria', + 'iso' => 'SY', + 'countryaff' => 'Syrian Arab Republic', + 'aff_iso' => 'SY', + ], + [ + 'OGR_FID' => 473, + 'geometry' => '', + 'country' => 'Tajikistan', + 'iso' => 'TJ', + 'countryaff' => 'Tajikistan', + 'aff_iso' => 'TJ', + ], + [ + 'OGR_FID' => 474, + 'geometry' => '', + 'country' => 'Tanzania', + 'iso' => 'TZ', + 'countryaff' => 'Tanzania, United Republic of', + 'aff_iso' => 'TZ', + ], + [ + 'OGR_FID' => 475, + 'geometry' => '', + 'country' => 'Thailand', + 'iso' => 'TH', + 'countryaff' => 'Thailand', + 'aff_iso' => 'TH', + ], + [ + 'OGR_FID' => 476, + 'geometry' => '', + 'country' => 'Timor-Leste', + 'iso' => 'TL', + 'countryaff' => 'Timor-Leste', + 'aff_iso' => 'TL', + ], + [ + 'OGR_FID' => 477, + 'geometry' => '', + 'country' => 'Togo', + 'iso' => 'TG', + 'countryaff' => 'Togo', + 'aff_iso' => 'TG', + ], + [ + 'OGR_FID' => 478, + 'geometry' => '', + 'country' => 'Tokelau', + 'iso' => 'TK', + 'countryaff' => 'New Zealand', + 'aff_iso' => 'NZ', + ], + [ + 'OGR_FID' => 479, + 'geometry' => '', + 'country' => 'Tonga', + 'iso' => 'TO', + 'countryaff' => 'Tonga', + 'aff_iso' => 'TO', + ], + [ + 'OGR_FID' => 480, + 'geometry' => '', + 'country' => 'Trinidad and Tobago', + 'iso' => 'TT', + 'countryaff' => 'Trinidad and Tobago', + 'aff_iso' => 'TT', + ], + [ + 'OGR_FID' => 481, + 'geometry' => '', + 'country' => 'Tunisia', + 'iso' => 'TN', + 'countryaff' => 'Tunisia', + 'aff_iso' => 'TN', + ], + [ + 'OGR_FID' => 482, + 'geometry' => '', + 'country' => 'Turkiye', + 'iso' => 'TR', + 'countryaff' => 'Turkiye', + 'aff_iso' => 'TR', + ], + [ + 'OGR_FID' => 483, + 'geometry' => '', + 'country' => 'Turkmenistan', + 'iso' => 'TM', + 'countryaff' => 'Turkmenistan', + 'aff_iso' => 'TM', + ], + [ + 'OGR_FID' => 484, + 'geometry' => '', + 'country' => 'Turks and Caicos Islands', + 'iso' => 'TC', + 'countryaff' => 'United Kingdom', + 'aff_iso' => 'GB', + ], + [ + 'OGR_FID' => 485, + 'geometry' => '', + 'country' => 'Tuvalu', + 'iso' => 'TV', + 'countryaff' => 'Tuvalu', + 'aff_iso' => 'TV', + ], + [ + 'OGR_FID' => 486, + 'geometry' => '', + 'country' => 'Uganda', + 'iso' => 'UG', + 'countryaff' => 'Uganda', + 'aff_iso' => 'UG', + ], + [ + 'OGR_FID' => 487, + 'geometry' => '', + 'country' => 'Ukraine', + 'iso' => 'UA', + 'countryaff' => 'Ukraine', + 'aff_iso' => 'UA', + ], + [ + 'OGR_FID' => 488, + 'geometry' => '', + 'country' => 'United Arab Emirates', + 'iso' => 'AE', + 'countryaff' => 'United Arab Emirates', + 'aff_iso' => 'AE', + ], + [ + 'OGR_FID' => 489, + 'geometry' => '', + 'country' => 'United Kingdom', + 'iso' => 'GB', + 'countryaff' => 'United Kingdom', + 'aff_iso' => 'GB', + ], + [ + 'OGR_FID' => 490, + 'geometry' => '', + 'country' => 'United States', + 'iso' => 'US', + 'countryaff' => 'United States', + 'aff_iso' => 'US', + ], + [ + 'OGR_FID' => 491, + 'geometry' => '', + 'country' => 'United States Minor Outlying Islands', + 'iso' => 'UM', + 'countryaff' => 'United States', + 'aff_iso' => 'US', + ], + [ + 'OGR_FID' => 492, + 'geometry' => '', + 'country' => 'Uruguay', + 'iso' => 'UY', + 'countryaff' => 'Uruguay', + 'aff_iso' => 'UY', + ], + [ + 'OGR_FID' => 493, + 'geometry' => '', + 'country' => 'US Virgin Islands', + 'iso' => 'VI', + 'countryaff' => 'United States', + 'aff_iso' => 'US', + ], + [ + 'OGR_FID' => 494, + 'geometry' => '', + 'country' => 'Uzbekistan', + 'iso' => 'UZ', + 'countryaff' => 'Uzbekistan', + 'aff_iso' => 'UZ', + ], + [ + 'OGR_FID' => 495, + 'geometry' => '', + 'country' => 'Vanuatu', + 'iso' => 'VU', + 'countryaff' => 'Vanuatu', + 'aff_iso' => 'VU', + ], + [ + 'OGR_FID' => 496, + 'geometry' => '', + 'country' => 'Vatican City', + 'iso' => 'VA', + 'countryaff' => 'Holy See (Vatican City State)', + 'aff_iso' => 'VA', + ], + [ + 'OGR_FID' => 497, + 'geometry' => '', + 'country' => 'Venezuela', + 'iso' => 'VE', + 'countryaff' => 'Venezuela, Bolivarian Republic of', + 'aff_iso' => 'VE', + ], + [ + 'OGR_FID' => 498, + 'geometry' => '', + 'country' => 'Vietnam', + 'iso' => 'VN', + 'countryaff' => 'Viet Nam', + 'aff_iso' => 'VN', + ], + [ + 'OGR_FID' => 499, + 'geometry' => '', + 'country' => 'Wallis and Futuna', + 'iso' => 'WF', + 'countryaff' => 'France', + 'aff_iso' => 'FR', + ], + [ + 'OGR_FID' => 500, + 'geometry' => '', + 'country' => 'Yemen', + 'iso' => 'YE', + 'countryaff' => 'Yemen', + 'aff_iso' => 'YE', + ], + [ + 'OGR_FID' => 501, + 'geometry' => '', + 'country' => 'Zambia', + 'iso' => 'ZM', + 'countryaff' => 'Zambia', + 'aff_iso' => 'ZM', + ], + [ + 'OGR_FID' => 502, + 'geometry' => '', + 'country' => 'Zimbabwe', + 'iso' => 'ZW', + 'countryaff' => 'Zimbabwe', + 'aff_iso' => 'ZW', + ], + ]; + + foreach ($countries as $country) { + $country['geometry'] = DB::raw('ST_GeomFromGeoJSON(' . json_encode($geometry[$country['OGR_FID']]) . ')'); + DB::table('world_countries_generalized')->insert($country); + } + } +} From 26c4fa0df7bd5078a2399671f17692badf7d2576 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Thu, 9 May 2024 15:09:50 -0700 Subject: [PATCH 25/64] [TM-799] Test the within country validation. --- app/Models/V2/Sites/SitePolygon.php | 12 ++++- app/Services/PolygonService.php | 2 +- .../Extensions/Polygons/WithinCountry.php | 2 +- app/Validators/SitePolygonValidator.php | 4 +- database/seeders/PolygonValidationSeeder.php | 24 ++++++++++ .../Extensions/PolygonValidatorsTest.php | 44 +++++++++++++++++-- .../TestFiles/within_country_fail.geojson | 1 + .../TestFiles/within_country_pass.geojson | 1 + 8 files changed, 81 insertions(+), 9 deletions(-) create mode 100644 database/seeders/PolygonValidationSeeder.php create mode 100644 tests/Unit/Validators/Extensions/Polygons/TestFiles/within_country_fail.geojson create mode 100644 tests/Unit/Validators/Extensions/Polygons/TestFiles/within_country_pass.geojson diff --git a/app/Models/V2/Sites/SitePolygon.php b/app/Models/V2/Sites/SitePolygon.php index d4311bb78..5c5439c3e 100644 --- a/app/Models/V2/Sites/SitePolygon.php +++ b/app/Models/V2/Sites/SitePolygon.php @@ -8,6 +8,8 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; +use Znck\Eloquent\Relations\BelongsToThrough; +use Znck\Eloquent\Traits\BelongsToThrough as BelongsToThroughTrait; /** * @method static forPolygonGeometry($value): Builder @@ -16,6 +18,7 @@ class SitePolygon extends Model { use HasUuid; use SoftDeletes; + use BelongsToThroughTrait; protected $table = 'site_polygon'; @@ -48,8 +51,13 @@ public function scopeForPolygonGeometry($query, $uuid): Builder return $query->where('poly_id', $uuid); } - public function project() + public function project(): BelongsToThrough { - return $this->belongsTo(Project::class, 'project_id', 'uuid'); + return $this->belongsToThrough( + Project::class, + Site::class, + foreignKeyLookup: [Project::class => 'project_id', Site::class => 'site_id'], + localKeyLookup: [Site::class => 'uuid'] + ); } } diff --git a/app/Services/PolygonService.php b/app/Services/PolygonService.php index b71699b78..94d00b6c9 100644 --- a/app/Services/PolygonService.php +++ b/app/Services/PolygonService.php @@ -99,7 +99,7 @@ private function insertSitePolygon(string $polygonUuid, array $properties, float { try { $validationGeojson = ['features' => [ - 'feature' => ['properties' => $properties] + 'feature' => ['properties' => $properties], ]]; $validSchema = SitePolygonValidator::isValid('SCHEMA', $validationGeojson); $validData = SitePolygonValidator::isValid('DATA', $validationGeojson); diff --git a/app/Validators/Extensions/Polygons/WithinCountry.php b/app/Validators/Extensions/Polygons/WithinCountry.php index 129f398e9..6734cbb6c 100644 --- a/app/Validators/Extensions/Polygons/WithinCountry.php +++ b/app/Validators/Extensions/Polygons/WithinCountry.php @@ -36,7 +36,7 @@ public static function getIntersectionData(string $polygonUuid): array return ['valid' => false, 'status' => 404, 'error' => 'Geometry not found']; } - $sitePolygonData = SitePolygon::forPolygonGeometry($polygonUuid)->select('id', 'project_id')->first(); + $sitePolygonData = SitePolygon::forPolygonGeometry($polygonUuid)->select('site_id')->first(); if ($sitePolygonData == null) { return ['valid' => false, 'status' => 404, 'error' => 'Site polygon data not found for the specified polygonUuid']; } diff --git a/app/Validators/SitePolygonValidator.php b/app/Validators/SitePolygonValidator.php index a5c25e7d2..9d5bec16d 100644 --- a/app/Validators/SitePolygonValidator.php +++ b/app/Validators/SitePolygonValidator.php @@ -25,11 +25,11 @@ class SitePolygonValidator extends Validator ]; public const WITHIN_COUNTRY = [ - '*' => 'required|string|uuid|has_polygon_site|within_country', + '*' => 'string|uuid|has_polygon_site|within_country', ]; public const OVERLAPPING = [ - '*' => 'required|string|uuid|has_polygon_site|not_overlapping', + '*' => 'string|uuid|has_polygon_site|not_overlapping', ]; public const SCHEMA = [ diff --git a/database/seeders/PolygonValidationSeeder.php b/database/seeders/PolygonValidationSeeder.php new file mode 100644 index 000000000..2d9cc5757 --- /dev/null +++ b/database/seeders/PolygonValidationSeeder.php @@ -0,0 +1,24 @@ +create([ + 'country' => 'AU', + ]); + + $site = Site::factory()->create([ + 'uuid' => self::TEST_SITE_UUID, + 'project_id' => $project->id, + ]); + } +} diff --git a/tests/Unit/Validators/Extensions/PolygonValidatorsTest.php b/tests/Unit/Validators/Extensions/PolygonValidatorsTest.php index 8260dcf9e..c37089a90 100644 --- a/tests/Unit/Validators/Extensions/PolygonValidatorsTest.php +++ b/tests/Unit/Validators/Extensions/PolygonValidatorsTest.php @@ -2,13 +2,18 @@ namespace Tests\Unit\Validators\Extensions; +use App\Models\V2\WorldCountryGeneralized; +use App\Services\PolygonService; use App\Validators\SitePolygonValidator; +use Database\Seeders\PolygonValidationSeeder; +use Database\Seeders\WorldCountriesGeneralizedTableSeeder; +use Illuminate\Support\Facades\App; use Illuminate\Support\Str; use Tests\TestCase; class PolygonValidatorsTest extends TestCase { - const FILES_DIR = "tests/Unit/Validators/Extensions/Polygons/TestFiles/"; + public const FILES_DIR = 'tests/Unit/Validators/Extensions/Polygons/TestFiles/'; public function test_coordinate_system() { @@ -25,6 +30,11 @@ public function test_size_limit() $this->runValidationTest('POLYGON_SIZE'); } + public function test_within_country() + { + $this->runValidationImportTest('WITHIN_COUNTRY'); + } + public function test_detect_spikes() { $this->runValidationTest('SPIKES'); @@ -41,13 +51,41 @@ public function test_data() } protected function runValidationTest(string $validationName): void + { + $this->readGeojsons($validationName, function ($passGeojson, $failGeojson) use ($validationName) { + $this->assertValidation($validationName, $passGeojson, $failGeojson); + }); + } + + protected function runValidationImportTest(string $validationName): void + { + $this->seed(PolygonValidationSeeder::class); + if (WorldCountryGeneralized::count() == 0) { + $this->seed(WorldCountriesGeneralizedTableSeeder::class); + } + + $this->readGeojsons($validationName, function ($passGeojson, $failGeojson) use ($validationName) { + /** @var PolygonService $service */ + $service = App::make(PolygonService::class); + $passUuids = $service->createGeojsonModels($passGeojson); + $failUuids = $service->createGeojsonModels($failGeojson); + + $this->assertValidation($validationName, $passUuids, $failUuids); + }); + } + + protected function readGeojsons(string $validationName, callable $callback): void { $passFile = self::FILES_DIR . Str::lower($validationName) . '_pass.geojson'; $passGeojson = json_decode(file_get_contents($passFile), true); $failFile = self::FILES_DIR . Str::lower($validationName) . '_fail.geojson'; $failGeojson = json_decode(file_get_contents($failFile), true); + $callback($passGeojson, $failGeojson); + } - $this->assertTrue(SitePolygonValidator::isValid($validationName, $passGeojson, false)); - $this->assertFalse(SitePolygonValidator::isValid($validationName, $failGeojson, false)); + protected function assertValidation(string $validationName, $passData, $failData): void + { + $this->assertTrue(SitePolygonValidator::isValid($validationName, $passData, false)); + $this->assertFalse(SitePolygonValidator::isValid($validationName, $failData, false)); } } diff --git a/tests/Unit/Validators/Extensions/Polygons/TestFiles/within_country_fail.geojson b/tests/Unit/Validators/Extensions/Polygons/TestFiles/within_country_fail.geojson new file mode 100644 index 000000000..f7115446a --- /dev/null +++ b/tests/Unit/Validators/Extensions/Polygons/TestFiles/within_country_fail.geojson @@ -0,0 +1 @@ +{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"poly_name":"Thai Polygon","site_id":"bc5d87ab-4c98-42f1-9902-b2848bb466b7","num_trees":2143,"distr":"Single Line, Partial","target_sys":"Agroforest","practice":"Tree Planting, Direct Seeding","plantend":"2024-05-31","plantstart":"2021-10-01"},"geometry":{"coordinates":[[[100.54229463787874,13.668840890298853],[100.53591730989513,13.664365447665531],[100.54052315788289,13.657479985360268],[100.55150633385443,13.65541430745617],[100.56071802983126,13.655758588363028],[100.57134690980291,13.664021179327435],[100.5812671977771,13.67228348062757],[100.58906170975803,13.681922465824954],[100.58941600575673,13.691561056280406],[100.57949571778249,13.70360873885393],[100.56992972580696,13.707050820443769],[100.55930084583406,13.707739230709194],[100.55186062985314,13.707739230709194],[100.55115203785573,13.690872598629554],[100.54902626186117,13.678135768702504],[100.54796337386392,13.671250708811598],[100.54229463787874,13.668840890298853]]],"type":"Polygon"}}]} \ No newline at end of file diff --git a/tests/Unit/Validators/Extensions/Polygons/TestFiles/within_country_pass.geojson b/tests/Unit/Validators/Extensions/Polygons/TestFiles/within_country_pass.geojson new file mode 100644 index 000000000..0513b56a2 --- /dev/null +++ b/tests/Unit/Validators/Extensions/Polygons/TestFiles/within_country_pass.geojson @@ -0,0 +1 @@ +{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"poly_name":"Aussie Polygon","plantstart":"2021-08-03","practice":"Tree Planting, Direct Seeding","plantend":"2021-08-03","target_sys":"Natural Forest","distr":"Full Coverage, Partial","num_trees":214,"site_id":"bc5d87ab-4c98-42f1-9902-b2848bb466b7"},"geometry":{"coordinates":[[[149.99503436199097,-34.48327996343278],[149.99560496457218,-34.48666636303284],[149.99954212238652,-34.48633713577154],[150.0007689179373,-34.482974240036334],[149.99503436199097,-34.48327996343278]]],"type":"Polygon"}}]} \ No newline at end of file From c57dfd98b76f2a3a8f75afee012d53094ff8adaa Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Thu, 9 May 2024 20:42:33 -0700 Subject: [PATCH 26/64] [TM-799] Test the not overlapping validation. Not yet passing; probably needs new test files. --- app/Validators/SitePolygonValidator.php | 2 +- .../factories/V2/PolygonGeometryFactory.php | 26 +++++++++ .../factories/V2/Sites/SitePolygonFactory.php | 36 ++++++++++++ database/seeders/PolygonValidationSeeder.php | 58 ++++++++++++++++--- .../Extensions/PolygonValidatorsTest.php | 6 ++ .../TestFiles/not_overlapping_fail.geojson | 1 + .../TestFiles/not_overlapping_pass.geojson | 1 + 7 files changed, 121 insertions(+), 9 deletions(-) create mode 100644 database/factories/V2/PolygonGeometryFactory.php create mode 100644 database/factories/V2/Sites/SitePolygonFactory.php create mode 100644 tests/Unit/Validators/Extensions/Polygons/TestFiles/not_overlapping_fail.geojson create mode 100644 tests/Unit/Validators/Extensions/Polygons/TestFiles/not_overlapping_pass.geojson diff --git a/app/Validators/SitePolygonValidator.php b/app/Validators/SitePolygonValidator.php index 9d5bec16d..ac593bed9 100644 --- a/app/Validators/SitePolygonValidator.php +++ b/app/Validators/SitePolygonValidator.php @@ -28,7 +28,7 @@ class SitePolygonValidator extends Validator '*' => 'string|uuid|has_polygon_site|within_country', ]; - public const OVERLAPPING = [ + public const NOT_OVERLAPPING = [ '*' => 'string|uuid|has_polygon_site|not_overlapping', ]; diff --git a/database/factories/V2/PolygonGeometryFactory.php b/database/factories/V2/PolygonGeometryFactory.php new file mode 100644 index 000000000..1a6925c30 --- /dev/null +++ b/database/factories/V2/PolygonGeometryFactory.php @@ -0,0 +1,26 @@ + $this->faker->uuid(), + ]; + } + + public function geojson(string|array $geojson) + { + $geom = DB::raw("ST_GeomFromGeoJSON('$geojson')"); + return $this->state(function (array $attributes) use ($geom) { + return [ + 'geom' => $geom + ]; + }); + } +} diff --git a/database/factories/V2/Sites/SitePolygonFactory.php b/database/factories/V2/Sites/SitePolygonFactory.php new file mode 100644 index 000000000..6b9084dea --- /dev/null +++ b/database/factories/V2/Sites/SitePolygonFactory.php @@ -0,0 +1,36 @@ + PolygonGeometry::factory()->create()->uuid, + 'site_id' => Site::factory()->create()->uuid, + ]; + } + + public function site(Site $site) + { + return $this->state(function (array $attributes) use ($site) { + return [ + 'site_id' => $site->uuid, + ]; + }); + } + + public function geometry(PolygonGeometry $geometry) + { + return $this->state(function (array $attributes) use ($geometry) { + return [ + 'poly_id' => $geometry->uuid, + ]; + }); + } +} diff --git a/database/seeders/PolygonValidationSeeder.php b/database/seeders/PolygonValidationSeeder.php index 2d9cc5757..2eda0ad0b 100644 --- a/database/seeders/PolygonValidationSeeder.php +++ b/database/seeders/PolygonValidationSeeder.php @@ -2,23 +2,65 @@ namespace Database\Seeders; +use App\Models\V2\PolygonGeometry; use App\Models\V2\Projects\Project; use App\Models\V2\Sites\Site; +use App\Models\V2\Sites\SitePolygon; use Illuminate\Database\Seeder; class PolygonValidationSeeder extends Seeder { - public const TEST_SITE_UUID = 'bc5d87ab-4c98-42f1-9902-b2848bb466b7'; + public const TEST_PROJECTS = [ + // Used for within country + [ + 'country' => 'AU', + 'sites' => [ + [ + 'uuid' => 'bc5d87ab-4c98-42f1-9902-b2848bb466b7', + ] + ] + ], + + // Used for overlapping + [ + 'country' => 'GH', + 'sites' => [ + [ + 'uuid' => '8d86e97a-3d4e-4132-95e1-435225d47f28', + 'geometry' => [ + '{"type": "Polygon", "coordinates": [[[-2.27521108782139, 4.909556846471332], [-2.275458274579648, 4.909575790690212], [-2.275572360775755, 4.909518957134196], [-2.275572360775755, 4.909320038788792], [-2.275534332043719, 4.909140064662324], [-2.275581867509118, 4.908979035653999], [-2.275724475703953, 4.908884311861527], [-2.275981170094894, 4.90894114631692], [-2.276275892318552, 4.908884311861527], [-2.276437514879376, 4.9089506179767], [-2.276504064710764, 4.909073758547152], [-2.276675194904271, 4.909121120443388], [-2.276760759101705, 4.909016924991136], [-2.27692238166253, 4.908780116409275], [-2.27702696022601, 4.908675920957023], [-2.277084003324035, 4.908732755412359], [-2.277093510956718, 4.908931673757763], [-2.277074496590672, 4.909177953999404], [-2.277179075154152, 4.909386344903965], [-2.277435769545093, 4.909509484575096], [-2.277701970669341, 4.909509484575096], [-2.277987187059011, 4.909537901353133], [-2.277968172692965, 4.909367399785708], [-2.278006201425001, 4.909035869210072], [-2.277911128695564, 4.908799061527532], [-2.277835071231493, 4.908514891948755], [-2.277949157427599, 4.908448585833639], [-2.278101273255118, 4.908533836167635], [-2.278158316353142, 4.908666449297243], [-2.278405503111458, 4.908685393516123], [-2.278481560575528, 4.908581198063871], [-2.278538603673553, 4.908401223937403], [-2.278576632405588, 4.908259139148015], [-2.278614661137681, 4.908145471136606], [-2.278700226234378, 4.908079165021491], [-2.278823819163904, 4.908012858906375], [-2.27883332679653, 4.908126526018407], [-2.278899876627975, 4.908278083366895], [-2.278890369894611, 4.908382278819147], [-2.279004456090718, 4.908514891948755], [-2.279080513554788, 4.908609614841907], [-2.279185092118269, 4.908619087401007], [-2.279318192680421, 4.908685393516123], [-2.279375236677765, 4.908874840201747], [-2.279356222311776, 4.909016924991136], [-2.27944178650921, 4.909035869210072], [-2.279593901437352, 4.9089506179767], [-2.279660452168059, 4.909026396650916], [-2.279879116927646, 4.90918742655856], [-2.279964682024399, 4.909367399785708], [-2.279997956940122, 4.909576738575652], [-2.279855349644606, 4.909586210235489], [-2.279608161987028, 4.90971882246572], [-2.279494075790922, 4.909936685929381], [-2.27954161215564, 4.910145076833885], [-2.279617669619711, 4.910381883617106], [-2.279560626521629, 4.910618691299589], [-2.279503583423605, 4.910789192867014], [-2.279199353567265, 4.91085549808281], [-2.278971180275676, 4.911016527091078], [-2.278771529882135, 4.911224917995639], [-2.278562371855912, 4.911348057666771], [-2.27842927129376, 4.911565920231055], [-2.278420349119756, 4.911593476357893], [-2.27521108782139, 4.909556846471332]]]}', + '{"type": "Polygon", "coordinates": [[[-2.273037249269578, 4.908108314746983], [-2.273165733611677, 4.908453105826197], [-2.273179740552564, 4.908740329502677], [-2.27310784245293, 4.909027459649565], [-2.273021594770739, 4.909343295256917], [-2.272806396897636, 4.909745153715903], [-2.272591494002199, 4.909874163262089], [-2.27237662258301, 4.909974452059373], [-2.272261941935028, 4.910103570423473], [-2.27230464534307, 4.910333384078456], [-2.27260486242443, 4.910750164088824], [-2.27244699633178, 4.911094642204034], [-2.272231672553573, 4.911611382759133], [-2.27213115352987, 4.911884121954699], [-2.272087920421143, 4.912142561929727], [-2.272130608540692, 4.91238673595916], [-2.272216279757458, 4.912602236004432], [-2.272373477653844, 4.912875255788492], [-2.272315927338127, 4.913133681374347], [-2.272244089493086, 4.913363369124227], [-2.27181420366253, 4.913693190088679], [-2.271241296848302, 4.913879249927504], [-2.270854549697674, 4.914036792264199], [-2.270539453333072, 4.914136970444872], [-2.270081274830943, 4.914150828997606], [-2.269809161563501, 4.914222333194175], [-2.269350983061372, 4.914236191746909], [-2.26892147044947, 4.914221361027046], [-2.268692420318871, 4.914192388467995], [-2.268434828404565, 4.914077223085428], [-2.268105649556048, 4.913961978562497], [-2.267848105305745, 4.913803731157316], [-2.267662148889031, 4.913645561993121], [-2.267504906026545, 4.913415623332412], [-2.267390631872104, 4.913171370162672], [-2.267347898786454, 4.912970277256534], [-2.267477054922097, 4.912697571335912], [-2.26752016392436, 4.912554014356317], [-2.267649257107507, 4.912338749933383], [-2.267821209281351, 4.912209695421154], [-2.268107748573698, 4.912037684791358], [-2.268193794807758, 4.911908534950953], [-2.26836568312973, 4.911836921936469], [-2.268509078231318, 4.911636033176421], [-2.268738345997804, 4.91146395959413], [-2.26885310578615, 4.911263039357834], [-2.269010770430668, 4.911105247009573], [-2.2691398159497, 4.91093306370999], [-2.26932600619017, 4.910875825459016], [-2.269612529294761, 4.91071817430435], [-2.269755735538695, 4.910689610936856], [-2.269927452090201, 4.910775961141724], [-2.269884359275693, 4.910905157746868], [-2.269769647151463, 4.911062996859869], [-2.269669283710414, 4.911192130512461], [-2.269583440723181, 4.911134595485237], [-2.269569279998223, 4.910990975553148], [-2.269425979325433, 4.91110570206655], [-2.269454411391905, 4.911292418410426], [-2.269353859992577, 4.911593877455516], [-2.26932503762032, 4.911766170472447], [-2.26956829524056, 4.911895680940972], [-2.269740230327329, 4.911780985903818], [-2.26978327727636, 4.911694870421968], [-2.269869213793129, 4.911666244101923], [-2.269940708097181, 4.911752484589556], [-2.26996926427006, 4.911824317937999], [-2.270069347122615, 4.911953671025174], [-2.270312745037131, 4.911953937224496], [-2.270341443302925, 4.911896527202998], [-2.270484727787903, 4.911796161064046], [-2.270628043749184, 4.911667073276874], [-2.270699584817919, 4.911710233540475], [-2.270871379610412, 4.911724780973884], [-2.271057601327186, 4.911638821974122], [-2.271157871238699, 4.911595850568119], [-2.27115802772073, 4.911452246823785], [-2.271172438457199, 4.911366099865688], [-2.271172578751475, 4.911236855596485], [-2.271172781098926, 4.911050170728856], [-2.271258842621421, 4.910906660514001], [-2.2713020442539, 4.910676940388555], [-2.27134502825038, 4.910648266404394], [-2.271402485935937, 4.910476003964448], [-2.271488515082865, 4.910361214498494], [-2.271560258499107, 4.910217688995203], [-2.271674970623337, 4.910059848982883], [-2.271804015243049, 4.909887664783923], [-2.271947439122982, 4.909658054375768], [-2.271990610178477, 4.909457054999109], [-2.272091050061874, 4.909256118575058], [-2.272162887007596, 4.909026429925859], [-2.272248932342279, 4.908897280085455], [-2.272292087210019, 4.908710641083246], [-2.272220701723938, 4.908523877974631], [-2.272249429667397, 4.908437747204346], [-2.272106363717683, 4.908337068101275], [-2.272077993704386, 4.908092909360334], [-2.272121101807329, 4.90794935238074], [-2.272235828320788, 4.907777151994026], [-2.272364825276441, 4.907648048918361], [-2.272465079000142, 4.907619436987488], [-2.272636779363836, 4.907720147566806], [-2.272736924269566, 4.907792058256916], [-2.272851355805358, 4.907892705883739], [-2.273037249269578, 4.908108314746983]]]}', + '{"type": "Polygon", "coordinates": [[[-2.277179075154152, 4.911432361014704], [-2.2783341994637, 4.911859560570292], [-2.278514835491194, 4.912058478016377], [-2.278657443686029, 4.912191090246665], [-2.278828572980217, 4.912339804838155], [-2.278904630444288, 4.912425055172207], [-2.27903773100644, 4.912491360388003], [-2.279180339201218, 4.912576611621375], [-2.279256396665346, 4.912690278733407], [-2.279313439763371, 4.912927084617309], [-2.279265903398652, 4.913220724956545], [-2.279284917764699, 4.913400697284374], [-2.279370482861452, 4.913590143070678], [-2.279513090156911, 4.913684865064511], [-2.279703233817145, 4.913703810182767], [-2.2797412634485, 4.913864838291715], [-2.279731755815817, 4.9140542831787], [-2.279674712717792, 4.914167949391413], [-2.279665205085109, 4.914400966431515], [-2.279655698351746, 4.914561994540463], [-2.279750770181863, 4.914647244874516], [-2.279931407108677, 4.914751439427448], [-2.280093028770239, 4.914760911986548], [-2.280235636965017, 4.914647244874516], [-2.280378244260532, 4.914486216765567], [-2.280539866821357, 4.914419911549714], [-2.280758532480263, 4.914561994540463], [-2.280882125409732, 4.914656717433616], [-2.281091283435956, 4.914770383646328], [-2.281224384897371, 4.914817745542564], [-2.281386006558932, 4.914912467536396], [-2.281309949094805, 4.915035606308209], [-2.281262413629463, 4.91527241219211], [-2.281157834166663, 4.915433440301058], [-2.281062762336546, 4.915622884288723], [-2.28094867614044, 4.915746023060592], [-2.280853604310323, 4.915831273394588], [-2.280625431918111, 4.915954412166457], [-2.280615924285428, 4.916087023497369], [-2.280796561212298, 4.916238579047217], [-2.280910647408405, 4.916352245259986], [-2.281119805434628, 4.916399606256846], [-2.281338471093534, 4.916446967253762], [-2.281443049656957, 4.916579578584731], [-2.281576150219109, 4.91672166157548], [-2.281794815878015, 4.916759550013296], [-2.281927916440168, 4.916835327788192], [-2.282241653929134, 4.916769022572396], [-2.282431797589368, 4.916854272007072], [-2.282270175028486, 4.916996354997821], [-2.282194117564416, 4.917138437988569], [-2.282194117564416, 4.917318410316454], [-2.282089539000992, 4.917451020748047], [-2.281918409706805, 4.917356298754214], [-2.281817633476976, 4.9172663125903], [-2.28149438835527, 4.917218951593384], [-2.28129473796173, 4.917370507143232], [-2.281237694863705, 4.917531534352861], [-2.281228187231022, 4.917711505781426], [-2.281323259061082, 4.917910422328191], [-2.281209172864976, 4.9180809211976], [-2.281228187231022, 4.918260892626165], [-2.281323259061082, 4.918535586048506], [-2.281152129766951, 4.918687140699035], [-2.281161637399578, 4.918819752030004], [-2.281123608667542, 4.919056556115208], [-2.280981000472764, 4.919236527543774], [-2.280762334813858, 4.91929336020047], [-2.280600713152353, 4.919558581063711], [-2.280420076225482, 4.919757496711156], [-2.280125353102505, 4.919681718936204], [-2.279964682024399, 4.919584155984069], [-2.279774538364222, 4.919726238075498], [-2.279660452168059, 4.91984937594799], [-2.279422772143164, 4.91983990428821], [-2.279299178314375, 4.919820960069273], [-2.279090020288152, 4.919934625382723], [-2.279118542286824, 4.920038819935655], [-2.278928398626647, 4.920114596811231], [-2.278776283698505, 4.920029347376556], [-2.278519589307564, 4.920161957808148], [-2.278548110406916, 4.920322984118457], [-2.278510081674881, 4.920502955547022], [-2.278300923648658, 4.920427177772069], [-2.278205851818541, 4.920569259863498], [-2.278082258889071, 4.920663981857331], [-2.277759013767422, 4.920749231292064], [-2.277597392105918, 4.920787119729823], [-2.277445277177776, 4.920720814514027], [-2.277245625884859, 4.920654509298231], [-2.277074496590672, 4.920493482987922], [-2.276827309832413, 4.92039876099409], [-2.276646672905599, 4.920484010428765], [-2.276429767219952, 4.920042248151276], [-2.276392082928226, 4.919582673002026], [-2.276087299089511, 4.918969629444916], [-2.275897182408983, 4.918241826998781], [-2.276050276698868, 4.917897342588333], [-2.276489436338352, 4.917821231164908], [-2.276757133934552, 4.917419429363235], [-2.276872212082878, 4.916921724757856], [-2.27696797279367, 4.916634619791921], [-2.277292858278713, 4.916309469006819], [-2.277789162639692, 4.916348301732796], [-2.278285405846759, 4.916444575057142], [-2.278819891824526, 4.916483448252563], [-2.279297312221161, 4.916330786536605], [-2.279393051348165, 4.916062828137058], [-2.279240843789864, 4.915583982314956], [-2.279279312290384, 4.915315961862177], [-2.279165306133905, 4.914818009943247], [-2.278994050035351, 4.914300847606057], [-2.278994503293632, 4.913879608756986], [-2.278708541366029, 4.9135155011395], [-2.277906589817235, 4.913667811220535], [-2.277505490386034, 4.913858849706173], [-2.277199759561199, 4.914126579677941], [-2.277084784834926, 4.914528547854275], [-2.276740811239392, 4.914853677954909], [-2.276701886782575, 4.915542937256816], [-2.276205148948407, 4.91590619591426], [-2.275842311173676, 4.916020685906005], [-2.275708099948815, 4.916556662959692], [-2.275478458963676, 4.917073389125676], [-2.275172766809703, 4.917302824166029], [-2.274548559068705, 4.917291881215419], [-2.274574106110038, 4.917251157215162], [-2.274574106110038, 4.91713749100245], [-2.274697699039507, 4.91715643522133], [-2.274773756503635, 4.917080657446434], [-2.274849813967705, 4.917014352230638], [-2.274821292868296, 4.916853325021009], [-2.274764249770271, 4.916777547246056], [-2.274574106110038, 4.916673352693124], [-2.274412483549213, 4.916588103258391], [-2.274460019913931, 4.916512325483495], [-2.274631149208119, 4.916398659270783], [-2.274726221038236, 4.916294464717794], [-2.274802278502307, 4.916218686942898], [-2.274887842699741, 4.916048187174169], [-2.274992422162484, 4.915953465180337], [-2.275201580188707, 4.915962936840117], [-2.27541073821493, 4.915887159065164], [-2.275477288945694, 4.915735604414635], [-2.275581867509118, 4.915593520524624], [-2.275638910607199, 4.915375659758922], [-2.275638910607199, 4.915176743212157], [-2.275638910607199, 4.915044131881245], [-2.275638910607199, 4.914949408988093], [-2.275467781313012, 4.914911520550277], [-2.275353695116905, 4.914835742775381], [-2.275401231481624, 4.914712603104192], [-2.275543838777082, 4.914655770447496], [-2.275676939339235, 4.914693658885312], [-2.275667432605871, 4.914485269779448], [-2.275648418239882, 4.914371602667359], [-2.275762504435988, 4.914267408114426], [-2.275886098264778, 4.914257936454646], [-2.276000184460884, 4.91422004711751], [-2.276123777390353, 4.914153741002394], [-2.276285399951178, 4.914134796783515], [-2.276408992880647, 4.914077963227442], [-2.276551601075482, 4.91396429611541], [-2.276608644173564, 4.913888518340514], [-2.276684701637635, 4.913793796346681], [-2.276817802199787, 4.913755907009545], [-2.276789281100378, 4.913651712456613], [-2.276827309832413, 4.913509628566544], [-2.276865338564448, 4.913367544676476], [-2.277007945859964, 4.91329176690158], [-2.277131539688753, 4.913367544676476], [-2.277064988958045, 4.913481211788564], [-2.276941396028576, 4.913575934681717], [-2.276979424760611, 4.913708546012629], [-2.277150554054799, 4.913661185015712], [-2.27722661151887, 4.913727490231565], [-2.277331190981613, 4.913594878900597], [-2.277350205347659, 4.913490684347664], [-2.277464291543765, 4.913424378232492], [-2.277635420837953, 4.913443322451428], [-2.277682956303352, 4.913367544676476], [-2.277806550132141, 4.913310711120459], [-2.277920636328247, 4.913253877564443], [-2.277806550132141, 4.913111793674375], [-2.277920636328247, 4.913026543340379], [-2.278006201425001, 4.912874987790531], [-2.278006201425001, 4.912730062042783], [-2.277939650694293, 4.912663755927667], [-2.277844578864176, 4.912578505593615], [-2.277920636328247, 4.912502727818662], [-2.277996693792318, 4.91236064392865], [-2.278015708158364, 4.912265921035498], [-2.277911128695564, 4.912256448476342], [-2.277844578864176, 4.912332227150614], [-2.277759013767422, 4.91239853236641], [-2.277682956303352, 4.912303809473258], [-2.277701970669341, 4.912237504257462], [-2.277739999401376, 4.91211436458633], [-2.277587884473235, 4.91213330880521], [-2.277454783911082, 4.912142781364309], [-2.277293162249578, 4.912095420367393], [-2.277074496590672, 4.912010169134021], [-2.277112525322764, 4.911858613584172], [-2.277179075154152, 4.911716528794841], [-2.277274147883588, 4.911602861682752], [-2.277302668982941, 4.911517611348756], [-2.277179075154152, 4.911432361014704]]]}' + ], + ], + [ + 'uuid' => 'c1e2d5a6-288a-479f-977d-531ca5b52933', + 'geometry' => [ + '{"type": "Polygon", "coordinates": [[[-2.407034725072435, 4.966373290195236], [-2.406825572442187, 4.96646313786357], [-2.406527084757101, 4.96655107717055], [-2.40646541284849, 4.966550920688462], [-2.406418320748855, 4.966583120914322], [-2.406354637056779, 4.966599121652166], [-2.406274894170963, 4.966615083719205], [-2.406211697911488, 4.966647264159974], [-2.406164037440305, 4.966663315259837], [-2.406084259480963, 4.966679285420753], [-2.405988114759623, 4.966679190991897], [-2.405925446402193, 4.966711966783862], [-2.405862278921006, 4.966728569168197], [-2.405814544705436, 4.966728881232939], [-2.405735817154209, 4.966761750554383], [-2.405670963444152, 4.966761977183523], [-2.40559161626004, 4.96674599802941], [-2.40541747243833, 4.966521707111156], [-2.40529831766321, 4.96633012273702], [-2.405298542493711, 4.966078808989892], [-2.405274956873711, 4.965767637266708], [-2.405180795157548, 4.9653899615775], [-2.405194353336697, 4.965310467803874], [-2.405192444075965, 4.965246876742015], [-2.405174486413387, 4.965183174164224], [-2.405141153941031, 4.965135346419174], [-2.405076068205915, 4.965087407158137], [-2.405011061611162, 4.96503952545379], [-2.404962473938951, 4.965007609413703], [-2.40488127684938, 4.964943930218226], [-2.404863579090829, 4.96488064402655], [-2.404830069452089, 4.964817365928752], [-2.404812879810493, 4.964769997737278], [-2.404794808833287, 4.964691194642967], [-2.404793461648865, 4.964644011711812], [-2.404743845152268, 4.964565279663987], [-2.404726302077108, 4.96450247550888], [-2.404692996584458, 4.964439682145667], [-2.404644317181464, 4.964392507308389], [-2.404580847528052, 4.964376630677009], [-2.404516979474977, 4.964345122029783], [-2.404453170777174, 4.964313643060223], [-2.404420018169276, 4.964251067332953], [-2.404418325645167, 4.964188675067362], [-2.404416214936305, 4.96411079647612], [-2.40444590065772, 4.964048668611213], [-2.404475984778855, 4.964002136789247], [-2.404521284529608, 4.96394016720501], [-2.404566534817661, 4.963878262371964], [-2.40459652630858, 4.963831869045578], [-2.404641695657688, 4.963770079325741], [-2.404703401740562, 4.963739285639576], [-2.404765086239706, 4.963708500946609], [-2.404859043809779, 4.963708678113051], [-2.404922206794367, 4.963724250773623], [-2.405002116054789, 4.963770806877278], [-2.405051260407276, 4.963832852903863], [-2.405245799953661, 4.964004109901794], [-2.405294709583131, 4.964050934902787], [-2.405328484521874, 4.964113365839182], [-2.405378082132756, 4.964175933472575], [-2.40539559822821, 4.964222858298342], [-2.405352835464953, 4.964347932411215], [-2.405323511270979, 4.964410551305946], [-2.405294082755688, 4.96447320527426], [-2.405249216478126, 4.964551525432626], [-2.405251283120151, 4.964614403332121], [-2.405268676008518, 4.964661675296099], [-2.405335147598919, 4.964756442256032], [-2.405398787224215, 4.964772291008444], [-2.405460813465709, 4.964740819233441], [-2.40552336401197, 4.964725124265101], [-2.405569005505129, 4.96467790446178], [-2.405630372543612, 4.964630737718437], [-2.405660750742982, 4.964599304614239], [-2.405722041339061, 4.964552200823448], [-2.405767535343443, 4.964505118616387], [-2.405828735108003, 4.964458090368623], [-2.405890557203406, 4.964426803853939], [-2.405952953965595, 4.964411201515759], [-2.406014823725116, 4.964379974356405], [-2.406076431781855, 4.964333249180129], [-2.406138009261667, 4.96428654738628], [-2.406168484587852, 4.964255393971314], [-2.406229401965277, 4.96419312581213], [-2.406273832970953, 4.964115267905356], [-2.406287198695225, 4.964052987155696], [-2.406284743546053, 4.963990730687726], [-2.406282291094783, 4.963928539870267], [-2.406296244076373, 4.963881977471317], [-2.406279228003939, 4.96385089690142], [-2.406178637034429, 4.963711115275771], [-2.405954633899171, 4.963602635453299], [-2.405874890114035, 4.963571643916282], [-2.405810381743663, 4.963525287462119], [-2.405746480415701, 4.963494382259967], [-2.405683161848401, 4.963478896833692], [-2.40563556612841, 4.963463441084969], [-2.405571354534345, 4.963432508003905], [-2.405506501723607, 4.963386207307678], [-2.405426006105245, 4.963339943483675], [-2.405409108743299, 4.963309209152726], [-2.40531198286169, 4.96323239356019], [-2.405279079365982, 4.963186444498888], [-2.405245602102809, 4.963125256425485], [-2.405227151611712, 4.963048898587829], [-2.405209508711835, 4.962987920955811], [-2.40517628685609, 4.962927017967559], [-2.405173793935376, 4.962851005469588], [-2.405171802836378, 4.962790276050441], [-2.40516981353602, 4.962729615879141], [-2.405214522432232, 4.962669000673884], [-2.40526021338809, 4.962638696218903], [-2.405321463514667, 4.962608389065963], [-2.405367780398706, 4.962593220200972], [-2.405499865626268, 4.96279021309789], [-2.405533955327712, 4.962866229193196], [-2.405583232779918, 4.962927142973342], [-2.405617449285785, 4.963003400086905], [-2.405651151379459, 4.963064504523288], [-2.405669947210242, 4.96314100355454], [-2.405703926295132, 4.963202324727547], [-2.405751816093414, 4.963217727416236], [-2.405830623684267, 4.963217845227405], [-2.405878383080847, 4.963233238023577], [-2.405957330066656, 4.963248639812946], [-2.406191967685118, 4.963279303996728], [-2.406254715182911, 4.963294634739668], [-2.406317478868516, 4.963309969979207], [-2.406395183890595, 4.963309939402222], [-2.406472267481092, 4.963294548404747], [-2.406535112105701, 4.963294605062003], [-2.406578970243231, 4.963233157983893], [-2.406622778018743, 4.963171781052893], [-2.406619224797339, 4.963095177700325], [-2.406585192652472, 4.963033977036446], [-2.4065664579756, 4.96297283302988], [-2.406546944485854, 4.962896518359685], [-2.406512794529817, 4.962835565908676], [-2.406478692237783, 4.962774695295991], [-2.406415299026776, 4.962744309002687], [-2.406352717903587, 4.962729137439794], [-2.406290677272864, 4.96271396857486], [-2.406242075211537, 4.962653247249591], [-2.406208213937873, 4.962577428105874], [-2.406175030753047, 4.962516846175561], [-2.406142508569872, 4.962471450197313], [-2.406078773616457, 4.962410977084971], [-2.406045229803453, 4.962350608294003], [-2.405996544105165, 4.962290303354848], [-2.405932569932133, 4.962260327152421], [-2.405868671302073, 4.96223038692284], [-2.405790607450513, 4.962230568585937], [-2.405729530893097, 4.962260776813423], [-2.405668444443108, 4.962290992235523], [-2.405607993813817, 4.962336265006684], [-2.40556183251249, 4.962351410489248], [-2.40550128385712, 4.962396716535295], [-2.405441037373919, 4.962457150077455], [-2.405379166715136, 4.962472331532922], [-2.405315760014275, 4.962442249210483], [-2.405267716431922, 4.96239708885463], [-2.405219957934662, 4.962351950082507], [-2.405187329631531, 4.962291783638932], [-2.405169785657051, 4.962216635389439], [-2.405136812913554, 4.962141610347089], [-2.405119732089929, 4.962081645351702], [-2.405087236886402, 4.962021773885795], [-2.405085220606395, 4.961946943097018], [-2.40508385723416, 4.961887079724988], [-2.405082234857218, 4.961812296600272], [-2.405080614278859, 4.961737586320623], [-2.405094023170591, 4.961663024429072], [-2.405139519872876, 4.961648087589197], [-2.405200773596789, 4.961648010247529], [-2.405277812221186, 4.961662796900612], [-2.405339446358312, 4.961677640210951], [-2.405385396318934, 4.961677588949613], [-2.405477334911041, 4.961677475635042], [-2.405539218160357, 4.961692270382002], [-2.405616400676308, 4.961707066927659], [-2.405678203885941, 4.961721906640719], [-2.405755848653428, 4.961751657113325], [-2.405817215691911, 4.961751584268256], [-2.405879580977853, 4.961781370713709], [-2.405958851719561, 4.961825879960429], [-2.40602205607297, 4.961855562084565], [-2.406068114851564, 4.961870586158682], [-2.406130722954458, 4.961915504596902], [-2.406194095481055, 4.96197539854586], [-2.406271915616344, 4.96202041141288], [-2.406303652691349, 4.96205039930652], [-2.406352520052735, 4.962110327429741], [-2.406415219886469, 4.9621403207193], [-2.40649498075868, 4.962200340573304], [-2.406544328357995, 4.962260426977195], [-2.406592831493924, 4.962305547762867], [-2.406627029114077, 4.962365774460977], [-2.406659294091128, 4.962395932326444], [-2.40670853736907, 4.96245630291611], [-2.406757066585328, 4.962501626049232], [-2.406820157624111, 4.962531857659144], [-2.406870479189195, 4.96260754280388], [-2.406905502386962, 4.962683356551736], [-2.406939717993566, 4.96274409406476], [-2.406958578575484, 4.962804917013329], [-2.406992871523755, 4.962865811907761], [-2.407042627415251, 4.962926776949246], [-2.407091555031172, 4.962972544347508], [-2.407155056160889, 4.963003061941777], [-2.407234022931789, 4.963033585831397], [-2.407296673302767, 4.963048839232613], [-2.40736027695516, 4.963079399994342], [-2.407422967795696, 4.963094665986091], [-2.40750111348558, 4.963109923883906], [-2.407563834902987, 4.963125196170893], [-2.407625589549298, 4.963125152104112], [-2.407719207175603, 4.963140404606008], [-2.407898268490271, 4.963278364205166], [-2.407995194722389, 4.963339788800283], [-2.408058111292746, 4.963355121341806], [-2.408107306906629, 4.963401229583155], [-2.408125809558442, 4.963447429555288], [-2.408147523689195, 4.963539994075518], [-2.408167141500314, 4.963601790090593], [-2.408173766805874, 4.963694725131518], [-2.408194816337641, 4.963772295255296], [-2.408229465417435, 4.963834273832731], [-2.408265092060276, 4.963911858345625], [-2.408269799111906, 4.963989620025018], [-2.408290007777566, 4.964067456348062], [-2.408293793024086, 4.964129835123856], [-2.408314091622003, 4.964207880089646], [-2.408318225805431, 4.964270498984376], [-2.408335902879571, 4.964301813378029], [-2.408401469751993, 4.96434875708951], [-2.408478467906946, 4.964332942511362], [-2.408539828650134, 4.964317161208044], [-2.408584484486312, 4.964285741593756], [-2.408660292838192, 4.964254296798401], [-2.408719363907494, 4.964207255060842], [-2.408780585255784, 4.96419152142164], [-2.408839549305696, 4.964144547133174], [-2.408900702305516, 4.964128838675038], [-2.408960696079191, 4.964097531475943], [-2.409020644886823, 4.964066247659275], [-2.409063856411819, 4.9640194280542], [-2.40910586734185, 4.963957096942522], [-2.409148982639408, 4.963910388853435], [-2.409211082625291, 4.963910307015112], [-2.409262368263626, 4.963972444772537], [-2.409329256240142, 4.964034634690677], [-2.409379471685213, 4.96408132389405], [-2.40933521334938, 4.964112587925626], [-2.409229858670983, 4.964159582898503], [-2.409167604001652, 4.964159677327302], [-2.409093240860329, 4.964206678595417], [-2.409049963684822, 4.964253680762795], [-2.409007787279506, 4.96431640038162], [-2.408965554216934, 4.964379202738087], [-2.408954493455042, 4.964442036570802], [-2.408942052233897, 4.964489165542602], [-2.40886705327182, 4.964536441103917], [-2.408823257187521, 4.964583706772714], [-2.408780445860828, 4.964646769033209], [-2.40873554001314, 4.964678373907873], [-2.408671910280361, 4.964662731100191], [-2.408608292238853, 4.964647090990525], [-2.408530024241202, 4.964647238479301], [-2.408467403547775, 4.964647356290527], [-2.408389117563672, 4.964647502879984], [-2.408343106449138, 4.964663351632396], [-2.408249131791933, 4.964663529698157], [-2.408172687619412, 4.964695216411144], [-2.40809618948748, 4.96472692830514], [-2.408036095889088, 4.964774380133576], [-2.407990650448085, 4.96480600928993], [-2.407914549816553, 4.964853490796031], [-2.407853260119794, 4.964885173012362], [-2.407823494358752, 4.964916860624669], [-2.407731452344592, 4.964964447351406], [-2.407670918078338, 4.965012062856488], [-2.407610328953467, 4.965059720629654], [-2.407581304233759, 4.965107408979804], [-2.407520340091594, 4.965155031679444], [-2.407458348923626, 4.965186710298497], [-2.40738176265819, 4.965250090919028], [-2.40732081650242, 4.965297895281708], [-2.407290027312854, 4.965313887026298], [-2.407212109151487, 4.965330091010912], [-2.407118906112601, 4.965362181519538], [-2.407057091211755, 4.965394155116201], [-2.406978807026292, 4.965410264672016], [-2.406916921978336, 4.965442267946344], [-2.406870168923092, 4.965458314549608], [-2.406792420733609, 4.965490387071782], [-2.406729821623912, 4.965506490332302], [-2.406684744005702, 4.965570378170469], [-2.406687818787759, 4.965650259551865], [-2.406690282030866, 4.965714236422912], [-2.406724425691664, 4.965778188112949], [-2.406772358657406, 4.96579394963112], [-2.406900366358798, 4.965841295339544], [-2.407027872238473, 4.965872691571519], [-2.407136305296206, 4.965824056235249], [-2.407198140881462, 4.965791782265001], [-2.407262405535562, 4.965823406924756], [-2.407280883905628, 4.965887396386279], [-2.407316914344051, 4.965968036795516], [-2.407322320168873, 4.966065176166978], [-2.407325929148271, 4.966130039769553], [-2.407280599719854, 4.966178633737002], [-2.407218326164752, 4.966210979653056], [-2.407156893475815, 4.966259606895392], [-2.407109697954127, 4.966275763215947], [-2.407065076292213, 4.966340773408035], [-2.407034725072435, 4.966373290195236]]]}', + ], + ], + ], + ], + ]; public function run(): void { - $project = Project::factory()->create([ - 'country' => 'AU', - ]); + foreach (self::TEST_PROJECTS as $projectDef) { + $project = Project::factory()->create([ + 'country' => $projectDef['country'], + ]); + + foreach ($projectDef['sites'] as $site) { + $site = Site::factory()->create([ + 'uuid' => $site['uuid'], + 'project_id' => $project->id, + ]); - $site = Site::factory()->create([ - 'uuid' => self::TEST_SITE_UUID, - 'project_id' => $project->id, - ]); + foreach ($site['geometry'] ?? [] as $geojsonString) { + $geometry = PolygonGeometry::factory()->geojson($geojsonString)->create(); + SitePolygon::factory()->site($site)->geometry($geometry)->create(); + } + } + } } } diff --git a/tests/Unit/Validators/Extensions/PolygonValidatorsTest.php b/tests/Unit/Validators/Extensions/PolygonValidatorsTest.php index c37089a90..1d3416e65 100644 --- a/tests/Unit/Validators/Extensions/PolygonValidatorsTest.php +++ b/tests/Unit/Validators/Extensions/PolygonValidatorsTest.php @@ -8,6 +8,7 @@ use Database\Seeders\PolygonValidationSeeder; use Database\Seeders\WorldCountriesGeneralizedTableSeeder; use Illuminate\Support\Facades\App; +use Illuminate\Support\Facades\Log; use Illuminate\Support\Str; use Tests\TestCase; @@ -20,6 +21,11 @@ public function test_coordinate_system() $this->runValidationTest('FEATURE_BOUNDS'); } + public function test_not_overlapping() + { + $this->runValidationImportTest('NOT_OVERLAPPING'); + } + public function test_self_intersection() { $this->runValidationTest('SELF_INTERSECTION'); diff --git a/tests/Unit/Validators/Extensions/Polygons/TestFiles/not_overlapping_fail.geojson b/tests/Unit/Validators/Extensions/Polygons/TestFiles/not_overlapping_fail.geojson new file mode 100644 index 000000000..55358ac09 --- /dev/null +++ b/tests/Unit/Validators/Extensions/Polygons/TestFiles/not_overlapping_fail.geojson @@ -0,0 +1 @@ +{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"poly_name":"overlapping polygon","plantstart":"2021-08-03","plantend":"2021-08-03","practice":"Tree Planting, Assisted Natural Regeneration","target_sys":"Natural Forest","distr":"Full Coverage, Partial","num_trees":214,"site_id":"c1e2d5a6-288a-479f-977d-531ca5b52933"},"geometry":{"coordinates":[[[149.98722294615868,-34.48513610575039],[149.98625469584226,-34.48704171558581],[149.99313754217428,-34.486272942260506],[149.99362172183106,-34.48630424349619],[149.99384957108282,-34.48610078525555],[149.9939729894259,-34.48588950117119],[149.99363121554984,-34.48580342231815],[149.98722294615868,-34.48513610575039]]],"type":"Polygon"}}]} diff --git a/tests/Unit/Validators/Extensions/Polygons/TestFiles/not_overlapping_pass.geojson b/tests/Unit/Validators/Extensions/Polygons/TestFiles/not_overlapping_pass.geojson new file mode 100644 index 000000000..cc24efcb9 --- /dev/null +++ b/tests/Unit/Validators/Extensions/Polygons/TestFiles/not_overlapping_pass.geojson @@ -0,0 +1 @@ +{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"poly_name":"non-overlapping polygon","plantstart":"2021-08-03","plantend":"2021-08-03","practice":"Tree Planting, Direct Seeding","target_sys":"Natural Forest","distr":"Full Coverage, Partial","num_trees":214,"site_id":"c1e2d5a6-288a-479f-977d-531ca5b52933"},"geometry":{"coordinates":[[[149.9984566668453,-34.47506259655472],[150.00151474055508,-34.478274209769985],[150.00247824321912,-34.47526980111218],[150.00084447783348,-34.47316319719226],[149.9984566668453,-34.47506259655472]]],"type":"Polygon"}}]} From db20678440400fd9b6792029dc03a6f669cc0d0d Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Thu, 9 May 2024 22:14:26 -0700 Subject: [PATCH 27/64] [TM-799] Test estimated area validation. --- app/Models/V2/PolygonGeometry.php | 2 + app/Models/V2/Projects/Project.php | 14 ++++ app/Models/V2/Sites/SitePolygon.php | 2 + .../Extensions/Polygons/EstimatedArea.php | 5 +- app/Validators/SitePolygonValidator.php | 4 + .../factories/V2/Sites/SitePolygonFactory.php | 1 + database/seeders/PolygonValidationSeeder.php | 80 +++++++++++++++++-- .../Extensions/PolygonValidatorsTest.php | 24 +++--- .../TestFiles/estimated_area_fail.geojson | 1 + .../TestFiles/estimated_area_pass.geojson | 1 + 10 files changed, 113 insertions(+), 21 deletions(-) create mode 100644 tests/Unit/Validators/Extensions/Polygons/TestFiles/estimated_area_fail.geojson create mode 100644 tests/Unit/Validators/Extensions/Polygons/TestFiles/estimated_area_pass.geojson diff --git a/app/Models/V2/PolygonGeometry.php b/app/Models/V2/PolygonGeometry.php index d082498a8..dfdec53f0 100644 --- a/app/Models/V2/PolygonGeometry.php +++ b/app/Models/V2/PolygonGeometry.php @@ -4,6 +4,7 @@ use App\Models\Traits\HasUuid; use App\Models\V2\Sites\CriteriaSite; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; @@ -15,6 +16,7 @@ class PolygonGeometry extends Model { use HasUuid; use SoftDeletes; + use HasFactory; protected $table = 'polygon_geometry'; diff --git a/app/Models/V2/Projects/Project.php b/app/Models/V2/Projects/Project.php index 9b9950037..d9d153bfc 100644 --- a/app/Models/V2/Projects/Project.php +++ b/app/Models/V2/Projects/Project.php @@ -21,6 +21,7 @@ use App\Models\V2\Polygon; use App\Models\V2\Seeding; use App\Models\V2\Sites\Site; +use App\Models\V2\Sites\SitePolygon; use App\Models\V2\Sites\SiteReport; use App\Models\V2\Tasks\Task; use App\Models\V2\TreeSpecies\TreeSpecies; @@ -261,11 +262,24 @@ public function monitoringHistoric(): HasMany ->isStatus(ProjectMonitoring::STATUS_ARCHIVED); } + // @deprecated public function polygons() { return $this->morphMany(Polygon::class, 'polygonable'); } + public function sitePolygons(): HasManyThrough + { + return $this->hasManyThrough( + SitePolygon::class, + Site::class, + 'project_id', + 'site_id', + 'id', + 'uuid' + ); + } + public function treeSpecies() { return $this->morphMany(TreeSpecies::class, 'speciesable'); diff --git a/app/Models/V2/Sites/SitePolygon.php b/app/Models/V2/Sites/SitePolygon.php index 5c5439c3e..0600fc97f 100644 --- a/app/Models/V2/Sites/SitePolygon.php +++ b/app/Models/V2/Sites/SitePolygon.php @@ -6,6 +6,7 @@ use App\Models\V2\PolygonGeometry; use App\Models\V2\Projects\Project; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; use Znck\Eloquent\Relations\BelongsToThrough; @@ -19,6 +20,7 @@ class SitePolygon extends Model use HasUuid; use SoftDeletes; use BelongsToThroughTrait; + use HasFactory; protected $table = 'site_polygon'; diff --git a/app/Validators/Extensions/Polygons/EstimatedArea.php b/app/Validators/Extensions/Polygons/EstimatedArea.php index 13f636dff..14ab73683 100644 --- a/app/Validators/Extensions/Polygons/EstimatedArea.php +++ b/app/Validators/Extensions/Polygons/EstimatedArea.php @@ -2,7 +2,6 @@ namespace App\Validators\Extensions\Polygons; -use App\Models\V2\Projects\Project; use App\Models\V2\Sites\SitePolygon; use App\Validators\Extensions\Extension; @@ -32,7 +31,7 @@ public static function getAreaData(string $polygonUuid): array return ['valid' => false, 'error' => 'Site polygon not found for the given polygon ID', 'status' => 404]; } - $project = Project::isUuid($sitePolygon->project_uuid)->first(); + $project = $sitePolygon->project; if ($project == null) { return [ 'valid' => false, @@ -45,7 +44,7 @@ public static function getAreaData(string $polygonUuid): array return ['valid' => false, 'error' => 'Total hectares restored goal not set for the project', 'status' => 500]; } - $sumEstArea = SitePolygon::where('project_id', $sitePolygon->project_id)->sum('est_area'); + $sumEstArea = $project->sitePolygons()->sum('est_area'); $lowerBound = self::LOWER_BOUND_MULTIPLIER * $project->total_hectares_restored_goal; $upperBound = self::UPPER_BOUND_MULTIPLIER * $project->total_hectares_restored_goal; $valid = $sumEstArea >= $lowerBound && $sumEstArea <= $upperBound; diff --git a/app/Validators/SitePolygonValidator.php b/app/Validators/SitePolygonValidator.php index ac593bed9..cb765452d 100644 --- a/app/Validators/SitePolygonValidator.php +++ b/app/Validators/SitePolygonValidator.php @@ -32,6 +32,10 @@ class SitePolygonValidator extends Validator '*' => 'string|uuid|has_polygon_site|not_overlapping', ]; + public const ESTIMATED_AREA = [ + '*' => 'string|uuid|has_polygon_site|estimated_area', + ]; + public const SCHEMA = [ 'features' => 'required|array', 'features.*.properties.poly_name' => 'required', diff --git a/database/factories/V2/Sites/SitePolygonFactory.php b/database/factories/V2/Sites/SitePolygonFactory.php index 6b9084dea..b12f83099 100644 --- a/database/factories/V2/Sites/SitePolygonFactory.php +++ b/database/factories/V2/Sites/SitePolygonFactory.php @@ -13,6 +13,7 @@ public function definition() return [ 'poly_id' => PolygonGeometry::factory()->create()->uuid, 'site_id' => Site::factory()->create()->uuid, + 'est_area' => $this->faker->numberBetween(2.0, 50.0), ]; } diff --git a/database/seeders/PolygonValidationSeeder.php b/database/seeders/PolygonValidationSeeder.php index 2eda0ad0b..419b51cc6 100644 --- a/database/seeders/PolygonValidationSeeder.php +++ b/database/seeders/PolygonValidationSeeder.php @@ -28,19 +28,78 @@ class PolygonValidationSeeder extends Seeder [ 'uuid' => '8d86e97a-3d4e-4132-95e1-435225d47f28', 'geometry' => [ - '{"type": "Polygon", "coordinates": [[[-2.27521108782139, 4.909556846471332], [-2.275458274579648, 4.909575790690212], [-2.275572360775755, 4.909518957134196], [-2.275572360775755, 4.909320038788792], [-2.275534332043719, 4.909140064662324], [-2.275581867509118, 4.908979035653999], [-2.275724475703953, 4.908884311861527], [-2.275981170094894, 4.90894114631692], [-2.276275892318552, 4.908884311861527], [-2.276437514879376, 4.9089506179767], [-2.276504064710764, 4.909073758547152], [-2.276675194904271, 4.909121120443388], [-2.276760759101705, 4.909016924991136], [-2.27692238166253, 4.908780116409275], [-2.27702696022601, 4.908675920957023], [-2.277084003324035, 4.908732755412359], [-2.277093510956718, 4.908931673757763], [-2.277074496590672, 4.909177953999404], [-2.277179075154152, 4.909386344903965], [-2.277435769545093, 4.909509484575096], [-2.277701970669341, 4.909509484575096], [-2.277987187059011, 4.909537901353133], [-2.277968172692965, 4.909367399785708], [-2.278006201425001, 4.909035869210072], [-2.277911128695564, 4.908799061527532], [-2.277835071231493, 4.908514891948755], [-2.277949157427599, 4.908448585833639], [-2.278101273255118, 4.908533836167635], [-2.278158316353142, 4.908666449297243], [-2.278405503111458, 4.908685393516123], [-2.278481560575528, 4.908581198063871], [-2.278538603673553, 4.908401223937403], [-2.278576632405588, 4.908259139148015], [-2.278614661137681, 4.908145471136606], [-2.278700226234378, 4.908079165021491], [-2.278823819163904, 4.908012858906375], [-2.27883332679653, 4.908126526018407], [-2.278899876627975, 4.908278083366895], [-2.278890369894611, 4.908382278819147], [-2.279004456090718, 4.908514891948755], [-2.279080513554788, 4.908609614841907], [-2.279185092118269, 4.908619087401007], [-2.279318192680421, 4.908685393516123], [-2.279375236677765, 4.908874840201747], [-2.279356222311776, 4.909016924991136], [-2.27944178650921, 4.909035869210072], [-2.279593901437352, 4.9089506179767], [-2.279660452168059, 4.909026396650916], [-2.279879116927646, 4.90918742655856], [-2.279964682024399, 4.909367399785708], [-2.279997956940122, 4.909576738575652], [-2.279855349644606, 4.909586210235489], [-2.279608161987028, 4.90971882246572], [-2.279494075790922, 4.909936685929381], [-2.27954161215564, 4.910145076833885], [-2.279617669619711, 4.910381883617106], [-2.279560626521629, 4.910618691299589], [-2.279503583423605, 4.910789192867014], [-2.279199353567265, 4.91085549808281], [-2.278971180275676, 4.911016527091078], [-2.278771529882135, 4.911224917995639], [-2.278562371855912, 4.911348057666771], [-2.27842927129376, 4.911565920231055], [-2.278420349119756, 4.911593476357893], [-2.27521108782139, 4.909556846471332]]]}', - '{"type": "Polygon", "coordinates": [[[-2.273037249269578, 4.908108314746983], [-2.273165733611677, 4.908453105826197], [-2.273179740552564, 4.908740329502677], [-2.27310784245293, 4.909027459649565], [-2.273021594770739, 4.909343295256917], [-2.272806396897636, 4.909745153715903], [-2.272591494002199, 4.909874163262089], [-2.27237662258301, 4.909974452059373], [-2.272261941935028, 4.910103570423473], [-2.27230464534307, 4.910333384078456], [-2.27260486242443, 4.910750164088824], [-2.27244699633178, 4.911094642204034], [-2.272231672553573, 4.911611382759133], [-2.27213115352987, 4.911884121954699], [-2.272087920421143, 4.912142561929727], [-2.272130608540692, 4.91238673595916], [-2.272216279757458, 4.912602236004432], [-2.272373477653844, 4.912875255788492], [-2.272315927338127, 4.913133681374347], [-2.272244089493086, 4.913363369124227], [-2.27181420366253, 4.913693190088679], [-2.271241296848302, 4.913879249927504], [-2.270854549697674, 4.914036792264199], [-2.270539453333072, 4.914136970444872], [-2.270081274830943, 4.914150828997606], [-2.269809161563501, 4.914222333194175], [-2.269350983061372, 4.914236191746909], [-2.26892147044947, 4.914221361027046], [-2.268692420318871, 4.914192388467995], [-2.268434828404565, 4.914077223085428], [-2.268105649556048, 4.913961978562497], [-2.267848105305745, 4.913803731157316], [-2.267662148889031, 4.913645561993121], [-2.267504906026545, 4.913415623332412], [-2.267390631872104, 4.913171370162672], [-2.267347898786454, 4.912970277256534], [-2.267477054922097, 4.912697571335912], [-2.26752016392436, 4.912554014356317], [-2.267649257107507, 4.912338749933383], [-2.267821209281351, 4.912209695421154], [-2.268107748573698, 4.912037684791358], [-2.268193794807758, 4.911908534950953], [-2.26836568312973, 4.911836921936469], [-2.268509078231318, 4.911636033176421], [-2.268738345997804, 4.91146395959413], [-2.26885310578615, 4.911263039357834], [-2.269010770430668, 4.911105247009573], [-2.2691398159497, 4.91093306370999], [-2.26932600619017, 4.910875825459016], [-2.269612529294761, 4.91071817430435], [-2.269755735538695, 4.910689610936856], [-2.269927452090201, 4.910775961141724], [-2.269884359275693, 4.910905157746868], [-2.269769647151463, 4.911062996859869], [-2.269669283710414, 4.911192130512461], [-2.269583440723181, 4.911134595485237], [-2.269569279998223, 4.910990975553148], [-2.269425979325433, 4.91110570206655], [-2.269454411391905, 4.911292418410426], [-2.269353859992577, 4.911593877455516], [-2.26932503762032, 4.911766170472447], [-2.26956829524056, 4.911895680940972], [-2.269740230327329, 4.911780985903818], [-2.26978327727636, 4.911694870421968], [-2.269869213793129, 4.911666244101923], [-2.269940708097181, 4.911752484589556], [-2.26996926427006, 4.911824317937999], [-2.270069347122615, 4.911953671025174], [-2.270312745037131, 4.911953937224496], [-2.270341443302925, 4.911896527202998], [-2.270484727787903, 4.911796161064046], [-2.270628043749184, 4.911667073276874], [-2.270699584817919, 4.911710233540475], [-2.270871379610412, 4.911724780973884], [-2.271057601327186, 4.911638821974122], [-2.271157871238699, 4.911595850568119], [-2.27115802772073, 4.911452246823785], [-2.271172438457199, 4.911366099865688], [-2.271172578751475, 4.911236855596485], [-2.271172781098926, 4.911050170728856], [-2.271258842621421, 4.910906660514001], [-2.2713020442539, 4.910676940388555], [-2.27134502825038, 4.910648266404394], [-2.271402485935937, 4.910476003964448], [-2.271488515082865, 4.910361214498494], [-2.271560258499107, 4.910217688995203], [-2.271674970623337, 4.910059848982883], [-2.271804015243049, 4.909887664783923], [-2.271947439122982, 4.909658054375768], [-2.271990610178477, 4.909457054999109], [-2.272091050061874, 4.909256118575058], [-2.272162887007596, 4.909026429925859], [-2.272248932342279, 4.908897280085455], [-2.272292087210019, 4.908710641083246], [-2.272220701723938, 4.908523877974631], [-2.272249429667397, 4.908437747204346], [-2.272106363717683, 4.908337068101275], [-2.272077993704386, 4.908092909360334], [-2.272121101807329, 4.90794935238074], [-2.272235828320788, 4.907777151994026], [-2.272364825276441, 4.907648048918361], [-2.272465079000142, 4.907619436987488], [-2.272636779363836, 4.907720147566806], [-2.272736924269566, 4.907792058256916], [-2.272851355805358, 4.907892705883739], [-2.273037249269578, 4.908108314746983]]]}', - '{"type": "Polygon", "coordinates": [[[-2.277179075154152, 4.911432361014704], [-2.2783341994637, 4.911859560570292], [-2.278514835491194, 4.912058478016377], [-2.278657443686029, 4.912191090246665], [-2.278828572980217, 4.912339804838155], [-2.278904630444288, 4.912425055172207], [-2.27903773100644, 4.912491360388003], [-2.279180339201218, 4.912576611621375], [-2.279256396665346, 4.912690278733407], [-2.279313439763371, 4.912927084617309], [-2.279265903398652, 4.913220724956545], [-2.279284917764699, 4.913400697284374], [-2.279370482861452, 4.913590143070678], [-2.279513090156911, 4.913684865064511], [-2.279703233817145, 4.913703810182767], [-2.2797412634485, 4.913864838291715], [-2.279731755815817, 4.9140542831787], [-2.279674712717792, 4.914167949391413], [-2.279665205085109, 4.914400966431515], [-2.279655698351746, 4.914561994540463], [-2.279750770181863, 4.914647244874516], [-2.279931407108677, 4.914751439427448], [-2.280093028770239, 4.914760911986548], [-2.280235636965017, 4.914647244874516], [-2.280378244260532, 4.914486216765567], [-2.280539866821357, 4.914419911549714], [-2.280758532480263, 4.914561994540463], [-2.280882125409732, 4.914656717433616], [-2.281091283435956, 4.914770383646328], [-2.281224384897371, 4.914817745542564], [-2.281386006558932, 4.914912467536396], [-2.281309949094805, 4.915035606308209], [-2.281262413629463, 4.91527241219211], [-2.281157834166663, 4.915433440301058], [-2.281062762336546, 4.915622884288723], [-2.28094867614044, 4.915746023060592], [-2.280853604310323, 4.915831273394588], [-2.280625431918111, 4.915954412166457], [-2.280615924285428, 4.916087023497369], [-2.280796561212298, 4.916238579047217], [-2.280910647408405, 4.916352245259986], [-2.281119805434628, 4.916399606256846], [-2.281338471093534, 4.916446967253762], [-2.281443049656957, 4.916579578584731], [-2.281576150219109, 4.91672166157548], [-2.281794815878015, 4.916759550013296], [-2.281927916440168, 4.916835327788192], [-2.282241653929134, 4.916769022572396], [-2.282431797589368, 4.916854272007072], [-2.282270175028486, 4.916996354997821], [-2.282194117564416, 4.917138437988569], [-2.282194117564416, 4.917318410316454], [-2.282089539000992, 4.917451020748047], [-2.281918409706805, 4.917356298754214], [-2.281817633476976, 4.9172663125903], [-2.28149438835527, 4.917218951593384], [-2.28129473796173, 4.917370507143232], [-2.281237694863705, 4.917531534352861], [-2.281228187231022, 4.917711505781426], [-2.281323259061082, 4.917910422328191], [-2.281209172864976, 4.9180809211976], [-2.281228187231022, 4.918260892626165], [-2.281323259061082, 4.918535586048506], [-2.281152129766951, 4.918687140699035], [-2.281161637399578, 4.918819752030004], [-2.281123608667542, 4.919056556115208], [-2.280981000472764, 4.919236527543774], [-2.280762334813858, 4.91929336020047], [-2.280600713152353, 4.919558581063711], [-2.280420076225482, 4.919757496711156], [-2.280125353102505, 4.919681718936204], [-2.279964682024399, 4.919584155984069], [-2.279774538364222, 4.919726238075498], [-2.279660452168059, 4.91984937594799], [-2.279422772143164, 4.91983990428821], [-2.279299178314375, 4.919820960069273], [-2.279090020288152, 4.919934625382723], [-2.279118542286824, 4.920038819935655], [-2.278928398626647, 4.920114596811231], [-2.278776283698505, 4.920029347376556], [-2.278519589307564, 4.920161957808148], [-2.278548110406916, 4.920322984118457], [-2.278510081674881, 4.920502955547022], [-2.278300923648658, 4.920427177772069], [-2.278205851818541, 4.920569259863498], [-2.278082258889071, 4.920663981857331], [-2.277759013767422, 4.920749231292064], [-2.277597392105918, 4.920787119729823], [-2.277445277177776, 4.920720814514027], [-2.277245625884859, 4.920654509298231], [-2.277074496590672, 4.920493482987922], [-2.276827309832413, 4.92039876099409], [-2.276646672905599, 4.920484010428765], [-2.276429767219952, 4.920042248151276], [-2.276392082928226, 4.919582673002026], [-2.276087299089511, 4.918969629444916], [-2.275897182408983, 4.918241826998781], [-2.276050276698868, 4.917897342588333], [-2.276489436338352, 4.917821231164908], [-2.276757133934552, 4.917419429363235], [-2.276872212082878, 4.916921724757856], [-2.27696797279367, 4.916634619791921], [-2.277292858278713, 4.916309469006819], [-2.277789162639692, 4.916348301732796], [-2.278285405846759, 4.916444575057142], [-2.278819891824526, 4.916483448252563], [-2.279297312221161, 4.916330786536605], [-2.279393051348165, 4.916062828137058], [-2.279240843789864, 4.915583982314956], [-2.279279312290384, 4.915315961862177], [-2.279165306133905, 4.914818009943247], [-2.278994050035351, 4.914300847606057], [-2.278994503293632, 4.913879608756986], [-2.278708541366029, 4.9135155011395], [-2.277906589817235, 4.913667811220535], [-2.277505490386034, 4.913858849706173], [-2.277199759561199, 4.914126579677941], [-2.277084784834926, 4.914528547854275], [-2.276740811239392, 4.914853677954909], [-2.276701886782575, 4.915542937256816], [-2.276205148948407, 4.91590619591426], [-2.275842311173676, 4.916020685906005], [-2.275708099948815, 4.916556662959692], [-2.275478458963676, 4.917073389125676], [-2.275172766809703, 4.917302824166029], [-2.274548559068705, 4.917291881215419], [-2.274574106110038, 4.917251157215162], [-2.274574106110038, 4.91713749100245], [-2.274697699039507, 4.91715643522133], [-2.274773756503635, 4.917080657446434], [-2.274849813967705, 4.917014352230638], [-2.274821292868296, 4.916853325021009], [-2.274764249770271, 4.916777547246056], [-2.274574106110038, 4.916673352693124], [-2.274412483549213, 4.916588103258391], [-2.274460019913931, 4.916512325483495], [-2.274631149208119, 4.916398659270783], [-2.274726221038236, 4.916294464717794], [-2.274802278502307, 4.916218686942898], [-2.274887842699741, 4.916048187174169], [-2.274992422162484, 4.915953465180337], [-2.275201580188707, 4.915962936840117], [-2.27541073821493, 4.915887159065164], [-2.275477288945694, 4.915735604414635], [-2.275581867509118, 4.915593520524624], [-2.275638910607199, 4.915375659758922], [-2.275638910607199, 4.915176743212157], [-2.275638910607199, 4.915044131881245], [-2.275638910607199, 4.914949408988093], [-2.275467781313012, 4.914911520550277], [-2.275353695116905, 4.914835742775381], [-2.275401231481624, 4.914712603104192], [-2.275543838777082, 4.914655770447496], [-2.275676939339235, 4.914693658885312], [-2.275667432605871, 4.914485269779448], [-2.275648418239882, 4.914371602667359], [-2.275762504435988, 4.914267408114426], [-2.275886098264778, 4.914257936454646], [-2.276000184460884, 4.91422004711751], [-2.276123777390353, 4.914153741002394], [-2.276285399951178, 4.914134796783515], [-2.276408992880647, 4.914077963227442], [-2.276551601075482, 4.91396429611541], [-2.276608644173564, 4.913888518340514], [-2.276684701637635, 4.913793796346681], [-2.276817802199787, 4.913755907009545], [-2.276789281100378, 4.913651712456613], [-2.276827309832413, 4.913509628566544], [-2.276865338564448, 4.913367544676476], [-2.277007945859964, 4.91329176690158], [-2.277131539688753, 4.913367544676476], [-2.277064988958045, 4.913481211788564], [-2.276941396028576, 4.913575934681717], [-2.276979424760611, 4.913708546012629], [-2.277150554054799, 4.913661185015712], [-2.27722661151887, 4.913727490231565], [-2.277331190981613, 4.913594878900597], [-2.277350205347659, 4.913490684347664], [-2.277464291543765, 4.913424378232492], [-2.277635420837953, 4.913443322451428], [-2.277682956303352, 4.913367544676476], [-2.277806550132141, 4.913310711120459], [-2.277920636328247, 4.913253877564443], [-2.277806550132141, 4.913111793674375], [-2.277920636328247, 4.913026543340379], [-2.278006201425001, 4.912874987790531], [-2.278006201425001, 4.912730062042783], [-2.277939650694293, 4.912663755927667], [-2.277844578864176, 4.912578505593615], [-2.277920636328247, 4.912502727818662], [-2.277996693792318, 4.91236064392865], [-2.278015708158364, 4.912265921035498], [-2.277911128695564, 4.912256448476342], [-2.277844578864176, 4.912332227150614], [-2.277759013767422, 4.91239853236641], [-2.277682956303352, 4.912303809473258], [-2.277701970669341, 4.912237504257462], [-2.277739999401376, 4.91211436458633], [-2.277587884473235, 4.91213330880521], [-2.277454783911082, 4.912142781364309], [-2.277293162249578, 4.912095420367393], [-2.277074496590672, 4.912010169134021], [-2.277112525322764, 4.911858613584172], [-2.277179075154152, 4.911716528794841], [-2.277274147883588, 4.911602861682752], [-2.277302668982941, 4.911517611348756], [-2.277179075154152, 4.911432361014704]]]}' + [ + 'geojson' => '{"type": "Polygon", "coordinates": [[[-2.27521108782139, 4.909556846471332], [-2.275458274579648, 4.909575790690212], [-2.275572360775755, 4.909518957134196], [-2.275572360775755, 4.909320038788792], [-2.275534332043719, 4.909140064662324], [-2.275581867509118, 4.908979035653999], [-2.275724475703953, 4.908884311861527], [-2.275981170094894, 4.90894114631692], [-2.276275892318552, 4.908884311861527], [-2.276437514879376, 4.9089506179767], [-2.276504064710764, 4.909073758547152], [-2.276675194904271, 4.909121120443388], [-2.276760759101705, 4.909016924991136], [-2.27692238166253, 4.908780116409275], [-2.27702696022601, 4.908675920957023], [-2.277084003324035, 4.908732755412359], [-2.277093510956718, 4.908931673757763], [-2.277074496590672, 4.909177953999404], [-2.277179075154152, 4.909386344903965], [-2.277435769545093, 4.909509484575096], [-2.277701970669341, 4.909509484575096], [-2.277987187059011, 4.909537901353133], [-2.277968172692965, 4.909367399785708], [-2.278006201425001, 4.909035869210072], [-2.277911128695564, 4.908799061527532], [-2.277835071231493, 4.908514891948755], [-2.277949157427599, 4.908448585833639], [-2.278101273255118, 4.908533836167635], [-2.278158316353142, 4.908666449297243], [-2.278405503111458, 4.908685393516123], [-2.278481560575528, 4.908581198063871], [-2.278538603673553, 4.908401223937403], [-2.278576632405588, 4.908259139148015], [-2.278614661137681, 4.908145471136606], [-2.278700226234378, 4.908079165021491], [-2.278823819163904, 4.908012858906375], [-2.27883332679653, 4.908126526018407], [-2.278899876627975, 4.908278083366895], [-2.278890369894611, 4.908382278819147], [-2.279004456090718, 4.908514891948755], [-2.279080513554788, 4.908609614841907], [-2.279185092118269, 4.908619087401007], [-2.279318192680421, 4.908685393516123], [-2.279375236677765, 4.908874840201747], [-2.279356222311776, 4.909016924991136], [-2.27944178650921, 4.909035869210072], [-2.279593901437352, 4.9089506179767], [-2.279660452168059, 4.909026396650916], [-2.279879116927646, 4.90918742655856], [-2.279964682024399, 4.909367399785708], [-2.279997956940122, 4.909576738575652], [-2.279855349644606, 4.909586210235489], [-2.279608161987028, 4.90971882246572], [-2.279494075790922, 4.909936685929381], [-2.27954161215564, 4.910145076833885], [-2.279617669619711, 4.910381883617106], [-2.279560626521629, 4.910618691299589], [-2.279503583423605, 4.910789192867014], [-2.279199353567265, 4.91085549808281], [-2.278971180275676, 4.911016527091078], [-2.278771529882135, 4.911224917995639], [-2.278562371855912, 4.911348057666771], [-2.27842927129376, 4.911565920231055], [-2.278420349119756, 4.911593476357893], [-2.27521108782139, 4.909556846471332]]]}', + ], + [ + 'geojson' => '{"type": "Polygon", "coordinates": [[[-2.273037249269578, 4.908108314746983], [-2.273165733611677, 4.908453105826197], [-2.273179740552564, 4.908740329502677], [-2.27310784245293, 4.909027459649565], [-2.273021594770739, 4.909343295256917], [-2.272806396897636, 4.909745153715903], [-2.272591494002199, 4.909874163262089], [-2.27237662258301, 4.909974452059373], [-2.272261941935028, 4.910103570423473], [-2.27230464534307, 4.910333384078456], [-2.27260486242443, 4.910750164088824], [-2.27244699633178, 4.911094642204034], [-2.272231672553573, 4.911611382759133], [-2.27213115352987, 4.911884121954699], [-2.272087920421143, 4.912142561929727], [-2.272130608540692, 4.91238673595916], [-2.272216279757458, 4.912602236004432], [-2.272373477653844, 4.912875255788492], [-2.272315927338127, 4.913133681374347], [-2.272244089493086, 4.913363369124227], [-2.27181420366253, 4.913693190088679], [-2.271241296848302, 4.913879249927504], [-2.270854549697674, 4.914036792264199], [-2.270539453333072, 4.914136970444872], [-2.270081274830943, 4.914150828997606], [-2.269809161563501, 4.914222333194175], [-2.269350983061372, 4.914236191746909], [-2.26892147044947, 4.914221361027046], [-2.268692420318871, 4.914192388467995], [-2.268434828404565, 4.914077223085428], [-2.268105649556048, 4.913961978562497], [-2.267848105305745, 4.913803731157316], [-2.267662148889031, 4.913645561993121], [-2.267504906026545, 4.913415623332412], [-2.267390631872104, 4.913171370162672], [-2.267347898786454, 4.912970277256534], [-2.267477054922097, 4.912697571335912], [-2.26752016392436, 4.912554014356317], [-2.267649257107507, 4.912338749933383], [-2.267821209281351, 4.912209695421154], [-2.268107748573698, 4.912037684791358], [-2.268193794807758, 4.911908534950953], [-2.26836568312973, 4.911836921936469], [-2.268509078231318, 4.911636033176421], [-2.268738345997804, 4.91146395959413], [-2.26885310578615, 4.911263039357834], [-2.269010770430668, 4.911105247009573], [-2.2691398159497, 4.91093306370999], [-2.26932600619017, 4.910875825459016], [-2.269612529294761, 4.91071817430435], [-2.269755735538695, 4.910689610936856], [-2.269927452090201, 4.910775961141724], [-2.269884359275693, 4.910905157746868], [-2.269769647151463, 4.911062996859869], [-2.269669283710414, 4.911192130512461], [-2.269583440723181, 4.911134595485237], [-2.269569279998223, 4.910990975553148], [-2.269425979325433, 4.91110570206655], [-2.269454411391905, 4.911292418410426], [-2.269353859992577, 4.911593877455516], [-2.26932503762032, 4.911766170472447], [-2.26956829524056, 4.911895680940972], [-2.269740230327329, 4.911780985903818], [-2.26978327727636, 4.911694870421968], [-2.269869213793129, 4.911666244101923], [-2.269940708097181, 4.911752484589556], [-2.26996926427006, 4.911824317937999], [-2.270069347122615, 4.911953671025174], [-2.270312745037131, 4.911953937224496], [-2.270341443302925, 4.911896527202998], [-2.270484727787903, 4.911796161064046], [-2.270628043749184, 4.911667073276874], [-2.270699584817919, 4.911710233540475], [-2.270871379610412, 4.911724780973884], [-2.271057601327186, 4.911638821974122], [-2.271157871238699, 4.911595850568119], [-2.27115802772073, 4.911452246823785], [-2.271172438457199, 4.911366099865688], [-2.271172578751475, 4.911236855596485], [-2.271172781098926, 4.911050170728856], [-2.271258842621421, 4.910906660514001], [-2.2713020442539, 4.910676940388555], [-2.27134502825038, 4.910648266404394], [-2.271402485935937, 4.910476003964448], [-2.271488515082865, 4.910361214498494], [-2.271560258499107, 4.910217688995203], [-2.271674970623337, 4.910059848982883], [-2.271804015243049, 4.909887664783923], [-2.271947439122982, 4.909658054375768], [-2.271990610178477, 4.909457054999109], [-2.272091050061874, 4.909256118575058], [-2.272162887007596, 4.909026429925859], [-2.272248932342279, 4.908897280085455], [-2.272292087210019, 4.908710641083246], [-2.272220701723938, 4.908523877974631], [-2.272249429667397, 4.908437747204346], [-2.272106363717683, 4.908337068101275], [-2.272077993704386, 4.908092909360334], [-2.272121101807329, 4.90794935238074], [-2.272235828320788, 4.907777151994026], [-2.272364825276441, 4.907648048918361], [-2.272465079000142, 4.907619436987488], [-2.272636779363836, 4.907720147566806], [-2.272736924269566, 4.907792058256916], [-2.272851355805358, 4.907892705883739], [-2.273037249269578, 4.908108314746983]]]}', + ], + [ + 'geojson' => '{"type": "Polygon", "coordinates": [[[-2.277179075154152, 4.911432361014704], [-2.2783341994637, 4.911859560570292], [-2.278514835491194, 4.912058478016377], [-2.278657443686029, 4.912191090246665], [-2.278828572980217, 4.912339804838155], [-2.278904630444288, 4.912425055172207], [-2.27903773100644, 4.912491360388003], [-2.279180339201218, 4.912576611621375], [-2.279256396665346, 4.912690278733407], [-2.279313439763371, 4.912927084617309], [-2.279265903398652, 4.913220724956545], [-2.279284917764699, 4.913400697284374], [-2.279370482861452, 4.913590143070678], [-2.279513090156911, 4.913684865064511], [-2.279703233817145, 4.913703810182767], [-2.2797412634485, 4.913864838291715], [-2.279731755815817, 4.9140542831787], [-2.279674712717792, 4.914167949391413], [-2.279665205085109, 4.914400966431515], [-2.279655698351746, 4.914561994540463], [-2.279750770181863, 4.914647244874516], [-2.279931407108677, 4.914751439427448], [-2.280093028770239, 4.914760911986548], [-2.280235636965017, 4.914647244874516], [-2.280378244260532, 4.914486216765567], [-2.280539866821357, 4.914419911549714], [-2.280758532480263, 4.914561994540463], [-2.280882125409732, 4.914656717433616], [-2.281091283435956, 4.914770383646328], [-2.281224384897371, 4.914817745542564], [-2.281386006558932, 4.914912467536396], [-2.281309949094805, 4.915035606308209], [-2.281262413629463, 4.91527241219211], [-2.281157834166663, 4.915433440301058], [-2.281062762336546, 4.915622884288723], [-2.28094867614044, 4.915746023060592], [-2.280853604310323, 4.915831273394588], [-2.280625431918111, 4.915954412166457], [-2.280615924285428, 4.916087023497369], [-2.280796561212298, 4.916238579047217], [-2.280910647408405, 4.916352245259986], [-2.281119805434628, 4.916399606256846], [-2.281338471093534, 4.916446967253762], [-2.281443049656957, 4.916579578584731], [-2.281576150219109, 4.91672166157548], [-2.281794815878015, 4.916759550013296], [-2.281927916440168, 4.916835327788192], [-2.282241653929134, 4.916769022572396], [-2.282431797589368, 4.916854272007072], [-2.282270175028486, 4.916996354997821], [-2.282194117564416, 4.917138437988569], [-2.282194117564416, 4.917318410316454], [-2.282089539000992, 4.917451020748047], [-2.281918409706805, 4.917356298754214], [-2.281817633476976, 4.9172663125903], [-2.28149438835527, 4.917218951593384], [-2.28129473796173, 4.917370507143232], [-2.281237694863705, 4.917531534352861], [-2.281228187231022, 4.917711505781426], [-2.281323259061082, 4.917910422328191], [-2.281209172864976, 4.9180809211976], [-2.281228187231022, 4.918260892626165], [-2.281323259061082, 4.918535586048506], [-2.281152129766951, 4.918687140699035], [-2.281161637399578, 4.918819752030004], [-2.281123608667542, 4.919056556115208], [-2.280981000472764, 4.919236527543774], [-2.280762334813858, 4.91929336020047], [-2.280600713152353, 4.919558581063711], [-2.280420076225482, 4.919757496711156], [-2.280125353102505, 4.919681718936204], [-2.279964682024399, 4.919584155984069], [-2.279774538364222, 4.919726238075498], [-2.279660452168059, 4.91984937594799], [-2.279422772143164, 4.91983990428821], [-2.279299178314375, 4.919820960069273], [-2.279090020288152, 4.919934625382723], [-2.279118542286824, 4.920038819935655], [-2.278928398626647, 4.920114596811231], [-2.278776283698505, 4.920029347376556], [-2.278519589307564, 4.920161957808148], [-2.278548110406916, 4.920322984118457], [-2.278510081674881, 4.920502955547022], [-2.278300923648658, 4.920427177772069], [-2.278205851818541, 4.920569259863498], [-2.278082258889071, 4.920663981857331], [-2.277759013767422, 4.920749231292064], [-2.277597392105918, 4.920787119729823], [-2.277445277177776, 4.920720814514027], [-2.277245625884859, 4.920654509298231], [-2.277074496590672, 4.920493482987922], [-2.276827309832413, 4.92039876099409], [-2.276646672905599, 4.920484010428765], [-2.276429767219952, 4.920042248151276], [-2.276392082928226, 4.919582673002026], [-2.276087299089511, 4.918969629444916], [-2.275897182408983, 4.918241826998781], [-2.276050276698868, 4.917897342588333], [-2.276489436338352, 4.917821231164908], [-2.276757133934552, 4.917419429363235], [-2.276872212082878, 4.916921724757856], [-2.27696797279367, 4.916634619791921], [-2.277292858278713, 4.916309469006819], [-2.277789162639692, 4.916348301732796], [-2.278285405846759, 4.916444575057142], [-2.278819891824526, 4.916483448252563], [-2.279297312221161, 4.916330786536605], [-2.279393051348165, 4.916062828137058], [-2.279240843789864, 4.915583982314956], [-2.279279312290384, 4.915315961862177], [-2.279165306133905, 4.914818009943247], [-2.278994050035351, 4.914300847606057], [-2.278994503293632, 4.913879608756986], [-2.278708541366029, 4.9135155011395], [-2.277906589817235, 4.913667811220535], [-2.277505490386034, 4.913858849706173], [-2.277199759561199, 4.914126579677941], [-2.277084784834926, 4.914528547854275], [-2.276740811239392, 4.914853677954909], [-2.276701886782575, 4.915542937256816], [-2.276205148948407, 4.91590619591426], [-2.275842311173676, 4.916020685906005], [-2.275708099948815, 4.916556662959692], [-2.275478458963676, 4.917073389125676], [-2.275172766809703, 4.917302824166029], [-2.274548559068705, 4.917291881215419], [-2.274574106110038, 4.917251157215162], [-2.274574106110038, 4.91713749100245], [-2.274697699039507, 4.91715643522133], [-2.274773756503635, 4.917080657446434], [-2.274849813967705, 4.917014352230638], [-2.274821292868296, 4.916853325021009], [-2.274764249770271, 4.916777547246056], [-2.274574106110038, 4.916673352693124], [-2.274412483549213, 4.916588103258391], [-2.274460019913931, 4.916512325483495], [-2.274631149208119, 4.916398659270783], [-2.274726221038236, 4.916294464717794], [-2.274802278502307, 4.916218686942898], [-2.274887842699741, 4.916048187174169], [-2.274992422162484, 4.915953465180337], [-2.275201580188707, 4.915962936840117], [-2.27541073821493, 4.915887159065164], [-2.275477288945694, 4.915735604414635], [-2.275581867509118, 4.915593520524624], [-2.275638910607199, 4.915375659758922], [-2.275638910607199, 4.915176743212157], [-2.275638910607199, 4.915044131881245], [-2.275638910607199, 4.914949408988093], [-2.275467781313012, 4.914911520550277], [-2.275353695116905, 4.914835742775381], [-2.275401231481624, 4.914712603104192], [-2.275543838777082, 4.914655770447496], [-2.275676939339235, 4.914693658885312], [-2.275667432605871, 4.914485269779448], [-2.275648418239882, 4.914371602667359], [-2.275762504435988, 4.914267408114426], [-2.275886098264778, 4.914257936454646], [-2.276000184460884, 4.91422004711751], [-2.276123777390353, 4.914153741002394], [-2.276285399951178, 4.914134796783515], [-2.276408992880647, 4.914077963227442], [-2.276551601075482, 4.91396429611541], [-2.276608644173564, 4.913888518340514], [-2.276684701637635, 4.913793796346681], [-2.276817802199787, 4.913755907009545], [-2.276789281100378, 4.913651712456613], [-2.276827309832413, 4.913509628566544], [-2.276865338564448, 4.913367544676476], [-2.277007945859964, 4.91329176690158], [-2.277131539688753, 4.913367544676476], [-2.277064988958045, 4.913481211788564], [-2.276941396028576, 4.913575934681717], [-2.276979424760611, 4.913708546012629], [-2.277150554054799, 4.913661185015712], [-2.27722661151887, 4.913727490231565], [-2.277331190981613, 4.913594878900597], [-2.277350205347659, 4.913490684347664], [-2.277464291543765, 4.913424378232492], [-2.277635420837953, 4.913443322451428], [-2.277682956303352, 4.913367544676476], [-2.277806550132141, 4.913310711120459], [-2.277920636328247, 4.913253877564443], [-2.277806550132141, 4.913111793674375], [-2.277920636328247, 4.913026543340379], [-2.278006201425001, 4.912874987790531], [-2.278006201425001, 4.912730062042783], [-2.277939650694293, 4.912663755927667], [-2.277844578864176, 4.912578505593615], [-2.277920636328247, 4.912502727818662], [-2.277996693792318, 4.91236064392865], [-2.278015708158364, 4.912265921035498], [-2.277911128695564, 4.912256448476342], [-2.277844578864176, 4.912332227150614], [-2.277759013767422, 4.91239853236641], [-2.277682956303352, 4.912303809473258], [-2.277701970669341, 4.912237504257462], [-2.277739999401376, 4.91211436458633], [-2.277587884473235, 4.91213330880521], [-2.277454783911082, 4.912142781364309], [-2.277293162249578, 4.912095420367393], [-2.277074496590672, 4.912010169134021], [-2.277112525322764, 4.911858613584172], [-2.277179075154152, 4.911716528794841], [-2.277274147883588, 4.911602861682752], [-2.277302668982941, 4.911517611348756], [-2.277179075154152, 4.911432361014704]]]}' + ], ], ], [ 'uuid' => 'c1e2d5a6-288a-479f-977d-531ca5b52933', 'geometry' => [ - '{"type": "Polygon", "coordinates": [[[-2.407034725072435, 4.966373290195236], [-2.406825572442187, 4.96646313786357], [-2.406527084757101, 4.96655107717055], [-2.40646541284849, 4.966550920688462], [-2.406418320748855, 4.966583120914322], [-2.406354637056779, 4.966599121652166], [-2.406274894170963, 4.966615083719205], [-2.406211697911488, 4.966647264159974], [-2.406164037440305, 4.966663315259837], [-2.406084259480963, 4.966679285420753], [-2.405988114759623, 4.966679190991897], [-2.405925446402193, 4.966711966783862], [-2.405862278921006, 4.966728569168197], [-2.405814544705436, 4.966728881232939], [-2.405735817154209, 4.966761750554383], [-2.405670963444152, 4.966761977183523], [-2.40559161626004, 4.96674599802941], [-2.40541747243833, 4.966521707111156], [-2.40529831766321, 4.96633012273702], [-2.405298542493711, 4.966078808989892], [-2.405274956873711, 4.965767637266708], [-2.405180795157548, 4.9653899615775], [-2.405194353336697, 4.965310467803874], [-2.405192444075965, 4.965246876742015], [-2.405174486413387, 4.965183174164224], [-2.405141153941031, 4.965135346419174], [-2.405076068205915, 4.965087407158137], [-2.405011061611162, 4.96503952545379], [-2.404962473938951, 4.965007609413703], [-2.40488127684938, 4.964943930218226], [-2.404863579090829, 4.96488064402655], [-2.404830069452089, 4.964817365928752], [-2.404812879810493, 4.964769997737278], [-2.404794808833287, 4.964691194642967], [-2.404793461648865, 4.964644011711812], [-2.404743845152268, 4.964565279663987], [-2.404726302077108, 4.96450247550888], [-2.404692996584458, 4.964439682145667], [-2.404644317181464, 4.964392507308389], [-2.404580847528052, 4.964376630677009], [-2.404516979474977, 4.964345122029783], [-2.404453170777174, 4.964313643060223], [-2.404420018169276, 4.964251067332953], [-2.404418325645167, 4.964188675067362], [-2.404416214936305, 4.96411079647612], [-2.40444590065772, 4.964048668611213], [-2.404475984778855, 4.964002136789247], [-2.404521284529608, 4.96394016720501], [-2.404566534817661, 4.963878262371964], [-2.40459652630858, 4.963831869045578], [-2.404641695657688, 4.963770079325741], [-2.404703401740562, 4.963739285639576], [-2.404765086239706, 4.963708500946609], [-2.404859043809779, 4.963708678113051], [-2.404922206794367, 4.963724250773623], [-2.405002116054789, 4.963770806877278], [-2.405051260407276, 4.963832852903863], [-2.405245799953661, 4.964004109901794], [-2.405294709583131, 4.964050934902787], [-2.405328484521874, 4.964113365839182], [-2.405378082132756, 4.964175933472575], [-2.40539559822821, 4.964222858298342], [-2.405352835464953, 4.964347932411215], [-2.405323511270979, 4.964410551305946], [-2.405294082755688, 4.96447320527426], [-2.405249216478126, 4.964551525432626], [-2.405251283120151, 4.964614403332121], [-2.405268676008518, 4.964661675296099], [-2.405335147598919, 4.964756442256032], [-2.405398787224215, 4.964772291008444], [-2.405460813465709, 4.964740819233441], [-2.40552336401197, 4.964725124265101], [-2.405569005505129, 4.96467790446178], [-2.405630372543612, 4.964630737718437], [-2.405660750742982, 4.964599304614239], [-2.405722041339061, 4.964552200823448], [-2.405767535343443, 4.964505118616387], [-2.405828735108003, 4.964458090368623], [-2.405890557203406, 4.964426803853939], [-2.405952953965595, 4.964411201515759], [-2.406014823725116, 4.964379974356405], [-2.406076431781855, 4.964333249180129], [-2.406138009261667, 4.96428654738628], [-2.406168484587852, 4.964255393971314], [-2.406229401965277, 4.96419312581213], [-2.406273832970953, 4.964115267905356], [-2.406287198695225, 4.964052987155696], [-2.406284743546053, 4.963990730687726], [-2.406282291094783, 4.963928539870267], [-2.406296244076373, 4.963881977471317], [-2.406279228003939, 4.96385089690142], [-2.406178637034429, 4.963711115275771], [-2.405954633899171, 4.963602635453299], [-2.405874890114035, 4.963571643916282], [-2.405810381743663, 4.963525287462119], [-2.405746480415701, 4.963494382259967], [-2.405683161848401, 4.963478896833692], [-2.40563556612841, 4.963463441084969], [-2.405571354534345, 4.963432508003905], [-2.405506501723607, 4.963386207307678], [-2.405426006105245, 4.963339943483675], [-2.405409108743299, 4.963309209152726], [-2.40531198286169, 4.96323239356019], [-2.405279079365982, 4.963186444498888], [-2.405245602102809, 4.963125256425485], [-2.405227151611712, 4.963048898587829], [-2.405209508711835, 4.962987920955811], [-2.40517628685609, 4.962927017967559], [-2.405173793935376, 4.962851005469588], [-2.405171802836378, 4.962790276050441], [-2.40516981353602, 4.962729615879141], [-2.405214522432232, 4.962669000673884], [-2.40526021338809, 4.962638696218903], [-2.405321463514667, 4.962608389065963], [-2.405367780398706, 4.962593220200972], [-2.405499865626268, 4.96279021309789], [-2.405533955327712, 4.962866229193196], [-2.405583232779918, 4.962927142973342], [-2.405617449285785, 4.963003400086905], [-2.405651151379459, 4.963064504523288], [-2.405669947210242, 4.96314100355454], [-2.405703926295132, 4.963202324727547], [-2.405751816093414, 4.963217727416236], [-2.405830623684267, 4.963217845227405], [-2.405878383080847, 4.963233238023577], [-2.405957330066656, 4.963248639812946], [-2.406191967685118, 4.963279303996728], [-2.406254715182911, 4.963294634739668], [-2.406317478868516, 4.963309969979207], [-2.406395183890595, 4.963309939402222], [-2.406472267481092, 4.963294548404747], [-2.406535112105701, 4.963294605062003], [-2.406578970243231, 4.963233157983893], [-2.406622778018743, 4.963171781052893], [-2.406619224797339, 4.963095177700325], [-2.406585192652472, 4.963033977036446], [-2.4065664579756, 4.96297283302988], [-2.406546944485854, 4.962896518359685], [-2.406512794529817, 4.962835565908676], [-2.406478692237783, 4.962774695295991], [-2.406415299026776, 4.962744309002687], [-2.406352717903587, 4.962729137439794], [-2.406290677272864, 4.96271396857486], [-2.406242075211537, 4.962653247249591], [-2.406208213937873, 4.962577428105874], [-2.406175030753047, 4.962516846175561], [-2.406142508569872, 4.962471450197313], [-2.406078773616457, 4.962410977084971], [-2.406045229803453, 4.962350608294003], [-2.405996544105165, 4.962290303354848], [-2.405932569932133, 4.962260327152421], [-2.405868671302073, 4.96223038692284], [-2.405790607450513, 4.962230568585937], [-2.405729530893097, 4.962260776813423], [-2.405668444443108, 4.962290992235523], [-2.405607993813817, 4.962336265006684], [-2.40556183251249, 4.962351410489248], [-2.40550128385712, 4.962396716535295], [-2.405441037373919, 4.962457150077455], [-2.405379166715136, 4.962472331532922], [-2.405315760014275, 4.962442249210483], [-2.405267716431922, 4.96239708885463], [-2.405219957934662, 4.962351950082507], [-2.405187329631531, 4.962291783638932], [-2.405169785657051, 4.962216635389439], [-2.405136812913554, 4.962141610347089], [-2.405119732089929, 4.962081645351702], [-2.405087236886402, 4.962021773885795], [-2.405085220606395, 4.961946943097018], [-2.40508385723416, 4.961887079724988], [-2.405082234857218, 4.961812296600272], [-2.405080614278859, 4.961737586320623], [-2.405094023170591, 4.961663024429072], [-2.405139519872876, 4.961648087589197], [-2.405200773596789, 4.961648010247529], [-2.405277812221186, 4.961662796900612], [-2.405339446358312, 4.961677640210951], [-2.405385396318934, 4.961677588949613], [-2.405477334911041, 4.961677475635042], [-2.405539218160357, 4.961692270382002], [-2.405616400676308, 4.961707066927659], [-2.405678203885941, 4.961721906640719], [-2.405755848653428, 4.961751657113325], [-2.405817215691911, 4.961751584268256], [-2.405879580977853, 4.961781370713709], [-2.405958851719561, 4.961825879960429], [-2.40602205607297, 4.961855562084565], [-2.406068114851564, 4.961870586158682], [-2.406130722954458, 4.961915504596902], [-2.406194095481055, 4.96197539854586], [-2.406271915616344, 4.96202041141288], [-2.406303652691349, 4.96205039930652], [-2.406352520052735, 4.962110327429741], [-2.406415219886469, 4.9621403207193], [-2.40649498075868, 4.962200340573304], [-2.406544328357995, 4.962260426977195], [-2.406592831493924, 4.962305547762867], [-2.406627029114077, 4.962365774460977], [-2.406659294091128, 4.962395932326444], [-2.40670853736907, 4.96245630291611], [-2.406757066585328, 4.962501626049232], [-2.406820157624111, 4.962531857659144], [-2.406870479189195, 4.96260754280388], [-2.406905502386962, 4.962683356551736], [-2.406939717993566, 4.96274409406476], [-2.406958578575484, 4.962804917013329], [-2.406992871523755, 4.962865811907761], [-2.407042627415251, 4.962926776949246], [-2.407091555031172, 4.962972544347508], [-2.407155056160889, 4.963003061941777], [-2.407234022931789, 4.963033585831397], [-2.407296673302767, 4.963048839232613], [-2.40736027695516, 4.963079399994342], [-2.407422967795696, 4.963094665986091], [-2.40750111348558, 4.963109923883906], [-2.407563834902987, 4.963125196170893], [-2.407625589549298, 4.963125152104112], [-2.407719207175603, 4.963140404606008], [-2.407898268490271, 4.963278364205166], [-2.407995194722389, 4.963339788800283], [-2.408058111292746, 4.963355121341806], [-2.408107306906629, 4.963401229583155], [-2.408125809558442, 4.963447429555288], [-2.408147523689195, 4.963539994075518], [-2.408167141500314, 4.963601790090593], [-2.408173766805874, 4.963694725131518], [-2.408194816337641, 4.963772295255296], [-2.408229465417435, 4.963834273832731], [-2.408265092060276, 4.963911858345625], [-2.408269799111906, 4.963989620025018], [-2.408290007777566, 4.964067456348062], [-2.408293793024086, 4.964129835123856], [-2.408314091622003, 4.964207880089646], [-2.408318225805431, 4.964270498984376], [-2.408335902879571, 4.964301813378029], [-2.408401469751993, 4.96434875708951], [-2.408478467906946, 4.964332942511362], [-2.408539828650134, 4.964317161208044], [-2.408584484486312, 4.964285741593756], [-2.408660292838192, 4.964254296798401], [-2.408719363907494, 4.964207255060842], [-2.408780585255784, 4.96419152142164], [-2.408839549305696, 4.964144547133174], [-2.408900702305516, 4.964128838675038], [-2.408960696079191, 4.964097531475943], [-2.409020644886823, 4.964066247659275], [-2.409063856411819, 4.9640194280542], [-2.40910586734185, 4.963957096942522], [-2.409148982639408, 4.963910388853435], [-2.409211082625291, 4.963910307015112], [-2.409262368263626, 4.963972444772537], [-2.409329256240142, 4.964034634690677], [-2.409379471685213, 4.96408132389405], [-2.40933521334938, 4.964112587925626], [-2.409229858670983, 4.964159582898503], [-2.409167604001652, 4.964159677327302], [-2.409093240860329, 4.964206678595417], [-2.409049963684822, 4.964253680762795], [-2.409007787279506, 4.96431640038162], [-2.408965554216934, 4.964379202738087], [-2.408954493455042, 4.964442036570802], [-2.408942052233897, 4.964489165542602], [-2.40886705327182, 4.964536441103917], [-2.408823257187521, 4.964583706772714], [-2.408780445860828, 4.964646769033209], [-2.40873554001314, 4.964678373907873], [-2.408671910280361, 4.964662731100191], [-2.408608292238853, 4.964647090990525], [-2.408530024241202, 4.964647238479301], [-2.408467403547775, 4.964647356290527], [-2.408389117563672, 4.964647502879984], [-2.408343106449138, 4.964663351632396], [-2.408249131791933, 4.964663529698157], [-2.408172687619412, 4.964695216411144], [-2.40809618948748, 4.96472692830514], [-2.408036095889088, 4.964774380133576], [-2.407990650448085, 4.96480600928993], [-2.407914549816553, 4.964853490796031], [-2.407853260119794, 4.964885173012362], [-2.407823494358752, 4.964916860624669], [-2.407731452344592, 4.964964447351406], [-2.407670918078338, 4.965012062856488], [-2.407610328953467, 4.965059720629654], [-2.407581304233759, 4.965107408979804], [-2.407520340091594, 4.965155031679444], [-2.407458348923626, 4.965186710298497], [-2.40738176265819, 4.965250090919028], [-2.40732081650242, 4.965297895281708], [-2.407290027312854, 4.965313887026298], [-2.407212109151487, 4.965330091010912], [-2.407118906112601, 4.965362181519538], [-2.407057091211755, 4.965394155116201], [-2.406978807026292, 4.965410264672016], [-2.406916921978336, 4.965442267946344], [-2.406870168923092, 4.965458314549608], [-2.406792420733609, 4.965490387071782], [-2.406729821623912, 4.965506490332302], [-2.406684744005702, 4.965570378170469], [-2.406687818787759, 4.965650259551865], [-2.406690282030866, 4.965714236422912], [-2.406724425691664, 4.965778188112949], [-2.406772358657406, 4.96579394963112], [-2.406900366358798, 4.965841295339544], [-2.407027872238473, 4.965872691571519], [-2.407136305296206, 4.965824056235249], [-2.407198140881462, 4.965791782265001], [-2.407262405535562, 4.965823406924756], [-2.407280883905628, 4.965887396386279], [-2.407316914344051, 4.965968036795516], [-2.407322320168873, 4.966065176166978], [-2.407325929148271, 4.966130039769553], [-2.407280599719854, 4.966178633737002], [-2.407218326164752, 4.966210979653056], [-2.407156893475815, 4.966259606895392], [-2.407109697954127, 4.966275763215947], [-2.407065076292213, 4.966340773408035], [-2.407034725072435, 4.966373290195236]]]}', + [ + 'geojson' => '{"type": "Polygon", "coordinates": [[[-2.407034725072435, 4.966373290195236], [-2.406825572442187, 4.96646313786357], [-2.406527084757101, 4.96655107717055], [-2.40646541284849, 4.966550920688462], [-2.406418320748855, 4.966583120914322], [-2.406354637056779, 4.966599121652166], [-2.406274894170963, 4.966615083719205], [-2.406211697911488, 4.966647264159974], [-2.406164037440305, 4.966663315259837], [-2.406084259480963, 4.966679285420753], [-2.405988114759623, 4.966679190991897], [-2.405925446402193, 4.966711966783862], [-2.405862278921006, 4.966728569168197], [-2.405814544705436, 4.966728881232939], [-2.405735817154209, 4.966761750554383], [-2.405670963444152, 4.966761977183523], [-2.40559161626004, 4.96674599802941], [-2.40541747243833, 4.966521707111156], [-2.40529831766321, 4.96633012273702], [-2.405298542493711, 4.966078808989892], [-2.405274956873711, 4.965767637266708], [-2.405180795157548, 4.9653899615775], [-2.405194353336697, 4.965310467803874], [-2.405192444075965, 4.965246876742015], [-2.405174486413387, 4.965183174164224], [-2.405141153941031, 4.965135346419174], [-2.405076068205915, 4.965087407158137], [-2.405011061611162, 4.96503952545379], [-2.404962473938951, 4.965007609413703], [-2.40488127684938, 4.964943930218226], [-2.404863579090829, 4.96488064402655], [-2.404830069452089, 4.964817365928752], [-2.404812879810493, 4.964769997737278], [-2.404794808833287, 4.964691194642967], [-2.404793461648865, 4.964644011711812], [-2.404743845152268, 4.964565279663987], [-2.404726302077108, 4.96450247550888], [-2.404692996584458, 4.964439682145667], [-2.404644317181464, 4.964392507308389], [-2.404580847528052, 4.964376630677009], [-2.404516979474977, 4.964345122029783], [-2.404453170777174, 4.964313643060223], [-2.404420018169276, 4.964251067332953], [-2.404418325645167, 4.964188675067362], [-2.404416214936305, 4.96411079647612], [-2.40444590065772, 4.964048668611213], [-2.404475984778855, 4.964002136789247], [-2.404521284529608, 4.96394016720501], [-2.404566534817661, 4.963878262371964], [-2.40459652630858, 4.963831869045578], [-2.404641695657688, 4.963770079325741], [-2.404703401740562, 4.963739285639576], [-2.404765086239706, 4.963708500946609], [-2.404859043809779, 4.963708678113051], [-2.404922206794367, 4.963724250773623], [-2.405002116054789, 4.963770806877278], [-2.405051260407276, 4.963832852903863], [-2.405245799953661, 4.964004109901794], [-2.405294709583131, 4.964050934902787], [-2.405328484521874, 4.964113365839182], [-2.405378082132756, 4.964175933472575], [-2.40539559822821, 4.964222858298342], [-2.405352835464953, 4.964347932411215], [-2.405323511270979, 4.964410551305946], [-2.405294082755688, 4.96447320527426], [-2.405249216478126, 4.964551525432626], [-2.405251283120151, 4.964614403332121], [-2.405268676008518, 4.964661675296099], [-2.405335147598919, 4.964756442256032], [-2.405398787224215, 4.964772291008444], [-2.405460813465709, 4.964740819233441], [-2.40552336401197, 4.964725124265101], [-2.405569005505129, 4.96467790446178], [-2.405630372543612, 4.964630737718437], [-2.405660750742982, 4.964599304614239], [-2.405722041339061, 4.964552200823448], [-2.405767535343443, 4.964505118616387], [-2.405828735108003, 4.964458090368623], [-2.405890557203406, 4.964426803853939], [-2.405952953965595, 4.964411201515759], [-2.406014823725116, 4.964379974356405], [-2.406076431781855, 4.964333249180129], [-2.406138009261667, 4.96428654738628], [-2.406168484587852, 4.964255393971314], [-2.406229401965277, 4.96419312581213], [-2.406273832970953, 4.964115267905356], [-2.406287198695225, 4.964052987155696], [-2.406284743546053, 4.963990730687726], [-2.406282291094783, 4.963928539870267], [-2.406296244076373, 4.963881977471317], [-2.406279228003939, 4.96385089690142], [-2.406178637034429, 4.963711115275771], [-2.405954633899171, 4.963602635453299], [-2.405874890114035, 4.963571643916282], [-2.405810381743663, 4.963525287462119], [-2.405746480415701, 4.963494382259967], [-2.405683161848401, 4.963478896833692], [-2.40563556612841, 4.963463441084969], [-2.405571354534345, 4.963432508003905], [-2.405506501723607, 4.963386207307678], [-2.405426006105245, 4.963339943483675], [-2.405409108743299, 4.963309209152726], [-2.40531198286169, 4.96323239356019], [-2.405279079365982, 4.963186444498888], [-2.405245602102809, 4.963125256425485], [-2.405227151611712, 4.963048898587829], [-2.405209508711835, 4.962987920955811], [-2.40517628685609, 4.962927017967559], [-2.405173793935376, 4.962851005469588], [-2.405171802836378, 4.962790276050441], [-2.40516981353602, 4.962729615879141], [-2.405214522432232, 4.962669000673884], [-2.40526021338809, 4.962638696218903], [-2.405321463514667, 4.962608389065963], [-2.405367780398706, 4.962593220200972], [-2.405499865626268, 4.96279021309789], [-2.405533955327712, 4.962866229193196], [-2.405583232779918, 4.962927142973342], [-2.405617449285785, 4.963003400086905], [-2.405651151379459, 4.963064504523288], [-2.405669947210242, 4.96314100355454], [-2.405703926295132, 4.963202324727547], [-2.405751816093414, 4.963217727416236], [-2.405830623684267, 4.963217845227405], [-2.405878383080847, 4.963233238023577], [-2.405957330066656, 4.963248639812946], [-2.406191967685118, 4.963279303996728], [-2.406254715182911, 4.963294634739668], [-2.406317478868516, 4.963309969979207], [-2.406395183890595, 4.963309939402222], [-2.406472267481092, 4.963294548404747], [-2.406535112105701, 4.963294605062003], [-2.406578970243231, 4.963233157983893], [-2.406622778018743, 4.963171781052893], [-2.406619224797339, 4.963095177700325], [-2.406585192652472, 4.963033977036446], [-2.4065664579756, 4.96297283302988], [-2.406546944485854, 4.962896518359685], [-2.406512794529817, 4.962835565908676], [-2.406478692237783, 4.962774695295991], [-2.406415299026776, 4.962744309002687], [-2.406352717903587, 4.962729137439794], [-2.406290677272864, 4.96271396857486], [-2.406242075211537, 4.962653247249591], [-2.406208213937873, 4.962577428105874], [-2.406175030753047, 4.962516846175561], [-2.406142508569872, 4.962471450197313], [-2.406078773616457, 4.962410977084971], [-2.406045229803453, 4.962350608294003], [-2.405996544105165, 4.962290303354848], [-2.405932569932133, 4.962260327152421], [-2.405868671302073, 4.96223038692284], [-2.405790607450513, 4.962230568585937], [-2.405729530893097, 4.962260776813423], [-2.405668444443108, 4.962290992235523], [-2.405607993813817, 4.962336265006684], [-2.40556183251249, 4.962351410489248], [-2.40550128385712, 4.962396716535295], [-2.405441037373919, 4.962457150077455], [-2.405379166715136, 4.962472331532922], [-2.405315760014275, 4.962442249210483], [-2.405267716431922, 4.96239708885463], [-2.405219957934662, 4.962351950082507], [-2.405187329631531, 4.962291783638932], [-2.405169785657051, 4.962216635389439], [-2.405136812913554, 4.962141610347089], [-2.405119732089929, 4.962081645351702], [-2.405087236886402, 4.962021773885795], [-2.405085220606395, 4.961946943097018], [-2.40508385723416, 4.961887079724988], [-2.405082234857218, 4.961812296600272], [-2.405080614278859, 4.961737586320623], [-2.405094023170591, 4.961663024429072], [-2.405139519872876, 4.961648087589197], [-2.405200773596789, 4.961648010247529], [-2.405277812221186, 4.961662796900612], [-2.405339446358312, 4.961677640210951], [-2.405385396318934, 4.961677588949613], [-2.405477334911041, 4.961677475635042], [-2.405539218160357, 4.961692270382002], [-2.405616400676308, 4.961707066927659], [-2.405678203885941, 4.961721906640719], [-2.405755848653428, 4.961751657113325], [-2.405817215691911, 4.961751584268256], [-2.405879580977853, 4.961781370713709], [-2.405958851719561, 4.961825879960429], [-2.40602205607297, 4.961855562084565], [-2.406068114851564, 4.961870586158682], [-2.406130722954458, 4.961915504596902], [-2.406194095481055, 4.96197539854586], [-2.406271915616344, 4.96202041141288], [-2.406303652691349, 4.96205039930652], [-2.406352520052735, 4.962110327429741], [-2.406415219886469, 4.9621403207193], [-2.40649498075868, 4.962200340573304], [-2.406544328357995, 4.962260426977195], [-2.406592831493924, 4.962305547762867], [-2.406627029114077, 4.962365774460977], [-2.406659294091128, 4.962395932326444], [-2.40670853736907, 4.96245630291611], [-2.406757066585328, 4.962501626049232], [-2.406820157624111, 4.962531857659144], [-2.406870479189195, 4.96260754280388], [-2.406905502386962, 4.962683356551736], [-2.406939717993566, 4.96274409406476], [-2.406958578575484, 4.962804917013329], [-2.406992871523755, 4.962865811907761], [-2.407042627415251, 4.962926776949246], [-2.407091555031172, 4.962972544347508], [-2.407155056160889, 4.963003061941777], [-2.407234022931789, 4.963033585831397], [-2.407296673302767, 4.963048839232613], [-2.40736027695516, 4.963079399994342], [-2.407422967795696, 4.963094665986091], [-2.40750111348558, 4.963109923883906], [-2.407563834902987, 4.963125196170893], [-2.407625589549298, 4.963125152104112], [-2.407719207175603, 4.963140404606008], [-2.407898268490271, 4.963278364205166], [-2.407995194722389, 4.963339788800283], [-2.408058111292746, 4.963355121341806], [-2.408107306906629, 4.963401229583155], [-2.408125809558442, 4.963447429555288], [-2.408147523689195, 4.963539994075518], [-2.408167141500314, 4.963601790090593], [-2.408173766805874, 4.963694725131518], [-2.408194816337641, 4.963772295255296], [-2.408229465417435, 4.963834273832731], [-2.408265092060276, 4.963911858345625], [-2.408269799111906, 4.963989620025018], [-2.408290007777566, 4.964067456348062], [-2.408293793024086, 4.964129835123856], [-2.408314091622003, 4.964207880089646], [-2.408318225805431, 4.964270498984376], [-2.408335902879571, 4.964301813378029], [-2.408401469751993, 4.96434875708951], [-2.408478467906946, 4.964332942511362], [-2.408539828650134, 4.964317161208044], [-2.408584484486312, 4.964285741593756], [-2.408660292838192, 4.964254296798401], [-2.408719363907494, 4.964207255060842], [-2.408780585255784, 4.96419152142164], [-2.408839549305696, 4.964144547133174], [-2.408900702305516, 4.964128838675038], [-2.408960696079191, 4.964097531475943], [-2.409020644886823, 4.964066247659275], [-2.409063856411819, 4.9640194280542], [-2.40910586734185, 4.963957096942522], [-2.409148982639408, 4.963910388853435], [-2.409211082625291, 4.963910307015112], [-2.409262368263626, 4.963972444772537], [-2.409329256240142, 4.964034634690677], [-2.409379471685213, 4.96408132389405], [-2.40933521334938, 4.964112587925626], [-2.409229858670983, 4.964159582898503], [-2.409167604001652, 4.964159677327302], [-2.409093240860329, 4.964206678595417], [-2.409049963684822, 4.964253680762795], [-2.409007787279506, 4.96431640038162], [-2.408965554216934, 4.964379202738087], [-2.408954493455042, 4.964442036570802], [-2.408942052233897, 4.964489165542602], [-2.40886705327182, 4.964536441103917], [-2.408823257187521, 4.964583706772714], [-2.408780445860828, 4.964646769033209], [-2.40873554001314, 4.964678373907873], [-2.408671910280361, 4.964662731100191], [-2.408608292238853, 4.964647090990525], [-2.408530024241202, 4.964647238479301], [-2.408467403547775, 4.964647356290527], [-2.408389117563672, 4.964647502879984], [-2.408343106449138, 4.964663351632396], [-2.408249131791933, 4.964663529698157], [-2.408172687619412, 4.964695216411144], [-2.40809618948748, 4.96472692830514], [-2.408036095889088, 4.964774380133576], [-2.407990650448085, 4.96480600928993], [-2.407914549816553, 4.964853490796031], [-2.407853260119794, 4.964885173012362], [-2.407823494358752, 4.964916860624669], [-2.407731452344592, 4.964964447351406], [-2.407670918078338, 4.965012062856488], [-2.407610328953467, 4.965059720629654], [-2.407581304233759, 4.965107408979804], [-2.407520340091594, 4.965155031679444], [-2.407458348923626, 4.965186710298497], [-2.40738176265819, 4.965250090919028], [-2.40732081650242, 4.965297895281708], [-2.407290027312854, 4.965313887026298], [-2.407212109151487, 4.965330091010912], [-2.407118906112601, 4.965362181519538], [-2.407057091211755, 4.965394155116201], [-2.406978807026292, 4.965410264672016], [-2.406916921978336, 4.965442267946344], [-2.406870168923092, 4.965458314549608], [-2.406792420733609, 4.965490387071782], [-2.406729821623912, 4.965506490332302], [-2.406684744005702, 4.965570378170469], [-2.406687818787759, 4.965650259551865], [-2.406690282030866, 4.965714236422912], [-2.406724425691664, 4.965778188112949], [-2.406772358657406, 4.96579394963112], [-2.406900366358798, 4.965841295339544], [-2.407027872238473, 4.965872691571519], [-2.407136305296206, 4.965824056235249], [-2.407198140881462, 4.965791782265001], [-2.407262405535562, 4.965823406924756], [-2.407280883905628, 4.965887396386279], [-2.407316914344051, 4.965968036795516], [-2.407322320168873, 4.966065176166978], [-2.407325929148271, 4.966130039769553], [-2.407280599719854, 4.966178633737002], [-2.407218326164752, 4.966210979653056], [-2.407156893475815, 4.966259606895392], [-2.407109697954127, 4.966275763215947], [-2.407065076292213, 4.966340773408035], [-2.407034725072435, 4.966373290195236]]]}', + ] ], ], ], ], + + // Used for estimated area + [ + 'country' => 'MZ', + 'total_hectares_restored_goal' => 64, + 'sites' => [ + [ + 'uuid' => '9699c720-fa51-47fc-96c9-0675b29dcbbe', + 'geometry' => [ + [ + 'geojson' => '{"type": "Polygon", "coordinates": [[[40.42039202036633, -12.986826569522448], [40.420395177605975, -12.986824096985375], [40.42038654475881, -12.98670798951762], [40.42037374475881, -12.98657808951762], [40.42036344475881, -12.98647018951762], [40.420353944758816, -12.98636398951762], [40.42034624475881, -12.986278589517621], [40.42037564475881, -12.986192889517621], [40.420425644758815, -12.98612818951762], [40.42047954475881, -12.986106689517621], [40.42062474475881, -12.98606338951762], [40.42073464475881, -12.98604168951762], [40.42084664475881, -12.98604248951762], [40.42095864475881, -12.98604348951762], [40.42101464475881, -12.98604398951762], [40.42108934475881, -12.98604458951762], [40.42116404475881, -12.98604518951762], [40.42125904475881, -12.98606828951762], [40.421372844758814, -12.98609218951762], [40.42148894475881, -12.98615798951762], [40.42156724475881, -12.98622208951762], [40.42160844475881, -12.98628648951762], [40.421631244758814, -12.98635168951762], [40.421675744758815, -12.98646138951762], [40.421720044758814, -12.986570589517621], [40.42174424475881, -12.98665748951762], [40.42178664475881, -12.98674408951762], [40.421810644758814, -12.98683018951762], [40.421852744758816, -12.98691618951762], [40.421911644758815, -12.98698058951762], [40.42198854475881, -12.98704498951762], [40.42210404475881, -12.98715168951762], [40.422141544758816, -12.987172989517621], [40.422254044758816, -12.98723718951762], [40.42229374475881, -12.98730048951762], [40.42233774475881, -12.987447089517621], [40.422342244758816, -12.98753018951762], [40.42234774475881, -12.987633589517621], [40.42235324475881, -12.987736489517621], [40.422360944758815, -12.98787978951762], [40.422433344758815, -12.98790068951762], [40.42252254475881, -12.98790128951762], [40.42261364475881, -12.98794268951762], [40.42267084475881, -12.98802418951762], [40.422692244758814, -12.98810508951762], [40.42269684475881, -12.98820568951762], [40.422717244758815, -12.98826588951762], [40.42272184475881, -12.98836568951762], [40.42274294475881, -12.988445289517621], [40.42281594475881, -12.98850508951762], [40.42288604475881, -12.988504889517621], [40.422938544758814, -12.98850448951762], [40.422990344758816, -12.98848438951762], [40.423113044758814, -12.988483289517621], [40.423183144758816, -12.988482689517621], [40.42325514475881, -12.98854088951762], [40.423292444758815, -12.988618289517621], [40.423329644758816, -12.98869528951762], [40.423331944758814, -12.98877298951762], [40.42338714475881, -12.98887068951762], [40.42342364475881, -12.98892908951762], [40.42346044475881, -12.98900658951762], [40.42347834475881, -12.98902598951762], [40.423534344758814, -12.98918018951762], [40.423554044758816, -12.98927518951762], [40.42355534475881, -12.989331789517621], [40.42352334475881, -12.989425589517621], [40.423525244758814, -12.98950048951762], [40.423492444758814, -12.98955628951762], [40.42346074475881, -12.98964908951762], [40.423394044758815, -12.98970428951762], [40.42334454475881, -12.98975948951762], [40.42327934475881, -12.989851189517621], [40.42323074475881, -12.98992428951762], [40.423181244758815, -12.98996098951762], [40.42311704475881, -12.99005358951762], [40.423069344758815, -12.99012728951762], [40.42303864475881, -12.99020088951762], [40.42300804475881, -12.990274089517621], [40.42297834475881, -12.990365289517621], [40.42296554475881, -12.990456189517621], [40.422917844758814, -12.990510289517621], [40.42290434475881, -12.99058208951762], [40.422874144758815, -12.99065328951762], [40.42286054475881, -12.99072368951762], [40.42283124475881, -12.99081138951762], [40.42283434475881, -12.990881189517621], [40.42283814475881, -12.99096818951762], [40.42282474475881, -12.99103748951762], [40.42281134475881, -12.99110648951762], [40.422814444758814, -12.99117528951762], [40.42280104475881, -12.991243889517621], [40.42280574475881, -12.99134628951762], [40.42280874475881, -12.99141418951762], [40.422813344758815, -12.99151578951762], [40.42281714475881, -12.99159998951762], [40.42282094475881, -12.991683789517621], [40.42282474475881, -12.99176718951762], [40.42282774475881, -12.99183378951762], [40.422846844758816, -12.991900089517621], [40.422882044758815, -12.99196628951762], [40.42290254475881, -12.99206558951762], [40.422921644758816, -12.99213168951762], [40.42294064475881, -12.99219758951762], [40.42294434475881, -12.992279489517621], [40.42294724475881, -12.99234478951762], [40.42295094475881, -12.99242608951762], [40.42290484475881, -12.99245828951762], [40.422843644758814, -12.992506589517621], [40.42279774475881, -12.99253868951762], [40.422736044758814, -12.99257068951762], [40.42267264475881, -12.99257048951762], [40.42259354475881, -12.99257018951762], [40.422529344758814, -12.99255388951762], [40.42244814475881, -12.99252138951762], [40.422368944758816, -12.992521189517621], [40.42228964475881, -12.992520889517621], [40.42219464475881, -12.99252098951762], [40.42215034475881, -12.99256918951762], [40.422120844758815, -12.99260118951762], [40.422110444758815, -12.992681089517621], [40.42206564475881, -12.99271338951762], [40.422002744758814, -12.99271368951762], [40.42198244475881, -12.992649789517621], [40.42191144475881, -12.99253818951762], [40.421890944758815, -12.99247398951762], [40.42186804475881, -12.99237718951762], [40.42184984475881, -12.99234488951762], [40.421810744758815, -12.99224718951762], [40.42179104475881, -12.99219798951762], [40.42175004475881, -12.99208268951762], [40.42173014475881, -12.992033089517621], [40.42167554475881, -12.99195028951762], [40.421638144758816, -12.99188388951762], [40.42160064475881, -12.99181728951762], [40.421577844758815, -12.99173368951762], [40.421522544758815, -12.99164968951762], [40.42145354475881, -12.99159918951762], [40.421402144758815, -12.99156558951762], [40.42135214475881, -12.99154898951762], [40.42125054475881, -12.99149868951762], [40.42118544475881, -12.991498789517621], [40.421104144758814, -12.99149888951762], [40.42107164475881, -12.99149888951762], [40.420957744758816, -12.99149898951762], [40.420907244758816, -12.99148198951762], [40.42080964475881, -12.991482089517621], [40.42067944475881, -12.99148218951762], [40.420565544758816, -12.99148228951762], [40.420500444758815, -12.991482389517621], [40.420417144758815, -12.99146538951762], [40.420335744758816, -12.99146538951762], [40.42023814475881, -12.991465489517621], [40.420170944758816, -12.991448489517621], [40.420105844758815, -12.99144858951762], [40.42002204475881, -12.991431289517621], [40.41992174475881, -12.991413689517621], [40.419854044758814, -12.99139628951762], [40.419788544758816, -12.99139598951762], [40.41972064475881, -12.99137848951762], [40.419638744758814, -12.99137818951762], [40.419556744758815, -12.991377789517621], [40.419474744758816, -12.991377489517621], [40.41938124475881, -12.991411389517621], [40.41936314475881, -12.991513989517621], [40.41937524475881, -12.99159908951762], [40.419350044758815, -12.99164998951762], [40.41934594475881, -12.991734389517621], [40.41935564475881, -12.99180168951762], [40.41935154475881, -12.991885489517621], [40.41934504475881, -12.99195218951762], [40.419341044758816, -12.99203518951762], [40.419318544758816, -12.99210138951762], [40.41929864475881, -12.99218378951762], [40.41930824475881, -12.992249389517621], [40.419301944758814, -12.99231478951762], [40.41929804475881, -12.99239618951762], [40.419307544758816, -12.99246108951762], [40.41930364475881, -12.99254178951762], [40.41924764475881, -12.99259008951762], [40.419173644758814, -12.992622289517621], [40.41909724475881, -12.992638389517621], [40.41900264475881, -12.992638389517621], [40.41893194475881, -12.99259038951762], [40.41889014475881, -12.99252598951762], [40.418850744758814, -12.99247758951762], [40.418827044758814, -12.992429089517621], [40.41877954475881, -12.99233158951762], [40.418721244758814, -12.992266289517621], [40.418694644758816, -12.99220078951762], [40.418633144758815, -12.99211848951762], [40.41857694475881, -12.992068989517621], [40.418501744758814, -12.99200278951762], [40.41843474475881, -12.991986289517621], [40.418332744758814, -12.99195308951762], [40.418246544758816, -12.99191978951762], [40.417351544758816, -12.992188289517621], [40.417190344758815, -12.99210668951762], [40.41714134475881, -12.992057889517621], [40.417091844758815, -12.99199238951762], [40.41707464475881, -12.99194258951762], [40.41705704475881, -12.99187588951762], [40.417055444758816, -12.99180898951762], [40.417053444758814, -12.99172488951762], [40.41705184475881, -12.99165728951762], [40.41705024475881, -12.99158958951762], [40.41704824475881, -12.99150448951762], [40.417062544758814, -12.99141888951762], [40.41706054475881, -12.99133298951762], [40.417042544758814, -12.991264089517621], [40.416959344758816, -12.99121268951762], [40.416908344758816, -12.99114348951762], [40.416856544758815, -12.99105638951762], [40.416804944758816, -12.99098528951762], [40.41680204475881, -12.99089628951762], [40.41679924475881, -12.99080688951762], [40.41681304475881, -12.99071708951762], [40.41684464475881, -12.99066308951762], [40.41689224475881, -12.990573989517621], [40.41694064475881, -12.99050308951762], [40.41698954475881, -12.99044988951762], [40.417039044758816, -12.990414389517621], [40.417105344758816, -12.99037898951762], [40.41718824475881, -12.99032558951762], [40.417254844758816, -12.99028998951762], [40.41728794475881, -12.990254189517621], [40.41735464475881, -12.99021818951762], [40.417421344758814, -12.990163889517621], [40.417488344758816, -12.99012758951762], [40.41757244475881, -12.99009118951762], [40.41765644475881, -12.99003658951762], [40.41772394475881, -12.99000008951762], [40.417757644758815, -12.98996348951762], [40.417842244758816, -12.98992678951762], [40.41791004475881, -12.98989008951762], [40.41794404475881, -12.98985328951762], [40.41802904475881, -12.98983468951762], [40.41809714475881, -12.989797389517621], [40.41818234475881, -12.98977828951762], [40.418267844758816, -12.989740689517621], [40.41833614475881, -12.989721689517621], [40.418404644758816, -12.98970258951762], [40.418473344758816, -12.989664989517621], [40.418524944758815, -12.989645989517621], [40.41862874475881, -12.98957078951762], [40.41868084475881, -12.98953308951762], [40.418750544758815, -12.98947598951762], [40.41885494475881, -12.98941808951762], [40.41890844475881, -12.989341989517621], [40.41896114475881, -12.98930348951762], [40.419049644758815, -12.98922608951762], [40.41910394475881, -12.98914808951762], [40.419158444758814, -12.98906978951762], [40.419212544758814, -12.98901058951762], [40.419233244758814, -12.98891278951762], [40.41923654475881, -12.988814689517621], [40.41923984475881, -12.98871608951762], [40.41924324475881, -12.98861698951762], [40.41924584475881, -12.98853778951762], [40.41924894475881, -12.988439389517621], [40.419250844758814, -12.988380089517621], [40.419253344758815, -12.98830078951762], [40.41925784475881, -12.98816128951762], [40.419242544758816, -12.98808148951762], [40.41919154475881, -12.98800148951762], [40.419121444758815, -12.98796148951762], [40.41910594475881, -12.98788008951762], [40.41907244475881, -12.987799289517621], [40.419075144758814, -12.98769738951762], [40.41907794475881, -12.987594989517621], [40.41909874475881, -12.98749188951762], [40.419137244758815, -12.98740888951762], [40.41919574475881, -12.98726288951762], [40.419252744758815, -12.987178789517621], [40.419327444758814, -12.98711518951762], [40.419401744758815, -12.987072389517621], [40.41954744475881, -12.98707138951762], [40.41954734475881, -12.987021289517621], [40.419545944758816, -12.98699158951762], [40.41954494475881, -12.98697178951762], [40.41954354475881, -12.98694198951762], [40.41954234475881, -12.98691698951762], [40.419550644758814, -12.98689688951762], [40.419554344758815, -12.98687678951762], [40.41955824475881, -12.98686158951762], [40.41957104475881, -12.98683618951762], [40.419579344758816, -12.98681588951762], [40.41959244475881, -12.98679548951762], [40.41961064475881, -12.986785089517621], [40.41962864475881, -12.98676968951762], [40.419646644758814, -12.98675418951762], [40.419660244758816, -12.98674378951762], [40.41968304475881, -12.98672818951762], [40.419705644758814, -12.98670738951762], [40.419719044758814, -12.98668668951762], [40.419732544758816, -12.98667098951762], [40.41975534475881, -12.986650089517621], [40.419769044758816, -12.98663438951762], [40.41978264475881, -12.98661858951762], [40.41979614475881, -12.98659758951762], [40.41981474475881, -12.986586889517621], [40.41982834475881, -12.98656578951762], [40.41984704475881, -12.986555089517621], [40.419865744758816, -12.98654438951762], [40.41988434475881, -12.98652838951762], [40.41990304475881, -12.98651768951762], [40.419936244758816, -12.986512089517621], [40.41996004475881, -12.986511789517621], [40.419993244758814, -12.986511489517621], [40.42001264475881, -12.98653248951762], [40.420031644758815, -12.98653758951762], [40.42005084475881, -12.98655348951762], [40.420070044758816, -12.986569389517621], [40.42008434475881, -12.98657998951762], [40.420108144758814, -12.98659578951762], [40.42012724475881, -12.98661158951762], [40.42014144475881, -12.98662208951762], [40.42015094475881, -12.986637789517621], [40.420160544758815, -12.98666398951762], [40.42017474475881, -12.98667438951762], [40.42018884475881, -12.98668488951762], [40.42022174475881, -12.98670038951762], [40.420231144758816, -12.986705589517621], [40.42025464475881, -12.98672118951762], [40.420277944758816, -12.98673668951762], [40.42028734475881, -12.98674178951762], [40.420310644758814, -12.98676248951762], [40.42032924475881, -12.98677268951762], [40.42034314475881, -12.98678808951762], [40.42036164475881, -12.986803489517621], [40.420370844758814, -12.98681368951762], [40.42038474475881, -12.98682388951762], [40.42039042272699, -12.986826303249225], [40.42039202036633, -12.986826569522448]]]}', + 'est_area' => 29.0, + ], + [ + 'geojson' => '{"type": "Polygon", "coordinates": [[[40.4208782, -12.9966219], [40.420668718590605, -12.996690702886378], [40.4205746, -12.9967071], [40.42057158698505, -12.996712167019878], [40.4205646, -12.9967249], [40.4206808, -12.9970237], [40.4205785, -12.9970738], [40.4204578, -12.9970399], [40.4201384, -12.9970048], [40.4199487, -12.9969966], [40.4198451, -12.9969883], [40.4195651, -12.9969206], [40.4193779, -12.9968437], [40.41915, -12.9967142], [40.419007, -12.9966792], [40.4187997, -12.9966181], [40.4186464, -12.9964872], [40.4182502, -12.9963375], [40.4178242, -12.996267], [40.4177966, -12.9962583], [40.4176934, -12.996151], [40.4176655, -12.9961421], [40.4174409, -12.9960624], [40.4174293, -12.9960442], [40.4171427, -12.9958793], [40.4167641, -12.9958254], [40.416554, -12.9955798], [40.4165486, -12.9952107], [40.416852, -12.9952486], [40.4170542, -12.9952964], [40.4173546, -12.9951463], [40.4176285, -12.9948334], [40.4177287, -12.9948741], [40.4178497, -12.9950051], [40.4182262, -12.9950137], [40.4184424, -12.9950135], [40.4185773, -12.9948521], [40.4187258, -12.9946058], [40.4187848, -12.9942907], [40.4187998, -12.9940106], [40.4186606, -12.9936752], [40.4184847, -12.9935047], [40.418567, -12.9934237], [40.4191292, -12.993422], [40.4194912, -12.9934221], [40.4198032, -12.9934243], [40.4197678, -12.9937522], [40.4197963, -12.9939596], [40.4200389, -12.9941628], [40.4203545, -12.9943043], [40.4205594, -12.9943907], [40.4205707, -12.9945175], [40.4205837, -12.9947875], [40.4205933, -12.9947979], [40.420737, -12.9950106], [40.4209999, -12.9951113], [40.4210092, -12.9951213], [40.4213239, -12.9952997], [40.4214468, -12.9955806], [40.4214587, -12.9958268], [40.4213012, -12.9960029], [40.4212651, -12.996012], [40.4209943, -12.996185], [40.4208782, -12.9966219]]]}', + 'est_area' => 11.0, + ], + [ + 'geojson' => '{"type": "Polygon", "coordinates": [[[40.422583542502316, -12.996359645998147], [40.422837542502315, -12.996105945998147], [40.422867442502316, -12.995839445998147], [40.42275404250232, -12.995605945998147], [40.422777942502314, -12.995321145998147], [40.42286394250232, -12.995240745998148], [40.42294434250232, -12.995202445998148], [40.42301354250232, -12.995182845998148], [40.42312124250232, -12.995165945998147], [40.423235242502315, -12.995097345998147], [40.42333114250231, -12.995037345998147], [40.42345394250231, -12.994979345998148], [40.42349344250232, -12.994923945998147], [40.423533042502314, -12.994886345998147], [40.423530042502314, -12.994822145998148], [40.423541042502315, -12.994772445998148], [40.423573942502316, -12.994691745998148], [40.42359924250232, -12.994634745998148], [40.42364354250232, -12.994580345998148], [40.42369814250232, -12.994557945998148], [40.42375044250232, -12.994438045998148], [40.423774642502316, -12.994418245998148], [40.423775742502315, -12.994391445998147], [40.42377714250232, -12.994357845998147], [40.42378474250231, -12.994317245998147], [40.423785542502316, -12.994296845998148], [40.42379264250231, -12.994269545998147], [40.423805642502316, -12.994242245998148], [40.42380714250232, -12.994207745998148], [40.42380834250232, -12.994179945998148], [40.42382714250232, -12.994159245998148], [40.42385704250232, -12.994159545998148], [40.423880842502314, -12.994159745998148], [40.42390444250232, -12.994166945998147], [40.423927142502315, -12.994188045998147], [40.42394384250232, -12.994208945998148], [40.42394754250232, -12.994305145998148], [40.423973342502315, -12.994332545998148], [40.42399254250232, -12.994352945998148], [40.424006842502315, -12.994386745998147], [40.424014642502314, -12.994413645998147], [40.42404004250232, -12.994440445998148], [40.424058442502314, -12.994453945998147], [40.424087642502315, -12.994454145998148], [40.424111442502316, -12.994461045998147], [40.42413604250232, -12.994481045998148], [40.424160642502315, -12.994501045998147], [40.424178842502315, -12.994514345998148], [40.42420244250231, -12.994520945998147], [40.424226342502315, -12.994534045998147], [40.42424474250232, -12.994553645998147], [40.42426304250232, -12.994573245998147], [40.42427594250232, -12.994599145998148], [40.42428364250232, -12.994637845998147], [40.424295742502316, -12.994650645998147], [40.42431424250232, -12.994676445998147], [40.424332042502314, -12.994689445998148], [40.424333242502314, -12.994715045998147], [40.424335042502314, -12.994753245998147], [40.42434704250232, -12.994766045998148], [40.42437024250231, -12.994778945998148], [40.42439864250232, -12.994779145998148], [40.424420842502315, -12.994766545998148], [40.424437342502316, -12.994754045998148], [40.424459142502315, -12.994728745998147], [40.42447534250232, -12.994703345998147], [40.42450324250232, -12.994684345998147], [40.42451984250231, -12.994665145998148], [40.424548042502316, -12.994652545998148], [40.42456454250232, -12.994626745998147], [40.424581442502316, -12.994613845998147], [40.42461024250232, -12.994614145998147], [40.42463894250232, -12.994614345998148], [40.42466194250232, -12.994614545998148], [40.42469064250232, -12.994614745998147], [40.424719442502315, -12.994614945998148], [40.42473674250232, -12.994621645998148], [40.42476544250231, -12.994628345998148], [40.424782742502316, -12.994641445998148], [40.42478294250232, -12.994673645998148], [40.424783242502315, -12.994705745998148], [40.424783342502316, -12.994731245998148], [40.42477794250232, -12.994762945998147], [40.424766842502315, -12.994794545998147], [40.42476704250232, -12.994819845998148], [40.42475604250232, -12.994844945998148], [40.42473354250232, -12.994844745998147], [40.42471104250232, -12.994844545998147], [40.42468854250232, -12.994844445998147], [40.424660442502315, -12.994844245998147], [40.42463224250232, -12.994844045998148], [40.424598542502316, -12.994843745998148], [40.424575942502315, -12.994843645998147], [40.42454224250232, -12.994843345998147], [40.42451404250232, -12.994843145998148], [40.42448614250232, -12.994849245998148], [40.42446424250232, -12.994867845998147], [40.42444794250232, -12.994880245998148], [40.42442594250232, -12.994892545998148], [40.424404342502314, -12.994911045998148], [40.42438274250232, -12.994929445998148], [40.424372742502314, -12.994954045998147], [40.42436274250232, -12.994978545998148], [40.424347242502314, -12.995002845998147], [40.42432564250232, -12.995014945998147], [40.424303442502314, -12.995014745998148], [40.42428194250232, -12.995026745998148], [40.424254242502315, -12.995026545998147], [40.42423684250232, -12.995014345998147], [40.424240942502315, -12.994989945998148], [40.42425624250232, -12.994965545998147], [40.424266342502314, -12.994947145998148], [40.42427054250231, -12.994922445998148], [40.42425834250232, -12.994903845998147], [40.42423584250232, -12.994903645998148], [40.42421984250232, -12.994915945998148], [40.424197742502315, -12.994921945998147], [40.424188542502314, -12.994952745998148], [40.42418454250232, -12.994977345998148], [40.424186142502315, -12.995001745998147], [40.424182542502315, -12.995032145998147], [40.42416714250232, -12.995050245998147], [40.424145442502315, -12.995056145998147], [40.42412324250232, -12.995055945998148], [40.42404734250232, -12.995202345998148], [40.42404604250232, -12.995213245998148], [40.42402214250232, -12.995293245998148], [40.424011642502315, -12.995363545998147], [40.42406714250232, -12.995417245998148], [40.424062842502316, -12.995469745998147], [40.423970942502315, -12.995494045998148], [40.42391594250232, -12.995506145998148], [40.42399504250232, -12.995623645998148], [40.42402784250232, -12.995740945998147], [40.424191642502315, -12.995772345998148], [40.42430614250232, -12.995772245998147], [40.42438404250232, -12.995702245998148], [40.42434714250231, -12.995631845998147], [40.424305242502314, -12.995537145998147], [40.42438774250232, -12.995497245998148], [40.42444734250232, -12.995457245998148], [40.42452044250231, -12.995433245998148], [40.42468254250232, -12.995441045998147], [40.42475674250232, -12.995464845998148], [40.42488624250232, -12.995480645998148], [40.42498594250232, -12.995481445998148], [40.425070242502315, -12.995482145998148], [40.42518614250232, -12.995522645998147], [40.42533004250232, -12.995703845998147], [40.425350642502316, -12.995758145998147], [40.42534114250232, -12.995811845998148], [40.42531814250231, -12.995941145998147], [40.425299442502315, -12.996046245998148], [40.425245442502316, -12.996142645998148], [40.425185242502316, -12.996230945998148], [40.42511004250232, -12.996281945998147], [40.42500364250232, -12.996303345998147], [40.42477794250232, -12.996331945998147], [40.42466664250232, -12.996332145998148], [40.424599742502316, -12.996332445998148], [40.424500542502315, -12.996296445998148], [40.424501242502316, -12.996238045998147], [40.424360142502316, -12.996231345998147], [40.42425834250232, -12.996268345998148], [40.42410754250232, -12.996283545998148], [40.424085842502315, -12.996341745998148], [40.424024542502316, -12.996511745998147], [40.42422704250232, -12.996710845998148], [40.42438334250232, -12.996770345998147], [40.42446174250232, -12.996746745998147], [40.42448634250232, -12.996746745998147], [40.424615342502314, -12.996746645998147], [40.42474954250232, -12.996752545998147], [40.42487664250232, -12.996764645998148], [40.42490214250232, -12.996795345998148], [40.42496894250232, -12.996904445998148], [40.42497214250232, -12.997023645998148], [40.42499224250231, -12.997111345998148], [40.42498654250232, -12.997209145998148], [40.42495304250232, -12.997328545998148], [40.42486254250232, -12.997521945998148], [40.42462544250232, -12.998119045998148], [40.42453624250231, -12.998417745998148], [40.42424784250232, -12.998550545998148], [40.424090842502316, -12.998580445998147], [40.423812742502314, -12.998598545998147], [40.42300324250232, -12.997743445998148], [40.42264044250231, -12.997648945998147], [40.42218104250232, -12.997461745998148], [40.421974142502314, -12.997376545998147], [40.42153534250232, -12.997305045998148], [40.42121454250232, -12.997115945998148], [40.420682142502315, -12.997014245998148], [40.420577064606924, -12.996716427392446], [40.422583542502316, -12.996359645998147]]]}', + 'est_area' => 10.0 + ], + ] + ], + [ + 'uuid' => 'bd530b53-43c2-4a04-b759-85588c65f5b3', + 'geometry' => [ + [ + 'geojson' => '{"type": "Polygon", "coordinates": [[[40.4030892, -12.9677323], [40.4031161, -12.9677189], [40.4031687, -12.9677027], [40.4032214, -12.9676864], [40.4032909, -12.9676864], [40.4033603, -12.9676865], [40.4034297, -12.9676865], [40.4034826, -12.9676702], [40.4035494, -12.9676539], [40.4036189, -12.9676539], [40.4036719, -12.9676376], [40.4037251, -12.9676213], [40.4037807, -12.9676213], [40.4038642, -12.9676213], [40.4039222, -12.9676377], [40.4039918, -12.9676377], [40.4040497, -12.9676541], [40.4041215, -12.9676704], [40.4041792, -12.9676867], [40.4042275, -12.9677355], [40.4042777, -12.9678003], [40.4043121, -12.9678492], [40.4043485, -12.967914], [40.4043826, -12.9679626], [40.4043951, -12.9680582], [40.4044173, -12.9681218], [40.4044235, -12.9681692], [40.4044612, -12.9682481], [40.4044967, -12.9683111], [40.4045437, -12.9683584], [40.4045884, -12.9683899], [40.4046446, -12.9684057], [40.4047142, -12.9684216], [40.404799, -12.9684529], [40.4048667, -12.9684534], [40.4049124, -12.9684995], [40.4049697, -12.9685303], [40.4050284, -12.9685758], [40.4050868, -12.9686211], [40.4051585, -12.9686663], [40.4052165, -12.9687113], [40.4052729, -12.9687412], [40.4053277, -12.9687562], [40.4053853, -12.9688009], [40.4054532, -12.9688158], [40.4055104, -12.9688603], [40.4055542, -12.9689046], [40.4055727, -12.9689636], [40.4055911, -12.9690222], [40.4055699, -12.9690806], [40.4055474, -12.9691242], [40.4054857, -12.9691677], [40.4054359, -12.9691966], [40.4053834, -12.9691966], [40.4053309, -12.9691966], [40.4052522, -12.9691966], [40.4051881, -12.969211], [40.4051372, -12.9692254], [40.4050748, -12.9692541], [40.4050288, -12.9693115], [40.4050058, -12.9693401], [40.4049714, -12.9693827], [40.4049147, -12.9694543], [40.4048809, -12.9694972], [40.40482, -12.9695264], [40.4047814, -12.9695267], [40.4047314, -12.969541], [40.4046833, -12.9695694], [40.4046206, -12.9695836], [40.4045689, -12.9695836], [40.4045211, -12.969612], [40.4044734, -12.9696403], [40.4044258, -12.9696685], [40.4043723, -12.9696544], [40.4043295, -12.9696261], [40.4042955, -12.9695693], [40.4042613, -12.9695124], [40.404255, -12.9694695], [40.4042296, -12.9693836], [40.4042105, -12.9693407], [40.4041742, -12.9692691], [40.4041269, -12.9692118], [40.4040815, -12.9691688], [40.404049, -12.9691254], [40.4040034, -12.9690821], [40.4039576, -12.9690387], [40.4039091, -12.9689799], [40.4038625, -12.9689355], [40.4038134, -12.9688759], [40.4037797, -12.9688311], [40.4037302, -12.968771], [40.4036804, -12.9687107], [40.4036306, -12.9686503], [40.4035318, -12.9686198], [40.4034729, -12.9685891], [40.4034273, -12.9685584], [40.4033654, -12.9685121], [40.4033303, -12.9684657], [40.4032787, -12.9684036], [40.4032433, -12.9683568], [40.4032049, -12.9682941], [40.4031635, -12.9682154], [40.4031247, -12.968152], [40.4030828, -12.9680724], [40.4030573, -12.9680084], [40.403032, -12.9679444], [40.4030175, -12.9678643], [40.4030307, -12.9677837], [40.4030664, -12.9677513], [40.4030892, -12.9677323]]]}', + 'est_area' => 3.0, + ], + [ + 'geojson' => '{"type": "Polygon", "coordinates": [[[40.405925, -12.968045], [40.4058902, -12.9680778], [40.4059127, -12.9680233], [40.4059379, -12.9679904], [40.4059631, -12.9679571], [40.4059994, -12.9679346], [40.4060385, -12.9679343], [40.4060899, -12.9679561], [40.406129, -12.9679558], [40.4061803, -12.9679776], [40.4062302, -12.9679882], [40.4062812, -12.9680097], [40.4063202, -12.9680094], [40.4063691, -12.9680095], [40.4064081, -12.9680096], [40.4064472, -12.9680096], [40.4064808, -12.9680537], [40.4065208, -12.9680647], [40.4065618, -12.9680866], [40.4066114, -12.9680976], [40.4066529, -12.9681298], [40.4066936, -12.9681515], [40.4067332, -12.9681624], [40.4067737, -12.968184], [40.4068141, -12.9682056], [40.4068554, -12.9682384], [40.4068683, -12.9682816], [40.4068724, -12.9683353], [40.4068661, -12.9683779], [40.4068303, -12.9684095], [40.4068032, -12.9684305], [40.4067458, -12.9684301], [40.4067075, -12.9684298], [40.4066606, -12.96844], [40.4066128, -12.9684397], [40.4065861, -12.9684606], [40.4065403, -12.9684814], [40.4065127, -12.9684917], [40.4064767, -12.9685126], [40.4064312, -12.9685334], [40.4063764, -12.9685541], [40.4063407, -12.9685748], [40.4062933, -12.9685748], [40.406246, -12.9685748], [40.4061892, -12.9685747], [40.4061513, -12.9685747], [40.4061134, -12.9685747], [40.4060755, -12.9685747], [40.4060281, -12.9685747], [40.4059903, -12.9685747], [40.4059429, -12.9685747], [40.4058956, -12.9685747], [40.4058549, -12.968554], [40.4058128, -12.9685228], [40.4057979, -12.9684814], [40.4058114, -12.9684397], [40.4058345, -12.9683978], [40.4058591, -12.9683662], [40.4058619, -12.9683135], [40.4058662, -12.9682711], [40.4058704, -12.9682285], [40.4058843, -12.9681856], [40.4058982, -12.9681426], [40.4059206, -12.9680884], [40.405925, -12.968045]]]}', + 'est_area' => 1.0, + ], + ] + ], + [ + 'uuid' => 'd99fc5ca-c2a9-44d3-ac49-97cdd3085b3d', + 'geometry' => [ + [ + 'geojson' => '{"type": "Polygon", "coordinates": [[[40.4067983, -12.9261098], [40.4068497, -12.9260495], [40.4069473, -12.9260279], [40.4069582, -12.9260272], [40.4069745, -12.9260261], [40.4069881, -12.9260253], [40.406996, -12.9260218], [40.4070096, -12.9260209], [40.4070204, -12.9260202], [40.4070311, -12.9260165], [40.4070389, -12.926013], [40.4070523, -12.9260091], [40.4070605, -12.9260086], [40.4070736, -12.9260017], [40.4070838, -12.925992], [40.4070947, -12.9259913], [40.4071054, -12.9259876], [40.4071161, -12.9259839], [40.4071268, -12.9259802], [40.4071375, -12.9259765], [40.4071476, -12.9259636], [40.4071613, -12.9259628], [40.407172, -12.925959], [40.4071885, -12.925958], [40.4072049, -12.9259569], [40.4072186, -12.9259561], [40.4072295, -12.9259554], [40.4072432, -12.9259545], [40.4072542, -12.9259538], [40.4072706, -12.9259528], [40.4072784, -12.9259431], [40.4072777, -12.9259278], [40.4072853, -12.9259149], [40.4072848, -12.9259026], [40.407287, -12.92589], [40.4072892, -12.9258773], [40.4072885, -12.9258617], [40.4072879, -12.9258491], [40.4072873, -12.9258365], [40.4072813, -12.9258274], [40.4072724, -12.9258152], [40.4072609, -12.9258095], [40.4072495, -12.925807], [40.407238, -12.9258014], [40.4072292, -12.9257955], [40.4072202, -12.9257864], [40.4072113, -12.9257773], [40.4072049, -12.9257647], [40.4071957, -12.9257523], [40.4071864, -12.9257398], [40.4071747, -12.925734], [40.4071629, -12.9257281], [40.4071511, -12.9257223], [40.4071395, -12.9257197], [40.4071277, -12.9257139], [40.4071182, -12.9257012], [40.4071061, -12.925692], [40.4070992, -12.9256758], [40.4070953, -12.9256626], [40.4070637, -12.9256274], [40.4070511, -12.9256146], [40.4070419, -12.9256084], [40.4070297, -12.9256023], [40.4070176, -12.9255962], [40.4070054, -12.9255901], [40.4069934, -12.9255874], [40.4069815, -12.9255847], [40.4069695, -12.925582], [40.4069572, -12.9255758], [40.4069472, -12.9255626], [40.40694, -12.9255491], [40.4069384, -12.9255318], [40.4069371, -12.9255178], [40.4069298, -12.925504], [40.4069199, -12.9254938], [40.4069077, -12.9254909], [40.4068959, -12.9254915], [40.4068833, -12.925485], [40.4068703, -12.9254748], [40.406858, -12.9254719], [40.4068446, -12.9254581], [40.4068344, -12.9254477], [40.4068268, -12.9254335], [40.4068196, -12.9254229], [40.406809, -12.9254088], [40.4067983, -12.9253947], [40.4067906, -12.9253804], [40.4067829, -12.925366], [40.4067751, -12.9253516], [40.4067734, -12.9253368], [40.4067778, -12.9253217], [40.4067891, -12.9253136], [40.4067978, -12.9253094], [40.4068126, -12.9253049], [40.4068279, -12.9253041], [40.4068401, -12.9253035], [40.406853, -12.9253104], [40.4068637, -12.9253248], [40.406877, -12.9253353], [40.4068872, -12.9253459], [40.4069, -12.9253527], [40.4069098, -12.9253596], [40.4069226, -12.9253663], [40.406933, -12.9253805], [40.4069457, -12.9253872], [40.4069584, -12.9253938], [40.4069713, -12.9254041], [40.4069812, -12.9254145], [40.4069912, -12.9254249], [40.407004, -12.9254352], [40.4070165, -12.9254417], [40.4070236, -12.9254556], [40.4070363, -12.9254655], [40.4070487, -12.9254719], [40.4070613, -12.9254817], [40.4070707, -12.9254882], [40.4070833, -12.9254981], [40.4070929, -12.925508], [40.4071022, -12.9255145], [40.407112, -12.9255279], [40.407121, -12.9255308], [40.407152, -12.9256014], [40.407186, -12.9256367], [40.4071981, -12.9256461], [40.4072048, -12.9256624], [40.4072166, -12.9256684], [40.4072284, -12.9256743], [40.4072402, -12.9256803], [40.4072493, -12.9256897], [40.4072555, -12.9256992], [40.4072673, -12.9257084], [40.4072763, -12.9257177], [40.4072853, -12.925727], [40.4072943, -12.9257395], [40.4073062, -12.9257518], [40.4073179, -12.9257608], [40.4073267, -12.92577], [40.4073382, -12.9257757], [40.4073471, -12.925788], [40.4073533, -12.9258037], [40.4073592, -12.9258129], [40.4073598, -12.9258319], [40.4073685, -12.9258409], [40.4073799, -12.9258496], [40.4073913, -12.9258583], [40.4074, -12.9258703], [40.4074085, -12.9258792], [40.4074171, -12.9258911], [40.4074284, -12.9259028], [40.4074287, -12.9259151], [40.4074291, -12.9259304], [40.4074347, -12.9259393], [40.4074432, -12.925951], [40.4074516, -12.9259626], [40.4074573, -12.9259743], [40.4074629, -12.925983], [40.4074685, -12.9259917], [40.4074741, -12.9260034], [40.4074797, -12.9260149], [40.4074853, -12.9260265], [40.4074855, -12.9260384], [40.407491, -12.9260498], [40.4074965, -12.9260612], [40.407502, -12.9260726], [40.4075022, -12.9260872], [40.4075077, -12.9260985], [40.4075131, -12.9261097], [40.4075158, -12.9261212], [40.4075185, -12.9261297], [40.4075213, -12.9261439], [40.407524, -12.9261552], [40.4075346, -12.9261631], [40.4075373, -12.9261772], [40.4075452, -12.9261852], [40.4075452, -12.9261993], [40.4075531, -12.9262072], [40.4075557, -12.9262183], [40.4075635, -12.926229], [40.4075661, -12.9262344], [40.4075713, -12.9262507], [40.4075713, -12.9262617], [40.4075764, -12.9262724], [40.407579, -12.9262832], [40.4075841, -12.9262938], [40.4075892, -12.9263043], [40.4075942, -12.9263149], [40.4075993, -12.9263253], [40.4076043, -12.9263384], [40.4076093, -12.9263488], [40.4076169, -12.9263563], [40.4076193, -12.9263695], [40.4076268, -12.9263796], [40.4076317, -12.9263898], [40.4076366, -12.9264], [40.4076415, -12.9264101], [40.4076464, -12.9264202], [40.4076512, -12.9264303], [40.4076561, -12.9264378], [40.4076634, -12.9264502], [40.4076657, -12.9264603], [40.407673, -12.9264675], [40.4076753, -12.9264776], [40.4076801, -12.9264874], [40.4076823, -12.9264974], [40.4076821, -12.9265075], [40.4076843, -12.9265174], [40.4076866, -12.9265273], [40.4076888, -12.9265371], [40.4076886, -12.9265471], [40.4077062, -12.9266194], [40.4077071, -12.9266744], [40.4077068, -12.9266838], [40.4077065, -12.9266956], [40.4077015, -12.9267053], [40.4076918, -12.9267106], [40.4076822, -12.9267159], [40.407675, -12.926721], [40.4076653, -12.9267286], [40.407658, -12.9267361], [40.4076485, -12.9267413], [40.4076389, -12.9267466], [40.4076271, -12.9267473], [40.4076176, -12.926748], [40.4076057, -12.926751], [40.4075986, -12.9267515], [40.4075844, -12.9267524], [40.4075726, -12.9267532], [40.4075607, -12.926754], [40.4075513, -12.9267523], [40.4075418, -12.9267506], [40.4075299, -12.9267514], [40.4075204, -12.9267474], [40.4074879, -12.9267836], [40.4074773, -12.9267872], [40.4074667, -12.9267879], [40.4074508, -12.9267889], [40.4074401, -12.9267896], [40.4074297, -12.926796], [40.4074245, -12.9268021], [40.407425, -12.9268164], [40.4074201, -12.9268282], [40.4074206, -12.9268423], [40.407421, -12.9268565], [40.4074214, -12.9268677], [40.4074297, -12.9268784], [40.4074402, -12.9268806], [40.4074482, -12.9268828], [40.4074613, -12.9268848], [40.4074692, -12.9268843], [40.4074824, -12.926889], [40.4074928, -12.9268884], [40.4075059, -12.9268875], [40.4075164, -12.9268869], [40.4075294, -12.926886], [40.4075425, -12.9268852], [40.4075529, -12.9268845], [40.4075634, -12.9268838], [40.4075791, -12.9268828], [40.4075921, -12.926882], [40.4076026, -12.9268813], [40.4076157, -12.9268805], [40.4076261, -12.9268798], [40.4076392, -12.9268789], [40.4076471, -12.9268756], [40.4076602, -12.9268747], [40.4076707, -12.9268684], [40.4076838, -12.9268647], [40.4076943, -12.9268612], [40.4077049, -12.9268577], [40.4077154, -12.9268541], [40.4077259, -12.9268507], [40.4077364, -12.92685], [40.4077469, -12.9268493], [40.4077574, -12.9268487], [40.4077677, -12.9268536], [40.4077701, -12.9268646], [40.4077697, -12.9268812], [40.40778, -12.9268889], [40.4077876, -12.9268966], [40.4077978, -12.9269015], [40.4078081, -12.9269063], [40.4078183, -12.9269111], [40.4078285, -12.9269159], [40.4078361, -12.9269209], [40.4078436, -12.9269286], [40.4078536, -12.9269361], [40.4078584, -12.9269466], [40.4078632, -12.9269544], [40.4078655, -12.9269623], [40.4078727, -12.9269752], [40.4078801, -12.9269801], [40.4078902, -12.9269848], [40.4078976, -12.9269896], [40.4079101, -12.9269941], [40.4079173, -12.9270043], [40.407922, -12.9270119], [40.4079266, -12.9270221], [40.4079286, -12.9270325], [40.4079307, -12.9270428], [40.4079299, -12.9270585], [40.4079343, -12.9270712], [40.4079388, -12.9270812], [40.407946, -12.9270885], [40.4079504, -12.9270984], [40.4079548, -12.9271109], [40.4079542, -12.9271212], [40.4079562, -12.9271312], [40.4079555, -12.9271439], [40.4079476, -12.9271519], [40.4079351, -12.9271552], [40.4079297, -12.9271631], [40.4079194, -12.9271713], [40.4079095, -12.9271719], [40.4078971, -12.9271727], [40.4078874, -12.9271684], [40.4078803, -12.9271613], [40.4078757, -12.9271515], [40.4078686, -12.9271444], [40.4078588, -12.92714], [40.4078491, -12.927133], [40.4078419, -12.9271258], [40.407832, -12.9271214], [40.4078197, -12.9271171], [40.4078099, -12.92711], [40.4078, -12.9271056], [40.4077927, -12.9270983], [40.4077878, -12.9270909], [40.4077805, -12.9270836], [40.4077731, -12.9270738], [40.4077632, -12.9270692], [40.4077557, -12.9270619], [40.4077482, -12.9270571], [40.4077408, -12.9270498], [40.4077332, -12.927045], [40.4077258, -12.927035], [40.4077182, -12.9270302], [40.4077106, -12.9270255], [40.4077031, -12.9270128], [40.4076955, -12.9270053], [40.4076905, -12.9269977], [40.4076854, -12.9269874], [40.4076829, -12.9269768], [40.4076779, -12.9269664], [40.4076675, -12.9269644], [40.4076572, -12.9269651], [40.4076469, -12.9269659], [40.4076366, -12.9269746], [40.4076314, -12.926983], [40.4076237, -12.9269943], [40.407616, -12.9270002], [40.4076032, -12.9270064], [40.4075904, -12.9270072], [40.4075801, -12.927008], [40.4075673, -12.9270088], [40.4075545, -12.9270097], [40.4075416, -12.9270053], [40.4075313, -12.9270007], [40.4075209, -12.926996], [40.4075131, -12.9269912], [40.4075027, -12.9269865], [40.4074923, -12.9269818], [40.4074794, -12.9269826], [40.4074691, -12.9269806], [40.4074586, -12.9269758], [40.4074482, -12.9269738], [40.4074377, -12.926969], [40.4074272, -12.9269642], [40.4074193, -12.9269593], [40.4074088, -12.9269545], [40.4074032, -12.9269439], [40.4073925, -12.9269364], [40.4073844, -12.9269286], [40.4073763, -12.9269236], [40.4073631, -12.9269189], [40.4073525, -12.9269168], [40.4073417, -12.9269092], [40.4073386, -12.9268982], [40.4073381, -12.9268871], [40.4073373, -12.9268703], [40.4073366, -12.9268562], [40.4073359, -12.9268421], [40.4073352, -12.9268279], [40.4073347, -12.9268165], [40.4073263, -12.9268084], [40.4073178, -12.9267974], [40.4073067, -12.9267894], [40.4072983, -12.9267812], [40.4072948, -12.9267668], [40.407289, -12.9267583], [40.4072857, -12.9267467], [40.407277, -12.9267354], [40.4072684, -12.9267271], [40.407265, -12.9267154], [40.4072641, -12.9267004], [40.4072607, -12.9266886], [40.4072546, -12.9266769], [40.4072512, -12.926665], [40.4072424, -12.9266564], [40.4072312, -12.926651], [40.4072224, -12.9266424], [40.4072136, -12.9266337], [40.407205, -12.9266281], [40.4071989, -12.9266192], [40.407187, -12.9266076], [40.4071754, -12.926599], [40.4071692, -12.9265901], [40.4071662, -12.9265871], [40.4071512, -12.9265724], [40.4071392, -12.9265606], [40.4071271, -12.9265487], [40.4071152, -12.9265399], [40.4071064, -12.9265341], [40.4070997, -12.9265218], [40.4070931, -12.9265094], [40.4070839, -12.9265003], [40.4070772, -12.9264878], [40.4070651, -12.9264789], [40.4070561, -12.926473], [40.4070442, -12.9264672], [40.4070355, -12.9264645], [40.407023, -12.9264523], [40.4070133, -12.9264398], [40.4070041, -12.9264339], [40.4069944, -12.9264214], [40.4069877, -12.9264119], [40.4069782, -12.9264026], [40.4069712, -12.9263898], [40.4069616, -12.9263804], [40.406952, -12.9263711], [40.4069398, -12.9263652], [40.4069302, -12.9263558], [40.4069205, -12.9263463], [40.4069082, -12.9263404], [40.406896, -12.9263345], [40.4068862, -12.926325], [40.4068814, -12.9263082], [40.4068893, -12.9263007], [40.4069037, -12.9262997], [40.4069146, -12.926292], [40.4069254, -12.9262843], [40.4069367, -12.92628], [40.4069472, -12.9262688], [40.4069458, -12.926255], [40.4069356, -12.9262417], [40.4069257, -12.9262319], [40.4069184, -12.9262183], [40.4069114, -12.9262081], [40.4068988, -12.9262018], [40.4068917, -12.9261914], [40.4068787, -12.9261814], [40.4068636, -12.9261788], [40.406853, -12.926165], [40.4068454, -12.9261509], [40.4068347, -12.9261369], [40.4068245, -12.9261266], [40.4068117, -12.92612], [40.4068013, -12.9261096], [40.4067983, -12.9261098]]]}', + 'est_area' => 2.0 + ], + [ + 'geojson' => '{"type": "Polygon", "coordinates": [[[40.4054372, -12.9235332], [40.405487, -12.9235331], [40.4055256, -12.9235219], [40.4055534, -12.9235], [40.4055902, -12.9234671], [40.4056281, -12.923445], [40.4056651, -12.9234121], [40.405735, -12.923412], [40.405775, -12.9234119], [40.405835, -12.9234119], [40.4058849, -12.9234118], [40.4059258, -12.9234227], [40.4059491, -12.9234662], [40.4059542, -12.9235314], [40.4059658, -12.923553], [40.4059882, -12.9235854], [40.4060336, -12.9236608], [40.4060378, -12.9237149], [40.4060404, -12.9237472], [40.4060248, -12.9238008], [40.4060002, -12.9238649], [40.4059946, -12.9239182], [40.4059981, -12.9239608], [40.4059908, -12.9239926], [40.4059844, -12.9240348], [40.40597, -12.9241073], [40.4059624, -12.9241382], [40.4059669, -12.9242], [40.4059698, -12.9242411], [40.405963, -12.924282], [40.405966, -12.9243228], [40.4059697, -12.9243738], [40.4059741, -12.9244347], [40.405977, -12.9244752], [40.4059807, -12.9245257], [40.405985, -12.9245861], [40.4059879, -12.9246262], [40.4059923, -12.9246862], [40.4059959, -12.9247361], [40.4060091, -12.9247859], [40.4060305, -12.9248157], [40.4060539, -12.9248751], [40.4060663, -12.9249146], [40.4060691, -12.924954], [40.4060814, -12.9249933], [40.4060935, -12.9250323], [40.4061056, -12.9250712], [40.4061272, -12.92511], [40.4061291, -12.9251391], [40.4061519, -12.925197], [40.4061923, -12.9252355], [40.4062325, -12.925274], [40.4062538, -12.9253123], [40.4062751, -12.9253506], [40.4063057, -12.9253888], [40.406327, -12.925427], [40.4063578, -12.9254655], [40.4063799, -12.9255134], [40.4063918, -12.9255516], [40.4064132, -12.9255898], [40.4064238, -12.9256089], [40.4064463, -12.9256659], [40.4064586, -12.9257131], [40.4064704, -12.9257509], [40.4064816, -12.9257791], [40.4064933, -12.9258167], [40.4065149, -12.9258636], [40.4065172, -12.9259009], [40.40652, -12.9259473], [40.4065222, -12.9259843], [40.406496, -12.9260119], [40.4064699, -12.9260395], [40.4064444, -12.9260762], [40.4064086, -12.9260945], [40.4063728, -12.9261128], [40.4063464, -12.9261312], [40.4063201, -12.9261496], [40.4062741, -12.9261497], [40.406238, -12.9261589], [40.4062012, -12.9261589], [40.4061652, -12.9261681], [40.40611, -12.9261682], [40.4060733, -12.9261682], [40.4060472, -12.9261866], [40.4060012, -12.9261866], [40.4059553, -12.9261867], [40.4059362, -12.9261775], [40.4058963, -12.9261408], [40.4058563, -12.926104], [40.4058171, -12.9260764], [40.4057955, -12.9260395], [40.4057654, -12.9260118], [40.4056975, -12.9259748], [40.4056673, -12.925947], [40.4056261, -12.9259005], [40.4055957, -12.9258726], [40.4055729, -12.9258259], [40.4055619, -12.9258071], [40.4055288, -12.9257508], [40.4055235, -12.9256943], [40.4055192, -12.925647], [40.405525, -12.9256091], [40.4055402, -12.9255711], [40.4055453, -12.9255234], [40.4055513, -12.9254854], [40.4055472, -12.9254377], [40.4055447, -12.925409], [40.4055414, -12.9253707], [40.4055364, -12.9253131], [40.405533, -12.9252745], [40.4055194, -12.9252263], [40.4054989, -12.9252071], [40.4054569, -12.9251588], [40.4054259, -12.9251298], [40.4053855, -12.9251008], [40.4053734, -12.9250716], [40.4053509, -12.9250327], [40.405318, -12.9249839], [40.4053144, -12.9249446], [40.4053012, -12.9249053], [40.4052939, -12.9248263], [40.4052789, -12.9247668], [40.4052561, -12.9247272], [40.4052523, -12.9246873], [40.405239, -12.9246474], [40.4052152, -12.9245975], [40.4052018, -12.9245574], [40.4051893, -12.9245272], [40.4051749, -12.9244769], [40.4051903, -12.9244363], [40.4052164, -12.9244057], [40.4052434, -12.9243852], [40.4052792, -12.9243545], [40.405317, -12.924344], [40.405352, -12.924303], [40.4053677, -12.924262], [40.4053834, -12.9242209], [40.4053895, -12.9241798], [40.4053956, -12.9241386], [40.4054017, -12.9240972], [40.4053981, -12.9240559], [40.405404, -12.924014], [40.4054097, -12.9239719], [40.4054057, -12.9239297], [40.4054017, -12.9238874], [40.4053977, -12.923845], [40.4053936, -12.9238024], [40.4053896, -12.9237598], [40.4053845, -12.9237063], [40.4053835, -12.9236956], [40.4054059, -12.9236202], [40.4054216, -12.9235768], [40.4054372, -12.9235332]]]}', + 'est_area' => 1.0 + ] + ] + ], + ] + ] ]; public function run(): void @@ -48,17 +107,22 @@ public function run(): void foreach (self::TEST_PROJECTS as $projectDef) { $project = Project::factory()->create([ 'country' => $projectDef['country'], + 'total_hectares_restored_goal' => $projectDef['total_hectares_restored_goal'] ?? 0, ]); - foreach ($projectDef['sites'] as $site) { + foreach ($projectDef['sites'] as $siteDef) { $site = Site::factory()->create([ - 'uuid' => $site['uuid'], + 'uuid' => $siteDef['uuid'], 'project_id' => $project->id, ]); - foreach ($site['geometry'] ?? [] as $geojsonString) { + foreach ($siteDef['geometry'] ?? [] as $geometryDef) { + $geojsonString = $geometryDef['geojson']; $geometry = PolygonGeometry::factory()->geojson($geojsonString)->create(); - SitePolygon::factory()->site($site)->geometry($geometry)->create(); + + SitePolygon::factory()->site($site)->geometry($geometry)->create([ + 'est_area' => $geometryDef['est_area'] ?? 0 + ]); } } } diff --git a/tests/Unit/Validators/Extensions/PolygonValidatorsTest.php b/tests/Unit/Validators/Extensions/PolygonValidatorsTest.php index 1d3416e65..ab910bec0 100644 --- a/tests/Unit/Validators/Extensions/PolygonValidatorsTest.php +++ b/tests/Unit/Validators/Extensions/PolygonValidatorsTest.php @@ -2,13 +2,14 @@ namespace Tests\Unit\Validators\Extensions; +use App\Models\V2\PolygonGeometry; +use App\Models\V2\Sites\SitePolygon; use App\Models\V2\WorldCountryGeneralized; use App\Services\PolygonService; use App\Validators\SitePolygonValidator; use Database\Seeders\PolygonValidationSeeder; use Database\Seeders\WorldCountriesGeneralizedTableSeeder; use Illuminate\Support\Facades\App; -use Illuminate\Support\Facades\Log; use Illuminate\Support\Str; use Tests\TestCase; @@ -46,6 +47,11 @@ public function test_detect_spikes() $this->runValidationTest('SPIKES'); } + public function test_estimated_area() + { + $this->runValidationImportTest('ESTIMATED_AREA'); + } + public function test_schema() { $this->runValidationTest('SCHEMA'); @@ -59,7 +65,8 @@ public function test_data() protected function runValidationTest(string $validationName): void { $this->readGeojsons($validationName, function ($passGeojson, $failGeojson) use ($validationName) { - $this->assertValidation($validationName, $passGeojson, $failGeojson); + $this->assertTrue(SitePolygonValidator::isValid($validationName, $passGeojson, false)); + $this->assertFalse(SitePolygonValidator::isValid($validationName, $failGeojson, false)); }); } @@ -74,9 +81,12 @@ protected function runValidationImportTest(string $validationName): void /** @var PolygonService $service */ $service = App::make(PolygonService::class); $passUuids = $service->createGeojsonModels($passGeojson); - $failUuids = $service->createGeojsonModels($failGeojson); + $this->assertTrue(SitePolygonValidator::isValid($validationName, $passUuids, false)); - $this->assertValidation($validationName, $passUuids, $failUuids); + SitePolygon::whereIn('poly_id', $passUuids)->delete(); + PolygonGeometry::whereIn('uuid', $passUuids)->delete(); + $failUuids = $service->createGeojsonModels($failGeojson); + $this->assertFalse(SitePolygonValidator::isValid($validationName, $failUuids, false)); }); } @@ -88,10 +98,4 @@ protected function readGeojsons(string $validationName, callable $callback): voi $failGeojson = json_decode(file_get_contents($failFile), true); $callback($passGeojson, $failGeojson); } - - protected function assertValidation(string $validationName, $passData, $failData): void - { - $this->assertTrue(SitePolygonValidator::isValid($validationName, $passData, false)); - $this->assertFalse(SitePolygonValidator::isValid($validationName, $failData, false)); - } } diff --git a/tests/Unit/Validators/Extensions/Polygons/TestFiles/estimated_area_fail.geojson b/tests/Unit/Validators/Extensions/Polygons/TestFiles/estimated_area_fail.geojson new file mode 100644 index 000000000..bd674d24c --- /dev/null +++ b/tests/Unit/Validators/Extensions/Polygons/TestFiles/estimated_area_fail.geojson @@ -0,0 +1 @@ +{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"poly_name":"estimated area fail","plantstart":"2022-05-02","plantend":"2023-07-30","practice":"Tree Planting","site_id":"9699c720-fa51-47fc-96c9-0675b29dcbbe","project_id":"652ba56f-2e75-4735-a0d1-aafebbd940c1","num_trees":300},"geometry":{"coordinates":[[[40.41342760608805,-12.992483642989782],[40.41322768037668,-12.991232296533354],[40.4136865091159,-12.990708291932222],[40.414258406780775,-12.99042300002327],[40.41381923714604,-12.988346686620588],[40.41422785636311,-12.986621298879271],[40.412729585904174,-12.986057227209983],[40.410958902633354,-12.986123588649136],[40.40762184570241,-12.987119008111023],[40.406566246060436,-12.989408457736133],[40.40704296848011,-12.991432446351197],[40.408677445344324,-12.994086832648122],[40.41140157345177,-12.994849963456033],[40.41303605031595,-12.994916322545905],[40.413989495152634,-12.994086832648122],[40.41480673358538,-12.993456418471126],[40.414710781697295,-12.993052661287372],[40.41402208184023,-12.992979319239563],[40.41342760608805,-12.992483642989782]]],"type":"Polygon"}}]} \ No newline at end of file diff --git a/tests/Unit/Validators/Extensions/Polygons/TestFiles/estimated_area_pass.geojson b/tests/Unit/Validators/Extensions/Polygons/TestFiles/estimated_area_pass.geojson new file mode 100644 index 000000000..b25b35828 --- /dev/null +++ b/tests/Unit/Validators/Extensions/Polygons/TestFiles/estimated_area_pass.geojson @@ -0,0 +1 @@ +{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"poly_name":"estimated area pass","plantstart":"2022-02-02","plantend":"2023-03-30","practice":"Tree Planting","site_id":"9699c720-fa51-47fc-96c9-0675b29dcbbe","project_id":"652ba56f-2e75-4735-a0d1-aafebbd940c1","num_trees":2},"geometry":{"coordinates":[[[40.41561666702927,-12.991460964758602],[40.415099336620074,-12.991330275086526],[40.41470654871674,-12.99101288559659],[40.41451494486148,-12.990602145654634],[40.41396887387245,-12.990984880622122],[40.41374852943889,-12.992067737336598],[40.41404551541453,-12.992534484463519],[40.414457463704935,-12.992917216452938],[40.414926893150295,-12.992945221209538],[40.41485983180098,-12.992646503643428],[40.415243039511495,-12.99231978089405],[40.41556876606546,-12.992506479660591],[40.41577953030628,-12.992553154330324],[40.41561666702927,-12.991460964758602]]],"type":"Polygon"}}]} \ No newline at end of file From 49d59e75b6b7a96cc89d6ebd7c3949453b1e0d9d Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Thu, 9 May 2024 22:35:22 -0700 Subject: [PATCH 28/64] [TM-799] Move polygon validator test sample data to a separate file. --- .../factories/V2/PolygonGeometryFactory.php | 7 +- .../Data/polygon_validation_projects.json | 86 ++++++++++++++++ database/seeders/PolygonValidationSeeder.php | 97 +------------------ 3 files changed, 94 insertions(+), 96 deletions(-) create mode 100644 database/seeders/Data/polygon_validation_projects.json diff --git a/database/factories/V2/PolygonGeometryFactory.php b/database/factories/V2/PolygonGeometryFactory.php index 1a6925c30..bea9177a1 100644 --- a/database/factories/V2/PolygonGeometryFactory.php +++ b/database/factories/V2/PolygonGeometryFactory.php @@ -17,10 +17,11 @@ public function definition() public function geojson(string|array $geojson) { $geom = DB::raw("ST_GeomFromGeoJSON('$geojson')"); + return $this->state(function (array $attributes) use ($geom) { - return [ - 'geom' => $geom - ]; + return [ + 'geom' => $geom, + ]; }); } } diff --git a/database/seeders/Data/polygon_validation_projects.json b/database/seeders/Data/polygon_validation_projects.json new file mode 100644 index 000000000..a4dd66d10 --- /dev/null +++ b/database/seeders/Data/polygon_validation_projects.json @@ -0,0 +1,86 @@ +[ + { + "country": "AU", + "sites": [ + { + "uuid": "bc5d87ab-4c98-42f1-9902-b2848bb466b7" + } + ] + }, + { + "country": "GH", + "sites": [ + { + "uuid": "8d86e97a-3d4e-4132-95e1-435225d47f28", + "geometry": [ + { + "geojson": "{\"type\": \"Polygon\", \"coordinates\": [[[-2.27521108782139, 4.909556846471332], [-2.275458274579648, 4.909575790690212], [-2.275572360775755, 4.909518957134196], [-2.275572360775755, 4.909320038788792], [-2.275534332043719, 4.909140064662324], [-2.275581867509118, 4.908979035653999], [-2.275724475703953, 4.908884311861527], [-2.275981170094894, 4.90894114631692], [-2.276275892318552, 4.908884311861527], [-2.276437514879376, 4.9089506179767], [-2.276504064710764, 4.909073758547152], [-2.276675194904271, 4.909121120443388], [-2.276760759101705, 4.909016924991136], [-2.27692238166253, 4.908780116409275], [-2.27702696022601, 4.908675920957023], [-2.277084003324035, 4.908732755412359], [-2.277093510956718, 4.908931673757763], [-2.277074496590672, 4.909177953999404], [-2.277179075154152, 4.909386344903965], [-2.277435769545093, 4.909509484575096], [-2.277701970669341, 4.909509484575096], [-2.277987187059011, 4.909537901353133], [-2.277968172692965, 4.909367399785708], [-2.278006201425001, 4.909035869210072], [-2.277911128695564, 4.908799061527532], [-2.277835071231493, 4.908514891948755], [-2.277949157427599, 4.908448585833639], [-2.278101273255118, 4.908533836167635], [-2.278158316353142, 4.908666449297243], [-2.278405503111458, 4.908685393516123], [-2.278481560575528, 4.908581198063871], [-2.278538603673553, 4.908401223937403], [-2.278576632405588, 4.908259139148015], [-2.278614661137681, 4.908145471136606], [-2.278700226234378, 4.908079165021491], [-2.278823819163904, 4.908012858906375], [-2.27883332679653, 4.908126526018407], [-2.278899876627975, 4.908278083366895], [-2.278890369894611, 4.908382278819147], [-2.279004456090718, 4.908514891948755], [-2.279080513554788, 4.908609614841907], [-2.279185092118269, 4.908619087401007], [-2.279318192680421, 4.908685393516123], [-2.279375236677765, 4.908874840201747], [-2.279356222311776, 4.909016924991136], [-2.27944178650921, 4.909035869210072], [-2.279593901437352, 4.9089506179767], [-2.279660452168059, 4.909026396650916], [-2.279879116927646, 4.90918742655856], [-2.279964682024399, 4.909367399785708], [-2.279997956940122, 4.909576738575652], [-2.279855349644606, 4.909586210235489], [-2.279608161987028, 4.90971882246572], [-2.279494075790922, 4.909936685929381], [-2.27954161215564, 4.910145076833885], [-2.279617669619711, 4.910381883617106], [-2.279560626521629, 4.910618691299589], [-2.279503583423605, 4.910789192867014], [-2.279199353567265, 4.91085549808281], [-2.278971180275676, 4.911016527091078], [-2.278771529882135, 4.911224917995639], [-2.278562371855912, 4.911348057666771], [-2.27842927129376, 4.911565920231055], [-2.278420349119756, 4.911593476357893], [-2.27521108782139, 4.909556846471332]]]}" + }, + { + "geojson": "{\"type\": \"Polygon\", \"coordinates\": [[[-2.273037249269578, 4.908108314746983], [-2.273165733611677, 4.908453105826197], [-2.273179740552564, 4.908740329502677], [-2.27310784245293, 4.909027459649565], [-2.273021594770739, 4.909343295256917], [-2.272806396897636, 4.909745153715903], [-2.272591494002199, 4.909874163262089], [-2.27237662258301, 4.909974452059373], [-2.272261941935028, 4.910103570423473], [-2.27230464534307, 4.910333384078456], [-2.27260486242443, 4.910750164088824], [-2.27244699633178, 4.911094642204034], [-2.272231672553573, 4.911611382759133], [-2.27213115352987, 4.911884121954699], [-2.272087920421143, 4.912142561929727], [-2.272130608540692, 4.91238673595916], [-2.272216279757458, 4.912602236004432], [-2.272373477653844, 4.912875255788492], [-2.272315927338127, 4.913133681374347], [-2.272244089493086, 4.913363369124227], [-2.27181420366253, 4.913693190088679], [-2.271241296848302, 4.913879249927504], [-2.270854549697674, 4.914036792264199], [-2.270539453333072, 4.914136970444872], [-2.270081274830943, 4.914150828997606], [-2.269809161563501, 4.914222333194175], [-2.269350983061372, 4.914236191746909], [-2.26892147044947, 4.914221361027046], [-2.268692420318871, 4.914192388467995], [-2.268434828404565, 4.914077223085428], [-2.268105649556048, 4.913961978562497], [-2.267848105305745, 4.913803731157316], [-2.267662148889031, 4.913645561993121], [-2.267504906026545, 4.913415623332412], [-2.267390631872104, 4.913171370162672], [-2.267347898786454, 4.912970277256534], [-2.267477054922097, 4.912697571335912], [-2.26752016392436, 4.912554014356317], [-2.267649257107507, 4.912338749933383], [-2.267821209281351, 4.912209695421154], [-2.268107748573698, 4.912037684791358], [-2.268193794807758, 4.911908534950953], [-2.26836568312973, 4.911836921936469], [-2.268509078231318, 4.911636033176421], [-2.268738345997804, 4.91146395959413], [-2.26885310578615, 4.911263039357834], [-2.269010770430668, 4.911105247009573], [-2.2691398159497, 4.91093306370999], [-2.26932600619017, 4.910875825459016], [-2.269612529294761, 4.91071817430435], [-2.269755735538695, 4.910689610936856], [-2.269927452090201, 4.910775961141724], [-2.269884359275693, 4.910905157746868], [-2.269769647151463, 4.911062996859869], [-2.269669283710414, 4.911192130512461], [-2.269583440723181, 4.911134595485237], [-2.269569279998223, 4.910990975553148], [-2.269425979325433, 4.91110570206655], [-2.269454411391905, 4.911292418410426], [-2.269353859992577, 4.911593877455516], [-2.26932503762032, 4.911766170472447], [-2.26956829524056, 4.911895680940972], [-2.269740230327329, 4.911780985903818], [-2.26978327727636, 4.911694870421968], [-2.269869213793129, 4.911666244101923], [-2.269940708097181, 4.911752484589556], [-2.26996926427006, 4.911824317937999], [-2.270069347122615, 4.911953671025174], [-2.270312745037131, 4.911953937224496], [-2.270341443302925, 4.911896527202998], [-2.270484727787903, 4.911796161064046], [-2.270628043749184, 4.911667073276874], [-2.270699584817919, 4.911710233540475], [-2.270871379610412, 4.911724780973884], [-2.271057601327186, 4.911638821974122], [-2.271157871238699, 4.911595850568119], [-2.27115802772073, 4.911452246823785], [-2.271172438457199, 4.911366099865688], [-2.271172578751475, 4.911236855596485], [-2.271172781098926, 4.911050170728856], [-2.271258842621421, 4.910906660514001], [-2.2713020442539, 4.910676940388555], [-2.27134502825038, 4.910648266404394], [-2.271402485935937, 4.910476003964448], [-2.271488515082865, 4.910361214498494], [-2.271560258499107, 4.910217688995203], [-2.271674970623337, 4.910059848982883], [-2.271804015243049, 4.909887664783923], [-2.271947439122982, 4.909658054375768], [-2.271990610178477, 4.909457054999109], [-2.272091050061874, 4.909256118575058], [-2.272162887007596, 4.909026429925859], [-2.272248932342279, 4.908897280085455], [-2.272292087210019, 4.908710641083246], [-2.272220701723938, 4.908523877974631], [-2.272249429667397, 4.908437747204346], [-2.272106363717683, 4.908337068101275], [-2.272077993704386, 4.908092909360334], [-2.272121101807329, 4.90794935238074], [-2.272235828320788, 4.907777151994026], [-2.272364825276441, 4.907648048918361], [-2.272465079000142, 4.907619436987488], [-2.272636779363836, 4.907720147566806], [-2.272736924269566, 4.907792058256916], [-2.272851355805358, 4.907892705883739], [-2.273037249269578, 4.908108314746983]]]}" + }, + { + "geojson": "{\"type\": \"Polygon\", \"coordinates\": [[[-2.277179075154152, 4.911432361014704], [-2.2783341994637, 4.911859560570292], [-2.278514835491194, 4.912058478016377], [-2.278657443686029, 4.912191090246665], [-2.278828572980217, 4.912339804838155], [-2.278904630444288, 4.912425055172207], [-2.27903773100644, 4.912491360388003], [-2.279180339201218, 4.912576611621375], [-2.279256396665346, 4.912690278733407], [-2.279313439763371, 4.912927084617309], [-2.279265903398652, 4.913220724956545], [-2.279284917764699, 4.913400697284374], [-2.279370482861452, 4.913590143070678], [-2.279513090156911, 4.913684865064511], [-2.279703233817145, 4.913703810182767], [-2.2797412634485, 4.913864838291715], [-2.279731755815817, 4.9140542831787], [-2.279674712717792, 4.914167949391413], [-2.279665205085109, 4.914400966431515], [-2.279655698351746, 4.914561994540463], [-2.279750770181863, 4.914647244874516], [-2.279931407108677, 4.914751439427448], [-2.280093028770239, 4.914760911986548], [-2.280235636965017, 4.914647244874516], [-2.280378244260532, 4.914486216765567], [-2.280539866821357, 4.914419911549714], [-2.280758532480263, 4.914561994540463], [-2.280882125409732, 4.914656717433616], [-2.281091283435956, 4.914770383646328], [-2.281224384897371, 4.914817745542564], [-2.281386006558932, 4.914912467536396], [-2.281309949094805, 4.915035606308209], [-2.281262413629463, 4.91527241219211], [-2.281157834166663, 4.915433440301058], [-2.281062762336546, 4.915622884288723], [-2.28094867614044, 4.915746023060592], [-2.280853604310323, 4.915831273394588], [-2.280625431918111, 4.915954412166457], [-2.280615924285428, 4.916087023497369], [-2.280796561212298, 4.916238579047217], [-2.280910647408405, 4.916352245259986], [-2.281119805434628, 4.916399606256846], [-2.281338471093534, 4.916446967253762], [-2.281443049656957, 4.916579578584731], [-2.281576150219109, 4.91672166157548], [-2.281794815878015, 4.916759550013296], [-2.281927916440168, 4.916835327788192], [-2.282241653929134, 4.916769022572396], [-2.282431797589368, 4.916854272007072], [-2.282270175028486, 4.916996354997821], [-2.282194117564416, 4.917138437988569], [-2.282194117564416, 4.917318410316454], [-2.282089539000992, 4.917451020748047], [-2.281918409706805, 4.917356298754214], [-2.281817633476976, 4.9172663125903], [-2.28149438835527, 4.917218951593384], [-2.28129473796173, 4.917370507143232], [-2.281237694863705, 4.917531534352861], [-2.281228187231022, 4.917711505781426], [-2.281323259061082, 4.917910422328191], [-2.281209172864976, 4.9180809211976], [-2.281228187231022, 4.918260892626165], [-2.281323259061082, 4.918535586048506], [-2.281152129766951, 4.918687140699035], [-2.281161637399578, 4.918819752030004], [-2.281123608667542, 4.919056556115208], [-2.280981000472764, 4.919236527543774], [-2.280762334813858, 4.91929336020047], [-2.280600713152353, 4.919558581063711], [-2.280420076225482, 4.919757496711156], [-2.280125353102505, 4.919681718936204], [-2.279964682024399, 4.919584155984069], [-2.279774538364222, 4.919726238075498], [-2.279660452168059, 4.91984937594799], [-2.279422772143164, 4.91983990428821], [-2.279299178314375, 4.919820960069273], [-2.279090020288152, 4.919934625382723], [-2.279118542286824, 4.920038819935655], [-2.278928398626647, 4.920114596811231], [-2.278776283698505, 4.920029347376556], [-2.278519589307564, 4.920161957808148], [-2.278548110406916, 4.920322984118457], [-2.278510081674881, 4.920502955547022], [-2.278300923648658, 4.920427177772069], [-2.278205851818541, 4.920569259863498], [-2.278082258889071, 4.920663981857331], [-2.277759013767422, 4.920749231292064], [-2.277597392105918, 4.920787119729823], [-2.277445277177776, 4.920720814514027], [-2.277245625884859, 4.920654509298231], [-2.277074496590672, 4.920493482987922], [-2.276827309832413, 4.92039876099409], [-2.276646672905599, 4.920484010428765], [-2.276429767219952, 4.920042248151276], [-2.276392082928226, 4.919582673002026], [-2.276087299089511, 4.918969629444916], [-2.275897182408983, 4.918241826998781], [-2.276050276698868, 4.917897342588333], [-2.276489436338352, 4.917821231164908], [-2.276757133934552, 4.917419429363235], [-2.276872212082878, 4.916921724757856], [-2.27696797279367, 4.916634619791921], [-2.277292858278713, 4.916309469006819], [-2.277789162639692, 4.916348301732796], [-2.278285405846759, 4.916444575057142], [-2.278819891824526, 4.916483448252563], [-2.279297312221161, 4.916330786536605], [-2.279393051348165, 4.916062828137058], [-2.279240843789864, 4.915583982314956], [-2.279279312290384, 4.915315961862177], [-2.279165306133905, 4.914818009943247], [-2.278994050035351, 4.914300847606057], [-2.278994503293632, 4.913879608756986], [-2.278708541366029, 4.9135155011395], [-2.277906589817235, 4.913667811220535], [-2.277505490386034, 4.913858849706173], [-2.277199759561199, 4.914126579677941], [-2.277084784834926, 4.914528547854275], [-2.276740811239392, 4.914853677954909], [-2.276701886782575, 4.915542937256816], [-2.276205148948407, 4.91590619591426], [-2.275842311173676, 4.916020685906005], [-2.275708099948815, 4.916556662959692], [-2.275478458963676, 4.917073389125676], [-2.275172766809703, 4.917302824166029], [-2.274548559068705, 4.917291881215419], [-2.274574106110038, 4.917251157215162], [-2.274574106110038, 4.91713749100245], [-2.274697699039507, 4.91715643522133], [-2.274773756503635, 4.917080657446434], [-2.274849813967705, 4.917014352230638], [-2.274821292868296, 4.916853325021009], [-2.274764249770271, 4.916777547246056], [-2.274574106110038, 4.916673352693124], [-2.274412483549213, 4.916588103258391], [-2.274460019913931, 4.916512325483495], [-2.274631149208119, 4.916398659270783], [-2.274726221038236, 4.916294464717794], [-2.274802278502307, 4.916218686942898], [-2.274887842699741, 4.916048187174169], [-2.274992422162484, 4.915953465180337], [-2.275201580188707, 4.915962936840117], [-2.27541073821493, 4.915887159065164], [-2.275477288945694, 4.915735604414635], [-2.275581867509118, 4.915593520524624], [-2.275638910607199, 4.915375659758922], [-2.275638910607199, 4.915176743212157], [-2.275638910607199, 4.915044131881245], [-2.275638910607199, 4.914949408988093], [-2.275467781313012, 4.914911520550277], [-2.275353695116905, 4.914835742775381], [-2.275401231481624, 4.914712603104192], [-2.275543838777082, 4.914655770447496], [-2.275676939339235, 4.914693658885312], [-2.275667432605871, 4.914485269779448], [-2.275648418239882, 4.914371602667359], [-2.275762504435988, 4.914267408114426], [-2.275886098264778, 4.914257936454646], [-2.276000184460884, 4.91422004711751], [-2.276123777390353, 4.914153741002394], [-2.276285399951178, 4.914134796783515], [-2.276408992880647, 4.914077963227442], [-2.276551601075482, 4.91396429611541], [-2.276608644173564, 4.913888518340514], [-2.276684701637635, 4.913793796346681], [-2.276817802199787, 4.913755907009545], [-2.276789281100378, 4.913651712456613], [-2.276827309832413, 4.913509628566544], [-2.276865338564448, 4.913367544676476], [-2.277007945859964, 4.91329176690158], [-2.277131539688753, 4.913367544676476], [-2.277064988958045, 4.913481211788564], [-2.276941396028576, 4.913575934681717], [-2.276979424760611, 4.913708546012629], [-2.277150554054799, 4.913661185015712], [-2.27722661151887, 4.913727490231565], [-2.277331190981613, 4.913594878900597], [-2.277350205347659, 4.913490684347664], [-2.277464291543765, 4.913424378232492], [-2.277635420837953, 4.913443322451428], [-2.277682956303352, 4.913367544676476], [-2.277806550132141, 4.913310711120459], [-2.277920636328247, 4.913253877564443], [-2.277806550132141, 4.913111793674375], [-2.277920636328247, 4.913026543340379], [-2.278006201425001, 4.912874987790531], [-2.278006201425001, 4.912730062042783], [-2.277939650694293, 4.912663755927667], [-2.277844578864176, 4.912578505593615], [-2.277920636328247, 4.912502727818662], [-2.277996693792318, 4.91236064392865], [-2.278015708158364, 4.912265921035498], [-2.277911128695564, 4.912256448476342], [-2.277844578864176, 4.912332227150614], [-2.277759013767422, 4.91239853236641], [-2.277682956303352, 4.912303809473258], [-2.277701970669341, 4.912237504257462], [-2.277739999401376, 4.91211436458633], [-2.277587884473235, 4.91213330880521], [-2.277454783911082, 4.912142781364309], [-2.277293162249578, 4.912095420367393], [-2.277074496590672, 4.912010169134021], [-2.277112525322764, 4.911858613584172], [-2.277179075154152, 4.911716528794841], [-2.277274147883588, 4.911602861682752], [-2.277302668982941, 4.911517611348756], [-2.277179075154152, 4.911432361014704]]]}" + } + ] + }, + { + "uuid": "c1e2d5a6-288a-479f-977d-531ca5b52933", + "geometry": [ + { + "geojson": "{\"type\": \"Polygon\", \"coordinates\": [[[-2.407034725072435, 4.966373290195236], [-2.406825572442187, 4.96646313786357], [-2.406527084757101, 4.96655107717055], [-2.40646541284849, 4.966550920688462], [-2.406418320748855, 4.966583120914322], [-2.406354637056779, 4.966599121652166], [-2.406274894170963, 4.966615083719205], [-2.406211697911488, 4.966647264159974], [-2.406164037440305, 4.966663315259837], [-2.406084259480963, 4.966679285420753], [-2.405988114759623, 4.966679190991897], [-2.405925446402193, 4.966711966783862], [-2.405862278921006, 4.966728569168197], [-2.405814544705436, 4.966728881232939], [-2.405735817154209, 4.966761750554383], [-2.405670963444152, 4.966761977183523], [-2.40559161626004, 4.96674599802941], [-2.40541747243833, 4.966521707111156], [-2.40529831766321, 4.96633012273702], [-2.405298542493711, 4.966078808989892], [-2.405274956873711, 4.965767637266708], [-2.405180795157548, 4.9653899615775], [-2.405194353336697, 4.965310467803874], [-2.405192444075965, 4.965246876742015], [-2.405174486413387, 4.965183174164224], [-2.405141153941031, 4.965135346419174], [-2.405076068205915, 4.965087407158137], [-2.405011061611162, 4.96503952545379], [-2.404962473938951, 4.965007609413703], [-2.40488127684938, 4.964943930218226], [-2.404863579090829, 4.96488064402655], [-2.404830069452089, 4.964817365928752], [-2.404812879810493, 4.964769997737278], [-2.404794808833287, 4.964691194642967], [-2.404793461648865, 4.964644011711812], [-2.404743845152268, 4.964565279663987], [-2.404726302077108, 4.96450247550888], [-2.404692996584458, 4.964439682145667], [-2.404644317181464, 4.964392507308389], [-2.404580847528052, 4.964376630677009], [-2.404516979474977, 4.964345122029783], [-2.404453170777174, 4.964313643060223], [-2.404420018169276, 4.964251067332953], [-2.404418325645167, 4.964188675067362], [-2.404416214936305, 4.96411079647612], [-2.40444590065772, 4.964048668611213], [-2.404475984778855, 4.964002136789247], [-2.404521284529608, 4.96394016720501], [-2.404566534817661, 4.963878262371964], [-2.40459652630858, 4.963831869045578], [-2.404641695657688, 4.963770079325741], [-2.404703401740562, 4.963739285639576], [-2.404765086239706, 4.963708500946609], [-2.404859043809779, 4.963708678113051], [-2.404922206794367, 4.963724250773623], [-2.405002116054789, 4.963770806877278], [-2.405051260407276, 4.963832852903863], [-2.405245799953661, 4.964004109901794], [-2.405294709583131, 4.964050934902787], [-2.405328484521874, 4.964113365839182], [-2.405378082132756, 4.964175933472575], [-2.40539559822821, 4.964222858298342], [-2.405352835464953, 4.964347932411215], [-2.405323511270979, 4.964410551305946], [-2.405294082755688, 4.96447320527426], [-2.405249216478126, 4.964551525432626], [-2.405251283120151, 4.964614403332121], [-2.405268676008518, 4.964661675296099], [-2.405335147598919, 4.964756442256032], [-2.405398787224215, 4.964772291008444], [-2.405460813465709, 4.964740819233441], [-2.40552336401197, 4.964725124265101], [-2.405569005505129, 4.96467790446178], [-2.405630372543612, 4.964630737718437], [-2.405660750742982, 4.964599304614239], [-2.405722041339061, 4.964552200823448], [-2.405767535343443, 4.964505118616387], [-2.405828735108003, 4.964458090368623], [-2.405890557203406, 4.964426803853939], [-2.405952953965595, 4.964411201515759], [-2.406014823725116, 4.964379974356405], [-2.406076431781855, 4.964333249180129], [-2.406138009261667, 4.96428654738628], [-2.406168484587852, 4.964255393971314], [-2.406229401965277, 4.96419312581213], [-2.406273832970953, 4.964115267905356], [-2.406287198695225, 4.964052987155696], [-2.406284743546053, 4.963990730687726], [-2.406282291094783, 4.963928539870267], [-2.406296244076373, 4.963881977471317], [-2.406279228003939, 4.96385089690142], [-2.406178637034429, 4.963711115275771], [-2.405954633899171, 4.963602635453299], [-2.405874890114035, 4.963571643916282], [-2.405810381743663, 4.963525287462119], [-2.405746480415701, 4.963494382259967], [-2.405683161848401, 4.963478896833692], [-2.40563556612841, 4.963463441084969], [-2.405571354534345, 4.963432508003905], [-2.405506501723607, 4.963386207307678], [-2.405426006105245, 4.963339943483675], [-2.405409108743299, 4.963309209152726], [-2.40531198286169, 4.96323239356019], [-2.405279079365982, 4.963186444498888], [-2.405245602102809, 4.963125256425485], [-2.405227151611712, 4.963048898587829], [-2.405209508711835, 4.962987920955811], [-2.40517628685609, 4.962927017967559], [-2.405173793935376, 4.962851005469588], [-2.405171802836378, 4.962790276050441], [-2.40516981353602, 4.962729615879141], [-2.405214522432232, 4.962669000673884], [-2.40526021338809, 4.962638696218903], [-2.405321463514667, 4.962608389065963], [-2.405367780398706, 4.962593220200972], [-2.405499865626268, 4.96279021309789], [-2.405533955327712, 4.962866229193196], [-2.405583232779918, 4.962927142973342], [-2.405617449285785, 4.963003400086905], [-2.405651151379459, 4.963064504523288], [-2.405669947210242, 4.96314100355454], [-2.405703926295132, 4.963202324727547], [-2.405751816093414, 4.963217727416236], [-2.405830623684267, 4.963217845227405], [-2.405878383080847, 4.963233238023577], [-2.405957330066656, 4.963248639812946], [-2.406191967685118, 4.963279303996728], [-2.406254715182911, 4.963294634739668], [-2.406317478868516, 4.963309969979207], [-2.406395183890595, 4.963309939402222], [-2.406472267481092, 4.963294548404747], [-2.406535112105701, 4.963294605062003], [-2.406578970243231, 4.963233157983893], [-2.406622778018743, 4.963171781052893], [-2.406619224797339, 4.963095177700325], [-2.406585192652472, 4.963033977036446], [-2.4065664579756, 4.96297283302988], [-2.406546944485854, 4.962896518359685], [-2.406512794529817, 4.962835565908676], [-2.406478692237783, 4.962774695295991], [-2.406415299026776, 4.962744309002687], [-2.406352717903587, 4.962729137439794], [-2.406290677272864, 4.96271396857486], [-2.406242075211537, 4.962653247249591], [-2.406208213937873, 4.962577428105874], [-2.406175030753047, 4.962516846175561], [-2.406142508569872, 4.962471450197313], [-2.406078773616457, 4.962410977084971], [-2.406045229803453, 4.962350608294003], [-2.405996544105165, 4.962290303354848], [-2.405932569932133, 4.962260327152421], [-2.405868671302073, 4.96223038692284], [-2.405790607450513, 4.962230568585937], [-2.405729530893097, 4.962260776813423], [-2.405668444443108, 4.962290992235523], [-2.405607993813817, 4.962336265006684], [-2.40556183251249, 4.962351410489248], [-2.40550128385712, 4.962396716535295], [-2.405441037373919, 4.962457150077455], [-2.405379166715136, 4.962472331532922], [-2.405315760014275, 4.962442249210483], [-2.405267716431922, 4.96239708885463], [-2.405219957934662, 4.962351950082507], [-2.405187329631531, 4.962291783638932], [-2.405169785657051, 4.962216635389439], [-2.405136812913554, 4.962141610347089], [-2.405119732089929, 4.962081645351702], [-2.405087236886402, 4.962021773885795], [-2.405085220606395, 4.961946943097018], [-2.40508385723416, 4.961887079724988], [-2.405082234857218, 4.961812296600272], [-2.405080614278859, 4.961737586320623], [-2.405094023170591, 4.961663024429072], [-2.405139519872876, 4.961648087589197], [-2.405200773596789, 4.961648010247529], [-2.405277812221186, 4.961662796900612], [-2.405339446358312, 4.961677640210951], [-2.405385396318934, 4.961677588949613], [-2.405477334911041, 4.961677475635042], [-2.405539218160357, 4.961692270382002], [-2.405616400676308, 4.961707066927659], [-2.405678203885941, 4.961721906640719], [-2.405755848653428, 4.961751657113325], [-2.405817215691911, 4.961751584268256], [-2.405879580977853, 4.961781370713709], [-2.405958851719561, 4.961825879960429], [-2.40602205607297, 4.961855562084565], [-2.406068114851564, 4.961870586158682], [-2.406130722954458, 4.961915504596902], [-2.406194095481055, 4.96197539854586], [-2.406271915616344, 4.96202041141288], [-2.406303652691349, 4.96205039930652], [-2.406352520052735, 4.962110327429741], [-2.406415219886469, 4.9621403207193], [-2.40649498075868, 4.962200340573304], [-2.406544328357995, 4.962260426977195], [-2.406592831493924, 4.962305547762867], [-2.406627029114077, 4.962365774460977], [-2.406659294091128, 4.962395932326444], [-2.40670853736907, 4.96245630291611], [-2.406757066585328, 4.962501626049232], [-2.406820157624111, 4.962531857659144], [-2.406870479189195, 4.96260754280388], [-2.406905502386962, 4.962683356551736], [-2.406939717993566, 4.96274409406476], [-2.406958578575484, 4.962804917013329], [-2.406992871523755, 4.962865811907761], [-2.407042627415251, 4.962926776949246], [-2.407091555031172, 4.962972544347508], [-2.407155056160889, 4.963003061941777], [-2.407234022931789, 4.963033585831397], [-2.407296673302767, 4.963048839232613], [-2.40736027695516, 4.963079399994342], [-2.407422967795696, 4.963094665986091], [-2.40750111348558, 4.963109923883906], [-2.407563834902987, 4.963125196170893], [-2.407625589549298, 4.963125152104112], [-2.407719207175603, 4.963140404606008], [-2.407898268490271, 4.963278364205166], [-2.407995194722389, 4.963339788800283], [-2.408058111292746, 4.963355121341806], [-2.408107306906629, 4.963401229583155], [-2.408125809558442, 4.963447429555288], [-2.408147523689195, 4.963539994075518], [-2.408167141500314, 4.963601790090593], [-2.408173766805874, 4.963694725131518], [-2.408194816337641, 4.963772295255296], [-2.408229465417435, 4.963834273832731], [-2.408265092060276, 4.963911858345625], [-2.408269799111906, 4.963989620025018], [-2.408290007777566, 4.964067456348062], [-2.408293793024086, 4.964129835123856], [-2.408314091622003, 4.964207880089646], [-2.408318225805431, 4.964270498984376], [-2.408335902879571, 4.964301813378029], [-2.408401469751993, 4.96434875708951], [-2.408478467906946, 4.964332942511362], [-2.408539828650134, 4.964317161208044], [-2.408584484486312, 4.964285741593756], [-2.408660292838192, 4.964254296798401], [-2.408719363907494, 4.964207255060842], [-2.408780585255784, 4.96419152142164], [-2.408839549305696, 4.964144547133174], [-2.408900702305516, 4.964128838675038], [-2.408960696079191, 4.964097531475943], [-2.409020644886823, 4.964066247659275], [-2.409063856411819, 4.9640194280542], [-2.40910586734185, 4.963957096942522], [-2.409148982639408, 4.963910388853435], [-2.409211082625291, 4.963910307015112], [-2.409262368263626, 4.963972444772537], [-2.409329256240142, 4.964034634690677], [-2.409379471685213, 4.96408132389405], [-2.40933521334938, 4.964112587925626], [-2.409229858670983, 4.964159582898503], [-2.409167604001652, 4.964159677327302], [-2.409093240860329, 4.964206678595417], [-2.409049963684822, 4.964253680762795], [-2.409007787279506, 4.96431640038162], [-2.408965554216934, 4.964379202738087], [-2.408954493455042, 4.964442036570802], [-2.408942052233897, 4.964489165542602], [-2.40886705327182, 4.964536441103917], [-2.408823257187521, 4.964583706772714], [-2.408780445860828, 4.964646769033209], [-2.40873554001314, 4.964678373907873], [-2.408671910280361, 4.964662731100191], [-2.408608292238853, 4.964647090990525], [-2.408530024241202, 4.964647238479301], [-2.408467403547775, 4.964647356290527], [-2.408389117563672, 4.964647502879984], [-2.408343106449138, 4.964663351632396], [-2.408249131791933, 4.964663529698157], [-2.408172687619412, 4.964695216411144], [-2.40809618948748, 4.96472692830514], [-2.408036095889088, 4.964774380133576], [-2.407990650448085, 4.96480600928993], [-2.407914549816553, 4.964853490796031], [-2.407853260119794, 4.964885173012362], [-2.407823494358752, 4.964916860624669], [-2.407731452344592, 4.964964447351406], [-2.407670918078338, 4.965012062856488], [-2.407610328953467, 4.965059720629654], [-2.407581304233759, 4.965107408979804], [-2.407520340091594, 4.965155031679444], [-2.407458348923626, 4.965186710298497], [-2.40738176265819, 4.965250090919028], [-2.40732081650242, 4.965297895281708], [-2.407290027312854, 4.965313887026298], [-2.407212109151487, 4.965330091010912], [-2.407118906112601, 4.965362181519538], [-2.407057091211755, 4.965394155116201], [-2.406978807026292, 4.965410264672016], [-2.406916921978336, 4.965442267946344], [-2.406870168923092, 4.965458314549608], [-2.406792420733609, 4.965490387071782], [-2.406729821623912, 4.965506490332302], [-2.406684744005702, 4.965570378170469], [-2.406687818787759, 4.965650259551865], [-2.406690282030866, 4.965714236422912], [-2.406724425691664, 4.965778188112949], [-2.406772358657406, 4.96579394963112], [-2.406900366358798, 4.965841295339544], [-2.407027872238473, 4.965872691571519], [-2.407136305296206, 4.965824056235249], [-2.407198140881462, 4.965791782265001], [-2.407262405535562, 4.965823406924756], [-2.407280883905628, 4.965887396386279], [-2.407316914344051, 4.965968036795516], [-2.407322320168873, 4.966065176166978], [-2.407325929148271, 4.966130039769553], [-2.407280599719854, 4.966178633737002], [-2.407218326164752, 4.966210979653056], [-2.407156893475815, 4.966259606895392], [-2.407109697954127, 4.966275763215947], [-2.407065076292213, 4.966340773408035], [-2.407034725072435, 4.966373290195236]]]}" + } + ] + } + ] + }, + { + "country": "MZ", + "total_hectares_restored_goal": 64, + "sites": [ + { + "uuid": "9699c720-fa51-47fc-96c9-0675b29dcbbe", + "geometry": [ + { + "geojson": "{\"type\": \"Polygon\", \"coordinates\": [[[40.42039202036633, -12.986826569522448], [40.420395177605975, -12.986824096985375], [40.42038654475881, -12.98670798951762], [40.42037374475881, -12.98657808951762], [40.42036344475881, -12.98647018951762], [40.420353944758816, -12.98636398951762], [40.42034624475881, -12.986278589517621], [40.42037564475881, -12.986192889517621], [40.420425644758815, -12.98612818951762], [40.42047954475881, -12.986106689517621], [40.42062474475881, -12.98606338951762], [40.42073464475881, -12.98604168951762], [40.42084664475881, -12.98604248951762], [40.42095864475881, -12.98604348951762], [40.42101464475881, -12.98604398951762], [40.42108934475881, -12.98604458951762], [40.42116404475881, -12.98604518951762], [40.42125904475881, -12.98606828951762], [40.421372844758814, -12.98609218951762], [40.42148894475881, -12.98615798951762], [40.42156724475881, -12.98622208951762], [40.42160844475881, -12.98628648951762], [40.421631244758814, -12.98635168951762], [40.421675744758815, -12.98646138951762], [40.421720044758814, -12.986570589517621], [40.42174424475881, -12.98665748951762], [40.42178664475881, -12.98674408951762], [40.421810644758814, -12.98683018951762], [40.421852744758816, -12.98691618951762], [40.421911644758815, -12.98698058951762], [40.42198854475881, -12.98704498951762], [40.42210404475881, -12.98715168951762], [40.422141544758816, -12.987172989517621], [40.422254044758816, -12.98723718951762], [40.42229374475881, -12.98730048951762], [40.42233774475881, -12.987447089517621], [40.422342244758816, -12.98753018951762], [40.42234774475881, -12.987633589517621], [40.42235324475881, -12.987736489517621], [40.422360944758815, -12.98787978951762], [40.422433344758815, -12.98790068951762], [40.42252254475881, -12.98790128951762], [40.42261364475881, -12.98794268951762], [40.42267084475881, -12.98802418951762], [40.422692244758814, -12.98810508951762], [40.42269684475881, -12.98820568951762], [40.422717244758815, -12.98826588951762], [40.42272184475881, -12.98836568951762], [40.42274294475881, -12.988445289517621], [40.42281594475881, -12.98850508951762], [40.42288604475881, -12.988504889517621], [40.422938544758814, -12.98850448951762], [40.422990344758816, -12.98848438951762], [40.423113044758814, -12.988483289517621], [40.423183144758816, -12.988482689517621], [40.42325514475881, -12.98854088951762], [40.423292444758815, -12.988618289517621], [40.423329644758816, -12.98869528951762], [40.423331944758814, -12.98877298951762], [40.42338714475881, -12.98887068951762], [40.42342364475881, -12.98892908951762], [40.42346044475881, -12.98900658951762], [40.42347834475881, -12.98902598951762], [40.423534344758814, -12.98918018951762], [40.423554044758816, -12.98927518951762], [40.42355534475881, -12.989331789517621], [40.42352334475881, -12.989425589517621], [40.423525244758814, -12.98950048951762], [40.423492444758814, -12.98955628951762], [40.42346074475881, -12.98964908951762], [40.423394044758815, -12.98970428951762], [40.42334454475881, -12.98975948951762], [40.42327934475881, -12.989851189517621], [40.42323074475881, -12.98992428951762], [40.423181244758815, -12.98996098951762], [40.42311704475881, -12.99005358951762], [40.423069344758815, -12.99012728951762], [40.42303864475881, -12.99020088951762], [40.42300804475881, -12.990274089517621], [40.42297834475881, -12.990365289517621], [40.42296554475881, -12.990456189517621], [40.422917844758814, -12.990510289517621], [40.42290434475881, -12.99058208951762], [40.422874144758815, -12.99065328951762], [40.42286054475881, -12.99072368951762], [40.42283124475881, -12.99081138951762], [40.42283434475881, -12.990881189517621], [40.42283814475881, -12.99096818951762], [40.42282474475881, -12.99103748951762], [40.42281134475881, -12.99110648951762], [40.422814444758814, -12.99117528951762], [40.42280104475881, -12.991243889517621], [40.42280574475881, -12.99134628951762], [40.42280874475881, -12.99141418951762], [40.422813344758815, -12.99151578951762], [40.42281714475881, -12.99159998951762], [40.42282094475881, -12.991683789517621], [40.42282474475881, -12.99176718951762], [40.42282774475881, -12.99183378951762], [40.422846844758816, -12.991900089517621], [40.422882044758815, -12.99196628951762], [40.42290254475881, -12.99206558951762], [40.422921644758816, -12.99213168951762], [40.42294064475881, -12.99219758951762], [40.42294434475881, -12.992279489517621], [40.42294724475881, -12.99234478951762], [40.42295094475881, -12.99242608951762], [40.42290484475881, -12.99245828951762], [40.422843644758814, -12.992506589517621], [40.42279774475881, -12.99253868951762], [40.422736044758814, -12.99257068951762], [40.42267264475881, -12.99257048951762], [40.42259354475881, -12.99257018951762], [40.422529344758814, -12.99255388951762], [40.42244814475881, -12.99252138951762], [40.422368944758816, -12.992521189517621], [40.42228964475881, -12.992520889517621], [40.42219464475881, -12.99252098951762], [40.42215034475881, -12.99256918951762], [40.422120844758815, -12.99260118951762], [40.422110444758815, -12.992681089517621], [40.42206564475881, -12.99271338951762], [40.422002744758814, -12.99271368951762], [40.42198244475881, -12.992649789517621], [40.42191144475881, -12.99253818951762], [40.421890944758815, -12.99247398951762], [40.42186804475881, -12.99237718951762], [40.42184984475881, -12.99234488951762], [40.421810744758815, -12.99224718951762], [40.42179104475881, -12.99219798951762], [40.42175004475881, -12.99208268951762], [40.42173014475881, -12.992033089517621], [40.42167554475881, -12.99195028951762], [40.421638144758816, -12.99188388951762], [40.42160064475881, -12.99181728951762], [40.421577844758815, -12.99173368951762], [40.421522544758815, -12.99164968951762], [40.42145354475881, -12.99159918951762], [40.421402144758815, -12.99156558951762], [40.42135214475881, -12.99154898951762], [40.42125054475881, -12.99149868951762], [40.42118544475881, -12.991498789517621], [40.421104144758814, -12.99149888951762], [40.42107164475881, -12.99149888951762], [40.420957744758816, -12.99149898951762], [40.420907244758816, -12.99148198951762], [40.42080964475881, -12.991482089517621], [40.42067944475881, -12.99148218951762], [40.420565544758816, -12.99148228951762], [40.420500444758815, -12.991482389517621], [40.420417144758815, -12.99146538951762], [40.420335744758816, -12.99146538951762], [40.42023814475881, -12.991465489517621], [40.420170944758816, -12.991448489517621], [40.420105844758815, -12.99144858951762], [40.42002204475881, -12.991431289517621], [40.41992174475881, -12.991413689517621], [40.419854044758814, -12.99139628951762], [40.419788544758816, -12.99139598951762], [40.41972064475881, -12.99137848951762], [40.419638744758814, -12.99137818951762], [40.419556744758815, -12.991377789517621], [40.419474744758816, -12.991377489517621], [40.41938124475881, -12.991411389517621], [40.41936314475881, -12.991513989517621], [40.41937524475881, -12.99159908951762], [40.419350044758815, -12.99164998951762], [40.41934594475881, -12.991734389517621], [40.41935564475881, -12.99180168951762], [40.41935154475881, -12.991885489517621], [40.41934504475881, -12.99195218951762], [40.419341044758816, -12.99203518951762], [40.419318544758816, -12.99210138951762], [40.41929864475881, -12.99218378951762], [40.41930824475881, -12.992249389517621], [40.419301944758814, -12.99231478951762], [40.41929804475881, -12.99239618951762], [40.419307544758816, -12.99246108951762], [40.41930364475881, -12.99254178951762], [40.41924764475881, -12.99259008951762], [40.419173644758814, -12.992622289517621], [40.41909724475881, -12.992638389517621], [40.41900264475881, -12.992638389517621], [40.41893194475881, -12.99259038951762], [40.41889014475881, -12.99252598951762], [40.418850744758814, -12.99247758951762], [40.418827044758814, -12.992429089517621], [40.41877954475881, -12.99233158951762], [40.418721244758814, -12.992266289517621], [40.418694644758816, -12.99220078951762], [40.418633144758815, -12.99211848951762], [40.41857694475881, -12.992068989517621], [40.418501744758814, -12.99200278951762], [40.41843474475881, -12.991986289517621], [40.418332744758814, -12.99195308951762], [40.418246544758816, -12.99191978951762], [40.417351544758816, -12.992188289517621], [40.417190344758815, -12.99210668951762], [40.41714134475881, -12.992057889517621], [40.417091844758815, -12.99199238951762], [40.41707464475881, -12.99194258951762], [40.41705704475881, -12.99187588951762], [40.417055444758816, -12.99180898951762], [40.417053444758814, -12.99172488951762], [40.41705184475881, -12.99165728951762], [40.41705024475881, -12.99158958951762], [40.41704824475881, -12.99150448951762], [40.417062544758814, -12.99141888951762], [40.41706054475881, -12.99133298951762], [40.417042544758814, -12.991264089517621], [40.416959344758816, -12.99121268951762], [40.416908344758816, -12.99114348951762], [40.416856544758815, -12.99105638951762], [40.416804944758816, -12.99098528951762], [40.41680204475881, -12.99089628951762], [40.41679924475881, -12.99080688951762], [40.41681304475881, -12.99071708951762], [40.41684464475881, -12.99066308951762], [40.41689224475881, -12.990573989517621], [40.41694064475881, -12.99050308951762], [40.41698954475881, -12.99044988951762], [40.417039044758816, -12.990414389517621], [40.417105344758816, -12.99037898951762], [40.41718824475881, -12.99032558951762], [40.417254844758816, -12.99028998951762], [40.41728794475881, -12.990254189517621], [40.41735464475881, -12.99021818951762], [40.417421344758814, -12.990163889517621], [40.417488344758816, -12.99012758951762], [40.41757244475881, -12.99009118951762], [40.41765644475881, -12.99003658951762], [40.41772394475881, -12.99000008951762], [40.417757644758815, -12.98996348951762], [40.417842244758816, -12.98992678951762], [40.41791004475881, -12.98989008951762], [40.41794404475881, -12.98985328951762], [40.41802904475881, -12.98983468951762], [40.41809714475881, -12.989797389517621], [40.41818234475881, -12.98977828951762], [40.418267844758816, -12.989740689517621], [40.41833614475881, -12.989721689517621], [40.418404644758816, -12.98970258951762], [40.418473344758816, -12.989664989517621], [40.418524944758815, -12.989645989517621], [40.41862874475881, -12.98957078951762], [40.41868084475881, -12.98953308951762], [40.418750544758815, -12.98947598951762], [40.41885494475881, -12.98941808951762], [40.41890844475881, -12.989341989517621], [40.41896114475881, -12.98930348951762], [40.419049644758815, -12.98922608951762], [40.41910394475881, -12.98914808951762], [40.419158444758814, -12.98906978951762], [40.419212544758814, -12.98901058951762], [40.419233244758814, -12.98891278951762], [40.41923654475881, -12.988814689517621], [40.41923984475881, -12.98871608951762], [40.41924324475881, -12.98861698951762], [40.41924584475881, -12.98853778951762], [40.41924894475881, -12.988439389517621], [40.419250844758814, -12.988380089517621], [40.419253344758815, -12.98830078951762], [40.41925784475881, -12.98816128951762], [40.419242544758816, -12.98808148951762], [40.41919154475881, -12.98800148951762], [40.419121444758815, -12.98796148951762], [40.41910594475881, -12.98788008951762], [40.41907244475881, -12.987799289517621], [40.419075144758814, -12.98769738951762], [40.41907794475881, -12.987594989517621], [40.41909874475881, -12.98749188951762], [40.419137244758815, -12.98740888951762], [40.41919574475881, -12.98726288951762], [40.419252744758815, -12.987178789517621], [40.419327444758814, -12.98711518951762], [40.419401744758815, -12.987072389517621], [40.41954744475881, -12.98707138951762], [40.41954734475881, -12.987021289517621], [40.419545944758816, -12.98699158951762], [40.41954494475881, -12.98697178951762], [40.41954354475881, -12.98694198951762], [40.41954234475881, -12.98691698951762], [40.419550644758814, -12.98689688951762], [40.419554344758815, -12.98687678951762], [40.41955824475881, -12.98686158951762], [40.41957104475881, -12.98683618951762], [40.419579344758816, -12.98681588951762], [40.41959244475881, -12.98679548951762], [40.41961064475881, -12.986785089517621], [40.41962864475881, -12.98676968951762], [40.419646644758814, -12.98675418951762], [40.419660244758816, -12.98674378951762], [40.41968304475881, -12.98672818951762], [40.419705644758814, -12.98670738951762], [40.419719044758814, -12.98668668951762], [40.419732544758816, -12.98667098951762], [40.41975534475881, -12.986650089517621], [40.419769044758816, -12.98663438951762], [40.41978264475881, -12.98661858951762], [40.41979614475881, -12.98659758951762], [40.41981474475881, -12.986586889517621], [40.41982834475881, -12.98656578951762], [40.41984704475881, -12.986555089517621], [40.419865744758816, -12.98654438951762], [40.41988434475881, -12.98652838951762], [40.41990304475881, -12.98651768951762], [40.419936244758816, -12.986512089517621], [40.41996004475881, -12.986511789517621], [40.419993244758814, -12.986511489517621], [40.42001264475881, -12.98653248951762], [40.420031644758815, -12.98653758951762], [40.42005084475881, -12.98655348951762], [40.420070044758816, -12.986569389517621], [40.42008434475881, -12.98657998951762], [40.420108144758814, -12.98659578951762], [40.42012724475881, -12.98661158951762], [40.42014144475881, -12.98662208951762], [40.42015094475881, -12.986637789517621], [40.420160544758815, -12.98666398951762], [40.42017474475881, -12.98667438951762], [40.42018884475881, -12.98668488951762], [40.42022174475881, -12.98670038951762], [40.420231144758816, -12.986705589517621], [40.42025464475881, -12.98672118951762], [40.420277944758816, -12.98673668951762], [40.42028734475881, -12.98674178951762], [40.420310644758814, -12.98676248951762], [40.42032924475881, -12.98677268951762], [40.42034314475881, -12.98678808951762], [40.42036164475881, -12.986803489517621], [40.420370844758814, -12.98681368951762], [40.42038474475881, -12.98682388951762], [40.42039042272699, -12.986826303249225], [40.42039202036633, -12.986826569522448]]]}", + "est_area": 29 + }, + { + "geojson": "{\"type\": \"Polygon\", \"coordinates\": [[[40.4208782, -12.9966219], [40.420668718590605, -12.996690702886378], [40.4205746, -12.9967071], [40.42057158698505, -12.996712167019878], [40.4205646, -12.9967249], [40.4206808, -12.9970237], [40.4205785, -12.9970738], [40.4204578, -12.9970399], [40.4201384, -12.9970048], [40.4199487, -12.9969966], [40.4198451, -12.9969883], [40.4195651, -12.9969206], [40.4193779, -12.9968437], [40.41915, -12.9967142], [40.419007, -12.9966792], [40.4187997, -12.9966181], [40.4186464, -12.9964872], [40.4182502, -12.9963375], [40.4178242, -12.996267], [40.4177966, -12.9962583], [40.4176934, -12.996151], [40.4176655, -12.9961421], [40.4174409, -12.9960624], [40.4174293, -12.9960442], [40.4171427, -12.9958793], [40.4167641, -12.9958254], [40.416554, -12.9955798], [40.4165486, -12.9952107], [40.416852, -12.9952486], [40.4170542, -12.9952964], [40.4173546, -12.9951463], [40.4176285, -12.9948334], [40.4177287, -12.9948741], [40.4178497, -12.9950051], [40.4182262, -12.9950137], [40.4184424, -12.9950135], [40.4185773, -12.9948521], [40.4187258, -12.9946058], [40.4187848, -12.9942907], [40.4187998, -12.9940106], [40.4186606, -12.9936752], [40.4184847, -12.9935047], [40.418567, -12.9934237], [40.4191292, -12.993422], [40.4194912, -12.9934221], [40.4198032, -12.9934243], [40.4197678, -12.9937522], [40.4197963, -12.9939596], [40.4200389, -12.9941628], [40.4203545, -12.9943043], [40.4205594, -12.9943907], [40.4205707, -12.9945175], [40.4205837, -12.9947875], [40.4205933, -12.9947979], [40.420737, -12.9950106], [40.4209999, -12.9951113], [40.4210092, -12.9951213], [40.4213239, -12.9952997], [40.4214468, -12.9955806], [40.4214587, -12.9958268], [40.4213012, -12.9960029], [40.4212651, -12.996012], [40.4209943, -12.996185], [40.4208782, -12.9966219]]]}", + "est_area": 11 + }, + { + "geojson": "{\"type\": \"Polygon\", \"coordinates\": [[[40.422583542502316, -12.996359645998147], [40.422837542502315, -12.996105945998147], [40.422867442502316, -12.995839445998147], [40.42275404250232, -12.995605945998147], [40.422777942502314, -12.995321145998147], [40.42286394250232, -12.995240745998148], [40.42294434250232, -12.995202445998148], [40.42301354250232, -12.995182845998148], [40.42312124250232, -12.995165945998147], [40.423235242502315, -12.995097345998147], [40.42333114250231, -12.995037345998147], [40.42345394250231, -12.994979345998148], [40.42349344250232, -12.994923945998147], [40.423533042502314, -12.994886345998147], [40.423530042502314, -12.994822145998148], [40.423541042502315, -12.994772445998148], [40.423573942502316, -12.994691745998148], [40.42359924250232, -12.994634745998148], [40.42364354250232, -12.994580345998148], [40.42369814250232, -12.994557945998148], [40.42375044250232, -12.994438045998148], [40.423774642502316, -12.994418245998148], [40.423775742502315, -12.994391445998147], [40.42377714250232, -12.994357845998147], [40.42378474250231, -12.994317245998147], [40.423785542502316, -12.994296845998148], [40.42379264250231, -12.994269545998147], [40.423805642502316, -12.994242245998148], [40.42380714250232, -12.994207745998148], [40.42380834250232, -12.994179945998148], [40.42382714250232, -12.994159245998148], [40.42385704250232, -12.994159545998148], [40.423880842502314, -12.994159745998148], [40.42390444250232, -12.994166945998147], [40.423927142502315, -12.994188045998147], [40.42394384250232, -12.994208945998148], [40.42394754250232, -12.994305145998148], [40.423973342502315, -12.994332545998148], [40.42399254250232, -12.994352945998148], [40.424006842502315, -12.994386745998147], [40.424014642502314, -12.994413645998147], [40.42404004250232, -12.994440445998148], [40.424058442502314, -12.994453945998147], [40.424087642502315, -12.994454145998148], [40.424111442502316, -12.994461045998147], [40.42413604250232, -12.994481045998148], [40.424160642502315, -12.994501045998147], [40.424178842502315, -12.994514345998148], [40.42420244250231, -12.994520945998147], [40.424226342502315, -12.994534045998147], [40.42424474250232, -12.994553645998147], [40.42426304250232, -12.994573245998147], [40.42427594250232, -12.994599145998148], [40.42428364250232, -12.994637845998147], [40.424295742502316, -12.994650645998147], [40.42431424250232, -12.994676445998147], [40.424332042502314, -12.994689445998148], [40.424333242502314, -12.994715045998147], [40.424335042502314, -12.994753245998147], [40.42434704250232, -12.994766045998148], [40.42437024250231, -12.994778945998148], [40.42439864250232, -12.994779145998148], [40.424420842502315, -12.994766545998148], [40.424437342502316, -12.994754045998148], [40.424459142502315, -12.994728745998147], [40.42447534250232, -12.994703345998147], [40.42450324250232, -12.994684345998147], [40.42451984250231, -12.994665145998148], [40.424548042502316, -12.994652545998148], [40.42456454250232, -12.994626745998147], [40.424581442502316, -12.994613845998147], [40.42461024250232, -12.994614145998147], [40.42463894250232, -12.994614345998148], [40.42466194250232, -12.994614545998148], [40.42469064250232, -12.994614745998147], [40.424719442502315, -12.994614945998148], [40.42473674250232, -12.994621645998148], [40.42476544250231, -12.994628345998148], [40.424782742502316, -12.994641445998148], [40.42478294250232, -12.994673645998148], [40.424783242502315, -12.994705745998148], [40.424783342502316, -12.994731245998148], [40.42477794250232, -12.994762945998147], [40.424766842502315, -12.994794545998147], [40.42476704250232, -12.994819845998148], [40.42475604250232, -12.994844945998148], [40.42473354250232, -12.994844745998147], [40.42471104250232, -12.994844545998147], [40.42468854250232, -12.994844445998147], [40.424660442502315, -12.994844245998147], [40.42463224250232, -12.994844045998148], [40.424598542502316, -12.994843745998148], [40.424575942502315, -12.994843645998147], [40.42454224250232, -12.994843345998147], [40.42451404250232, -12.994843145998148], [40.42448614250232, -12.994849245998148], [40.42446424250232, -12.994867845998147], [40.42444794250232, -12.994880245998148], [40.42442594250232, -12.994892545998148], [40.424404342502314, -12.994911045998148], [40.42438274250232, -12.994929445998148], [40.424372742502314, -12.994954045998147], [40.42436274250232, -12.994978545998148], [40.424347242502314, -12.995002845998147], [40.42432564250232, -12.995014945998147], [40.424303442502314, -12.995014745998148], [40.42428194250232, -12.995026745998148], [40.424254242502315, -12.995026545998147], [40.42423684250232, -12.995014345998147], [40.424240942502315, -12.994989945998148], [40.42425624250232, -12.994965545998147], [40.424266342502314, -12.994947145998148], [40.42427054250231, -12.994922445998148], [40.42425834250232, -12.994903845998147], [40.42423584250232, -12.994903645998148], [40.42421984250232, -12.994915945998148], [40.424197742502315, -12.994921945998147], [40.424188542502314, -12.994952745998148], [40.42418454250232, -12.994977345998148], [40.424186142502315, -12.995001745998147], [40.424182542502315, -12.995032145998147], [40.42416714250232, -12.995050245998147], [40.424145442502315, -12.995056145998147], [40.42412324250232, -12.995055945998148], [40.42404734250232, -12.995202345998148], [40.42404604250232, -12.995213245998148], [40.42402214250232, -12.995293245998148], [40.424011642502315, -12.995363545998147], [40.42406714250232, -12.995417245998148], [40.424062842502316, -12.995469745998147], [40.423970942502315, -12.995494045998148], [40.42391594250232, -12.995506145998148], [40.42399504250232, -12.995623645998148], [40.42402784250232, -12.995740945998147], [40.424191642502315, -12.995772345998148], [40.42430614250232, -12.995772245998147], [40.42438404250232, -12.995702245998148], [40.42434714250231, -12.995631845998147], [40.424305242502314, -12.995537145998147], [40.42438774250232, -12.995497245998148], [40.42444734250232, -12.995457245998148], [40.42452044250231, -12.995433245998148], [40.42468254250232, -12.995441045998147], [40.42475674250232, -12.995464845998148], [40.42488624250232, -12.995480645998148], [40.42498594250232, -12.995481445998148], [40.425070242502315, -12.995482145998148], [40.42518614250232, -12.995522645998147], [40.42533004250232, -12.995703845998147], [40.425350642502316, -12.995758145998147], [40.42534114250232, -12.995811845998148], [40.42531814250231, -12.995941145998147], [40.425299442502315, -12.996046245998148], [40.425245442502316, -12.996142645998148], [40.425185242502316, -12.996230945998148], [40.42511004250232, -12.996281945998147], [40.42500364250232, -12.996303345998147], [40.42477794250232, -12.996331945998147], [40.42466664250232, -12.996332145998148], [40.424599742502316, -12.996332445998148], [40.424500542502315, -12.996296445998148], [40.424501242502316, -12.996238045998147], [40.424360142502316, -12.996231345998147], [40.42425834250232, -12.996268345998148], [40.42410754250232, -12.996283545998148], [40.424085842502315, -12.996341745998148], [40.424024542502316, -12.996511745998147], [40.42422704250232, -12.996710845998148], [40.42438334250232, -12.996770345998147], [40.42446174250232, -12.996746745998147], [40.42448634250232, -12.996746745998147], [40.424615342502314, -12.996746645998147], [40.42474954250232, -12.996752545998147], [40.42487664250232, -12.996764645998148], [40.42490214250232, -12.996795345998148], [40.42496894250232, -12.996904445998148], [40.42497214250232, -12.997023645998148], [40.42499224250231, -12.997111345998148], [40.42498654250232, -12.997209145998148], [40.42495304250232, -12.997328545998148], [40.42486254250232, -12.997521945998148], [40.42462544250232, -12.998119045998148], [40.42453624250231, -12.998417745998148], [40.42424784250232, -12.998550545998148], [40.424090842502316, -12.998580445998147], [40.423812742502314, -12.998598545998147], [40.42300324250232, -12.997743445998148], [40.42264044250231, -12.997648945998147], [40.42218104250232, -12.997461745998148], [40.421974142502314, -12.997376545998147], [40.42153534250232, -12.997305045998148], [40.42121454250232, -12.997115945998148], [40.420682142502315, -12.997014245998148], [40.420577064606924, -12.996716427392446], [40.422583542502316, -12.996359645998147]]]}", + "est_area": 10 + } + ] + }, + { + "uuid": "bd530b53-43c2-4a04-b759-85588c65f5b3", + "geometry": [ + { + "geojson": "{\"type\": \"Polygon\", \"coordinates\": [[[40.4030892, -12.9677323], [40.4031161, -12.9677189], [40.4031687, -12.9677027], [40.4032214, -12.9676864], [40.4032909, -12.9676864], [40.4033603, -12.9676865], [40.4034297, -12.9676865], [40.4034826, -12.9676702], [40.4035494, -12.9676539], [40.4036189, -12.9676539], [40.4036719, -12.9676376], [40.4037251, -12.9676213], [40.4037807, -12.9676213], [40.4038642, -12.9676213], [40.4039222, -12.9676377], [40.4039918, -12.9676377], [40.4040497, -12.9676541], [40.4041215, -12.9676704], [40.4041792, -12.9676867], [40.4042275, -12.9677355], [40.4042777, -12.9678003], [40.4043121, -12.9678492], [40.4043485, -12.967914], [40.4043826, -12.9679626], [40.4043951, -12.9680582], [40.4044173, -12.9681218], [40.4044235, -12.9681692], [40.4044612, -12.9682481], [40.4044967, -12.9683111], [40.4045437, -12.9683584], [40.4045884, -12.9683899], [40.4046446, -12.9684057], [40.4047142, -12.9684216], [40.404799, -12.9684529], [40.4048667, -12.9684534], [40.4049124, -12.9684995], [40.4049697, -12.9685303], [40.4050284, -12.9685758], [40.4050868, -12.9686211], [40.4051585, -12.9686663], [40.4052165, -12.9687113], [40.4052729, -12.9687412], [40.4053277, -12.9687562], [40.4053853, -12.9688009], [40.4054532, -12.9688158], [40.4055104, -12.9688603], [40.4055542, -12.9689046], [40.4055727, -12.9689636], [40.4055911, -12.9690222], [40.4055699, -12.9690806], [40.4055474, -12.9691242], [40.4054857, -12.9691677], [40.4054359, -12.9691966], [40.4053834, -12.9691966], [40.4053309, -12.9691966], [40.4052522, -12.9691966], [40.4051881, -12.969211], [40.4051372, -12.9692254], [40.4050748, -12.9692541], [40.4050288, -12.9693115], [40.4050058, -12.9693401], [40.4049714, -12.9693827], [40.4049147, -12.9694543], [40.4048809, -12.9694972], [40.40482, -12.9695264], [40.4047814, -12.9695267], [40.4047314, -12.969541], [40.4046833, -12.9695694], [40.4046206, -12.9695836], [40.4045689, -12.9695836], [40.4045211, -12.969612], [40.4044734, -12.9696403], [40.4044258, -12.9696685], [40.4043723, -12.9696544], [40.4043295, -12.9696261], [40.4042955, -12.9695693], [40.4042613, -12.9695124], [40.404255, -12.9694695], [40.4042296, -12.9693836], [40.4042105, -12.9693407], [40.4041742, -12.9692691], [40.4041269, -12.9692118], [40.4040815, -12.9691688], [40.404049, -12.9691254], [40.4040034, -12.9690821], [40.4039576, -12.9690387], [40.4039091, -12.9689799], [40.4038625, -12.9689355], [40.4038134, -12.9688759], [40.4037797, -12.9688311], [40.4037302, -12.968771], [40.4036804, -12.9687107], [40.4036306, -12.9686503], [40.4035318, -12.9686198], [40.4034729, -12.9685891], [40.4034273, -12.9685584], [40.4033654, -12.9685121], [40.4033303, -12.9684657], [40.4032787, -12.9684036], [40.4032433, -12.9683568], [40.4032049, -12.9682941], [40.4031635, -12.9682154], [40.4031247, -12.968152], [40.4030828, -12.9680724], [40.4030573, -12.9680084], [40.403032, -12.9679444], [40.4030175, -12.9678643], [40.4030307, -12.9677837], [40.4030664, -12.9677513], [40.4030892, -12.9677323]]]}", + "est_area": 3 + }, + { + "geojson": "{\"type\": \"Polygon\", \"coordinates\": [[[40.405925, -12.968045], [40.4058902, -12.9680778], [40.4059127, -12.9680233], [40.4059379, -12.9679904], [40.4059631, -12.9679571], [40.4059994, -12.9679346], [40.4060385, -12.9679343], [40.4060899, -12.9679561], [40.406129, -12.9679558], [40.4061803, -12.9679776], [40.4062302, -12.9679882], [40.4062812, -12.9680097], [40.4063202, -12.9680094], [40.4063691, -12.9680095], [40.4064081, -12.9680096], [40.4064472, -12.9680096], [40.4064808, -12.9680537], [40.4065208, -12.9680647], [40.4065618, -12.9680866], [40.4066114, -12.9680976], [40.4066529, -12.9681298], [40.4066936, -12.9681515], [40.4067332, -12.9681624], [40.4067737, -12.968184], [40.4068141, -12.9682056], [40.4068554, -12.9682384], [40.4068683, -12.9682816], [40.4068724, -12.9683353], [40.4068661, -12.9683779], [40.4068303, -12.9684095], [40.4068032, -12.9684305], [40.4067458, -12.9684301], [40.4067075, -12.9684298], [40.4066606, -12.96844], [40.4066128, -12.9684397], [40.4065861, -12.9684606], [40.4065403, -12.9684814], [40.4065127, -12.9684917], [40.4064767, -12.9685126], [40.4064312, -12.9685334], [40.4063764, -12.9685541], [40.4063407, -12.9685748], [40.4062933, -12.9685748], [40.406246, -12.9685748], [40.4061892, -12.9685747], [40.4061513, -12.9685747], [40.4061134, -12.9685747], [40.4060755, -12.9685747], [40.4060281, -12.9685747], [40.4059903, -12.9685747], [40.4059429, -12.9685747], [40.4058956, -12.9685747], [40.4058549, -12.968554], [40.4058128, -12.9685228], [40.4057979, -12.9684814], [40.4058114, -12.9684397], [40.4058345, -12.9683978], [40.4058591, -12.9683662], [40.4058619, -12.9683135], [40.4058662, -12.9682711], [40.4058704, -12.9682285], [40.4058843, -12.9681856], [40.4058982, -12.9681426], [40.4059206, -12.9680884], [40.405925, -12.968045]]]}", + "est_area": 1 + } + ] + }, + { + "uuid": "d99fc5ca-c2a9-44d3-ac49-97cdd3085b3d", + "geometry": [ + { + "geojson": "{\"type\": \"Polygon\", \"coordinates\": [[[40.4067983, -12.9261098], [40.4068497, -12.9260495], [40.4069473, -12.9260279], [40.4069582, -12.9260272], [40.4069745, -12.9260261], [40.4069881, -12.9260253], [40.406996, -12.9260218], [40.4070096, -12.9260209], [40.4070204, -12.9260202], [40.4070311, -12.9260165], [40.4070389, -12.926013], [40.4070523, -12.9260091], [40.4070605, -12.9260086], [40.4070736, -12.9260017], [40.4070838, -12.925992], [40.4070947, -12.9259913], [40.4071054, -12.9259876], [40.4071161, -12.9259839], [40.4071268, -12.9259802], [40.4071375, -12.9259765], [40.4071476, -12.9259636], [40.4071613, -12.9259628], [40.407172, -12.925959], [40.4071885, -12.925958], [40.4072049, -12.9259569], [40.4072186, -12.9259561], [40.4072295, -12.9259554], [40.4072432, -12.9259545], [40.4072542, -12.9259538], [40.4072706, -12.9259528], [40.4072784, -12.9259431], [40.4072777, -12.9259278], [40.4072853, -12.9259149], [40.4072848, -12.9259026], [40.407287, -12.92589], [40.4072892, -12.9258773], [40.4072885, -12.9258617], [40.4072879, -12.9258491], [40.4072873, -12.9258365], [40.4072813, -12.9258274], [40.4072724, -12.9258152], [40.4072609, -12.9258095], [40.4072495, -12.925807], [40.407238, -12.9258014], [40.4072292, -12.9257955], [40.4072202, -12.9257864], [40.4072113, -12.9257773], [40.4072049, -12.9257647], [40.4071957, -12.9257523], [40.4071864, -12.9257398], [40.4071747, -12.925734], [40.4071629, -12.9257281], [40.4071511, -12.9257223], [40.4071395, -12.9257197], [40.4071277, -12.9257139], [40.4071182, -12.9257012], [40.4071061, -12.925692], [40.4070992, -12.9256758], [40.4070953, -12.9256626], [40.4070637, -12.9256274], [40.4070511, -12.9256146], [40.4070419, -12.9256084], [40.4070297, -12.9256023], [40.4070176, -12.9255962], [40.4070054, -12.9255901], [40.4069934, -12.9255874], [40.4069815, -12.9255847], [40.4069695, -12.925582], [40.4069572, -12.9255758], [40.4069472, -12.9255626], [40.40694, -12.9255491], [40.4069384, -12.9255318], [40.4069371, -12.9255178], [40.4069298, -12.925504], [40.4069199, -12.9254938], [40.4069077, -12.9254909], [40.4068959, -12.9254915], [40.4068833, -12.925485], [40.4068703, -12.9254748], [40.406858, -12.9254719], [40.4068446, -12.9254581], [40.4068344, -12.9254477], [40.4068268, -12.9254335], [40.4068196, -12.9254229], [40.406809, -12.9254088], [40.4067983, -12.9253947], [40.4067906, -12.9253804], [40.4067829, -12.925366], [40.4067751, -12.9253516], [40.4067734, -12.9253368], [40.4067778, -12.9253217], [40.4067891, -12.9253136], [40.4067978, -12.9253094], [40.4068126, -12.9253049], [40.4068279, -12.9253041], [40.4068401, -12.9253035], [40.406853, -12.9253104], [40.4068637, -12.9253248], [40.406877, -12.9253353], [40.4068872, -12.9253459], [40.4069, -12.9253527], [40.4069098, -12.9253596], [40.4069226, -12.9253663], [40.406933, -12.9253805], [40.4069457, -12.9253872], [40.4069584, -12.9253938], [40.4069713, -12.9254041], [40.4069812, -12.9254145], [40.4069912, -12.9254249], [40.407004, -12.9254352], [40.4070165, -12.9254417], [40.4070236, -12.9254556], [40.4070363, -12.9254655], [40.4070487, -12.9254719], [40.4070613, -12.9254817], [40.4070707, -12.9254882], [40.4070833, -12.9254981], [40.4070929, -12.925508], [40.4071022, -12.9255145], [40.407112, -12.9255279], [40.407121, -12.9255308], [40.407152, -12.9256014], [40.407186, -12.9256367], [40.4071981, -12.9256461], [40.4072048, -12.9256624], [40.4072166, -12.9256684], [40.4072284, -12.9256743], [40.4072402, -12.9256803], [40.4072493, -12.9256897], [40.4072555, -12.9256992], [40.4072673, -12.9257084], [40.4072763, -12.9257177], [40.4072853, -12.925727], [40.4072943, -12.9257395], [40.4073062, -12.9257518], [40.4073179, -12.9257608], [40.4073267, -12.92577], [40.4073382, -12.9257757], [40.4073471, -12.925788], [40.4073533, -12.9258037], [40.4073592, -12.9258129], [40.4073598, -12.9258319], [40.4073685, -12.9258409], [40.4073799, -12.9258496], [40.4073913, -12.9258583], [40.4074, -12.9258703], [40.4074085, -12.9258792], [40.4074171, -12.9258911], [40.4074284, -12.9259028], [40.4074287, -12.9259151], [40.4074291, -12.9259304], [40.4074347, -12.9259393], [40.4074432, -12.925951], [40.4074516, -12.9259626], [40.4074573, -12.9259743], [40.4074629, -12.925983], [40.4074685, -12.9259917], [40.4074741, -12.9260034], [40.4074797, -12.9260149], [40.4074853, -12.9260265], [40.4074855, -12.9260384], [40.407491, -12.9260498], [40.4074965, -12.9260612], [40.407502, -12.9260726], [40.4075022, -12.9260872], [40.4075077, -12.9260985], [40.4075131, -12.9261097], [40.4075158, -12.9261212], [40.4075185, -12.9261297], [40.4075213, -12.9261439], [40.407524, -12.9261552], [40.4075346, -12.9261631], [40.4075373, -12.9261772], [40.4075452, -12.9261852], [40.4075452, -12.9261993], [40.4075531, -12.9262072], [40.4075557, -12.9262183], [40.4075635, -12.926229], [40.4075661, -12.9262344], [40.4075713, -12.9262507], [40.4075713, -12.9262617], [40.4075764, -12.9262724], [40.407579, -12.9262832], [40.4075841, -12.9262938], [40.4075892, -12.9263043], [40.4075942, -12.9263149], [40.4075993, -12.9263253], [40.4076043, -12.9263384], [40.4076093, -12.9263488], [40.4076169, -12.9263563], [40.4076193, -12.9263695], [40.4076268, -12.9263796], [40.4076317, -12.9263898], [40.4076366, -12.9264], [40.4076415, -12.9264101], [40.4076464, -12.9264202], [40.4076512, -12.9264303], [40.4076561, -12.9264378], [40.4076634, -12.9264502], [40.4076657, -12.9264603], [40.407673, -12.9264675], [40.4076753, -12.9264776], [40.4076801, -12.9264874], [40.4076823, -12.9264974], [40.4076821, -12.9265075], [40.4076843, -12.9265174], [40.4076866, -12.9265273], [40.4076888, -12.9265371], [40.4076886, -12.9265471], [40.4077062, -12.9266194], [40.4077071, -12.9266744], [40.4077068, -12.9266838], [40.4077065, -12.9266956], [40.4077015, -12.9267053], [40.4076918, -12.9267106], [40.4076822, -12.9267159], [40.407675, -12.926721], [40.4076653, -12.9267286], [40.407658, -12.9267361], [40.4076485, -12.9267413], [40.4076389, -12.9267466], [40.4076271, -12.9267473], [40.4076176, -12.926748], [40.4076057, -12.926751], [40.4075986, -12.9267515], [40.4075844, -12.9267524], [40.4075726, -12.9267532], [40.4075607, -12.926754], [40.4075513, -12.9267523], [40.4075418, -12.9267506], [40.4075299, -12.9267514], [40.4075204, -12.9267474], [40.4074879, -12.9267836], [40.4074773, -12.9267872], [40.4074667, -12.9267879], [40.4074508, -12.9267889], [40.4074401, -12.9267896], [40.4074297, -12.926796], [40.4074245, -12.9268021], [40.407425, -12.9268164], [40.4074201, -12.9268282], [40.4074206, -12.9268423], [40.407421, -12.9268565], [40.4074214, -12.9268677], [40.4074297, -12.9268784], [40.4074402, -12.9268806], [40.4074482, -12.9268828], [40.4074613, -12.9268848], [40.4074692, -12.9268843], [40.4074824, -12.926889], [40.4074928, -12.9268884], [40.4075059, -12.9268875], [40.4075164, -12.9268869], [40.4075294, -12.926886], [40.4075425, -12.9268852], [40.4075529, -12.9268845], [40.4075634, -12.9268838], [40.4075791, -12.9268828], [40.4075921, -12.926882], [40.4076026, -12.9268813], [40.4076157, -12.9268805], [40.4076261, -12.9268798], [40.4076392, -12.9268789], [40.4076471, -12.9268756], [40.4076602, -12.9268747], [40.4076707, -12.9268684], [40.4076838, -12.9268647], [40.4076943, -12.9268612], [40.4077049, -12.9268577], [40.4077154, -12.9268541], [40.4077259, -12.9268507], [40.4077364, -12.92685], [40.4077469, -12.9268493], [40.4077574, -12.9268487], [40.4077677, -12.9268536], [40.4077701, -12.9268646], [40.4077697, -12.9268812], [40.40778, -12.9268889], [40.4077876, -12.9268966], [40.4077978, -12.9269015], [40.4078081, -12.9269063], [40.4078183, -12.9269111], [40.4078285, -12.9269159], [40.4078361, -12.9269209], [40.4078436, -12.9269286], [40.4078536, -12.9269361], [40.4078584, -12.9269466], [40.4078632, -12.9269544], [40.4078655, -12.9269623], [40.4078727, -12.9269752], [40.4078801, -12.9269801], [40.4078902, -12.9269848], [40.4078976, -12.9269896], [40.4079101, -12.9269941], [40.4079173, -12.9270043], [40.407922, -12.9270119], [40.4079266, -12.9270221], [40.4079286, -12.9270325], [40.4079307, -12.9270428], [40.4079299, -12.9270585], [40.4079343, -12.9270712], [40.4079388, -12.9270812], [40.407946, -12.9270885], [40.4079504, -12.9270984], [40.4079548, -12.9271109], [40.4079542, -12.9271212], [40.4079562, -12.9271312], [40.4079555, -12.9271439], [40.4079476, -12.9271519], [40.4079351, -12.9271552], [40.4079297, -12.9271631], [40.4079194, -12.9271713], [40.4079095, -12.9271719], [40.4078971, -12.9271727], [40.4078874, -12.9271684], [40.4078803, -12.9271613], [40.4078757, -12.9271515], [40.4078686, -12.9271444], [40.4078588, -12.92714], [40.4078491, -12.927133], [40.4078419, -12.9271258], [40.407832, -12.9271214], [40.4078197, -12.9271171], [40.4078099, -12.92711], [40.4078, -12.9271056], [40.4077927, -12.9270983], [40.4077878, -12.9270909], [40.4077805, -12.9270836], [40.4077731, -12.9270738], [40.4077632, -12.9270692], [40.4077557, -12.9270619], [40.4077482, -12.9270571], [40.4077408, -12.9270498], [40.4077332, -12.927045], [40.4077258, -12.927035], [40.4077182, -12.9270302], [40.4077106, -12.9270255], [40.4077031, -12.9270128], [40.4076955, -12.9270053], [40.4076905, -12.9269977], [40.4076854, -12.9269874], [40.4076829, -12.9269768], [40.4076779, -12.9269664], [40.4076675, -12.9269644], [40.4076572, -12.9269651], [40.4076469, -12.9269659], [40.4076366, -12.9269746], [40.4076314, -12.926983], [40.4076237, -12.9269943], [40.407616, -12.9270002], [40.4076032, -12.9270064], [40.4075904, -12.9270072], [40.4075801, -12.927008], [40.4075673, -12.9270088], [40.4075545, -12.9270097], [40.4075416, -12.9270053], [40.4075313, -12.9270007], [40.4075209, -12.926996], [40.4075131, -12.9269912], [40.4075027, -12.9269865], [40.4074923, -12.9269818], [40.4074794, -12.9269826], [40.4074691, -12.9269806], [40.4074586, -12.9269758], [40.4074482, -12.9269738], [40.4074377, -12.926969], [40.4074272, -12.9269642], [40.4074193, -12.9269593], [40.4074088, -12.9269545], [40.4074032, -12.9269439], [40.4073925, -12.9269364], [40.4073844, -12.9269286], [40.4073763, -12.9269236], [40.4073631, -12.9269189], [40.4073525, -12.9269168], [40.4073417, -12.9269092], [40.4073386, -12.9268982], [40.4073381, -12.9268871], [40.4073373, -12.9268703], [40.4073366, -12.9268562], [40.4073359, -12.9268421], [40.4073352, -12.9268279], [40.4073347, -12.9268165], [40.4073263, -12.9268084], [40.4073178, -12.9267974], [40.4073067, -12.9267894], [40.4072983, -12.9267812], [40.4072948, -12.9267668], [40.407289, -12.9267583], [40.4072857, -12.9267467], [40.407277, -12.9267354], [40.4072684, -12.9267271], [40.407265, -12.9267154], [40.4072641, -12.9267004], [40.4072607, -12.9266886], [40.4072546, -12.9266769], [40.4072512, -12.926665], [40.4072424, -12.9266564], [40.4072312, -12.926651], [40.4072224, -12.9266424], [40.4072136, -12.9266337], [40.407205, -12.9266281], [40.4071989, -12.9266192], [40.407187, -12.9266076], [40.4071754, -12.926599], [40.4071692, -12.9265901], [40.4071662, -12.9265871], [40.4071512, -12.9265724], [40.4071392, -12.9265606], [40.4071271, -12.9265487], [40.4071152, -12.9265399], [40.4071064, -12.9265341], [40.4070997, -12.9265218], [40.4070931, -12.9265094], [40.4070839, -12.9265003], [40.4070772, -12.9264878], [40.4070651, -12.9264789], [40.4070561, -12.926473], [40.4070442, -12.9264672], [40.4070355, -12.9264645], [40.407023, -12.9264523], [40.4070133, -12.9264398], [40.4070041, -12.9264339], [40.4069944, -12.9264214], [40.4069877, -12.9264119], [40.4069782, -12.9264026], [40.4069712, -12.9263898], [40.4069616, -12.9263804], [40.406952, -12.9263711], [40.4069398, -12.9263652], [40.4069302, -12.9263558], [40.4069205, -12.9263463], [40.4069082, -12.9263404], [40.406896, -12.9263345], [40.4068862, -12.926325], [40.4068814, -12.9263082], [40.4068893, -12.9263007], [40.4069037, -12.9262997], [40.4069146, -12.926292], [40.4069254, -12.9262843], [40.4069367, -12.92628], [40.4069472, -12.9262688], [40.4069458, -12.926255], [40.4069356, -12.9262417], [40.4069257, -12.9262319], [40.4069184, -12.9262183], [40.4069114, -12.9262081], [40.4068988, -12.9262018], [40.4068917, -12.9261914], [40.4068787, -12.9261814], [40.4068636, -12.9261788], [40.406853, -12.926165], [40.4068454, -12.9261509], [40.4068347, -12.9261369], [40.4068245, -12.9261266], [40.4068117, -12.92612], [40.4068013, -12.9261096], [40.4067983, -12.9261098]]]}", + "est_area": 2 + }, + { + "geojson": "{\"type\": \"Polygon\", \"coordinates\": [[[40.4054372, -12.9235332], [40.405487, -12.9235331], [40.4055256, -12.9235219], [40.4055534, -12.9235], [40.4055902, -12.9234671], [40.4056281, -12.923445], [40.4056651, -12.9234121], [40.405735, -12.923412], [40.405775, -12.9234119], [40.405835, -12.9234119], [40.4058849, -12.9234118], [40.4059258, -12.9234227], [40.4059491, -12.9234662], [40.4059542, -12.9235314], [40.4059658, -12.923553], [40.4059882, -12.9235854], [40.4060336, -12.9236608], [40.4060378, -12.9237149], [40.4060404, -12.9237472], [40.4060248, -12.9238008], [40.4060002, -12.9238649], [40.4059946, -12.9239182], [40.4059981, -12.9239608], [40.4059908, -12.9239926], [40.4059844, -12.9240348], [40.40597, -12.9241073], [40.4059624, -12.9241382], [40.4059669, -12.9242], [40.4059698, -12.9242411], [40.405963, -12.924282], [40.405966, -12.9243228], [40.4059697, -12.9243738], [40.4059741, -12.9244347], [40.405977, -12.9244752], [40.4059807, -12.9245257], [40.405985, -12.9245861], [40.4059879, -12.9246262], [40.4059923, -12.9246862], [40.4059959, -12.9247361], [40.4060091, -12.9247859], [40.4060305, -12.9248157], [40.4060539, -12.9248751], [40.4060663, -12.9249146], [40.4060691, -12.924954], [40.4060814, -12.9249933], [40.4060935, -12.9250323], [40.4061056, -12.9250712], [40.4061272, -12.92511], [40.4061291, -12.9251391], [40.4061519, -12.925197], [40.4061923, -12.9252355], [40.4062325, -12.925274], [40.4062538, -12.9253123], [40.4062751, -12.9253506], [40.4063057, -12.9253888], [40.406327, -12.925427], [40.4063578, -12.9254655], [40.4063799, -12.9255134], [40.4063918, -12.9255516], [40.4064132, -12.9255898], [40.4064238, -12.9256089], [40.4064463, -12.9256659], [40.4064586, -12.9257131], [40.4064704, -12.9257509], [40.4064816, -12.9257791], [40.4064933, -12.9258167], [40.4065149, -12.9258636], [40.4065172, -12.9259009], [40.40652, -12.9259473], [40.4065222, -12.9259843], [40.406496, -12.9260119], [40.4064699, -12.9260395], [40.4064444, -12.9260762], [40.4064086, -12.9260945], [40.4063728, -12.9261128], [40.4063464, -12.9261312], [40.4063201, -12.9261496], [40.4062741, -12.9261497], [40.406238, -12.9261589], [40.4062012, -12.9261589], [40.4061652, -12.9261681], [40.40611, -12.9261682], [40.4060733, -12.9261682], [40.4060472, -12.9261866], [40.4060012, -12.9261866], [40.4059553, -12.9261867], [40.4059362, -12.9261775], [40.4058963, -12.9261408], [40.4058563, -12.926104], [40.4058171, -12.9260764], [40.4057955, -12.9260395], [40.4057654, -12.9260118], [40.4056975, -12.9259748], [40.4056673, -12.925947], [40.4056261, -12.9259005], [40.4055957, -12.9258726], [40.4055729, -12.9258259], [40.4055619, -12.9258071], [40.4055288, -12.9257508], [40.4055235, -12.9256943], [40.4055192, -12.925647], [40.405525, -12.9256091], [40.4055402, -12.9255711], [40.4055453, -12.9255234], [40.4055513, -12.9254854], [40.4055472, -12.9254377], [40.4055447, -12.925409], [40.4055414, -12.9253707], [40.4055364, -12.9253131], [40.405533, -12.9252745], [40.4055194, -12.9252263], [40.4054989, -12.9252071], [40.4054569, -12.9251588], [40.4054259, -12.9251298], [40.4053855, -12.9251008], [40.4053734, -12.9250716], [40.4053509, -12.9250327], [40.405318, -12.9249839], [40.4053144, -12.9249446], [40.4053012, -12.9249053], [40.4052939, -12.9248263], [40.4052789, -12.9247668], [40.4052561, -12.9247272], [40.4052523, -12.9246873], [40.405239, -12.9246474], [40.4052152, -12.9245975], [40.4052018, -12.9245574], [40.4051893, -12.9245272], [40.4051749, -12.9244769], [40.4051903, -12.9244363], [40.4052164, -12.9244057], [40.4052434, -12.9243852], [40.4052792, -12.9243545], [40.405317, -12.924344], [40.405352, -12.924303], [40.4053677, -12.924262], [40.4053834, -12.9242209], [40.4053895, -12.9241798], [40.4053956, -12.9241386], [40.4054017, -12.9240972], [40.4053981, -12.9240559], [40.405404, -12.924014], [40.4054097, -12.9239719], [40.4054057, -12.9239297], [40.4054017, -12.9238874], [40.4053977, -12.923845], [40.4053936, -12.9238024], [40.4053896, -12.9237598], [40.4053845, -12.9237063], [40.4053835, -12.9236956], [40.4054059, -12.9236202], [40.4054216, -12.9235768], [40.4054372, -12.9235332]]]}", + "est_area": 1 + } + ] + } + ] + } +] diff --git a/database/seeders/PolygonValidationSeeder.php b/database/seeders/PolygonValidationSeeder.php index 419b51cc6..fbed7f03a 100644 --- a/database/seeders/PolygonValidationSeeder.php +++ b/database/seeders/PolygonValidationSeeder.php @@ -10,101 +10,12 @@ class PolygonValidationSeeder extends Seeder { - public const TEST_PROJECTS = [ - // Used for within country - [ - 'country' => 'AU', - 'sites' => [ - [ - 'uuid' => 'bc5d87ab-4c98-42f1-9902-b2848bb466b7', - ] - ] - ], - - // Used for overlapping - [ - 'country' => 'GH', - 'sites' => [ - [ - 'uuid' => '8d86e97a-3d4e-4132-95e1-435225d47f28', - 'geometry' => [ - [ - 'geojson' => '{"type": "Polygon", "coordinates": [[[-2.27521108782139, 4.909556846471332], [-2.275458274579648, 4.909575790690212], [-2.275572360775755, 4.909518957134196], [-2.275572360775755, 4.909320038788792], [-2.275534332043719, 4.909140064662324], [-2.275581867509118, 4.908979035653999], [-2.275724475703953, 4.908884311861527], [-2.275981170094894, 4.90894114631692], [-2.276275892318552, 4.908884311861527], [-2.276437514879376, 4.9089506179767], [-2.276504064710764, 4.909073758547152], [-2.276675194904271, 4.909121120443388], [-2.276760759101705, 4.909016924991136], [-2.27692238166253, 4.908780116409275], [-2.27702696022601, 4.908675920957023], [-2.277084003324035, 4.908732755412359], [-2.277093510956718, 4.908931673757763], [-2.277074496590672, 4.909177953999404], [-2.277179075154152, 4.909386344903965], [-2.277435769545093, 4.909509484575096], [-2.277701970669341, 4.909509484575096], [-2.277987187059011, 4.909537901353133], [-2.277968172692965, 4.909367399785708], [-2.278006201425001, 4.909035869210072], [-2.277911128695564, 4.908799061527532], [-2.277835071231493, 4.908514891948755], [-2.277949157427599, 4.908448585833639], [-2.278101273255118, 4.908533836167635], [-2.278158316353142, 4.908666449297243], [-2.278405503111458, 4.908685393516123], [-2.278481560575528, 4.908581198063871], [-2.278538603673553, 4.908401223937403], [-2.278576632405588, 4.908259139148015], [-2.278614661137681, 4.908145471136606], [-2.278700226234378, 4.908079165021491], [-2.278823819163904, 4.908012858906375], [-2.27883332679653, 4.908126526018407], [-2.278899876627975, 4.908278083366895], [-2.278890369894611, 4.908382278819147], [-2.279004456090718, 4.908514891948755], [-2.279080513554788, 4.908609614841907], [-2.279185092118269, 4.908619087401007], [-2.279318192680421, 4.908685393516123], [-2.279375236677765, 4.908874840201747], [-2.279356222311776, 4.909016924991136], [-2.27944178650921, 4.909035869210072], [-2.279593901437352, 4.9089506179767], [-2.279660452168059, 4.909026396650916], [-2.279879116927646, 4.90918742655856], [-2.279964682024399, 4.909367399785708], [-2.279997956940122, 4.909576738575652], [-2.279855349644606, 4.909586210235489], [-2.279608161987028, 4.90971882246572], [-2.279494075790922, 4.909936685929381], [-2.27954161215564, 4.910145076833885], [-2.279617669619711, 4.910381883617106], [-2.279560626521629, 4.910618691299589], [-2.279503583423605, 4.910789192867014], [-2.279199353567265, 4.91085549808281], [-2.278971180275676, 4.911016527091078], [-2.278771529882135, 4.911224917995639], [-2.278562371855912, 4.911348057666771], [-2.27842927129376, 4.911565920231055], [-2.278420349119756, 4.911593476357893], [-2.27521108782139, 4.909556846471332]]]}', - ], - [ - 'geojson' => '{"type": "Polygon", "coordinates": [[[-2.273037249269578, 4.908108314746983], [-2.273165733611677, 4.908453105826197], [-2.273179740552564, 4.908740329502677], [-2.27310784245293, 4.909027459649565], [-2.273021594770739, 4.909343295256917], [-2.272806396897636, 4.909745153715903], [-2.272591494002199, 4.909874163262089], [-2.27237662258301, 4.909974452059373], [-2.272261941935028, 4.910103570423473], [-2.27230464534307, 4.910333384078456], [-2.27260486242443, 4.910750164088824], [-2.27244699633178, 4.911094642204034], [-2.272231672553573, 4.911611382759133], [-2.27213115352987, 4.911884121954699], [-2.272087920421143, 4.912142561929727], [-2.272130608540692, 4.91238673595916], [-2.272216279757458, 4.912602236004432], [-2.272373477653844, 4.912875255788492], [-2.272315927338127, 4.913133681374347], [-2.272244089493086, 4.913363369124227], [-2.27181420366253, 4.913693190088679], [-2.271241296848302, 4.913879249927504], [-2.270854549697674, 4.914036792264199], [-2.270539453333072, 4.914136970444872], [-2.270081274830943, 4.914150828997606], [-2.269809161563501, 4.914222333194175], [-2.269350983061372, 4.914236191746909], [-2.26892147044947, 4.914221361027046], [-2.268692420318871, 4.914192388467995], [-2.268434828404565, 4.914077223085428], [-2.268105649556048, 4.913961978562497], [-2.267848105305745, 4.913803731157316], [-2.267662148889031, 4.913645561993121], [-2.267504906026545, 4.913415623332412], [-2.267390631872104, 4.913171370162672], [-2.267347898786454, 4.912970277256534], [-2.267477054922097, 4.912697571335912], [-2.26752016392436, 4.912554014356317], [-2.267649257107507, 4.912338749933383], [-2.267821209281351, 4.912209695421154], [-2.268107748573698, 4.912037684791358], [-2.268193794807758, 4.911908534950953], [-2.26836568312973, 4.911836921936469], [-2.268509078231318, 4.911636033176421], [-2.268738345997804, 4.91146395959413], [-2.26885310578615, 4.911263039357834], [-2.269010770430668, 4.911105247009573], [-2.2691398159497, 4.91093306370999], [-2.26932600619017, 4.910875825459016], [-2.269612529294761, 4.91071817430435], [-2.269755735538695, 4.910689610936856], [-2.269927452090201, 4.910775961141724], [-2.269884359275693, 4.910905157746868], [-2.269769647151463, 4.911062996859869], [-2.269669283710414, 4.911192130512461], [-2.269583440723181, 4.911134595485237], [-2.269569279998223, 4.910990975553148], [-2.269425979325433, 4.91110570206655], [-2.269454411391905, 4.911292418410426], [-2.269353859992577, 4.911593877455516], [-2.26932503762032, 4.911766170472447], [-2.26956829524056, 4.911895680940972], [-2.269740230327329, 4.911780985903818], [-2.26978327727636, 4.911694870421968], [-2.269869213793129, 4.911666244101923], [-2.269940708097181, 4.911752484589556], [-2.26996926427006, 4.911824317937999], [-2.270069347122615, 4.911953671025174], [-2.270312745037131, 4.911953937224496], [-2.270341443302925, 4.911896527202998], [-2.270484727787903, 4.911796161064046], [-2.270628043749184, 4.911667073276874], [-2.270699584817919, 4.911710233540475], [-2.270871379610412, 4.911724780973884], [-2.271057601327186, 4.911638821974122], [-2.271157871238699, 4.911595850568119], [-2.27115802772073, 4.911452246823785], [-2.271172438457199, 4.911366099865688], [-2.271172578751475, 4.911236855596485], [-2.271172781098926, 4.911050170728856], [-2.271258842621421, 4.910906660514001], [-2.2713020442539, 4.910676940388555], [-2.27134502825038, 4.910648266404394], [-2.271402485935937, 4.910476003964448], [-2.271488515082865, 4.910361214498494], [-2.271560258499107, 4.910217688995203], [-2.271674970623337, 4.910059848982883], [-2.271804015243049, 4.909887664783923], [-2.271947439122982, 4.909658054375768], [-2.271990610178477, 4.909457054999109], [-2.272091050061874, 4.909256118575058], [-2.272162887007596, 4.909026429925859], [-2.272248932342279, 4.908897280085455], [-2.272292087210019, 4.908710641083246], [-2.272220701723938, 4.908523877974631], [-2.272249429667397, 4.908437747204346], [-2.272106363717683, 4.908337068101275], [-2.272077993704386, 4.908092909360334], [-2.272121101807329, 4.90794935238074], [-2.272235828320788, 4.907777151994026], [-2.272364825276441, 4.907648048918361], [-2.272465079000142, 4.907619436987488], [-2.272636779363836, 4.907720147566806], [-2.272736924269566, 4.907792058256916], [-2.272851355805358, 4.907892705883739], [-2.273037249269578, 4.908108314746983]]]}', - ], - [ - 'geojson' => '{"type": "Polygon", "coordinates": [[[-2.277179075154152, 4.911432361014704], [-2.2783341994637, 4.911859560570292], [-2.278514835491194, 4.912058478016377], [-2.278657443686029, 4.912191090246665], [-2.278828572980217, 4.912339804838155], [-2.278904630444288, 4.912425055172207], [-2.27903773100644, 4.912491360388003], [-2.279180339201218, 4.912576611621375], [-2.279256396665346, 4.912690278733407], [-2.279313439763371, 4.912927084617309], [-2.279265903398652, 4.913220724956545], [-2.279284917764699, 4.913400697284374], [-2.279370482861452, 4.913590143070678], [-2.279513090156911, 4.913684865064511], [-2.279703233817145, 4.913703810182767], [-2.2797412634485, 4.913864838291715], [-2.279731755815817, 4.9140542831787], [-2.279674712717792, 4.914167949391413], [-2.279665205085109, 4.914400966431515], [-2.279655698351746, 4.914561994540463], [-2.279750770181863, 4.914647244874516], [-2.279931407108677, 4.914751439427448], [-2.280093028770239, 4.914760911986548], [-2.280235636965017, 4.914647244874516], [-2.280378244260532, 4.914486216765567], [-2.280539866821357, 4.914419911549714], [-2.280758532480263, 4.914561994540463], [-2.280882125409732, 4.914656717433616], [-2.281091283435956, 4.914770383646328], [-2.281224384897371, 4.914817745542564], [-2.281386006558932, 4.914912467536396], [-2.281309949094805, 4.915035606308209], [-2.281262413629463, 4.91527241219211], [-2.281157834166663, 4.915433440301058], [-2.281062762336546, 4.915622884288723], [-2.28094867614044, 4.915746023060592], [-2.280853604310323, 4.915831273394588], [-2.280625431918111, 4.915954412166457], [-2.280615924285428, 4.916087023497369], [-2.280796561212298, 4.916238579047217], [-2.280910647408405, 4.916352245259986], [-2.281119805434628, 4.916399606256846], [-2.281338471093534, 4.916446967253762], [-2.281443049656957, 4.916579578584731], [-2.281576150219109, 4.91672166157548], [-2.281794815878015, 4.916759550013296], [-2.281927916440168, 4.916835327788192], [-2.282241653929134, 4.916769022572396], [-2.282431797589368, 4.916854272007072], [-2.282270175028486, 4.916996354997821], [-2.282194117564416, 4.917138437988569], [-2.282194117564416, 4.917318410316454], [-2.282089539000992, 4.917451020748047], [-2.281918409706805, 4.917356298754214], [-2.281817633476976, 4.9172663125903], [-2.28149438835527, 4.917218951593384], [-2.28129473796173, 4.917370507143232], [-2.281237694863705, 4.917531534352861], [-2.281228187231022, 4.917711505781426], [-2.281323259061082, 4.917910422328191], [-2.281209172864976, 4.9180809211976], [-2.281228187231022, 4.918260892626165], [-2.281323259061082, 4.918535586048506], [-2.281152129766951, 4.918687140699035], [-2.281161637399578, 4.918819752030004], [-2.281123608667542, 4.919056556115208], [-2.280981000472764, 4.919236527543774], [-2.280762334813858, 4.91929336020047], [-2.280600713152353, 4.919558581063711], [-2.280420076225482, 4.919757496711156], [-2.280125353102505, 4.919681718936204], [-2.279964682024399, 4.919584155984069], [-2.279774538364222, 4.919726238075498], [-2.279660452168059, 4.91984937594799], [-2.279422772143164, 4.91983990428821], [-2.279299178314375, 4.919820960069273], [-2.279090020288152, 4.919934625382723], [-2.279118542286824, 4.920038819935655], [-2.278928398626647, 4.920114596811231], [-2.278776283698505, 4.920029347376556], [-2.278519589307564, 4.920161957808148], [-2.278548110406916, 4.920322984118457], [-2.278510081674881, 4.920502955547022], [-2.278300923648658, 4.920427177772069], [-2.278205851818541, 4.920569259863498], [-2.278082258889071, 4.920663981857331], [-2.277759013767422, 4.920749231292064], [-2.277597392105918, 4.920787119729823], [-2.277445277177776, 4.920720814514027], [-2.277245625884859, 4.920654509298231], [-2.277074496590672, 4.920493482987922], [-2.276827309832413, 4.92039876099409], [-2.276646672905599, 4.920484010428765], [-2.276429767219952, 4.920042248151276], [-2.276392082928226, 4.919582673002026], [-2.276087299089511, 4.918969629444916], [-2.275897182408983, 4.918241826998781], [-2.276050276698868, 4.917897342588333], [-2.276489436338352, 4.917821231164908], [-2.276757133934552, 4.917419429363235], [-2.276872212082878, 4.916921724757856], [-2.27696797279367, 4.916634619791921], [-2.277292858278713, 4.916309469006819], [-2.277789162639692, 4.916348301732796], [-2.278285405846759, 4.916444575057142], [-2.278819891824526, 4.916483448252563], [-2.279297312221161, 4.916330786536605], [-2.279393051348165, 4.916062828137058], [-2.279240843789864, 4.915583982314956], [-2.279279312290384, 4.915315961862177], [-2.279165306133905, 4.914818009943247], [-2.278994050035351, 4.914300847606057], [-2.278994503293632, 4.913879608756986], [-2.278708541366029, 4.9135155011395], [-2.277906589817235, 4.913667811220535], [-2.277505490386034, 4.913858849706173], [-2.277199759561199, 4.914126579677941], [-2.277084784834926, 4.914528547854275], [-2.276740811239392, 4.914853677954909], [-2.276701886782575, 4.915542937256816], [-2.276205148948407, 4.91590619591426], [-2.275842311173676, 4.916020685906005], [-2.275708099948815, 4.916556662959692], [-2.275478458963676, 4.917073389125676], [-2.275172766809703, 4.917302824166029], [-2.274548559068705, 4.917291881215419], [-2.274574106110038, 4.917251157215162], [-2.274574106110038, 4.91713749100245], [-2.274697699039507, 4.91715643522133], [-2.274773756503635, 4.917080657446434], [-2.274849813967705, 4.917014352230638], [-2.274821292868296, 4.916853325021009], [-2.274764249770271, 4.916777547246056], [-2.274574106110038, 4.916673352693124], [-2.274412483549213, 4.916588103258391], [-2.274460019913931, 4.916512325483495], [-2.274631149208119, 4.916398659270783], [-2.274726221038236, 4.916294464717794], [-2.274802278502307, 4.916218686942898], [-2.274887842699741, 4.916048187174169], [-2.274992422162484, 4.915953465180337], [-2.275201580188707, 4.915962936840117], [-2.27541073821493, 4.915887159065164], [-2.275477288945694, 4.915735604414635], [-2.275581867509118, 4.915593520524624], [-2.275638910607199, 4.915375659758922], [-2.275638910607199, 4.915176743212157], [-2.275638910607199, 4.915044131881245], [-2.275638910607199, 4.914949408988093], [-2.275467781313012, 4.914911520550277], [-2.275353695116905, 4.914835742775381], [-2.275401231481624, 4.914712603104192], [-2.275543838777082, 4.914655770447496], [-2.275676939339235, 4.914693658885312], [-2.275667432605871, 4.914485269779448], [-2.275648418239882, 4.914371602667359], [-2.275762504435988, 4.914267408114426], [-2.275886098264778, 4.914257936454646], [-2.276000184460884, 4.91422004711751], [-2.276123777390353, 4.914153741002394], [-2.276285399951178, 4.914134796783515], [-2.276408992880647, 4.914077963227442], [-2.276551601075482, 4.91396429611541], [-2.276608644173564, 4.913888518340514], [-2.276684701637635, 4.913793796346681], [-2.276817802199787, 4.913755907009545], [-2.276789281100378, 4.913651712456613], [-2.276827309832413, 4.913509628566544], [-2.276865338564448, 4.913367544676476], [-2.277007945859964, 4.91329176690158], [-2.277131539688753, 4.913367544676476], [-2.277064988958045, 4.913481211788564], [-2.276941396028576, 4.913575934681717], [-2.276979424760611, 4.913708546012629], [-2.277150554054799, 4.913661185015712], [-2.27722661151887, 4.913727490231565], [-2.277331190981613, 4.913594878900597], [-2.277350205347659, 4.913490684347664], [-2.277464291543765, 4.913424378232492], [-2.277635420837953, 4.913443322451428], [-2.277682956303352, 4.913367544676476], [-2.277806550132141, 4.913310711120459], [-2.277920636328247, 4.913253877564443], [-2.277806550132141, 4.913111793674375], [-2.277920636328247, 4.913026543340379], [-2.278006201425001, 4.912874987790531], [-2.278006201425001, 4.912730062042783], [-2.277939650694293, 4.912663755927667], [-2.277844578864176, 4.912578505593615], [-2.277920636328247, 4.912502727818662], [-2.277996693792318, 4.91236064392865], [-2.278015708158364, 4.912265921035498], [-2.277911128695564, 4.912256448476342], [-2.277844578864176, 4.912332227150614], [-2.277759013767422, 4.91239853236641], [-2.277682956303352, 4.912303809473258], [-2.277701970669341, 4.912237504257462], [-2.277739999401376, 4.91211436458633], [-2.277587884473235, 4.91213330880521], [-2.277454783911082, 4.912142781364309], [-2.277293162249578, 4.912095420367393], [-2.277074496590672, 4.912010169134021], [-2.277112525322764, 4.911858613584172], [-2.277179075154152, 4.911716528794841], [-2.277274147883588, 4.911602861682752], [-2.277302668982941, 4.911517611348756], [-2.277179075154152, 4.911432361014704]]]}' - ], - ], - ], - [ - 'uuid' => 'c1e2d5a6-288a-479f-977d-531ca5b52933', - 'geometry' => [ - [ - 'geojson' => '{"type": "Polygon", "coordinates": [[[-2.407034725072435, 4.966373290195236], [-2.406825572442187, 4.96646313786357], [-2.406527084757101, 4.96655107717055], [-2.40646541284849, 4.966550920688462], [-2.406418320748855, 4.966583120914322], [-2.406354637056779, 4.966599121652166], [-2.406274894170963, 4.966615083719205], [-2.406211697911488, 4.966647264159974], [-2.406164037440305, 4.966663315259837], [-2.406084259480963, 4.966679285420753], [-2.405988114759623, 4.966679190991897], [-2.405925446402193, 4.966711966783862], [-2.405862278921006, 4.966728569168197], [-2.405814544705436, 4.966728881232939], [-2.405735817154209, 4.966761750554383], [-2.405670963444152, 4.966761977183523], [-2.40559161626004, 4.96674599802941], [-2.40541747243833, 4.966521707111156], [-2.40529831766321, 4.96633012273702], [-2.405298542493711, 4.966078808989892], [-2.405274956873711, 4.965767637266708], [-2.405180795157548, 4.9653899615775], [-2.405194353336697, 4.965310467803874], [-2.405192444075965, 4.965246876742015], [-2.405174486413387, 4.965183174164224], [-2.405141153941031, 4.965135346419174], [-2.405076068205915, 4.965087407158137], [-2.405011061611162, 4.96503952545379], [-2.404962473938951, 4.965007609413703], [-2.40488127684938, 4.964943930218226], [-2.404863579090829, 4.96488064402655], [-2.404830069452089, 4.964817365928752], [-2.404812879810493, 4.964769997737278], [-2.404794808833287, 4.964691194642967], [-2.404793461648865, 4.964644011711812], [-2.404743845152268, 4.964565279663987], [-2.404726302077108, 4.96450247550888], [-2.404692996584458, 4.964439682145667], [-2.404644317181464, 4.964392507308389], [-2.404580847528052, 4.964376630677009], [-2.404516979474977, 4.964345122029783], [-2.404453170777174, 4.964313643060223], [-2.404420018169276, 4.964251067332953], [-2.404418325645167, 4.964188675067362], [-2.404416214936305, 4.96411079647612], [-2.40444590065772, 4.964048668611213], [-2.404475984778855, 4.964002136789247], [-2.404521284529608, 4.96394016720501], [-2.404566534817661, 4.963878262371964], [-2.40459652630858, 4.963831869045578], [-2.404641695657688, 4.963770079325741], [-2.404703401740562, 4.963739285639576], [-2.404765086239706, 4.963708500946609], [-2.404859043809779, 4.963708678113051], [-2.404922206794367, 4.963724250773623], [-2.405002116054789, 4.963770806877278], [-2.405051260407276, 4.963832852903863], [-2.405245799953661, 4.964004109901794], [-2.405294709583131, 4.964050934902787], [-2.405328484521874, 4.964113365839182], [-2.405378082132756, 4.964175933472575], [-2.40539559822821, 4.964222858298342], [-2.405352835464953, 4.964347932411215], [-2.405323511270979, 4.964410551305946], [-2.405294082755688, 4.96447320527426], [-2.405249216478126, 4.964551525432626], [-2.405251283120151, 4.964614403332121], [-2.405268676008518, 4.964661675296099], [-2.405335147598919, 4.964756442256032], [-2.405398787224215, 4.964772291008444], [-2.405460813465709, 4.964740819233441], [-2.40552336401197, 4.964725124265101], [-2.405569005505129, 4.96467790446178], [-2.405630372543612, 4.964630737718437], [-2.405660750742982, 4.964599304614239], [-2.405722041339061, 4.964552200823448], [-2.405767535343443, 4.964505118616387], [-2.405828735108003, 4.964458090368623], [-2.405890557203406, 4.964426803853939], [-2.405952953965595, 4.964411201515759], [-2.406014823725116, 4.964379974356405], [-2.406076431781855, 4.964333249180129], [-2.406138009261667, 4.96428654738628], [-2.406168484587852, 4.964255393971314], [-2.406229401965277, 4.96419312581213], [-2.406273832970953, 4.964115267905356], [-2.406287198695225, 4.964052987155696], [-2.406284743546053, 4.963990730687726], [-2.406282291094783, 4.963928539870267], [-2.406296244076373, 4.963881977471317], [-2.406279228003939, 4.96385089690142], [-2.406178637034429, 4.963711115275771], [-2.405954633899171, 4.963602635453299], [-2.405874890114035, 4.963571643916282], [-2.405810381743663, 4.963525287462119], [-2.405746480415701, 4.963494382259967], [-2.405683161848401, 4.963478896833692], [-2.40563556612841, 4.963463441084969], [-2.405571354534345, 4.963432508003905], [-2.405506501723607, 4.963386207307678], [-2.405426006105245, 4.963339943483675], [-2.405409108743299, 4.963309209152726], [-2.40531198286169, 4.96323239356019], [-2.405279079365982, 4.963186444498888], [-2.405245602102809, 4.963125256425485], [-2.405227151611712, 4.963048898587829], [-2.405209508711835, 4.962987920955811], [-2.40517628685609, 4.962927017967559], [-2.405173793935376, 4.962851005469588], [-2.405171802836378, 4.962790276050441], [-2.40516981353602, 4.962729615879141], [-2.405214522432232, 4.962669000673884], [-2.40526021338809, 4.962638696218903], [-2.405321463514667, 4.962608389065963], [-2.405367780398706, 4.962593220200972], [-2.405499865626268, 4.96279021309789], [-2.405533955327712, 4.962866229193196], [-2.405583232779918, 4.962927142973342], [-2.405617449285785, 4.963003400086905], [-2.405651151379459, 4.963064504523288], [-2.405669947210242, 4.96314100355454], [-2.405703926295132, 4.963202324727547], [-2.405751816093414, 4.963217727416236], [-2.405830623684267, 4.963217845227405], [-2.405878383080847, 4.963233238023577], [-2.405957330066656, 4.963248639812946], [-2.406191967685118, 4.963279303996728], [-2.406254715182911, 4.963294634739668], [-2.406317478868516, 4.963309969979207], [-2.406395183890595, 4.963309939402222], [-2.406472267481092, 4.963294548404747], [-2.406535112105701, 4.963294605062003], [-2.406578970243231, 4.963233157983893], [-2.406622778018743, 4.963171781052893], [-2.406619224797339, 4.963095177700325], [-2.406585192652472, 4.963033977036446], [-2.4065664579756, 4.96297283302988], [-2.406546944485854, 4.962896518359685], [-2.406512794529817, 4.962835565908676], [-2.406478692237783, 4.962774695295991], [-2.406415299026776, 4.962744309002687], [-2.406352717903587, 4.962729137439794], [-2.406290677272864, 4.96271396857486], [-2.406242075211537, 4.962653247249591], [-2.406208213937873, 4.962577428105874], [-2.406175030753047, 4.962516846175561], [-2.406142508569872, 4.962471450197313], [-2.406078773616457, 4.962410977084971], [-2.406045229803453, 4.962350608294003], [-2.405996544105165, 4.962290303354848], [-2.405932569932133, 4.962260327152421], [-2.405868671302073, 4.96223038692284], [-2.405790607450513, 4.962230568585937], [-2.405729530893097, 4.962260776813423], [-2.405668444443108, 4.962290992235523], [-2.405607993813817, 4.962336265006684], [-2.40556183251249, 4.962351410489248], [-2.40550128385712, 4.962396716535295], [-2.405441037373919, 4.962457150077455], [-2.405379166715136, 4.962472331532922], [-2.405315760014275, 4.962442249210483], [-2.405267716431922, 4.96239708885463], [-2.405219957934662, 4.962351950082507], [-2.405187329631531, 4.962291783638932], [-2.405169785657051, 4.962216635389439], [-2.405136812913554, 4.962141610347089], [-2.405119732089929, 4.962081645351702], [-2.405087236886402, 4.962021773885795], [-2.405085220606395, 4.961946943097018], [-2.40508385723416, 4.961887079724988], [-2.405082234857218, 4.961812296600272], [-2.405080614278859, 4.961737586320623], [-2.405094023170591, 4.961663024429072], [-2.405139519872876, 4.961648087589197], [-2.405200773596789, 4.961648010247529], [-2.405277812221186, 4.961662796900612], [-2.405339446358312, 4.961677640210951], [-2.405385396318934, 4.961677588949613], [-2.405477334911041, 4.961677475635042], [-2.405539218160357, 4.961692270382002], [-2.405616400676308, 4.961707066927659], [-2.405678203885941, 4.961721906640719], [-2.405755848653428, 4.961751657113325], [-2.405817215691911, 4.961751584268256], [-2.405879580977853, 4.961781370713709], [-2.405958851719561, 4.961825879960429], [-2.40602205607297, 4.961855562084565], [-2.406068114851564, 4.961870586158682], [-2.406130722954458, 4.961915504596902], [-2.406194095481055, 4.96197539854586], [-2.406271915616344, 4.96202041141288], [-2.406303652691349, 4.96205039930652], [-2.406352520052735, 4.962110327429741], [-2.406415219886469, 4.9621403207193], [-2.40649498075868, 4.962200340573304], [-2.406544328357995, 4.962260426977195], [-2.406592831493924, 4.962305547762867], [-2.406627029114077, 4.962365774460977], [-2.406659294091128, 4.962395932326444], [-2.40670853736907, 4.96245630291611], [-2.406757066585328, 4.962501626049232], [-2.406820157624111, 4.962531857659144], [-2.406870479189195, 4.96260754280388], [-2.406905502386962, 4.962683356551736], [-2.406939717993566, 4.96274409406476], [-2.406958578575484, 4.962804917013329], [-2.406992871523755, 4.962865811907761], [-2.407042627415251, 4.962926776949246], [-2.407091555031172, 4.962972544347508], [-2.407155056160889, 4.963003061941777], [-2.407234022931789, 4.963033585831397], [-2.407296673302767, 4.963048839232613], [-2.40736027695516, 4.963079399994342], [-2.407422967795696, 4.963094665986091], [-2.40750111348558, 4.963109923883906], [-2.407563834902987, 4.963125196170893], [-2.407625589549298, 4.963125152104112], [-2.407719207175603, 4.963140404606008], [-2.407898268490271, 4.963278364205166], [-2.407995194722389, 4.963339788800283], [-2.408058111292746, 4.963355121341806], [-2.408107306906629, 4.963401229583155], [-2.408125809558442, 4.963447429555288], [-2.408147523689195, 4.963539994075518], [-2.408167141500314, 4.963601790090593], [-2.408173766805874, 4.963694725131518], [-2.408194816337641, 4.963772295255296], [-2.408229465417435, 4.963834273832731], [-2.408265092060276, 4.963911858345625], [-2.408269799111906, 4.963989620025018], [-2.408290007777566, 4.964067456348062], [-2.408293793024086, 4.964129835123856], [-2.408314091622003, 4.964207880089646], [-2.408318225805431, 4.964270498984376], [-2.408335902879571, 4.964301813378029], [-2.408401469751993, 4.96434875708951], [-2.408478467906946, 4.964332942511362], [-2.408539828650134, 4.964317161208044], [-2.408584484486312, 4.964285741593756], [-2.408660292838192, 4.964254296798401], [-2.408719363907494, 4.964207255060842], [-2.408780585255784, 4.96419152142164], [-2.408839549305696, 4.964144547133174], [-2.408900702305516, 4.964128838675038], [-2.408960696079191, 4.964097531475943], [-2.409020644886823, 4.964066247659275], [-2.409063856411819, 4.9640194280542], [-2.40910586734185, 4.963957096942522], [-2.409148982639408, 4.963910388853435], [-2.409211082625291, 4.963910307015112], [-2.409262368263626, 4.963972444772537], [-2.409329256240142, 4.964034634690677], [-2.409379471685213, 4.96408132389405], [-2.40933521334938, 4.964112587925626], [-2.409229858670983, 4.964159582898503], [-2.409167604001652, 4.964159677327302], [-2.409093240860329, 4.964206678595417], [-2.409049963684822, 4.964253680762795], [-2.409007787279506, 4.96431640038162], [-2.408965554216934, 4.964379202738087], [-2.408954493455042, 4.964442036570802], [-2.408942052233897, 4.964489165542602], [-2.40886705327182, 4.964536441103917], [-2.408823257187521, 4.964583706772714], [-2.408780445860828, 4.964646769033209], [-2.40873554001314, 4.964678373907873], [-2.408671910280361, 4.964662731100191], [-2.408608292238853, 4.964647090990525], [-2.408530024241202, 4.964647238479301], [-2.408467403547775, 4.964647356290527], [-2.408389117563672, 4.964647502879984], [-2.408343106449138, 4.964663351632396], [-2.408249131791933, 4.964663529698157], [-2.408172687619412, 4.964695216411144], [-2.40809618948748, 4.96472692830514], [-2.408036095889088, 4.964774380133576], [-2.407990650448085, 4.96480600928993], [-2.407914549816553, 4.964853490796031], [-2.407853260119794, 4.964885173012362], [-2.407823494358752, 4.964916860624669], [-2.407731452344592, 4.964964447351406], [-2.407670918078338, 4.965012062856488], [-2.407610328953467, 4.965059720629654], [-2.407581304233759, 4.965107408979804], [-2.407520340091594, 4.965155031679444], [-2.407458348923626, 4.965186710298497], [-2.40738176265819, 4.965250090919028], [-2.40732081650242, 4.965297895281708], [-2.407290027312854, 4.965313887026298], [-2.407212109151487, 4.965330091010912], [-2.407118906112601, 4.965362181519538], [-2.407057091211755, 4.965394155116201], [-2.406978807026292, 4.965410264672016], [-2.406916921978336, 4.965442267946344], [-2.406870168923092, 4.965458314549608], [-2.406792420733609, 4.965490387071782], [-2.406729821623912, 4.965506490332302], [-2.406684744005702, 4.965570378170469], [-2.406687818787759, 4.965650259551865], [-2.406690282030866, 4.965714236422912], [-2.406724425691664, 4.965778188112949], [-2.406772358657406, 4.96579394963112], [-2.406900366358798, 4.965841295339544], [-2.407027872238473, 4.965872691571519], [-2.407136305296206, 4.965824056235249], [-2.407198140881462, 4.965791782265001], [-2.407262405535562, 4.965823406924756], [-2.407280883905628, 4.965887396386279], [-2.407316914344051, 4.965968036795516], [-2.407322320168873, 4.966065176166978], [-2.407325929148271, 4.966130039769553], [-2.407280599719854, 4.966178633737002], [-2.407218326164752, 4.966210979653056], [-2.407156893475815, 4.966259606895392], [-2.407109697954127, 4.966275763215947], [-2.407065076292213, 4.966340773408035], [-2.407034725072435, 4.966373290195236]]]}', - ] - ], - ], - ], - ], - - // Used for estimated area - [ - 'country' => 'MZ', - 'total_hectares_restored_goal' => 64, - 'sites' => [ - [ - 'uuid' => '9699c720-fa51-47fc-96c9-0675b29dcbbe', - 'geometry' => [ - [ - 'geojson' => '{"type": "Polygon", "coordinates": [[[40.42039202036633, -12.986826569522448], [40.420395177605975, -12.986824096985375], [40.42038654475881, -12.98670798951762], [40.42037374475881, -12.98657808951762], [40.42036344475881, -12.98647018951762], [40.420353944758816, -12.98636398951762], [40.42034624475881, -12.986278589517621], [40.42037564475881, -12.986192889517621], [40.420425644758815, -12.98612818951762], [40.42047954475881, -12.986106689517621], [40.42062474475881, -12.98606338951762], [40.42073464475881, -12.98604168951762], [40.42084664475881, -12.98604248951762], [40.42095864475881, -12.98604348951762], [40.42101464475881, -12.98604398951762], [40.42108934475881, -12.98604458951762], [40.42116404475881, -12.98604518951762], [40.42125904475881, -12.98606828951762], [40.421372844758814, -12.98609218951762], [40.42148894475881, -12.98615798951762], [40.42156724475881, -12.98622208951762], [40.42160844475881, -12.98628648951762], [40.421631244758814, -12.98635168951762], [40.421675744758815, -12.98646138951762], [40.421720044758814, -12.986570589517621], [40.42174424475881, -12.98665748951762], [40.42178664475881, -12.98674408951762], [40.421810644758814, -12.98683018951762], [40.421852744758816, -12.98691618951762], [40.421911644758815, -12.98698058951762], [40.42198854475881, -12.98704498951762], [40.42210404475881, -12.98715168951762], [40.422141544758816, -12.987172989517621], [40.422254044758816, -12.98723718951762], [40.42229374475881, -12.98730048951762], [40.42233774475881, -12.987447089517621], [40.422342244758816, -12.98753018951762], [40.42234774475881, -12.987633589517621], [40.42235324475881, -12.987736489517621], [40.422360944758815, -12.98787978951762], [40.422433344758815, -12.98790068951762], [40.42252254475881, -12.98790128951762], [40.42261364475881, -12.98794268951762], [40.42267084475881, -12.98802418951762], [40.422692244758814, -12.98810508951762], [40.42269684475881, -12.98820568951762], [40.422717244758815, -12.98826588951762], [40.42272184475881, -12.98836568951762], [40.42274294475881, -12.988445289517621], [40.42281594475881, -12.98850508951762], [40.42288604475881, -12.988504889517621], [40.422938544758814, -12.98850448951762], [40.422990344758816, -12.98848438951762], [40.423113044758814, -12.988483289517621], [40.423183144758816, -12.988482689517621], [40.42325514475881, -12.98854088951762], [40.423292444758815, -12.988618289517621], [40.423329644758816, -12.98869528951762], [40.423331944758814, -12.98877298951762], [40.42338714475881, -12.98887068951762], [40.42342364475881, -12.98892908951762], [40.42346044475881, -12.98900658951762], [40.42347834475881, -12.98902598951762], [40.423534344758814, -12.98918018951762], [40.423554044758816, -12.98927518951762], [40.42355534475881, -12.989331789517621], [40.42352334475881, -12.989425589517621], [40.423525244758814, -12.98950048951762], [40.423492444758814, -12.98955628951762], [40.42346074475881, -12.98964908951762], [40.423394044758815, -12.98970428951762], [40.42334454475881, -12.98975948951762], [40.42327934475881, -12.989851189517621], [40.42323074475881, -12.98992428951762], [40.423181244758815, -12.98996098951762], [40.42311704475881, -12.99005358951762], [40.423069344758815, -12.99012728951762], [40.42303864475881, -12.99020088951762], [40.42300804475881, -12.990274089517621], [40.42297834475881, -12.990365289517621], [40.42296554475881, -12.990456189517621], [40.422917844758814, -12.990510289517621], [40.42290434475881, -12.99058208951762], [40.422874144758815, -12.99065328951762], [40.42286054475881, -12.99072368951762], [40.42283124475881, -12.99081138951762], [40.42283434475881, -12.990881189517621], [40.42283814475881, -12.99096818951762], [40.42282474475881, -12.99103748951762], [40.42281134475881, -12.99110648951762], [40.422814444758814, -12.99117528951762], [40.42280104475881, -12.991243889517621], [40.42280574475881, -12.99134628951762], [40.42280874475881, -12.99141418951762], [40.422813344758815, -12.99151578951762], [40.42281714475881, -12.99159998951762], [40.42282094475881, -12.991683789517621], [40.42282474475881, -12.99176718951762], [40.42282774475881, -12.99183378951762], [40.422846844758816, -12.991900089517621], [40.422882044758815, -12.99196628951762], [40.42290254475881, -12.99206558951762], [40.422921644758816, -12.99213168951762], [40.42294064475881, -12.99219758951762], [40.42294434475881, -12.992279489517621], [40.42294724475881, -12.99234478951762], [40.42295094475881, -12.99242608951762], [40.42290484475881, -12.99245828951762], [40.422843644758814, -12.992506589517621], [40.42279774475881, -12.99253868951762], [40.422736044758814, -12.99257068951762], [40.42267264475881, -12.99257048951762], [40.42259354475881, -12.99257018951762], [40.422529344758814, -12.99255388951762], [40.42244814475881, -12.99252138951762], [40.422368944758816, -12.992521189517621], [40.42228964475881, -12.992520889517621], [40.42219464475881, -12.99252098951762], [40.42215034475881, -12.99256918951762], [40.422120844758815, -12.99260118951762], [40.422110444758815, -12.992681089517621], [40.42206564475881, -12.99271338951762], [40.422002744758814, -12.99271368951762], [40.42198244475881, -12.992649789517621], [40.42191144475881, -12.99253818951762], [40.421890944758815, -12.99247398951762], [40.42186804475881, -12.99237718951762], [40.42184984475881, -12.99234488951762], [40.421810744758815, -12.99224718951762], [40.42179104475881, -12.99219798951762], [40.42175004475881, -12.99208268951762], [40.42173014475881, -12.992033089517621], [40.42167554475881, -12.99195028951762], [40.421638144758816, -12.99188388951762], [40.42160064475881, -12.99181728951762], [40.421577844758815, -12.99173368951762], [40.421522544758815, -12.99164968951762], [40.42145354475881, -12.99159918951762], [40.421402144758815, -12.99156558951762], [40.42135214475881, -12.99154898951762], [40.42125054475881, -12.99149868951762], [40.42118544475881, -12.991498789517621], [40.421104144758814, -12.99149888951762], [40.42107164475881, -12.99149888951762], [40.420957744758816, -12.99149898951762], [40.420907244758816, -12.99148198951762], [40.42080964475881, -12.991482089517621], [40.42067944475881, -12.99148218951762], [40.420565544758816, -12.99148228951762], [40.420500444758815, -12.991482389517621], [40.420417144758815, -12.99146538951762], [40.420335744758816, -12.99146538951762], [40.42023814475881, -12.991465489517621], [40.420170944758816, -12.991448489517621], [40.420105844758815, -12.99144858951762], [40.42002204475881, -12.991431289517621], [40.41992174475881, -12.991413689517621], [40.419854044758814, -12.99139628951762], [40.419788544758816, -12.99139598951762], [40.41972064475881, -12.99137848951762], [40.419638744758814, -12.99137818951762], [40.419556744758815, -12.991377789517621], [40.419474744758816, -12.991377489517621], [40.41938124475881, -12.991411389517621], [40.41936314475881, -12.991513989517621], [40.41937524475881, -12.99159908951762], [40.419350044758815, -12.99164998951762], [40.41934594475881, -12.991734389517621], [40.41935564475881, -12.99180168951762], [40.41935154475881, -12.991885489517621], [40.41934504475881, -12.99195218951762], [40.419341044758816, -12.99203518951762], [40.419318544758816, -12.99210138951762], [40.41929864475881, -12.99218378951762], [40.41930824475881, -12.992249389517621], [40.419301944758814, -12.99231478951762], [40.41929804475881, -12.99239618951762], [40.419307544758816, -12.99246108951762], [40.41930364475881, -12.99254178951762], [40.41924764475881, -12.99259008951762], [40.419173644758814, -12.992622289517621], [40.41909724475881, -12.992638389517621], [40.41900264475881, -12.992638389517621], [40.41893194475881, -12.99259038951762], [40.41889014475881, -12.99252598951762], [40.418850744758814, -12.99247758951762], [40.418827044758814, -12.992429089517621], [40.41877954475881, -12.99233158951762], [40.418721244758814, -12.992266289517621], [40.418694644758816, -12.99220078951762], [40.418633144758815, -12.99211848951762], [40.41857694475881, -12.992068989517621], [40.418501744758814, -12.99200278951762], [40.41843474475881, -12.991986289517621], [40.418332744758814, -12.99195308951762], [40.418246544758816, -12.99191978951762], [40.417351544758816, -12.992188289517621], [40.417190344758815, -12.99210668951762], [40.41714134475881, -12.992057889517621], [40.417091844758815, -12.99199238951762], [40.41707464475881, -12.99194258951762], [40.41705704475881, -12.99187588951762], [40.417055444758816, -12.99180898951762], [40.417053444758814, -12.99172488951762], [40.41705184475881, -12.99165728951762], [40.41705024475881, -12.99158958951762], [40.41704824475881, -12.99150448951762], [40.417062544758814, -12.99141888951762], [40.41706054475881, -12.99133298951762], [40.417042544758814, -12.991264089517621], [40.416959344758816, -12.99121268951762], [40.416908344758816, -12.99114348951762], [40.416856544758815, -12.99105638951762], [40.416804944758816, -12.99098528951762], [40.41680204475881, -12.99089628951762], [40.41679924475881, -12.99080688951762], [40.41681304475881, -12.99071708951762], [40.41684464475881, -12.99066308951762], [40.41689224475881, -12.990573989517621], [40.41694064475881, -12.99050308951762], [40.41698954475881, -12.99044988951762], [40.417039044758816, -12.990414389517621], [40.417105344758816, -12.99037898951762], [40.41718824475881, -12.99032558951762], [40.417254844758816, -12.99028998951762], [40.41728794475881, -12.990254189517621], [40.41735464475881, -12.99021818951762], [40.417421344758814, -12.990163889517621], [40.417488344758816, -12.99012758951762], [40.41757244475881, -12.99009118951762], [40.41765644475881, -12.99003658951762], [40.41772394475881, -12.99000008951762], [40.417757644758815, -12.98996348951762], [40.417842244758816, -12.98992678951762], [40.41791004475881, -12.98989008951762], [40.41794404475881, -12.98985328951762], [40.41802904475881, -12.98983468951762], [40.41809714475881, -12.989797389517621], [40.41818234475881, -12.98977828951762], [40.418267844758816, -12.989740689517621], [40.41833614475881, -12.989721689517621], [40.418404644758816, -12.98970258951762], [40.418473344758816, -12.989664989517621], [40.418524944758815, -12.989645989517621], [40.41862874475881, -12.98957078951762], [40.41868084475881, -12.98953308951762], [40.418750544758815, -12.98947598951762], [40.41885494475881, -12.98941808951762], [40.41890844475881, -12.989341989517621], [40.41896114475881, -12.98930348951762], [40.419049644758815, -12.98922608951762], [40.41910394475881, -12.98914808951762], [40.419158444758814, -12.98906978951762], [40.419212544758814, -12.98901058951762], [40.419233244758814, -12.98891278951762], [40.41923654475881, -12.988814689517621], [40.41923984475881, -12.98871608951762], [40.41924324475881, -12.98861698951762], [40.41924584475881, -12.98853778951762], [40.41924894475881, -12.988439389517621], [40.419250844758814, -12.988380089517621], [40.419253344758815, -12.98830078951762], [40.41925784475881, -12.98816128951762], [40.419242544758816, -12.98808148951762], [40.41919154475881, -12.98800148951762], [40.419121444758815, -12.98796148951762], [40.41910594475881, -12.98788008951762], [40.41907244475881, -12.987799289517621], [40.419075144758814, -12.98769738951762], [40.41907794475881, -12.987594989517621], [40.41909874475881, -12.98749188951762], [40.419137244758815, -12.98740888951762], [40.41919574475881, -12.98726288951762], [40.419252744758815, -12.987178789517621], [40.419327444758814, -12.98711518951762], [40.419401744758815, -12.987072389517621], [40.41954744475881, -12.98707138951762], [40.41954734475881, -12.987021289517621], [40.419545944758816, -12.98699158951762], [40.41954494475881, -12.98697178951762], [40.41954354475881, -12.98694198951762], [40.41954234475881, -12.98691698951762], [40.419550644758814, -12.98689688951762], [40.419554344758815, -12.98687678951762], [40.41955824475881, -12.98686158951762], [40.41957104475881, -12.98683618951762], [40.419579344758816, -12.98681588951762], [40.41959244475881, -12.98679548951762], [40.41961064475881, -12.986785089517621], [40.41962864475881, -12.98676968951762], [40.419646644758814, -12.98675418951762], [40.419660244758816, -12.98674378951762], [40.41968304475881, -12.98672818951762], [40.419705644758814, -12.98670738951762], [40.419719044758814, -12.98668668951762], [40.419732544758816, -12.98667098951762], [40.41975534475881, -12.986650089517621], [40.419769044758816, -12.98663438951762], [40.41978264475881, -12.98661858951762], [40.41979614475881, -12.98659758951762], [40.41981474475881, -12.986586889517621], [40.41982834475881, -12.98656578951762], [40.41984704475881, -12.986555089517621], [40.419865744758816, -12.98654438951762], [40.41988434475881, -12.98652838951762], [40.41990304475881, -12.98651768951762], [40.419936244758816, -12.986512089517621], [40.41996004475881, -12.986511789517621], [40.419993244758814, -12.986511489517621], [40.42001264475881, -12.98653248951762], [40.420031644758815, -12.98653758951762], [40.42005084475881, -12.98655348951762], [40.420070044758816, -12.986569389517621], [40.42008434475881, -12.98657998951762], [40.420108144758814, -12.98659578951762], [40.42012724475881, -12.98661158951762], [40.42014144475881, -12.98662208951762], [40.42015094475881, -12.986637789517621], [40.420160544758815, -12.98666398951762], [40.42017474475881, -12.98667438951762], [40.42018884475881, -12.98668488951762], [40.42022174475881, -12.98670038951762], [40.420231144758816, -12.986705589517621], [40.42025464475881, -12.98672118951762], [40.420277944758816, -12.98673668951762], [40.42028734475881, -12.98674178951762], [40.420310644758814, -12.98676248951762], [40.42032924475881, -12.98677268951762], [40.42034314475881, -12.98678808951762], [40.42036164475881, -12.986803489517621], [40.420370844758814, -12.98681368951762], [40.42038474475881, -12.98682388951762], [40.42039042272699, -12.986826303249225], [40.42039202036633, -12.986826569522448]]]}', - 'est_area' => 29.0, - ], - [ - 'geojson' => '{"type": "Polygon", "coordinates": [[[40.4208782, -12.9966219], [40.420668718590605, -12.996690702886378], [40.4205746, -12.9967071], [40.42057158698505, -12.996712167019878], [40.4205646, -12.9967249], [40.4206808, -12.9970237], [40.4205785, -12.9970738], [40.4204578, -12.9970399], [40.4201384, -12.9970048], [40.4199487, -12.9969966], [40.4198451, -12.9969883], [40.4195651, -12.9969206], [40.4193779, -12.9968437], [40.41915, -12.9967142], [40.419007, -12.9966792], [40.4187997, -12.9966181], [40.4186464, -12.9964872], [40.4182502, -12.9963375], [40.4178242, -12.996267], [40.4177966, -12.9962583], [40.4176934, -12.996151], [40.4176655, -12.9961421], [40.4174409, -12.9960624], [40.4174293, -12.9960442], [40.4171427, -12.9958793], [40.4167641, -12.9958254], [40.416554, -12.9955798], [40.4165486, -12.9952107], [40.416852, -12.9952486], [40.4170542, -12.9952964], [40.4173546, -12.9951463], [40.4176285, -12.9948334], [40.4177287, -12.9948741], [40.4178497, -12.9950051], [40.4182262, -12.9950137], [40.4184424, -12.9950135], [40.4185773, -12.9948521], [40.4187258, -12.9946058], [40.4187848, -12.9942907], [40.4187998, -12.9940106], [40.4186606, -12.9936752], [40.4184847, -12.9935047], [40.418567, -12.9934237], [40.4191292, -12.993422], [40.4194912, -12.9934221], [40.4198032, -12.9934243], [40.4197678, -12.9937522], [40.4197963, -12.9939596], [40.4200389, -12.9941628], [40.4203545, -12.9943043], [40.4205594, -12.9943907], [40.4205707, -12.9945175], [40.4205837, -12.9947875], [40.4205933, -12.9947979], [40.420737, -12.9950106], [40.4209999, -12.9951113], [40.4210092, -12.9951213], [40.4213239, -12.9952997], [40.4214468, -12.9955806], [40.4214587, -12.9958268], [40.4213012, -12.9960029], [40.4212651, -12.996012], [40.4209943, -12.996185], [40.4208782, -12.9966219]]]}', - 'est_area' => 11.0, - ], - [ - 'geojson' => '{"type": "Polygon", "coordinates": [[[40.422583542502316, -12.996359645998147], [40.422837542502315, -12.996105945998147], [40.422867442502316, -12.995839445998147], [40.42275404250232, -12.995605945998147], [40.422777942502314, -12.995321145998147], [40.42286394250232, -12.995240745998148], [40.42294434250232, -12.995202445998148], [40.42301354250232, -12.995182845998148], [40.42312124250232, -12.995165945998147], [40.423235242502315, -12.995097345998147], [40.42333114250231, -12.995037345998147], [40.42345394250231, -12.994979345998148], [40.42349344250232, -12.994923945998147], [40.423533042502314, -12.994886345998147], [40.423530042502314, -12.994822145998148], [40.423541042502315, -12.994772445998148], [40.423573942502316, -12.994691745998148], [40.42359924250232, -12.994634745998148], [40.42364354250232, -12.994580345998148], [40.42369814250232, -12.994557945998148], [40.42375044250232, -12.994438045998148], [40.423774642502316, -12.994418245998148], [40.423775742502315, -12.994391445998147], [40.42377714250232, -12.994357845998147], [40.42378474250231, -12.994317245998147], [40.423785542502316, -12.994296845998148], [40.42379264250231, -12.994269545998147], [40.423805642502316, -12.994242245998148], [40.42380714250232, -12.994207745998148], [40.42380834250232, -12.994179945998148], [40.42382714250232, -12.994159245998148], [40.42385704250232, -12.994159545998148], [40.423880842502314, -12.994159745998148], [40.42390444250232, -12.994166945998147], [40.423927142502315, -12.994188045998147], [40.42394384250232, -12.994208945998148], [40.42394754250232, -12.994305145998148], [40.423973342502315, -12.994332545998148], [40.42399254250232, -12.994352945998148], [40.424006842502315, -12.994386745998147], [40.424014642502314, -12.994413645998147], [40.42404004250232, -12.994440445998148], [40.424058442502314, -12.994453945998147], [40.424087642502315, -12.994454145998148], [40.424111442502316, -12.994461045998147], [40.42413604250232, -12.994481045998148], [40.424160642502315, -12.994501045998147], [40.424178842502315, -12.994514345998148], [40.42420244250231, -12.994520945998147], [40.424226342502315, -12.994534045998147], [40.42424474250232, -12.994553645998147], [40.42426304250232, -12.994573245998147], [40.42427594250232, -12.994599145998148], [40.42428364250232, -12.994637845998147], [40.424295742502316, -12.994650645998147], [40.42431424250232, -12.994676445998147], [40.424332042502314, -12.994689445998148], [40.424333242502314, -12.994715045998147], [40.424335042502314, -12.994753245998147], [40.42434704250232, -12.994766045998148], [40.42437024250231, -12.994778945998148], [40.42439864250232, -12.994779145998148], [40.424420842502315, -12.994766545998148], [40.424437342502316, -12.994754045998148], [40.424459142502315, -12.994728745998147], [40.42447534250232, -12.994703345998147], [40.42450324250232, -12.994684345998147], [40.42451984250231, -12.994665145998148], [40.424548042502316, -12.994652545998148], [40.42456454250232, -12.994626745998147], [40.424581442502316, -12.994613845998147], [40.42461024250232, -12.994614145998147], [40.42463894250232, -12.994614345998148], [40.42466194250232, -12.994614545998148], [40.42469064250232, -12.994614745998147], [40.424719442502315, -12.994614945998148], [40.42473674250232, -12.994621645998148], [40.42476544250231, -12.994628345998148], [40.424782742502316, -12.994641445998148], [40.42478294250232, -12.994673645998148], [40.424783242502315, -12.994705745998148], [40.424783342502316, -12.994731245998148], [40.42477794250232, -12.994762945998147], [40.424766842502315, -12.994794545998147], [40.42476704250232, -12.994819845998148], [40.42475604250232, -12.994844945998148], [40.42473354250232, -12.994844745998147], [40.42471104250232, -12.994844545998147], [40.42468854250232, -12.994844445998147], [40.424660442502315, -12.994844245998147], [40.42463224250232, -12.994844045998148], [40.424598542502316, -12.994843745998148], [40.424575942502315, -12.994843645998147], [40.42454224250232, -12.994843345998147], [40.42451404250232, -12.994843145998148], [40.42448614250232, -12.994849245998148], [40.42446424250232, -12.994867845998147], [40.42444794250232, -12.994880245998148], [40.42442594250232, -12.994892545998148], [40.424404342502314, -12.994911045998148], [40.42438274250232, -12.994929445998148], [40.424372742502314, -12.994954045998147], [40.42436274250232, -12.994978545998148], [40.424347242502314, -12.995002845998147], [40.42432564250232, -12.995014945998147], [40.424303442502314, -12.995014745998148], [40.42428194250232, -12.995026745998148], [40.424254242502315, -12.995026545998147], [40.42423684250232, -12.995014345998147], [40.424240942502315, -12.994989945998148], [40.42425624250232, -12.994965545998147], [40.424266342502314, -12.994947145998148], [40.42427054250231, -12.994922445998148], [40.42425834250232, -12.994903845998147], [40.42423584250232, -12.994903645998148], [40.42421984250232, -12.994915945998148], [40.424197742502315, -12.994921945998147], [40.424188542502314, -12.994952745998148], [40.42418454250232, -12.994977345998148], [40.424186142502315, -12.995001745998147], [40.424182542502315, -12.995032145998147], [40.42416714250232, -12.995050245998147], [40.424145442502315, -12.995056145998147], [40.42412324250232, -12.995055945998148], [40.42404734250232, -12.995202345998148], [40.42404604250232, -12.995213245998148], [40.42402214250232, -12.995293245998148], [40.424011642502315, -12.995363545998147], [40.42406714250232, -12.995417245998148], [40.424062842502316, -12.995469745998147], [40.423970942502315, -12.995494045998148], [40.42391594250232, -12.995506145998148], [40.42399504250232, -12.995623645998148], [40.42402784250232, -12.995740945998147], [40.424191642502315, -12.995772345998148], [40.42430614250232, -12.995772245998147], [40.42438404250232, -12.995702245998148], [40.42434714250231, -12.995631845998147], [40.424305242502314, -12.995537145998147], [40.42438774250232, -12.995497245998148], [40.42444734250232, -12.995457245998148], [40.42452044250231, -12.995433245998148], [40.42468254250232, -12.995441045998147], [40.42475674250232, -12.995464845998148], [40.42488624250232, -12.995480645998148], [40.42498594250232, -12.995481445998148], [40.425070242502315, -12.995482145998148], [40.42518614250232, -12.995522645998147], [40.42533004250232, -12.995703845998147], [40.425350642502316, -12.995758145998147], [40.42534114250232, -12.995811845998148], [40.42531814250231, -12.995941145998147], [40.425299442502315, -12.996046245998148], [40.425245442502316, -12.996142645998148], [40.425185242502316, -12.996230945998148], [40.42511004250232, -12.996281945998147], [40.42500364250232, -12.996303345998147], [40.42477794250232, -12.996331945998147], [40.42466664250232, -12.996332145998148], [40.424599742502316, -12.996332445998148], [40.424500542502315, -12.996296445998148], [40.424501242502316, -12.996238045998147], [40.424360142502316, -12.996231345998147], [40.42425834250232, -12.996268345998148], [40.42410754250232, -12.996283545998148], [40.424085842502315, -12.996341745998148], [40.424024542502316, -12.996511745998147], [40.42422704250232, -12.996710845998148], [40.42438334250232, -12.996770345998147], [40.42446174250232, -12.996746745998147], [40.42448634250232, -12.996746745998147], [40.424615342502314, -12.996746645998147], [40.42474954250232, -12.996752545998147], [40.42487664250232, -12.996764645998148], [40.42490214250232, -12.996795345998148], [40.42496894250232, -12.996904445998148], [40.42497214250232, -12.997023645998148], [40.42499224250231, -12.997111345998148], [40.42498654250232, -12.997209145998148], [40.42495304250232, -12.997328545998148], [40.42486254250232, -12.997521945998148], [40.42462544250232, -12.998119045998148], [40.42453624250231, -12.998417745998148], [40.42424784250232, -12.998550545998148], [40.424090842502316, -12.998580445998147], [40.423812742502314, -12.998598545998147], [40.42300324250232, -12.997743445998148], [40.42264044250231, -12.997648945998147], [40.42218104250232, -12.997461745998148], [40.421974142502314, -12.997376545998147], [40.42153534250232, -12.997305045998148], [40.42121454250232, -12.997115945998148], [40.420682142502315, -12.997014245998148], [40.420577064606924, -12.996716427392446], [40.422583542502316, -12.996359645998147]]]}', - 'est_area' => 10.0 - ], - ] - ], - [ - 'uuid' => 'bd530b53-43c2-4a04-b759-85588c65f5b3', - 'geometry' => [ - [ - 'geojson' => '{"type": "Polygon", "coordinates": [[[40.4030892, -12.9677323], [40.4031161, -12.9677189], [40.4031687, -12.9677027], [40.4032214, -12.9676864], [40.4032909, -12.9676864], [40.4033603, -12.9676865], [40.4034297, -12.9676865], [40.4034826, -12.9676702], [40.4035494, -12.9676539], [40.4036189, -12.9676539], [40.4036719, -12.9676376], [40.4037251, -12.9676213], [40.4037807, -12.9676213], [40.4038642, -12.9676213], [40.4039222, -12.9676377], [40.4039918, -12.9676377], [40.4040497, -12.9676541], [40.4041215, -12.9676704], [40.4041792, -12.9676867], [40.4042275, -12.9677355], [40.4042777, -12.9678003], [40.4043121, -12.9678492], [40.4043485, -12.967914], [40.4043826, -12.9679626], [40.4043951, -12.9680582], [40.4044173, -12.9681218], [40.4044235, -12.9681692], [40.4044612, -12.9682481], [40.4044967, -12.9683111], [40.4045437, -12.9683584], [40.4045884, -12.9683899], [40.4046446, -12.9684057], [40.4047142, -12.9684216], [40.404799, -12.9684529], [40.4048667, -12.9684534], [40.4049124, -12.9684995], [40.4049697, -12.9685303], [40.4050284, -12.9685758], [40.4050868, -12.9686211], [40.4051585, -12.9686663], [40.4052165, -12.9687113], [40.4052729, -12.9687412], [40.4053277, -12.9687562], [40.4053853, -12.9688009], [40.4054532, -12.9688158], [40.4055104, -12.9688603], [40.4055542, -12.9689046], [40.4055727, -12.9689636], [40.4055911, -12.9690222], [40.4055699, -12.9690806], [40.4055474, -12.9691242], [40.4054857, -12.9691677], [40.4054359, -12.9691966], [40.4053834, -12.9691966], [40.4053309, -12.9691966], [40.4052522, -12.9691966], [40.4051881, -12.969211], [40.4051372, -12.9692254], [40.4050748, -12.9692541], [40.4050288, -12.9693115], [40.4050058, -12.9693401], [40.4049714, -12.9693827], [40.4049147, -12.9694543], [40.4048809, -12.9694972], [40.40482, -12.9695264], [40.4047814, -12.9695267], [40.4047314, -12.969541], [40.4046833, -12.9695694], [40.4046206, -12.9695836], [40.4045689, -12.9695836], [40.4045211, -12.969612], [40.4044734, -12.9696403], [40.4044258, -12.9696685], [40.4043723, -12.9696544], [40.4043295, -12.9696261], [40.4042955, -12.9695693], [40.4042613, -12.9695124], [40.404255, -12.9694695], [40.4042296, -12.9693836], [40.4042105, -12.9693407], [40.4041742, -12.9692691], [40.4041269, -12.9692118], [40.4040815, -12.9691688], [40.404049, -12.9691254], [40.4040034, -12.9690821], [40.4039576, -12.9690387], [40.4039091, -12.9689799], [40.4038625, -12.9689355], [40.4038134, -12.9688759], [40.4037797, -12.9688311], [40.4037302, -12.968771], [40.4036804, -12.9687107], [40.4036306, -12.9686503], [40.4035318, -12.9686198], [40.4034729, -12.9685891], [40.4034273, -12.9685584], [40.4033654, -12.9685121], [40.4033303, -12.9684657], [40.4032787, -12.9684036], [40.4032433, -12.9683568], [40.4032049, -12.9682941], [40.4031635, -12.9682154], [40.4031247, -12.968152], [40.4030828, -12.9680724], [40.4030573, -12.9680084], [40.403032, -12.9679444], [40.4030175, -12.9678643], [40.4030307, -12.9677837], [40.4030664, -12.9677513], [40.4030892, -12.9677323]]]}', - 'est_area' => 3.0, - ], - [ - 'geojson' => '{"type": "Polygon", "coordinates": [[[40.405925, -12.968045], [40.4058902, -12.9680778], [40.4059127, -12.9680233], [40.4059379, -12.9679904], [40.4059631, -12.9679571], [40.4059994, -12.9679346], [40.4060385, -12.9679343], [40.4060899, -12.9679561], [40.406129, -12.9679558], [40.4061803, -12.9679776], [40.4062302, -12.9679882], [40.4062812, -12.9680097], [40.4063202, -12.9680094], [40.4063691, -12.9680095], [40.4064081, -12.9680096], [40.4064472, -12.9680096], [40.4064808, -12.9680537], [40.4065208, -12.9680647], [40.4065618, -12.9680866], [40.4066114, -12.9680976], [40.4066529, -12.9681298], [40.4066936, -12.9681515], [40.4067332, -12.9681624], [40.4067737, -12.968184], [40.4068141, -12.9682056], [40.4068554, -12.9682384], [40.4068683, -12.9682816], [40.4068724, -12.9683353], [40.4068661, -12.9683779], [40.4068303, -12.9684095], [40.4068032, -12.9684305], [40.4067458, -12.9684301], [40.4067075, -12.9684298], [40.4066606, -12.96844], [40.4066128, -12.9684397], [40.4065861, -12.9684606], [40.4065403, -12.9684814], [40.4065127, -12.9684917], [40.4064767, -12.9685126], [40.4064312, -12.9685334], [40.4063764, -12.9685541], [40.4063407, -12.9685748], [40.4062933, -12.9685748], [40.406246, -12.9685748], [40.4061892, -12.9685747], [40.4061513, -12.9685747], [40.4061134, -12.9685747], [40.4060755, -12.9685747], [40.4060281, -12.9685747], [40.4059903, -12.9685747], [40.4059429, -12.9685747], [40.4058956, -12.9685747], [40.4058549, -12.968554], [40.4058128, -12.9685228], [40.4057979, -12.9684814], [40.4058114, -12.9684397], [40.4058345, -12.9683978], [40.4058591, -12.9683662], [40.4058619, -12.9683135], [40.4058662, -12.9682711], [40.4058704, -12.9682285], [40.4058843, -12.9681856], [40.4058982, -12.9681426], [40.4059206, -12.9680884], [40.405925, -12.968045]]]}', - 'est_area' => 1.0, - ], - ] - ], - [ - 'uuid' => 'd99fc5ca-c2a9-44d3-ac49-97cdd3085b3d', - 'geometry' => [ - [ - 'geojson' => '{"type": "Polygon", "coordinates": [[[40.4067983, -12.9261098], [40.4068497, -12.9260495], [40.4069473, -12.9260279], [40.4069582, -12.9260272], [40.4069745, -12.9260261], [40.4069881, -12.9260253], [40.406996, -12.9260218], [40.4070096, -12.9260209], [40.4070204, -12.9260202], [40.4070311, -12.9260165], [40.4070389, -12.926013], [40.4070523, -12.9260091], [40.4070605, -12.9260086], [40.4070736, -12.9260017], [40.4070838, -12.925992], [40.4070947, -12.9259913], [40.4071054, -12.9259876], [40.4071161, -12.9259839], [40.4071268, -12.9259802], [40.4071375, -12.9259765], [40.4071476, -12.9259636], [40.4071613, -12.9259628], [40.407172, -12.925959], [40.4071885, -12.925958], [40.4072049, -12.9259569], [40.4072186, -12.9259561], [40.4072295, -12.9259554], [40.4072432, -12.9259545], [40.4072542, -12.9259538], [40.4072706, -12.9259528], [40.4072784, -12.9259431], [40.4072777, -12.9259278], [40.4072853, -12.9259149], [40.4072848, -12.9259026], [40.407287, -12.92589], [40.4072892, -12.9258773], [40.4072885, -12.9258617], [40.4072879, -12.9258491], [40.4072873, -12.9258365], [40.4072813, -12.9258274], [40.4072724, -12.9258152], [40.4072609, -12.9258095], [40.4072495, -12.925807], [40.407238, -12.9258014], [40.4072292, -12.9257955], [40.4072202, -12.9257864], [40.4072113, -12.9257773], [40.4072049, -12.9257647], [40.4071957, -12.9257523], [40.4071864, -12.9257398], [40.4071747, -12.925734], [40.4071629, -12.9257281], [40.4071511, -12.9257223], [40.4071395, -12.9257197], [40.4071277, -12.9257139], [40.4071182, -12.9257012], [40.4071061, -12.925692], [40.4070992, -12.9256758], [40.4070953, -12.9256626], [40.4070637, -12.9256274], [40.4070511, -12.9256146], [40.4070419, -12.9256084], [40.4070297, -12.9256023], [40.4070176, -12.9255962], [40.4070054, -12.9255901], [40.4069934, -12.9255874], [40.4069815, -12.9255847], [40.4069695, -12.925582], [40.4069572, -12.9255758], [40.4069472, -12.9255626], [40.40694, -12.9255491], [40.4069384, -12.9255318], [40.4069371, -12.9255178], [40.4069298, -12.925504], [40.4069199, -12.9254938], [40.4069077, -12.9254909], [40.4068959, -12.9254915], [40.4068833, -12.925485], [40.4068703, -12.9254748], [40.406858, -12.9254719], [40.4068446, -12.9254581], [40.4068344, -12.9254477], [40.4068268, -12.9254335], [40.4068196, -12.9254229], [40.406809, -12.9254088], [40.4067983, -12.9253947], [40.4067906, -12.9253804], [40.4067829, -12.925366], [40.4067751, -12.9253516], [40.4067734, -12.9253368], [40.4067778, -12.9253217], [40.4067891, -12.9253136], [40.4067978, -12.9253094], [40.4068126, -12.9253049], [40.4068279, -12.9253041], [40.4068401, -12.9253035], [40.406853, -12.9253104], [40.4068637, -12.9253248], [40.406877, -12.9253353], [40.4068872, -12.9253459], [40.4069, -12.9253527], [40.4069098, -12.9253596], [40.4069226, -12.9253663], [40.406933, -12.9253805], [40.4069457, -12.9253872], [40.4069584, -12.9253938], [40.4069713, -12.9254041], [40.4069812, -12.9254145], [40.4069912, -12.9254249], [40.407004, -12.9254352], [40.4070165, -12.9254417], [40.4070236, -12.9254556], [40.4070363, -12.9254655], [40.4070487, -12.9254719], [40.4070613, -12.9254817], [40.4070707, -12.9254882], [40.4070833, -12.9254981], [40.4070929, -12.925508], [40.4071022, -12.9255145], [40.407112, -12.9255279], [40.407121, -12.9255308], [40.407152, -12.9256014], [40.407186, -12.9256367], [40.4071981, -12.9256461], [40.4072048, -12.9256624], [40.4072166, -12.9256684], [40.4072284, -12.9256743], [40.4072402, -12.9256803], [40.4072493, -12.9256897], [40.4072555, -12.9256992], [40.4072673, -12.9257084], [40.4072763, -12.9257177], [40.4072853, -12.925727], [40.4072943, -12.9257395], [40.4073062, -12.9257518], [40.4073179, -12.9257608], [40.4073267, -12.92577], [40.4073382, -12.9257757], [40.4073471, -12.925788], [40.4073533, -12.9258037], [40.4073592, -12.9258129], [40.4073598, -12.9258319], [40.4073685, -12.9258409], [40.4073799, -12.9258496], [40.4073913, -12.9258583], [40.4074, -12.9258703], [40.4074085, -12.9258792], [40.4074171, -12.9258911], [40.4074284, -12.9259028], [40.4074287, -12.9259151], [40.4074291, -12.9259304], [40.4074347, -12.9259393], [40.4074432, -12.925951], [40.4074516, -12.9259626], [40.4074573, -12.9259743], [40.4074629, -12.925983], [40.4074685, -12.9259917], [40.4074741, -12.9260034], [40.4074797, -12.9260149], [40.4074853, -12.9260265], [40.4074855, -12.9260384], [40.407491, -12.9260498], [40.4074965, -12.9260612], [40.407502, -12.9260726], [40.4075022, -12.9260872], [40.4075077, -12.9260985], [40.4075131, -12.9261097], [40.4075158, -12.9261212], [40.4075185, -12.9261297], [40.4075213, -12.9261439], [40.407524, -12.9261552], [40.4075346, -12.9261631], [40.4075373, -12.9261772], [40.4075452, -12.9261852], [40.4075452, -12.9261993], [40.4075531, -12.9262072], [40.4075557, -12.9262183], [40.4075635, -12.926229], [40.4075661, -12.9262344], [40.4075713, -12.9262507], [40.4075713, -12.9262617], [40.4075764, -12.9262724], [40.407579, -12.9262832], [40.4075841, -12.9262938], [40.4075892, -12.9263043], [40.4075942, -12.9263149], [40.4075993, -12.9263253], [40.4076043, -12.9263384], [40.4076093, -12.9263488], [40.4076169, -12.9263563], [40.4076193, -12.9263695], [40.4076268, -12.9263796], [40.4076317, -12.9263898], [40.4076366, -12.9264], [40.4076415, -12.9264101], [40.4076464, -12.9264202], [40.4076512, -12.9264303], [40.4076561, -12.9264378], [40.4076634, -12.9264502], [40.4076657, -12.9264603], [40.407673, -12.9264675], [40.4076753, -12.9264776], [40.4076801, -12.9264874], [40.4076823, -12.9264974], [40.4076821, -12.9265075], [40.4076843, -12.9265174], [40.4076866, -12.9265273], [40.4076888, -12.9265371], [40.4076886, -12.9265471], [40.4077062, -12.9266194], [40.4077071, -12.9266744], [40.4077068, -12.9266838], [40.4077065, -12.9266956], [40.4077015, -12.9267053], [40.4076918, -12.9267106], [40.4076822, -12.9267159], [40.407675, -12.926721], [40.4076653, -12.9267286], [40.407658, -12.9267361], [40.4076485, -12.9267413], [40.4076389, -12.9267466], [40.4076271, -12.9267473], [40.4076176, -12.926748], [40.4076057, -12.926751], [40.4075986, -12.9267515], [40.4075844, -12.9267524], [40.4075726, -12.9267532], [40.4075607, -12.926754], [40.4075513, -12.9267523], [40.4075418, -12.9267506], [40.4075299, -12.9267514], [40.4075204, -12.9267474], [40.4074879, -12.9267836], [40.4074773, -12.9267872], [40.4074667, -12.9267879], [40.4074508, -12.9267889], [40.4074401, -12.9267896], [40.4074297, -12.926796], [40.4074245, -12.9268021], [40.407425, -12.9268164], [40.4074201, -12.9268282], [40.4074206, -12.9268423], [40.407421, -12.9268565], [40.4074214, -12.9268677], [40.4074297, -12.9268784], [40.4074402, -12.9268806], [40.4074482, -12.9268828], [40.4074613, -12.9268848], [40.4074692, -12.9268843], [40.4074824, -12.926889], [40.4074928, -12.9268884], [40.4075059, -12.9268875], [40.4075164, -12.9268869], [40.4075294, -12.926886], [40.4075425, -12.9268852], [40.4075529, -12.9268845], [40.4075634, -12.9268838], [40.4075791, -12.9268828], [40.4075921, -12.926882], [40.4076026, -12.9268813], [40.4076157, -12.9268805], [40.4076261, -12.9268798], [40.4076392, -12.9268789], [40.4076471, -12.9268756], [40.4076602, -12.9268747], [40.4076707, -12.9268684], [40.4076838, -12.9268647], [40.4076943, -12.9268612], [40.4077049, -12.9268577], [40.4077154, -12.9268541], [40.4077259, -12.9268507], [40.4077364, -12.92685], [40.4077469, -12.9268493], [40.4077574, -12.9268487], [40.4077677, -12.9268536], [40.4077701, -12.9268646], [40.4077697, -12.9268812], [40.40778, -12.9268889], [40.4077876, -12.9268966], [40.4077978, -12.9269015], [40.4078081, -12.9269063], [40.4078183, -12.9269111], [40.4078285, -12.9269159], [40.4078361, -12.9269209], [40.4078436, -12.9269286], [40.4078536, -12.9269361], [40.4078584, -12.9269466], [40.4078632, -12.9269544], [40.4078655, -12.9269623], [40.4078727, -12.9269752], [40.4078801, -12.9269801], [40.4078902, -12.9269848], [40.4078976, -12.9269896], [40.4079101, -12.9269941], [40.4079173, -12.9270043], [40.407922, -12.9270119], [40.4079266, -12.9270221], [40.4079286, -12.9270325], [40.4079307, -12.9270428], [40.4079299, -12.9270585], [40.4079343, -12.9270712], [40.4079388, -12.9270812], [40.407946, -12.9270885], [40.4079504, -12.9270984], [40.4079548, -12.9271109], [40.4079542, -12.9271212], [40.4079562, -12.9271312], [40.4079555, -12.9271439], [40.4079476, -12.9271519], [40.4079351, -12.9271552], [40.4079297, -12.9271631], [40.4079194, -12.9271713], [40.4079095, -12.9271719], [40.4078971, -12.9271727], [40.4078874, -12.9271684], [40.4078803, -12.9271613], [40.4078757, -12.9271515], [40.4078686, -12.9271444], [40.4078588, -12.92714], [40.4078491, -12.927133], [40.4078419, -12.9271258], [40.407832, -12.9271214], [40.4078197, -12.9271171], [40.4078099, -12.92711], [40.4078, -12.9271056], [40.4077927, -12.9270983], [40.4077878, -12.9270909], [40.4077805, -12.9270836], [40.4077731, -12.9270738], [40.4077632, -12.9270692], [40.4077557, -12.9270619], [40.4077482, -12.9270571], [40.4077408, -12.9270498], [40.4077332, -12.927045], [40.4077258, -12.927035], [40.4077182, -12.9270302], [40.4077106, -12.9270255], [40.4077031, -12.9270128], [40.4076955, -12.9270053], [40.4076905, -12.9269977], [40.4076854, -12.9269874], [40.4076829, -12.9269768], [40.4076779, -12.9269664], [40.4076675, -12.9269644], [40.4076572, -12.9269651], [40.4076469, -12.9269659], [40.4076366, -12.9269746], [40.4076314, -12.926983], [40.4076237, -12.9269943], [40.407616, -12.9270002], [40.4076032, -12.9270064], [40.4075904, -12.9270072], [40.4075801, -12.927008], [40.4075673, -12.9270088], [40.4075545, -12.9270097], [40.4075416, -12.9270053], [40.4075313, -12.9270007], [40.4075209, -12.926996], [40.4075131, -12.9269912], [40.4075027, -12.9269865], [40.4074923, -12.9269818], [40.4074794, -12.9269826], [40.4074691, -12.9269806], [40.4074586, -12.9269758], [40.4074482, -12.9269738], [40.4074377, -12.926969], [40.4074272, -12.9269642], [40.4074193, -12.9269593], [40.4074088, -12.9269545], [40.4074032, -12.9269439], [40.4073925, -12.9269364], [40.4073844, -12.9269286], [40.4073763, -12.9269236], [40.4073631, -12.9269189], [40.4073525, -12.9269168], [40.4073417, -12.9269092], [40.4073386, -12.9268982], [40.4073381, -12.9268871], [40.4073373, -12.9268703], [40.4073366, -12.9268562], [40.4073359, -12.9268421], [40.4073352, -12.9268279], [40.4073347, -12.9268165], [40.4073263, -12.9268084], [40.4073178, -12.9267974], [40.4073067, -12.9267894], [40.4072983, -12.9267812], [40.4072948, -12.9267668], [40.407289, -12.9267583], [40.4072857, -12.9267467], [40.407277, -12.9267354], [40.4072684, -12.9267271], [40.407265, -12.9267154], [40.4072641, -12.9267004], [40.4072607, -12.9266886], [40.4072546, -12.9266769], [40.4072512, -12.926665], [40.4072424, -12.9266564], [40.4072312, -12.926651], [40.4072224, -12.9266424], [40.4072136, -12.9266337], [40.407205, -12.9266281], [40.4071989, -12.9266192], [40.407187, -12.9266076], [40.4071754, -12.926599], [40.4071692, -12.9265901], [40.4071662, -12.9265871], [40.4071512, -12.9265724], [40.4071392, -12.9265606], [40.4071271, -12.9265487], [40.4071152, -12.9265399], [40.4071064, -12.9265341], [40.4070997, -12.9265218], [40.4070931, -12.9265094], [40.4070839, -12.9265003], [40.4070772, -12.9264878], [40.4070651, -12.9264789], [40.4070561, -12.926473], [40.4070442, -12.9264672], [40.4070355, -12.9264645], [40.407023, -12.9264523], [40.4070133, -12.9264398], [40.4070041, -12.9264339], [40.4069944, -12.9264214], [40.4069877, -12.9264119], [40.4069782, -12.9264026], [40.4069712, -12.9263898], [40.4069616, -12.9263804], [40.406952, -12.9263711], [40.4069398, -12.9263652], [40.4069302, -12.9263558], [40.4069205, -12.9263463], [40.4069082, -12.9263404], [40.406896, -12.9263345], [40.4068862, -12.926325], [40.4068814, -12.9263082], [40.4068893, -12.9263007], [40.4069037, -12.9262997], [40.4069146, -12.926292], [40.4069254, -12.9262843], [40.4069367, -12.92628], [40.4069472, -12.9262688], [40.4069458, -12.926255], [40.4069356, -12.9262417], [40.4069257, -12.9262319], [40.4069184, -12.9262183], [40.4069114, -12.9262081], [40.4068988, -12.9262018], [40.4068917, -12.9261914], [40.4068787, -12.9261814], [40.4068636, -12.9261788], [40.406853, -12.926165], [40.4068454, -12.9261509], [40.4068347, -12.9261369], [40.4068245, -12.9261266], [40.4068117, -12.92612], [40.4068013, -12.9261096], [40.4067983, -12.9261098]]]}', - 'est_area' => 2.0 - ], - [ - 'geojson' => '{"type": "Polygon", "coordinates": [[[40.4054372, -12.9235332], [40.405487, -12.9235331], [40.4055256, -12.9235219], [40.4055534, -12.9235], [40.4055902, -12.9234671], [40.4056281, -12.923445], [40.4056651, -12.9234121], [40.405735, -12.923412], [40.405775, -12.9234119], [40.405835, -12.9234119], [40.4058849, -12.9234118], [40.4059258, -12.9234227], [40.4059491, -12.9234662], [40.4059542, -12.9235314], [40.4059658, -12.923553], [40.4059882, -12.9235854], [40.4060336, -12.9236608], [40.4060378, -12.9237149], [40.4060404, -12.9237472], [40.4060248, -12.9238008], [40.4060002, -12.9238649], [40.4059946, -12.9239182], [40.4059981, -12.9239608], [40.4059908, -12.9239926], [40.4059844, -12.9240348], [40.40597, -12.9241073], [40.4059624, -12.9241382], [40.4059669, -12.9242], [40.4059698, -12.9242411], [40.405963, -12.924282], [40.405966, -12.9243228], [40.4059697, -12.9243738], [40.4059741, -12.9244347], [40.405977, -12.9244752], [40.4059807, -12.9245257], [40.405985, -12.9245861], [40.4059879, -12.9246262], [40.4059923, -12.9246862], [40.4059959, -12.9247361], [40.4060091, -12.9247859], [40.4060305, -12.9248157], [40.4060539, -12.9248751], [40.4060663, -12.9249146], [40.4060691, -12.924954], [40.4060814, -12.9249933], [40.4060935, -12.9250323], [40.4061056, -12.9250712], [40.4061272, -12.92511], [40.4061291, -12.9251391], [40.4061519, -12.925197], [40.4061923, -12.9252355], [40.4062325, -12.925274], [40.4062538, -12.9253123], [40.4062751, -12.9253506], [40.4063057, -12.9253888], [40.406327, -12.925427], [40.4063578, -12.9254655], [40.4063799, -12.9255134], [40.4063918, -12.9255516], [40.4064132, -12.9255898], [40.4064238, -12.9256089], [40.4064463, -12.9256659], [40.4064586, -12.9257131], [40.4064704, -12.9257509], [40.4064816, -12.9257791], [40.4064933, -12.9258167], [40.4065149, -12.9258636], [40.4065172, -12.9259009], [40.40652, -12.9259473], [40.4065222, -12.9259843], [40.406496, -12.9260119], [40.4064699, -12.9260395], [40.4064444, -12.9260762], [40.4064086, -12.9260945], [40.4063728, -12.9261128], [40.4063464, -12.9261312], [40.4063201, -12.9261496], [40.4062741, -12.9261497], [40.406238, -12.9261589], [40.4062012, -12.9261589], [40.4061652, -12.9261681], [40.40611, -12.9261682], [40.4060733, -12.9261682], [40.4060472, -12.9261866], [40.4060012, -12.9261866], [40.4059553, -12.9261867], [40.4059362, -12.9261775], [40.4058963, -12.9261408], [40.4058563, -12.926104], [40.4058171, -12.9260764], [40.4057955, -12.9260395], [40.4057654, -12.9260118], [40.4056975, -12.9259748], [40.4056673, -12.925947], [40.4056261, -12.9259005], [40.4055957, -12.9258726], [40.4055729, -12.9258259], [40.4055619, -12.9258071], [40.4055288, -12.9257508], [40.4055235, -12.9256943], [40.4055192, -12.925647], [40.405525, -12.9256091], [40.4055402, -12.9255711], [40.4055453, -12.9255234], [40.4055513, -12.9254854], [40.4055472, -12.9254377], [40.4055447, -12.925409], [40.4055414, -12.9253707], [40.4055364, -12.9253131], [40.405533, -12.9252745], [40.4055194, -12.9252263], [40.4054989, -12.9252071], [40.4054569, -12.9251588], [40.4054259, -12.9251298], [40.4053855, -12.9251008], [40.4053734, -12.9250716], [40.4053509, -12.9250327], [40.405318, -12.9249839], [40.4053144, -12.9249446], [40.4053012, -12.9249053], [40.4052939, -12.9248263], [40.4052789, -12.9247668], [40.4052561, -12.9247272], [40.4052523, -12.9246873], [40.405239, -12.9246474], [40.4052152, -12.9245975], [40.4052018, -12.9245574], [40.4051893, -12.9245272], [40.4051749, -12.9244769], [40.4051903, -12.9244363], [40.4052164, -12.9244057], [40.4052434, -12.9243852], [40.4052792, -12.9243545], [40.405317, -12.924344], [40.405352, -12.924303], [40.4053677, -12.924262], [40.4053834, -12.9242209], [40.4053895, -12.9241798], [40.4053956, -12.9241386], [40.4054017, -12.9240972], [40.4053981, -12.9240559], [40.405404, -12.924014], [40.4054097, -12.9239719], [40.4054057, -12.9239297], [40.4054017, -12.9238874], [40.4053977, -12.923845], [40.4053936, -12.9238024], [40.4053896, -12.9237598], [40.4053845, -12.9237063], [40.4053835, -12.9236956], [40.4054059, -12.9236202], [40.4054216, -12.9235768], [40.4054372, -12.9235332]]]}', - 'est_area' => 1.0 - ] - ] - ], - ] - ] - ]; + public const TEST_PROJECTS_FILE = 'database/seeders/Data/polygon_validation_projects.json'; public function run(): void { - foreach (self::TEST_PROJECTS as $projectDef) { + $testProjects = json_decode(file_get_contents(self::TEST_PROJECTS_FILE), true); + foreach ($testProjects as $projectDef) { $project = Project::factory()->create([ 'country' => $projectDef['country'], 'total_hectares_restored_goal' => $projectDef['total_hectares_restored_goal'] ?? 0, @@ -121,7 +32,7 @@ public function run(): void $geometry = PolygonGeometry::factory()->geojson($geojsonString)->create(); SitePolygon::factory()->site($site)->geometry($geometry)->create([ - 'est_area' => $geometryDef['est_area'] ?? 0 + 'est_area' => $geometryDef['est_area'] ?? 0, ]); } } From ae51bb9942dbd161bcd4c383d3b686e40efffb11 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Thu, 9 May 2024 23:47:51 -0700 Subject: [PATCH 29/64] [TM-799] Sketch out the bulk polygon post endpoint. --- .../Sites/StoreBulkSitePolygonsController.php | 45 ++++++ .../TerrafundCreateGeometryController.php | 2 +- app/Models/V2/PolygonGeometry.php | 2 +- app/Models/V2/Sites/Site.php | 6 + app/Models/V2/Sites/SitePolygon.php | 3 +- app/Validators/SitePolygonValidator.php | 12 ++ openapi-src/V2/definitions/GeoJSON.yml | 52 +++++++ .../V2/definitions/SitePolygonPost.yml | 5 + openapi-src/V2/definitions/_index.yml | 4 + .../Sites/post-v2-sites-uuid-polygons.yml | 23 +++ openapi-src/V2/paths/_index.yml | 3 + resources/docs/swagger-v2.yml | 139 ++++++++++++++++++ 12 files changed, 293 insertions(+), 3 deletions(-) create mode 100644 openapi-src/V2/definitions/GeoJSON.yml create mode 100644 openapi-src/V2/definitions/SitePolygonPost.yml create mode 100644 openapi-src/V2/paths/Sites/post-v2-sites-uuid-polygons.yml diff --git a/app/Http/Controllers/V2/Sites/StoreBulkSitePolygonsController.php b/app/Http/Controllers/V2/Sites/StoreBulkSitePolygonsController.php index a6abd1754..7e3e26c54 100644 --- a/app/Http/Controllers/V2/Sites/StoreBulkSitePolygonsController.php +++ b/app/Http/Controllers/V2/Sites/StoreBulkSitePolygonsController.php @@ -4,15 +4,60 @@ use App\Http\Controllers\Controller; use App\Models\V2\Sites\Site; +use App\Models\V2\Sites\SitePolygon; +use App\Services\PolygonService; +use App\Validators\SitePolygonValidator; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +use Illuminate\Support\Facades\App; +use Illuminate\Validation\ValidationException; class StoreBulkSitePolygonsController extends Controller { + const VALIDATIONS = [ + PolygonService::OVERLAPPING_CRITERIA_ID => 'NOT_OVERLAPPING', + PolygonService::SELF_CRITERIA_ID => 'SELF_INTERSECTION_UUID', + PolygonService::SIZE_CRITERIA_ID => 'POLYGON_SIZE_UUID', + PolygonService::WITHIN_COUNTRY_CRITERIA_ID => 'WITHIN_COUNTRY', + PolygonService::SPIKE_CRITERIA_ID => 'SPIKES_UUID', + // TODO +// PolygonService::GEOMETRY_TYPE_CRITERIA_ID => + PolygonService::ESTIMATED_AREA_CRITERIA_ID => 'ESTIMATED_AREA', + ]; + public function __invoke(Request $request, Site $site): JsonResponse { $this->authorize('uploadPolygons', $site); + $request->validate([ + 'geometries' => 'required|array', + ]); + + /** @var PolygonService $service */ + $service = App::make(PolygonService::class); + $polygonUuids = []; + foreach ($request->input('geometries') as $geometry) { + // We expect single polys on this endpoint, so just pull the first uuid returned + $polygonUuids[] = $service->createGeojsonModels($geometry, ['site_id' => $site->uuid])[0]; + } + + // Do the validation in a separate step so that all of the existing polygons are taken into account + // for things like overlapping and estimated area. + foreach ($polygonUuids as $polygonUuid) { + $data = ['polygon_uuid' => $polygonUuid]; + foreach (self::VALIDATIONS as $criteriaId => $validation) { + $valid = true; + try { + SitePolygonValidator::validate($validation, $data); + } catch (ValidationException $exception) { + $valid = false; + } + + $service->createCriteriaSite($polygonUuid, $criteriaId, $valid); + // TODO: accumulate errors and add to response + } + } + return response()->json(['polygon_uuids' => $polygonUuids]); } } diff --git a/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php b/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php index 4e0a02969..24404b07c 100644 --- a/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php +++ b/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php @@ -42,7 +42,7 @@ public function processGeometry(string $uuid) public function storeGeometry(Request $request) { $request->validate([ - 'geometry' => 'required|json|geo_json', + 'geometry' => 'required|json', ]); $geometry = json_decode($request->input('geometry')); diff --git a/app/Models/V2/PolygonGeometry.php b/app/Models/V2/PolygonGeometry.php index dfdec53f0..cf6e337d0 100644 --- a/app/Models/V2/PolygonGeometry.php +++ b/app/Models/V2/PolygonGeometry.php @@ -26,7 +26,7 @@ class PolygonGeometry extends Model public function criteriaSite() { - return $this->hasMany(CriteriaSite::class, 'polygon_id', 'polygon_id'); + return $this->hasMany(CriteriaSite::class, 'polygon_id', 'uuid'); } public static function getGeoJson(string $uuid): ?array diff --git a/app/Models/V2/Sites/Site.php b/app/Models/V2/Sites/Site.php index 8918a17c7..4c9689eed 100644 --- a/app/Models/V2/Sites/Site.php +++ b/app/Models/V2/Sites/Site.php @@ -208,11 +208,17 @@ public function stratas() return $this->morphMany(Strata::class, 'stratasable'); } + // @deprecated public function polygons() { return $this->morphMany(Polygon::class, 'polygonable'); } + public function sitePolygons() + { + return $this->hasMany(SitePolygon::class, 'site_id', 'uuid'); + } + public function treeSpecies() { return $this->morphMany(TreeSpecies::class, 'speciesable')->where('collection', 'tree-planted'); diff --git a/app/Models/V2/Sites/SitePolygon.php b/app/Models/V2/Sites/SitePolygon.php index 0600fc97f..8c823d3d9 100644 --- a/app/Models/V2/Sites/SitePolygon.php +++ b/app/Models/V2/Sites/SitePolygon.php @@ -8,6 +8,7 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\SoftDeletes; use Znck\Eloquent\Relations\BelongsToThrough; use Znck\Eloquent\Traits\BelongsToThrough as BelongsToThroughTrait; @@ -43,7 +44,7 @@ class SitePolygon extends Model 'country', ]; - public function polygonGeometry() + public function polygonGeometry(): BelongsTo { return $this->belongsTo(PolygonGeometry::class, 'poly_id', 'uuid'); } diff --git a/app/Validators/SitePolygonValidator.php b/app/Validators/SitePolygonValidator.php index cb765452d..85b2f8858 100644 --- a/app/Validators/SitePolygonValidator.php +++ b/app/Validators/SitePolygonValidator.php @@ -14,16 +14,28 @@ class SitePolygonValidator extends Validator 'features.*.geometry' => 'polygon_spikes', ]; + public const SPIKES_UUID = [ + '*' => 'string|uuid|has_polygon_site|polygon_spikes', + ]; + public const POLYGON_SIZE = [ 'features' => 'required|array', 'features.*' => 'polygon_size', ]; + public const POLYGON_SIZE_UUID = [ + '*' => 'string|uuid|has_polygon_site|polygon_size', + ]; + public const SELF_INTERSECTION = [ 'features' => 'required|array', 'features.*' => 'polygon_self_intersection', ]; + public const SELF_INTERSECTION_UUID = [ + '*' => 'string|uuid|has_polygon_site|polygon_self_intersection', + ]; + public const WITHIN_COUNTRY = [ '*' => 'string|uuid|has_polygon_site|within_country', ]; diff --git a/openapi-src/V2/definitions/GeoJSON.yml b/openapi-src/V2/definitions/GeoJSON.yml new file mode 100644 index 000000000..0c42310e6 --- /dev/null +++ b/openapi-src/V2/definitions/GeoJSON.yml @@ -0,0 +1,52 @@ +title: GeoJSON +type: object +properties: + type: + type: string + enum: [FeatureCollection] + features: + type: array + items: + type: object + properties: + type: + type: string + enum: [Feature] + properties: + type: object + properties: + poly_name: + type: string + plantstart: + type: string + format: date + plantend: + type: string + format: date + practice: + type: string + target_sys: + type: string + distr: + type: string + num_trees: + type: number + site_id: + type: string + geometry: + type: object + properties: + type: + type: string + enum: [Polygon] + coordinates: + type: array + items: + type: array + items: + type: array + items: + type: number + + + diff --git a/openapi-src/V2/definitions/SitePolygonPost.yml b/openapi-src/V2/definitions/SitePolygonPost.yml new file mode 100644 index 000000000..9b59d7318 --- /dev/null +++ b/openapi-src/V2/definitions/SitePolygonPost.yml @@ -0,0 +1,5 @@ +title: SitePolygonPost +type: object +properties: + uuid: + type: string diff --git a/openapi-src/V2/definitions/_index.yml b/openapi-src/V2/definitions/_index.yml index 5ebd90ac2..a5d1bc9b6 100644 --- a/openapi-src/V2/definitions/_index.yml +++ b/openapi-src/V2/definitions/_index.yml @@ -272,3 +272,7 @@ V2ProjectInviteRead: $ref: './V2ProjectInviteRead.yml' V2ProjectInviteCreate: $ref: './V2ProjectInviteCreate.yml' +GeoJSON: + $ref: './GeoJSON.yml' +SitePolygonPost: + $ref: './SitePolygonPost.yml' diff --git a/openapi-src/V2/paths/Sites/post-v2-sites-uuid-polygons.yml b/openapi-src/V2/paths/Sites/post-v2-sites-uuid-polygons.yml new file mode 100644 index 000000000..bbe448311 --- /dev/null +++ b/openapi-src/V2/paths/Sites/post-v2-sites-uuid-polygons.yml @@ -0,0 +1,23 @@ +summary: Upload bulk polygons to a specific site. +operationId: post-v2-sites-uuid-polygons +tags: + - V2 Sites +parameters: + - type: string + name: UUID + in: path + required: true + - in: body + name: body + schema: + type: object + properties: + geometries: + type: array + items: + $ref: '../../definitions/_index.yml#/GeoJSON' +responses: + '200': + description: Created + schema: + $ref: '../../definitions/_index.yml#/SitePolygonPost' diff --git a/openapi-src/V2/paths/_index.yml b/openapi-src/V2/paths/_index.yml index ec43250d3..2e7fabe9c 100644 --- a/openapi-src/V2/paths/_index.yml +++ b/openapi-src/V2/paths/_index.yml @@ -2481,6 +2481,9 @@ $ref: './Sites/delete-v2-sites-uuid.yml' get: $ref: './Sites/get-v2-sites-uuid.yml' +/v2/sites/{UUID}/polygons: + post: + $ref: './Sites/post-v2-sites-uuid-polygons.yml' /v2/site-monitorings/{UUID}: get: $ref: './Sites/Monitoring/get-v2-site-monitorings-uuid.yml' diff --git a/resources/docs/swagger-v2.yml b/resources/docs/swagger-v2.yml index 74a86ac98..c38457de7 100644 --- a/resources/docs/swagger-v2.yml +++ b/resources/docs/swagger-v2.yml @@ -43997,6 +43997,65 @@ definitions: properties: email_address: type: string + GeoJSON: + title: GeoJSON + type: object + properties: + type: + type: string + enum: + - FeatureCollection + features: + type: array + items: + type: object + properties: + type: + type: string + enum: + - Feature + properties: + type: object + properties: + poly_name: + type: string + plantstart: + type: string + format: date + plantend: + type: string + format: date + practice: + type: string + target_sys: + type: string + distr: + type: string + num_trees: + type: number + site_id: + type: string + geometry: + type: object + properties: + type: + type: string + enum: + - Polygon + coordinates: + type: array + items: + type: array + items: + type: array + items: + type: number + SitePolygonPost: + title: SitePolygonPost + type: object + properties: + uuid: + type: string paths: '/v2/tree-species/{entity}/{UUID}': get: @@ -93046,6 +93105,86 @@ paths: description: 'this is a list of key value pairs eg slug: name ' items: type: string + '/v2/sites/{UUID}/polygons': + post: + summary: Upload bulk polygons to a specific site. + operationId: post-v2-sites-uuid-polygons + tags: + - V2 Sites + parameters: + - type: string + name: UUID + in: path + required: true + - in: body + name: body + schema: + type: object + properties: + geometries: + type: array + items: + title: GeoJSON + type: object + properties: + type: + type: string + enum: + - FeatureCollection + features: + type: array + items: + type: object + properties: + type: + type: string + enum: + - Feature + properties: + type: object + properties: + poly_name: + type: string + plantstart: + type: string + format: date + plantend: + type: string + format: date + practice: + type: string + target_sys: + type: string + distr: + type: string + num_trees: + type: number + site_id: + type: string + geometry: + type: object + properties: + type: + type: string + enum: + - Polygon + coordinates: + type: array + items: + type: array + items: + type: array + items: + type: number + responses: + '200': + description: Created + schema: + title: SitePolygonPost + type: object + properties: + uuid: + type: string '/v2/site-monitorings/{UUID}': get: summary: View a specific site monitoring From d7682052dda5eade39261f3582c1c9e8d7f3ca6c Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Fri, 10 May 2024 09:20:48 -0700 Subject: [PATCH 30/64] [TM-799] All current validator tests now passing. --- .../Controllers/V2/Sites/StoreBulkSitePolygonsController.php | 4 ++-- app/Validators/Extensions/Polygons/NotOverlapping.php | 5 +---- .../Polygons/TestFiles/not_overlapping_fail.geojson | 2 +- .../Polygons/TestFiles/not_overlapping_pass.geojson | 2 +- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/app/Http/Controllers/V2/Sites/StoreBulkSitePolygonsController.php b/app/Http/Controllers/V2/Sites/StoreBulkSitePolygonsController.php index 7e3e26c54..dfb3be481 100644 --- a/app/Http/Controllers/V2/Sites/StoreBulkSitePolygonsController.php +++ b/app/Http/Controllers/V2/Sites/StoreBulkSitePolygonsController.php @@ -4,7 +4,6 @@ use App\Http\Controllers\Controller; use App\Models\V2\Sites\Site; -use App\Models\V2\Sites\SitePolygon; use App\Services\PolygonService; use App\Validators\SitePolygonValidator; use Illuminate\Http\JsonResponse; @@ -14,7 +13,7 @@ class StoreBulkSitePolygonsController extends Controller { - const VALIDATIONS = [ + public const VALIDATIONS = [ PolygonService::OVERLAPPING_CRITERIA_ID => 'NOT_OVERLAPPING', PolygonService::SELF_CRITERIA_ID => 'SELF_INTERSECTION_UUID', PolygonService::SIZE_CRITERIA_ID => 'POLYGON_SIZE_UUID', @@ -47,6 +46,7 @@ public function __invoke(Request $request, Site $site): JsonResponse $data = ['polygon_uuid' => $polygonUuid]; foreach (self::VALIDATIONS as $criteriaId => $validation) { $valid = true; + try { SitePolygonValidator::validate($validation, $data); } catch (ValidationException $exception) { diff --git a/app/Validators/Extensions/Polygons/NotOverlapping.php b/app/Validators/Extensions/Polygons/NotOverlapping.php index 136982f7c..e79db68dc 100644 --- a/app/Validators/Extensions/Polygons/NotOverlapping.php +++ b/app/Validators/Extensions/Polygons/NotOverlapping.php @@ -29,10 +29,7 @@ public static function getIntersectionData(string $polygonUuid): array return ['valid' => false, 'error' => 'Site polygon not found for the given polygon ID', 'status' => 404]; } - $relatedPolyIds = SitePolygon::where('project_id', $sitePolygon->project_id) - ->where('poly_id', '!=', $polygonUuid) - ->pluck('poly_id'); - + $relatedPolyIds = $sitePolygon->project->sitePolygons()->whereNot('poly_id', $polygonUuid)->pluck('poly_id'); $intersects = PolygonGeometry::whereIn('uuid', $relatedPolyIds) ->selectRaw( 'ST_Intersects( diff --git a/tests/Unit/Validators/Extensions/Polygons/TestFiles/not_overlapping_fail.geojson b/tests/Unit/Validators/Extensions/Polygons/TestFiles/not_overlapping_fail.geojson index 55358ac09..c82f6e56f 100644 --- a/tests/Unit/Validators/Extensions/Polygons/TestFiles/not_overlapping_fail.geojson +++ b/tests/Unit/Validators/Extensions/Polygons/TestFiles/not_overlapping_fail.geojson @@ -1 +1 @@ -{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"poly_name":"overlapping polygon","plantstart":"2021-08-03","plantend":"2021-08-03","practice":"Tree Planting, Assisted Natural Regeneration","target_sys":"Natural Forest","distr":"Full Coverage, Partial","num_trees":214,"site_id":"c1e2d5a6-288a-479f-977d-531ca5b52933"},"geometry":{"coordinates":[[[149.98722294615868,-34.48513610575039],[149.98625469584226,-34.48704171558581],[149.99313754217428,-34.486272942260506],[149.99362172183106,-34.48630424349619],[149.99384957108282,-34.48610078525555],[149.9939729894259,-34.48588950117119],[149.99363121554984,-34.48580342231815],[149.98722294615868,-34.48513610575039]]],"type":"Polygon"}}]} +{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-2.2724479716456756,4.919594661506721],[-2.2738890686493107,4.912786212453284],[-2.27086741364252,4.912276733876439],[-2.265428434632497,4.911952520033779],[-2.262546240626193,4.9176493974501625],[-2.2724479716456756,4.919594661506721]]]},"properties":{"poly_name":"Overlapping-Polygon","plantstart":"2024-05-09","plantend":"2024-05-10","practice":"Tree Planting","target_sys":"Agroforest","distr":"Single Line","num_trees":234,"uuid":"52e5fa6c-00a5-47fb-8465-720325503646","site_id":"8d86e97a-3d4e-4132-95e1-435225d47f28","project_id":"12aefea5-2ec9-4181-93c3-2c739f7c8d64"}}]} \ No newline at end of file diff --git a/tests/Unit/Validators/Extensions/Polygons/TestFiles/not_overlapping_pass.geojson b/tests/Unit/Validators/Extensions/Polygons/TestFiles/not_overlapping_pass.geojson index cc24efcb9..8a42038bd 100644 --- a/tests/Unit/Validators/Extensions/Polygons/TestFiles/not_overlapping_pass.geojson +++ b/tests/Unit/Validators/Extensions/Polygons/TestFiles/not_overlapping_pass.geojson @@ -1 +1 @@ -{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"poly_name":"non-overlapping polygon","plantstart":"2021-08-03","plantend":"2021-08-03","practice":"Tree Planting, Direct Seeding","target_sys":"Natural Forest","distr":"Full Coverage, Partial","num_trees":214,"site_id":"c1e2d5a6-288a-479f-977d-531ca5b52933"},"geometry":{"coordinates":[[[149.9984566668453,-34.47506259655472],[150.00151474055508,-34.478274209769985],[150.00247824321912,-34.47526980111218],[150.00084447783348,-34.47316319719226],[149.9984566668453,-34.47506259655472]]],"type":"Polygon"}}]} +{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-2.2762134186535548,4.9237167498841075],[-2.2758880096530163,4.921400973040306],[-2.2718436406451303,4.921400973040306],[-2.271564718644072,4.924504112174461],[-2.275004756651441,4.925152527478133],[-2.2762134186535548,4.9237167498841075]]]},"properties":{"poly_name":"Non-Overlapping Polygon","plantstart":"2024-05-09","plantend":"2024-05-10","practice":null,"target_sys":null,"distr":null,"num_trees":234,"uuid":"062eaa23-1627-4ed5-91c8-20133fc55899","site_id":"8d86e97a-3d4e-4132-95e1-435225d47f28","project_id":"12aefea5-2ec9-4181-93c3-2c739f7c8d64"}}]} \ No newline at end of file From 699755c9f92116a36608fc34b41f161ec1529759 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Fri, 10 May 2024 09:24:08 -0700 Subject: [PATCH 31/64] [TM-799] Remove some more references to project_id on site polygon. --- app/Validators/Extensions/Polygons/EstimatedArea.php | 3 ++- app/Validators/Extensions/Polygons/NotOverlapping.php | 2 +- app/Validators/Extensions/Polygons/WithinCountry.php | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/Validators/Extensions/Polygons/EstimatedArea.php b/app/Validators/Extensions/Polygons/EstimatedArea.php index 14ab73683..41f53bb01 100644 --- a/app/Validators/Extensions/Polygons/EstimatedArea.php +++ b/app/Validators/Extensions/Polygons/EstimatedArea.php @@ -35,7 +35,8 @@ public static function getAreaData(string $polygonUuid): array if ($project == null) { return [ 'valid' => false, - 'error' => 'Project not found for the given Project ID', 'projectId' => $sitePolygon->project_id, + 'error' => 'Project not found for the given SitePolygon', + 'sitePolygonId' => $sitePolygon->uuid, 'status' => 404, ]; } diff --git a/app/Validators/Extensions/Polygons/NotOverlapping.php b/app/Validators/Extensions/Polygons/NotOverlapping.php index e79db68dc..f33992d08 100644 --- a/app/Validators/Extensions/Polygons/NotOverlapping.php +++ b/app/Validators/Extensions/Polygons/NotOverlapping.php @@ -44,7 +44,7 @@ public static function getIntersectionData(string $polygonUuid): array return [ 'valid' => ! in_array(1, $intersects->toArray()), 'uuid' => $polygonUuid, - 'project_id' => $sitePolygon->project_id, + 'project_id' => $sitePolygon->project->id, ]; } } diff --git a/app/Validators/Extensions/Polygons/WithinCountry.php b/app/Validators/Extensions/Polygons/WithinCountry.php index 6734cbb6c..52fa1e282 100644 --- a/app/Validators/Extensions/Polygons/WithinCountry.php +++ b/app/Validators/Extensions/Polygons/WithinCountry.php @@ -43,7 +43,7 @@ public static function getIntersectionData(string $polygonUuid): array $countryIso = $sitePolygonData->project->country; if ($countryIso == null) { - return ['valid' => false, 'status' => 404, 'error' => 'Country ISO not found for the specified project_id']; + return ['valid' => false, 'status' => 404, 'error' => 'Country ISO not found for the specified project']; } $intersectionData = WorldCountryGeneralized::forIso($countryIso) From c7e3c913eeefac895901b9df050f79b696742d57 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Fri, 10 May 2024 09:30:36 -0700 Subject: [PATCH 32/64] [TM-799] Rename endpoint to sites/{id}/geometry to make it a little more shape agnostic. --- ...oller.php => StoreBulkSiteGeometryController.php} | 2 +- .../{SitePolygonPost.yml => SiteGeometryPost.yml} | 2 +- openapi-src/V2/definitions/_index.yml | 4 ++-- ...-polygons.yml => post-v2-sites-uuid-geometry.yml} | 6 +++--- openapi-src/V2/paths/_index.yml | 4 ++-- resources/docs/swagger-v2.yml | 12 ++++++------ routes/api_v2.php | 4 ++-- 7 files changed, 17 insertions(+), 17 deletions(-) rename app/Http/Controllers/V2/Sites/{StoreBulkSitePolygonsController.php => StoreBulkSiteGeometryController.php} (97%) rename openapi-src/V2/definitions/{SitePolygonPost.yml => SiteGeometryPost.yml} (67%) rename openapi-src/V2/paths/Sites/{post-v2-sites-uuid-polygons.yml => post-v2-sites-uuid-geometry.yml} (69%) diff --git a/app/Http/Controllers/V2/Sites/StoreBulkSitePolygonsController.php b/app/Http/Controllers/V2/Sites/StoreBulkSiteGeometryController.php similarity index 97% rename from app/Http/Controllers/V2/Sites/StoreBulkSitePolygonsController.php rename to app/Http/Controllers/V2/Sites/StoreBulkSiteGeometryController.php index dfb3be481..1f2b405fd 100644 --- a/app/Http/Controllers/V2/Sites/StoreBulkSitePolygonsController.php +++ b/app/Http/Controllers/V2/Sites/StoreBulkSiteGeometryController.php @@ -11,7 +11,7 @@ use Illuminate\Support\Facades\App; use Illuminate\Validation\ValidationException; -class StoreBulkSitePolygonsController extends Controller +class StoreBulkSiteGeometryController extends Controller { public const VALIDATIONS = [ PolygonService::OVERLAPPING_CRITERIA_ID => 'NOT_OVERLAPPING', diff --git a/openapi-src/V2/definitions/SitePolygonPost.yml b/openapi-src/V2/definitions/SiteGeometryPost.yml similarity index 67% rename from openapi-src/V2/definitions/SitePolygonPost.yml rename to openapi-src/V2/definitions/SiteGeometryPost.yml index 9b59d7318..ff984504d 100644 --- a/openapi-src/V2/definitions/SitePolygonPost.yml +++ b/openapi-src/V2/definitions/SiteGeometryPost.yml @@ -1,4 +1,4 @@ -title: SitePolygonPost +title: SiteGeometryPost type: object properties: uuid: diff --git a/openapi-src/V2/definitions/_index.yml b/openapi-src/V2/definitions/_index.yml index a5d1bc9b6..7fb47dd61 100644 --- a/openapi-src/V2/definitions/_index.yml +++ b/openapi-src/V2/definitions/_index.yml @@ -274,5 +274,5 @@ V2ProjectInviteCreate: $ref: './V2ProjectInviteCreate.yml' GeoJSON: $ref: './GeoJSON.yml' -SitePolygonPost: - $ref: './SitePolygonPost.yml' +SiteGeometryPost: + $ref: './SiteGeometryPost.yml' diff --git a/openapi-src/V2/paths/Sites/post-v2-sites-uuid-polygons.yml b/openapi-src/V2/paths/Sites/post-v2-sites-uuid-geometry.yml similarity index 69% rename from openapi-src/V2/paths/Sites/post-v2-sites-uuid-polygons.yml rename to openapi-src/V2/paths/Sites/post-v2-sites-uuid-geometry.yml index bbe448311..0834e23fa 100644 --- a/openapi-src/V2/paths/Sites/post-v2-sites-uuid-polygons.yml +++ b/openapi-src/V2/paths/Sites/post-v2-sites-uuid-geometry.yml @@ -1,5 +1,5 @@ -summary: Upload bulk polygons to a specific site. -operationId: post-v2-sites-uuid-polygons +summary: Upload bulk geometry to a specific site. +operationId: post-v2-sites-uuid-geometry tags: - V2 Sites parameters: @@ -20,4 +20,4 @@ responses: '200': description: Created schema: - $ref: '../../definitions/_index.yml#/SitePolygonPost' + $ref: '../../definitions/_index.yml#/SiteGeometryPost' diff --git a/openapi-src/V2/paths/_index.yml b/openapi-src/V2/paths/_index.yml index 2e7fabe9c..8643e841a 100644 --- a/openapi-src/V2/paths/_index.yml +++ b/openapi-src/V2/paths/_index.yml @@ -2481,9 +2481,9 @@ $ref: './Sites/delete-v2-sites-uuid.yml' get: $ref: './Sites/get-v2-sites-uuid.yml' -/v2/sites/{UUID}/polygons: +/v2/sites/{UUID}/geometry: post: - $ref: './Sites/post-v2-sites-uuid-polygons.yml' + $ref: './Sites/post-v2-sites-uuid-geometry.yml' /v2/site-monitorings/{UUID}: get: $ref: './Sites/Monitoring/get-v2-site-monitorings-uuid.yml' diff --git a/resources/docs/swagger-v2.yml b/resources/docs/swagger-v2.yml index c38457de7..523a049ad 100644 --- a/resources/docs/swagger-v2.yml +++ b/resources/docs/swagger-v2.yml @@ -44050,8 +44050,8 @@ definitions: type: array items: type: number - SitePolygonPost: - title: SitePolygonPost + SiteGeometryPost: + title: SiteGeometryPost type: object properties: uuid: @@ -93105,10 +93105,10 @@ paths: description: 'this is a list of key value pairs eg slug: name ' items: type: string - '/v2/sites/{UUID}/polygons': + '/v2/sites/{UUID}/geometry': post: - summary: Upload bulk polygons to a specific site. - operationId: post-v2-sites-uuid-polygons + summary: Upload bulk geometry to a specific site. + operationId: post-v2-sites-uuid-geometry tags: - V2 Sites parameters: @@ -93180,7 +93180,7 @@ paths: '200': description: Created schema: - title: SitePolygonPost + title: SiteGeometryPost type: object properties: uuid: diff --git a/routes/api_v2.php b/routes/api_v2.php index a97fa0deb..3c246d953 100644 --- a/routes/api_v2.php +++ b/routes/api_v2.php @@ -156,7 +156,7 @@ use App\Http\Controllers\V2\Sites\Monitoring\AdminUpdateSiteMonitoringController; use App\Http\Controllers\V2\Sites\Monitoring\ViewSiteMonitoringController; use App\Http\Controllers\V2\Sites\SoftDeleteSiteController; -use App\Http\Controllers\V2\Sites\StoreBulkSitePolygonsController; +use App\Http\Controllers\V2\Sites\StoreBulkSiteGeometryController; use App\Http\Controllers\V2\Sites\ViewASitesMonitoringsController; use App\Http\Controllers\V2\Stages\DeleteStageController; use App\Http\Controllers\V2\Stages\IndexStageController; @@ -555,7 +555,7 @@ Route::get('/image/locations', SiteImageLocationsController::class); Route::delete('/', SoftDeleteSiteController::class); Route::get('/export', ExportAllSiteDataAsProjectDeveloperController::class); - Route::post('/polygons', StoreBulkSitePolygonsController::class); + Route::post('/geometry', StoreBulkSiteGeometryController::class); }); Route::prefix('project-monitorings')->group(function () { From 4fdd66b0847f6e68c73864379c70d58e53382eca Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Fri, 10 May 2024 10:34:16 -0700 Subject: [PATCH 33/64] [TM-799] Test the geometry type validation. --- .../V2/Sites/StoreBulkSiteGeometryController.php | 3 +-- .../V2/Terrafund/TerrafundCreateGeometryController.php | 4 ++-- .../Polygons/{PolygonType.php => GeometryType.php} | 10 +++++----- app/Validators/SitePolygonValidator.php | 9 +++++++++ app/Validators/Validator.php | 2 +- .../Validators/Extensions/PolygonValidatorsTest.php | 5 +++++ .../Polygons/TestFiles/geometry_type_fail.geojson | 1 + .../Polygons/TestFiles/geometry_type_pass.geojson | 1 + 8 files changed, 25 insertions(+), 10 deletions(-) rename app/Validators/Extensions/Polygons/{PolygonType.php => GeometryType.php} (79%) create mode 100644 tests/Unit/Validators/Extensions/Polygons/TestFiles/geometry_type_fail.geojson create mode 100644 tests/Unit/Validators/Extensions/Polygons/TestFiles/geometry_type_pass.geojson diff --git a/app/Http/Controllers/V2/Sites/StoreBulkSiteGeometryController.php b/app/Http/Controllers/V2/Sites/StoreBulkSiteGeometryController.php index 1f2b405fd..b2cf98f96 100644 --- a/app/Http/Controllers/V2/Sites/StoreBulkSiteGeometryController.php +++ b/app/Http/Controllers/V2/Sites/StoreBulkSiteGeometryController.php @@ -19,8 +19,7 @@ class StoreBulkSiteGeometryController extends Controller PolygonService::SIZE_CRITERIA_ID => 'POLYGON_SIZE_UUID', PolygonService::WITHIN_COUNTRY_CRITERIA_ID => 'WITHIN_COUNTRY', PolygonService::SPIKE_CRITERIA_ID => 'SPIKES_UUID', - // TODO -// PolygonService::GEOMETRY_TYPE_CRITERIA_ID => + PolygonService::GEOMETRY_TYPE_CRITERIA_ID => 'GEOMETRY_TYPE_UUID', PolygonService::ESTIMATED_AREA_CRITERIA_ID => 'ESTIMATED_AREA', ]; diff --git a/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php b/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php index 24404b07c..14068ba91 100644 --- a/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php +++ b/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php @@ -11,7 +11,7 @@ use App\Validators\Extensions\Polygons\EstimatedArea; use App\Validators\Extensions\Polygons\NotOverlapping; use App\Validators\Extensions\Polygons\PolygonSize; -use App\Validators\Extensions\Polygons\PolygonType; +use App\Validators\Extensions\Polygons\GeometryType; use App\Validators\Extensions\Polygons\SelfIntersection; use App\Validators\Extensions\Polygons\Spikes; use App\Validators\Extensions\Polygons\WithinCountry; @@ -290,7 +290,7 @@ public function getGeometryType(Request $request) $geometryType = PolygonGeometry::getGeometryType($uuid); if ($geometryType) { - $valid = $geometryType === PolygonType::VALID_TYPE; + $valid = $geometryType === GeometryType::VALID_TYPE; $insertionSuccess = App::make(PolygonService::class) ->createCriteriaSite($uuid, PolygonService::GEOMETRY_TYPE_CRITERIA_ID, $valid); diff --git a/app/Validators/Extensions/Polygons/PolygonType.php b/app/Validators/Extensions/Polygons/GeometryType.php similarity index 79% rename from app/Validators/Extensions/Polygons/PolygonType.php rename to app/Validators/Extensions/Polygons/GeometryType.php index cf91601aa..621ad2ba5 100644 --- a/app/Validators/Extensions/Polygons/PolygonType.php +++ b/app/Validators/Extensions/Polygons/GeometryType.php @@ -6,15 +6,15 @@ use App\Validators\Extensions\Extension; use Illuminate\Support\Facades\DB; -class PolygonType extends Extension +class GeometryType extends Extension { - public static $name = 'polygon_type'; + public static $name = 'geometry_type'; public static $message = [ - 'POLYGON_TYPE', - 'The {{attribute}} field must not represent geojson that is not polygon geometry', + 'GEOMETRY_TYPE', + 'The {{attribute}} field must represent geojson that is polygon geometry', ['attribute' => ':attribute'], - 'The :attribute field must not represent geojson that is not polygon geometry', + 'The :attribute field must represent geojson that is polygon geometry', ]; public const VALID_TYPE = 'POLYGON'; diff --git a/app/Validators/SitePolygonValidator.php b/app/Validators/SitePolygonValidator.php index 85b2f8858..830aba4fd 100644 --- a/app/Validators/SitePolygonValidator.php +++ b/app/Validators/SitePolygonValidator.php @@ -48,6 +48,15 @@ class SitePolygonValidator extends Validator '*' => 'string|uuid|has_polygon_site|estimated_area', ]; + public const GEOMETRY_TYPE = [ + 'features' => 'required|array', + 'features.*' => 'geometry_type', + ]; + + public const GEOMETRY_TYPE_UUID = [ + '*' => 'string|uuid|geometry_type', + ]; + public const SCHEMA = [ 'features' => 'required|array', 'features.*.properties.poly_name' => 'required', diff --git a/app/Validators/Validator.php b/app/Validators/Validator.php index a0cdcfaca..18caafe4c 100644 --- a/app/Validators/Validator.php +++ b/app/Validators/Validator.php @@ -71,7 +71,7 @@ class Validator \App\validators\Extensions\Polygons\HasPolygonSite::class, \App\validators\Extensions\Polygons\NotOverlapping::class, \App\validators\Extensions\Polygons\PolygonSize::class, - \App\validators\Extensions\Polygons\PolygonType::class, + \App\validators\Extensions\Polygons\GeometryType::class, \App\validators\Extensions\Polygons\SelfIntersection::class, \App\validators\Extensions\Polygons\Spikes::class, \App\validators\Extensions\Polygons\WithinCountry::class, diff --git a/tests/Unit/Validators/Extensions/PolygonValidatorsTest.php b/tests/Unit/Validators/Extensions/PolygonValidatorsTest.php index ab910bec0..2237a3e59 100644 --- a/tests/Unit/Validators/Extensions/PolygonValidatorsTest.php +++ b/tests/Unit/Validators/Extensions/PolygonValidatorsTest.php @@ -52,6 +52,11 @@ public function test_estimated_area() $this->runValidationImportTest('ESTIMATED_AREA'); } + public function test_geometry_type() + { + $this->runValidationTest('GEOMETRY_TYPE'); + } + public function test_schema() { $this->runValidationTest('SCHEMA'); diff --git a/tests/Unit/Validators/Extensions/Polygons/TestFiles/geometry_type_fail.geojson b/tests/Unit/Validators/Extensions/Polygons/TestFiles/geometry_type_fail.geojson new file mode 100644 index 000000000..416cb9f31 --- /dev/null +++ b/tests/Unit/Validators/Extensions/Polygons/TestFiles/geometry_type_fail.geojson @@ -0,0 +1 @@ +{"type":"FeatureCollection","features":[{"type":"Feature","properties":{},"geometry":{"coordinates":[[[33.03873351756249,-2.0430255122929424],[33.040856449501575,-2.0647156071144366]]],"type":"LineString"}}]} diff --git a/tests/Unit/Validators/Extensions/Polygons/TestFiles/geometry_type_pass.geojson b/tests/Unit/Validators/Extensions/Polygons/TestFiles/geometry_type_pass.geojson new file mode 100644 index 000000000..1a44b34d4 --- /dev/null +++ b/tests/Unit/Validators/Extensions/Polygons/TestFiles/geometry_type_pass.geojson @@ -0,0 +1 @@ +{"type":"FeatureCollection","features":[{"type":"Feature","properties":{},"geometry":{"coordinates":[[[32.98790342060309,-2.0425428020699883],[32.98790812940635,-2.048908036281489],[32.9968703696056,-2.04890837910024],[32.99781331634469,-2.0439575527450273],[32.99285096063568,-2.0371099340211885],[32.98790342060309,-2.0425428020699883]]],"type":"Polygon"}}]} \ No newline at end of file From 19099baba6d61ce91e676e523ac37dd74974959d Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Fri, 10 May 2024 12:45:10 -0700 Subject: [PATCH 34/64] [TM-799] Finished polygon post endpoint. --- .../Sites/StoreBulkSiteGeometryController.php | 32 +++++++++- .../TerrafundCreateGeometryController.php | 2 +- app/Models/V2/PolygonGeometry.php | 14 +++- app/Models/V2/Sites/CriteriaSite.php | 5 ++ app/Services/PolygonService.php | 26 ++++++-- .../Extensions/Polygons/EstimatedArea.php | 6 +- .../Extensions/Polygons/FeatureBounds.php | 29 +++++++-- .../Extensions/Polygons/GeometryType.php | 6 +- .../Extensions/Polygons/HasPolygonSite.php | 6 +- .../Extensions/Polygons/NotOverlapping.php | 6 +- .../Extensions/Polygons/PolygonSize.php | 6 +- .../Extensions/Polygons/SelfIntersection.php | 6 +- app/Validators/Extensions/Polygons/Spikes.php | 6 +- .../Extensions/Polygons/WithinCountry.php | 6 +- app/Validators/SitePolygonValidator.php | 4 ++ .../V2/definitions/SiteGeometryPost.yml | 38 ++++++++++- .../Sites/post-v2-sites-uuid-geometry.yml | 2 +- resources/docs/swagger-v2.yml | 64 +++++++++++++++++-- 18 files changed, 206 insertions(+), 58 deletions(-) diff --git a/app/Http/Controllers/V2/Sites/StoreBulkSiteGeometryController.php b/app/Http/Controllers/V2/Sites/StoreBulkSiteGeometryController.php index b2cf98f96..6740b226a 100644 --- a/app/Http/Controllers/V2/Sites/StoreBulkSiteGeometryController.php +++ b/app/Http/Controllers/V2/Sites/StoreBulkSiteGeometryController.php @@ -3,12 +3,14 @@ namespace App\Http\Controllers\V2\Sites; use App\Http\Controllers\Controller; +use App\Models\V2\PolygonGeometry; use App\Models\V2\Sites\Site; use App\Services\PolygonService; use App\Validators\SitePolygonValidator; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\App; +use Illuminate\Support\Facades\Log; use Illuminate\Validation\ValidationException; class StoreBulkSiteGeometryController extends Controller @@ -16,6 +18,7 @@ class StoreBulkSiteGeometryController extends Controller public const VALIDATIONS = [ PolygonService::OVERLAPPING_CRITERIA_ID => 'NOT_OVERLAPPING', PolygonService::SELF_CRITERIA_ID => 'SELF_INTERSECTION_UUID', + PolygonService::COORDINATE_SYSTEM_CRITERIA_ID => 'FEATURE_BOUNDS_UUID', PolygonService::SIZE_CRITERIA_ID => 'POLYGON_SIZE_UUID', PolygonService::WITHIN_COUNTRY_CRITERIA_ID => 'WITHIN_COUNTRY', PolygonService::SPIKE_CRITERIA_ID => 'SPIKES_UUID', @@ -41,8 +44,9 @@ public function __invoke(Request $request, Site $site): JsonResponse // Do the validation in a separate step so that all of the existing polygons are taken into account // for things like overlapping and estimated area. + $polygonErrors = []; foreach ($polygonUuids as $polygonUuid) { - $data = ['polygon_uuid' => $polygonUuid]; + $data = ['geometry' => $polygonUuid]; foreach (self::VALIDATIONS as $criteriaId => $validation) { $valid = true; @@ -50,13 +54,35 @@ public function __invoke(Request $request, Site $site): JsonResponse SitePolygonValidator::validate($validation, $data); } catch (ValidationException $exception) { $valid = false; + Log::info('ValidationException: ' . $validation . ', ' . $exception->getMessage()); + $polygonErrors[$polygonUuid][] = json_decode($exception->errors()['geometry'][0]); } $service->createCriteriaSite($polygonUuid, $criteriaId, $valid); - // TODO: accumulate errors and add to response + } + + // For these two, the createGeojsonModels already handled creating the site criteria, so we just need to + // report on them if not valid + $polygon = PolygonGeometry::isUuid($polygonUuid)->select('uuid')->first(); + $schemaCriteria = $polygon->criteriaSite()->forCriteria(PolygonService::SCHEMA_CRITERIA_ID)->first(); + if ($schemaCriteria != null && ! $schemaCriteria->valid) { + $polygonErrors[$polygonUuid][] = [ + 'key' => 'TABLE_SCHEMA', + 'message' => 'The properties for the geometry are missing some required values.', + ]; + } else { + // only report data validation if the schema was valid. When the schema is invalid, the data is + // always invalid as well. + $dataCriteria = $polygon->criteriaSite()->forCriteria(PolygonService::DATA_CRITERIA_ID)->first(); + if ($dataCriteria != null && ! $dataCriteria->valid) { + $polygonErrors[$polygonUuid][] = [ + 'key' => 'DATA_COMPLETED', + 'message' => 'The properties for the geometry have some invalid values.', + ]; + } } } - return response()->json(['polygon_uuids' => $polygonUuids]); + return response()->json(['polygon_uuids' => $polygonUuids, 'errors' => $polygonErrors], 201); } } diff --git a/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php b/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php index 14068ba91..92484e901 100644 --- a/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php +++ b/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php @@ -9,9 +9,9 @@ use App\Models\V2\WorldCountryGeneralized; use App\Services\PolygonService; use App\Validators\Extensions\Polygons\EstimatedArea; +use App\Validators\Extensions\Polygons\GeometryType; use App\Validators\Extensions\Polygons\NotOverlapping; use App\Validators\Extensions\Polygons\PolygonSize; -use App\Validators\Extensions\Polygons\GeometryType; use App\Validators\Extensions\Polygons\SelfIntersection; use App\Validators\Extensions\Polygons\Spikes; use App\Validators\Extensions\Polygons\WithinCountry; diff --git a/app/Models/V2/PolygonGeometry.php b/app/Models/V2/PolygonGeometry.php index cf6e337d0..7c2e03176 100644 --- a/app/Models/V2/PolygonGeometry.php +++ b/app/Models/V2/PolygonGeometry.php @@ -4,8 +4,11 @@ use App\Models\Traits\HasUuid; use App\Models\V2\Sites\CriteriaSite; +use App\Models\V2\Sites\SitePolygon; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; /** @@ -24,11 +27,16 @@ class PolygonGeometry extends Model 'polygon_id', 'geom', ]; - public function criteriaSite() + public function criteriaSite(): HasMany { return $this->hasMany(CriteriaSite::class, 'polygon_id', 'uuid'); } + public function sitePolygon(): BelongsTo + { + return $this->belongsTo(SitePolygon::class, 'uuid', 'poly_id'); + } + public static function getGeoJson(string $uuid): ?array { $geojson_string = PolygonGeometry::isUuid($uuid) @@ -47,9 +55,9 @@ public function getGeoJsonAttribute(): array public static function getGeometryType(string $uuid): ?string { return PolygonGeometry::isUuid($uuid) - ->selectRaw('ST_GeometryType(geom) as geometry_type') + ->selectRaw('ST_GeometryType(geom) as geometry_type_string') ->first() - ?->geometry_type; + ?->geometry_type_string; } public function getGeometryTypeAttribute(): string diff --git a/app/Models/V2/Sites/CriteriaSite.php b/app/Models/V2/Sites/CriteriaSite.php index 7d530b2d1..93c72f370 100644 --- a/app/Models/V2/Sites/CriteriaSite.php +++ b/app/Models/V2/Sites/CriteriaSite.php @@ -39,4 +39,9 @@ class CriteriaSite extends Model 'deleted_at', 'date_created', ]; + + public function scopeForCriteria($query, $criteriaId) + { + return $query->where('criteria_id', $criteriaId); + } } diff --git a/app/Services/PolygonService.php b/app/Services/PolygonService.php index 94d00b6c9..233320b6c 100644 --- a/app/Services/PolygonService.php +++ b/app/Services/PolygonService.php @@ -6,6 +6,7 @@ use App\Models\V2\Sites\CriteriaSite; use App\Models\V2\Sites\SitePolygon; use App\Validators\SitePolygonValidator; +use Illuminate\Support\Carbon; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; @@ -13,6 +14,7 @@ class PolygonService { public const OVERLAPPING_CRITERIA_ID = 3; public const SELF_CRITERIA_ID = 4; + public const COORDINATE_SYSTEM_CRITERIA_ID = 5; public const SIZE_CRITERIA_ID = 6; public const WITHIN_COUNTRY_CRITERIA_ID = 7; public const SPIKE_CRITERIA_ID = 8; @@ -98,6 +100,22 @@ private function insertSinglePolygon(array $geometry): array private function insertSitePolygon(string $polygonUuid, array $properties, float $area) { try { + // Avoid trying to store an invalid date string or int in the DB, as that will throw an exception and prevent + // the site polygon from storing. With an invalid date, this will end up reporting schema invalid and data + // invalid, which isn't necessarily correct for the payload given, but it does reflect the status in the DB + try { + $properties['plantstart'] = empty($properties['plantstart']) ? null : Carbon::parse($properties['plantstart']); + } catch (\Exception $e) { + $properties['plantstart'] = null; + } + + try { + $properties['plantend'] = empty($properties['plantend']) ? null : Carbon::parse($properties['plantend']); + } catch (\Exception $e) { + $properties['plantend'] = null; + } + $properties['num_trees'] = is_int($properties['num_trees'] ?? null) ? $properties['num_trees'] : null; + $validationGeojson = ['features' => [ 'feature' => ['properties' => $properties], ]]; @@ -116,12 +134,12 @@ private function insertSitePolygon(string $polygonUuid, array $properties, float $sitePolygon->site_id = $properties['site_id'] ?? null; $sitePolygon->site_name = $properties['site_name'] ?? null; $sitePolygon->poly_label = $properties['poly_label'] ?? null; - $sitePolygon->plantstart = ! empty($properties['plantstart']) ? $properties['plantstart'] : null; - $sitePolygon->plantend = ! empty($properties['plantend']) ? $properties['plantend'] : null; - $sitePolygon->practice = $properties['practice'] ?? null; + $sitePolygon->plantstart = $properties['plantstart'] ?? null; + $sitePolygon->plantend = $properties['plantend']; + $sitePolygon->practice = $properties['practice']; $sitePolygon->target_sys = $properties['target_sys'] ?? null; $sitePolygon->distr = $properties['distr'] ?? null; - $sitePolygon->num_trees = $properties['num_trees'] ?? null; + $sitePolygon->num_trees = $properties['num_trees']; $sitePolygon->est_area = $area ?? null; $sitePolygon->save(); diff --git a/app/Validators/Extensions/Polygons/EstimatedArea.php b/app/Validators/Extensions/Polygons/EstimatedArea.php index 41f53bb01..bff44f84c 100644 --- a/app/Validators/Extensions/Polygons/EstimatedArea.php +++ b/app/Validators/Extensions/Polygons/EstimatedArea.php @@ -10,10 +10,8 @@ class EstimatedArea extends Extension public static $name = 'estimated_area'; public static $message = [ - 'ESTIMATED_AREA', - 'The {{attribute}} field must represent a polygon that matches the site\'s estimated area', - ['attribute' => ':attribute'], - 'The :attribute field must represent a polygon that matches the site\'s estimated area', + 'key' => 'TOTAL_AREA_EXPECTED', + 'message' => 'The project\'s total geometry must match the project\'s estimated area.', ]; public static function passes($attribute, $value, $parameters, $validator): bool diff --git a/app/Validators/Extensions/Polygons/FeatureBounds.php b/app/Validators/Extensions/Polygons/FeatureBounds.php index ff41ab868..4f8bca13c 100644 --- a/app/Validators/Extensions/Polygons/FeatureBounds.php +++ b/app/Validators/Extensions/Polygons/FeatureBounds.php @@ -2,6 +2,7 @@ namespace App\Validators\Extensions\Polygons; +use App\Models\V2\PolygonGeometry; use App\Validators\Extensions\Extension; class FeatureBounds extends Extension @@ -9,19 +10,33 @@ class FeatureBounds extends Extension public static $name = 'polygon_feature_bounds'; public static $message = [ - 'FEATURE_BOUNDS', - 'The {{attribute}} field must have valid feature polygon bounds.', - ['attribute' => ':attribute'], - 'The :attribute field must have valid feature polygon bounds.', + 'key' => 'COORDINATE_SYSTEM', + 'message' => 'The coordinates must have valid lat/lng values.', ]; public static function passes($attribute, $value, $parameters, $validator): bool { - $type = data_get($value, 'geometry.type'); + if (is_string($value)) { + // assume we have a DB UUID + return self::uuidValid($value); + } + + // assume we have a GeoJSON + return self::geoJsonValid($value); + } + + public static function uuidValid($uuid): bool + { + return self::geoJsonValid(PolygonGeometry::getGeoJson($uuid)); + } + + public static function geoJsonValid($geojson): bool + { + $type = data_get($geojson, 'geometry.type'); if ($type === 'Polygon') { - return self::hasValidPolygonBounds(data_get($value, 'geometry.coordinates.0')); + return self::hasValidPolygonBounds(data_get($geojson, 'geometry.coordinates.0')); } elseif ($type === 'MultiPolygon') { - foreach (data_get($value, 'geometry.coordinates') as $coordinates) { + foreach (data_get($geojson, 'geometry.coordinates') as $coordinates) { if (! self::hasValidPolygonBounds($coordinates)) { return false; } diff --git a/app/Validators/Extensions/Polygons/GeometryType.php b/app/Validators/Extensions/Polygons/GeometryType.php index 621ad2ba5..f3361124b 100644 --- a/app/Validators/Extensions/Polygons/GeometryType.php +++ b/app/Validators/Extensions/Polygons/GeometryType.php @@ -11,10 +11,8 @@ class GeometryType extends Extension public static $name = 'geometry_type'; public static $message = [ - 'GEOMETRY_TYPE', - 'The {{attribute}} field must represent geojson that is polygon geometry', - ['attribute' => ':attribute'], - 'The :attribute field must represent geojson that is polygon geometry', + 'key' => 'GEOMETRY_TYPE', + 'message' => 'The geometry must by of polygon type', ]; public const VALID_TYPE = 'POLYGON'; diff --git a/app/Validators/Extensions/Polygons/HasPolygonSite.php b/app/Validators/Extensions/Polygons/HasPolygonSite.php index 1c6c7132a..0c4bfd032 100644 --- a/app/Validators/Extensions/Polygons/HasPolygonSite.php +++ b/app/Validators/Extensions/Polygons/HasPolygonSite.php @@ -10,10 +10,8 @@ class HasPolygonSite extends Extension public static $name = 'has_polygon_site'; public static $message = [ - 'HAS_POLYGON_SITE', - 'The {{attribute}} field must represent a polygon with an attached site', - ['attribute' => ':attribute'], - 'The :attribute field must represent a polygon with an attached site', + 'key' => 'HAS_POLYGON_SITE', + 'message' => 'The geometry must have an attached site', ]; public static function passes($attribute, $value, $parameters, $validator): bool diff --git a/app/Validators/Extensions/Polygons/NotOverlapping.php b/app/Validators/Extensions/Polygons/NotOverlapping.php index f33992d08..79dc3fb0c 100644 --- a/app/Validators/Extensions/Polygons/NotOverlapping.php +++ b/app/Validators/Extensions/Polygons/NotOverlapping.php @@ -11,10 +11,8 @@ class NotOverlapping extends Extension public static $name = 'not_overlapping'; public static $message = [ - 'NOT_OVERLAPPING', - 'The {{attribute}} field must represent a polygon that does not overlap with other site polygons', - ['attribute' => ':attribute'], - 'The :attribute field must represent a polygon that does not overlap with other site polygons', + 'key' => 'OVERLAPPING_POLYGON', + 'message' => 'The geometry must not overlap with other project geometry', ]; public static function passes($attribute, $value, $parameters, $validator): bool diff --git a/app/Validators/Extensions/Polygons/PolygonSize.php b/app/Validators/Extensions/Polygons/PolygonSize.php index 8f500706f..c51fc9abc 100644 --- a/app/Validators/Extensions/Polygons/PolygonSize.php +++ b/app/Validators/Extensions/Polygons/PolygonSize.php @@ -11,10 +11,8 @@ class PolygonSize extends Extension public static $name = 'polygon_size'; public static $message = [ - 'POLYGON_SIZE', - 'The {{attribute}} field must not represent a polygon that is too large.', - ['attribute' => ':attribute'], - 'The :attribute field must not represent a polygon that is too large.', + 'key' => 'SIZE_LIMIT', + 'message' => 'The geometry must not be larger than ' . self::SIZE_LIMIT . 'square kilometers', ]; public const SIZE_LIMIT = 10000000; diff --git a/app/Validators/Extensions/Polygons/SelfIntersection.php b/app/Validators/Extensions/Polygons/SelfIntersection.php index a7db606e6..e0e840754 100644 --- a/app/Validators/Extensions/Polygons/SelfIntersection.php +++ b/app/Validators/Extensions/Polygons/SelfIntersection.php @@ -10,10 +10,8 @@ class SelfIntersection extends Extension public static $name = 'polygon_self_intersection'; public static $message = [ - 'SELF_INTERSECTION', - 'The {{attribute}} geometry field must not self intersect.', - ['attribute' => ':attribute'], - 'The :attribute geometry field must not self intersect.', + 'key' => 'SELF_INTERSECTION', + 'message' => 'The geometry must not self intersect.', ]; public static function passes($attribute, $value, $parameters, $validator): bool diff --git a/app/Validators/Extensions/Polygons/Spikes.php b/app/Validators/Extensions/Polygons/Spikes.php index 65be09afb..be7176b16 100644 --- a/app/Validators/Extensions/Polygons/Spikes.php +++ b/app/Validators/Extensions/Polygons/Spikes.php @@ -10,10 +10,8 @@ class Spikes extends Extension public static $name = 'polygon_spikes'; public static $message = [ - 'POLYGON_SPIKES', - 'The {{attribute}} field must not represent a polygon with spikes', - ['attribute' => ':attribute'], - 'The :attribute field must not represent a polygon with spikes', + 'key' => 'SPIKE', + 'message' => 'The geometry must not have spikes', ]; public static function passes($attribute, $value, $parameters, $validator): bool diff --git a/app/Validators/Extensions/Polygons/WithinCountry.php b/app/Validators/Extensions/Polygons/WithinCountry.php index 52fa1e282..98571c3ba 100644 --- a/app/Validators/Extensions/Polygons/WithinCountry.php +++ b/app/Validators/Extensions/Polygons/WithinCountry.php @@ -12,10 +12,8 @@ class WithinCountry extends Extension public static $name = 'within_country'; public static $message = [ - 'WITHIN_COUNTRY', - 'The {{attribute}} field must represent a polygon that is within its assigned country', - ['attribute' => ':attribute'], - 'The :attribute field must represent a polygon that is within its assigned country', + 'key' => 'WITHIN_COUNTRY', + 'message' => 'The geometry must be within the project\'s assigned country', ]; public const THRESHOLD_PERCENTAGE = 75; diff --git a/app/Validators/SitePolygonValidator.php b/app/Validators/SitePolygonValidator.php index 830aba4fd..1f1bca768 100644 --- a/app/Validators/SitePolygonValidator.php +++ b/app/Validators/SitePolygonValidator.php @@ -9,6 +9,10 @@ class SitePolygonValidator extends Validator 'features.*' => 'polygon_feature_bounds', ]; + public const FEATURE_BOUNDS_UUID = [ + '*' => 'string|uuid||polygon_feature_bounds', + ]; + public const SPIKES = [ 'features' => 'required|array', 'features.*.geometry' => 'polygon_spikes', diff --git a/openapi-src/V2/definitions/SiteGeometryPost.yml b/openapi-src/V2/definitions/SiteGeometryPost.yml index ff984504d..d304c25d4 100644 --- a/openapi-src/V2/definitions/SiteGeometryPost.yml +++ b/openapi-src/V2/definitions/SiteGeometryPost.yml @@ -1,5 +1,39 @@ title: SiteGeometryPost type: object properties: - uuid: - type: string + polygon_uuids: + type: array + items: + type: string + description: + The UUIDs generated by the system for the uploaded polygons. They are in the same order as the polygons in the + request payload. + errors: + type: object + description: + Mapping of geometry UUID to the errors associated with the geometry. The geometry was saved in the DB and must + be updated instead of created once the issues are resolved. + additionalProperties: + type: array + items: + type: object + properties: + key: + type: string + enum: + - OVERLAPPING_POLYGON + - SELF_INTERSECTION + - COORDINATE_SYSTEM + - SIZE_LIMIT + - WITHIN_COUNTRY + - SPIKE + - GEOMETRY_TYPE + - TOTAL_AREA_EXPECTED + - TABLE_SCHEMA + - DATA_COMPLETED + message: + type: string + description: Human readable string in english to describe the error. + + + diff --git a/openapi-src/V2/paths/Sites/post-v2-sites-uuid-geometry.yml b/openapi-src/V2/paths/Sites/post-v2-sites-uuid-geometry.yml index 0834e23fa..d355001a6 100644 --- a/openapi-src/V2/paths/Sites/post-v2-sites-uuid-geometry.yml +++ b/openapi-src/V2/paths/Sites/post-v2-sites-uuid-geometry.yml @@ -17,7 +17,7 @@ parameters: items: $ref: '../../definitions/_index.yml#/GeoJSON' responses: - '200': + '201': description: Created schema: $ref: '../../definitions/_index.yml#/SiteGeometryPost' diff --git a/resources/docs/swagger-v2.yml b/resources/docs/swagger-v2.yml index 523a049ad..b8d72727c 100644 --- a/resources/docs/swagger-v2.yml +++ b/resources/docs/swagger-v2.yml @@ -44054,8 +44054,35 @@ definitions: title: SiteGeometryPost type: object properties: - uuid: - type: string + polygon_uuids: + type: array + items: + type: string + description: The UUIDs generated by the system for the uploaded polygons. They are in the same order as the polygons in the request payload. + errors: + type: object + description: Mapping of geometry UUID to the errors associated with the geometry. The geometry was saved in the DB and must be updated instead of created once the issues are resolved. + additionalProperties: + type: array + items: + type: object + properties: + key: + type: string + enum: + - OVERLAPPING_POLYGON + - SELF_INTERSECTION + - COORDINATE_SYSTEM + - SIZE_LIMIT + - WITHIN_COUNTRY + - SPIKE + - GEOMETRY_TYPE + - TOTAL_AREA_EXPECTED + - TABLE_SCHEMA + - DATA_COMPLETED + message: + type: string + description: Human readable string in english to describe the error. paths: '/v2/tree-species/{entity}/{UUID}': get: @@ -93177,14 +93204,41 @@ paths: items: type: number responses: - '200': + '201': description: Created schema: title: SiteGeometryPost type: object properties: - uuid: - type: string + polygon_uuids: + type: array + items: + type: string + description: The UUIDs generated by the system for the uploaded polygons. They are in the same order as the polygons in the request payload. + errors: + type: object + description: Mapping of geometry UUID to the errors associated with the geometry. The geometry was saved in the DB and must be updated instead of created once the issues are resolved. + additionalProperties: + type: array + items: + type: object + properties: + key: + type: string + enum: + - OVERLAPPING_POLYGON + - SELF_INTERSECTION + - COORDINATE_SYSTEM + - SIZE_LIMIT + - WITHIN_COUNTRY + - SPIKE + - GEOMETRY_TYPE + - TOTAL_AREA_EXPECTED + - TABLE_SCHEMA + - DATA_COMPLETED + message: + type: string + description: Human readable string in english to describe the error. '/v2/site-monitorings/{UUID}': get: summary: View a specific site monitoring From 36945c2451f299df8e2c202e07887844aee16c70 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Fri, 10 May 2024 13:17:30 -0700 Subject: [PATCH 35/64] [TM-799] Fix use path for polygon validators. --- app/Validators/Validator.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/Validators/Validator.php b/app/Validators/Validator.php index 18caafe4c..d4f1d4f69 100644 --- a/app/Validators/Validator.php +++ b/app/Validators/Validator.php @@ -66,15 +66,15 @@ class Validator \App\Validators\Extensions\TerrafundDisturbance::class, \App\Validators\Extensions\CompletePercentage::class, \App\Validators\Extensions\OrganisationFileType::class, - \App\validators\Extensions\Polygons\EstimatedArea::class, - \App\validators\Extensions\Polygons\FeatureBounds::class, - \App\validators\Extensions\Polygons\HasPolygonSite::class, - \App\validators\Extensions\Polygons\NotOverlapping::class, - \App\validators\Extensions\Polygons\PolygonSize::class, - \App\validators\Extensions\Polygons\GeometryType::class, - \App\validators\Extensions\Polygons\SelfIntersection::class, - \App\validators\Extensions\Polygons\Spikes::class, - \App\validators\Extensions\Polygons\WithinCountry::class, + \App\Validators\Extensions\Polygons\EstimatedArea::class, + \App\Validators\Extensions\Polygons\FeatureBounds::class, + \App\Validators\Extensions\Polygons\HasPolygonSite::class, + \App\Validators\Extensions\Polygons\NotOverlapping::class, + \App\Validators\Extensions\Polygons\PolygonSize::class, + \App\Validators\Extensions\Polygons\GeometryType::class, + \App\Validators\Extensions\Polygons\SelfIntersection::class, + \App\Validators\Extensions\Polygons\Spikes::class, + \App\Validators\Extensions\Polygons\WithinCountry::class, ]; private function __construct() From a7a4dea68523d377405f109525ab8786de782402 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Fri, 10 May 2024 14:14:00 -0700 Subject: [PATCH 36/64] [TM-800] GH polygon validation endpoint implemented. --- .../GeometryController.php} | 67 +++++++++-- app/Validators/SitePolygonValidator.php | 14 +-- .../V2/definitions/SiteGeometryPost.yml | 2 +- .../Geometry/post-v2-geometry-validate.yml | 55 +++++++++ openapi-src/V2/paths/_index.yml | 3 + resources/docs/swagger-v2.yml | 109 +++++++++++++++++- routes/api_v2.php | 8 +- 7 files changed, 239 insertions(+), 19 deletions(-) rename app/Http/Controllers/V2/{Sites/StoreBulkSiteGeometryController.php => Geometry/GeometryController.php} (56%) create mode 100644 openapi-src/V2/paths/Geometry/post-v2-geometry-validate.yml diff --git a/app/Http/Controllers/V2/Sites/StoreBulkSiteGeometryController.php b/app/Http/Controllers/V2/Geometry/GeometryController.php similarity index 56% rename from app/Http/Controllers/V2/Sites/StoreBulkSiteGeometryController.php rename to app/Http/Controllers/V2/Geometry/GeometryController.php index 6740b226a..24725d3e2 100644 --- a/app/Http/Controllers/V2/Sites/StoreBulkSiteGeometryController.php +++ b/app/Http/Controllers/V2/Geometry/GeometryController.php @@ -1,6 +1,6 @@ 'NOT_OVERLAPPING', PolygonService::SELF_CRITERIA_ID => 'SELF_INTERSECTION_UUID', PolygonService::COORDINATE_SYSTEM_CRITERIA_ID => 'FEATURE_BOUNDS_UUID', @@ -26,7 +25,17 @@ class StoreBulkSiteGeometryController extends Controller PolygonService::ESTIMATED_AREA_CRITERIA_ID => 'ESTIMATED_AREA', ]; - public function __invoke(Request $request, Site $site): JsonResponse + public const NON_PERSISTED_VALIDATIONS = [ + 'SELF_INTERSECTION', + 'FEATURE_BOUNDS', + 'POLYGON_SIZE', + 'SPIKES', + 'GEOMETRY_TYPE', + 'SCHEMA', + 'DATA', + ]; + + public function storeSiteGeometry(Request $request, Site $site): JsonResponse { $this->authorize('uploadPolygons', $site); @@ -47,14 +56,13 @@ public function __invoke(Request $request, Site $site): JsonResponse $polygonErrors = []; foreach ($polygonUuids as $polygonUuid) { $data = ['geometry' => $polygonUuid]; - foreach (self::VALIDATIONS as $criteriaId => $validation) { + foreach (self::STORE_GEOMETRY_VALIDATIONS as $criteriaId => $validation) { $valid = true; try { SitePolygonValidator::validate($validation, $data); } catch (ValidationException $exception) { $valid = false; - Log::info('ValidationException: ' . $validation . ', ' . $exception->getMessage()); $polygonErrors[$polygonUuid][] = json_decode($exception->errors()['geometry'][0]); } @@ -85,4 +93,49 @@ public function __invoke(Request $request, Site $site): JsonResponse return response()->json(['polygon_uuids' => $polygonUuids, 'errors' => $polygonErrors], 201); } + + public function validateGeometries(Request $request): JsonResponse + { + $request->validate([ + 'geometries' => 'required|array', + ]); + + $geometryErrors = collect(); + foreach ($request->input('geometries') as $geometry) { + $errors = collect(); + foreach (self::NON_PERSISTED_VALIDATIONS as $validation) { + try { + SitePolygonValidator::validate($validation, $geometry, false); + } catch (ValidationException $exception) { + $errors = $errors->merge(collect($exception->errors())->map( + function (array $errorItems, $field) use ($validation) { + return collect($errorItems)->map(function ($errorItemString) use ($validation, $field) { + $errorItem = json_decode($errorItemString, true); + if (array_key_exists('key', $errorItem)) { + // This is an error that came from one of our geometry validations + $errorItem['field'] = $field; + return $errorItem; + } else { + // This is an error that came from the schema or data validations. The last item in the + // array contains a descriptive message, so we can simply return that one. + return [ + 'field' => $field, + 'key' => $validation, + 'message' => array_pop($errorItem), + ]; + } + }); + } + )); + } + } + $geometryErrors->push($errors->values()->flatten(1)); + } + + if (collect($geometryErrors)->flatten()->isEmpty()) { + return response()->json(['errors' => []]); + } else { + return response()->json(['errors' => $geometryErrors], 422); + } + } } diff --git a/app/Validators/SitePolygonValidator.php b/app/Validators/SitePolygonValidator.php index 1f1bca768..dc303d764 100644 --- a/app/Validators/SitePolygonValidator.php +++ b/app/Validators/SitePolygonValidator.php @@ -74,12 +74,12 @@ class SitePolygonValidator extends Validator public const DATA = [ 'features' => 'required|array', - 'features.*.properties.poly_name' => 'required|string|not_in:null,NULL', - 'features.*.properties.plantstart' => 'required|date|', - 'features.*.properties.plantend' => 'required|date|', - 'features.*.properties.practice' => 'required|string|not_in:null,NULL', - 'features.*.properties.target_sys' => 'required|string|not_in:null,NULL', - 'features.*.properties.distr' => 'required|string|not_in:null,NULL', - 'features.*.properties.num_trees' => 'required|integer|', + 'features.*.properties.poly_name' => 'string|not_in:null,NULL', + 'features.*.properties.plantstart' => 'date', + 'features.*.properties.plantend' => 'date', + 'features.*.properties.practice' => 'string|not_in:null,NULL', + 'features.*.properties.target_sys' => 'string|not_in:null,NULL', + 'features.*.properties.distr' => 'string|not_in:null,NULL', + 'features.*.properties.num_trees' => 'integer', ]; } diff --git a/openapi-src/V2/definitions/SiteGeometryPost.yml b/openapi-src/V2/definitions/SiteGeometryPost.yml index d304c25d4..c4b833860 100644 --- a/openapi-src/V2/definitions/SiteGeometryPost.yml +++ b/openapi-src/V2/definitions/SiteGeometryPost.yml @@ -33,7 +33,7 @@ properties: - DATA_COMPLETED message: type: string - description: Human readable string in english to describe the error. + description: Human readable string in English to describe the error. diff --git a/openapi-src/V2/paths/Geometry/post-v2-geometry-validate.yml b/openapi-src/V2/paths/Geometry/post-v2-geometry-validate.yml new file mode 100644 index 000000000..b0a6865f7 --- /dev/null +++ b/openapi-src/V2/paths/Geometry/post-v2-geometry-validate.yml @@ -0,0 +1,55 @@ +summary: Test a set of geometries for validity +operationId: post-v2-geometry-validate +tags: + - V2 Geometry +parameters: + - in: body + name: body + schema: + type: object + properties: + geometries: + type: array + items: + $ref: '../../definitions/_index.yml#/GeoJSON' +responses: + '200': + description: 'OK: No validation errors occurred with the supplied geometries' + schema: + type: object + properties: + errors: + type: array + description: An empty array on the OK response is included for ease of parsing on the client side. + '422': + description: One or more errors was found with the supplied geometries + schema: + type: object + properties: + errors: + type: array + description: + This array is ordered in the same order as the original geometries. If a given geometry had no errors, an + empty array is included in its spot. + items: + type: array + items: + type: object + properties: + key: + type: string + enum: + - SELF_INTERSECTION + - COORDINATE_SYSTEM + - SIZE_LIMIT + - SPIKE + - GEOMETRY_TYPE + - TABLE_SCHEMA + - DATA_COMPLETED + message: + type: string + description: Human readable string in English to describe the error. + field: + type: string + description: A path string indicating where the error occurred. + diff --git a/openapi-src/V2/paths/_index.yml b/openapi-src/V2/paths/_index.yml index 8643e841a..279d67c42 100644 --- a/openapi-src/V2/paths/_index.yml +++ b/openapi-src/V2/paths/_index.yml @@ -2514,3 +2514,6 @@ /v2/{ENTITY}/{UUID}/export: get: $ref: './Exports/get-v2-entity-export-uuid.yml' +/v2/geometry/validate: + post: + $ref: './Geometry/post-v2-geometry-validate.yml' diff --git a/resources/docs/swagger-v2.yml b/resources/docs/swagger-v2.yml index b8d72727c..102b4f76d 100644 --- a/resources/docs/swagger-v2.yml +++ b/resources/docs/swagger-v2.yml @@ -44082,7 +44082,7 @@ definitions: - DATA_COMPLETED message: type: string - description: Human readable string in english to describe the error. + description: Human readable string in English to describe the error. paths: '/v2/tree-species/{entity}/{UUID}': get: @@ -93238,7 +93238,7 @@ paths: - DATA_COMPLETED message: type: string - description: Human readable string in english to describe the error. + description: Human readable string in English to describe the error. '/v2/site-monitorings/{UUID}': get: summary: View a specific site monitoring @@ -93753,3 +93753,108 @@ paths: description: OK schema: type: file + /v2/geometry/validate: + post: + summary: Test a set of geometries for validity + operationId: post-v2-geometry-validate + tags: + - V2 Geometry + parameters: + - in: body + name: body + schema: + type: object + properties: + geometries: + type: array + items: + title: GeoJSON + type: object + properties: + type: + type: string + enum: + - FeatureCollection + features: + type: array + items: + type: object + properties: + type: + type: string + enum: + - Feature + properties: + type: object + properties: + poly_name: + type: string + plantstart: + type: string + format: date + plantend: + type: string + format: date + practice: + type: string + target_sys: + type: string + distr: + type: string + num_trees: + type: number + site_id: + type: string + geometry: + type: object + properties: + type: + type: string + enum: + - Polygon + coordinates: + type: array + items: + type: array + items: + type: array + items: + type: number + responses: + '200': + description: 'OK: No validation errors occurred with the supplied geometries' + schema: + type: object + properties: + errors: + type: array + description: An empty array on the OK response is included for ease of parsing on the client side. + '422': + description: One or more errors was found with the supplied geometries + schema: + type: object + properties: + errors: + type: array + description: 'This array is ordered in the same order as the original geometries. If a given geometry had no errors, an empty array is included in its spot.' + items: + type: array + items: + type: object + properties: + key: + type: string + enum: + - SELF_INTERSECTION + - COORDINATE_SYSTEM + - SIZE_LIMIT + - SPIKE + - GEOMETRY_TYPE + - TABLE_SCHEMA + - DATA_COMPLETED + message: + type: string + description: Human readable string in English to describe the error. + field: + type: string + description: A path string indicating where the error occurred. diff --git a/routes/api_v2.php b/routes/api_v2.php index 3c246d953..22088ae76 100644 --- a/routes/api_v2.php +++ b/routes/api_v2.php @@ -78,6 +78,7 @@ use App\Http\Controllers\V2\FundingType\DeleteFundingTypeController; use App\Http\Controllers\V2\FundingType\StoreFundingTypeController; use App\Http\Controllers\V2\FundingType\UpdateFundingTypeController; +use App\Http\Controllers\V2\Geometry\GeometryController; use App\Http\Controllers\V2\Invasives\DeleteInvasiveController; use App\Http\Controllers\V2\Invasives\GetInvasivesForEntityController; use App\Http\Controllers\V2\Invasives\StoreInvasiveController; @@ -156,7 +157,6 @@ use App\Http\Controllers\V2\Sites\Monitoring\AdminUpdateSiteMonitoringController; use App\Http\Controllers\V2\Sites\Monitoring\ViewSiteMonitoringController; use App\Http\Controllers\V2\Sites\SoftDeleteSiteController; -use App\Http\Controllers\V2\Sites\StoreBulkSiteGeometryController; use App\Http\Controllers\V2\Sites\ViewASitesMonitoringsController; use App\Http\Controllers\V2\Stages\DeleteStageController; use App\Http\Controllers\V2\Stages\IndexStageController; @@ -555,7 +555,11 @@ Route::get('/image/locations', SiteImageLocationsController::class); Route::delete('/', SoftDeleteSiteController::class); Route::get('/export', ExportAllSiteDataAsProjectDeveloperController::class); - Route::post('/geometry', StoreBulkSiteGeometryController::class); + Route::post('/geometry', [GeometryController::class, 'storeSiteGeometry']); +}); + +Route::prefix('geometry')->group(function () { + Route::post('/validate', [GeometryController::class, 'validateGeometries']); }); Route::prefix('project-monitorings')->group(function () { From f180ec2721177458659baba7614c1b30c4fdec46 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Fri, 10 May 2024 15:02:42 -0700 Subject: [PATCH 37/64] [TM-801] Implement created_by field on site_polygon and polygon_geometry. --- .../TerrafundCreateGeometryController.php | 4 +- .../TerrafundEditGeometryController.php | 5 ++- app/Models/V2/PolygonGeometry.php | 10 ++++- app/Models/V2/Sites/SitePolygon.php | 8 ++++ app/Services/PolygonService.php | 3 ++ ...5235_add_created_by_to_geometry_tables.php | 39 +++++++++++++++++++ 6 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 database/migrations/2024_05_10_215235_add_created_by_to_geometry_tables.php diff --git a/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php b/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php index 92484e901..0950c961a 100644 --- a/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php +++ b/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php @@ -19,6 +19,7 @@ use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\App; +use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Storage; @@ -49,7 +50,8 @@ public function storeGeometry(Request $request) $geom = DB::raw("ST_GeomFromGeoJSON('" . json_encode($geometry) . "')"); $polygonGeometry = PolygonGeometry::create([ - 'geom' => $geom, + 'geom' => $geom, + 'created_by' => Auth::user()?->id, ]); return response()->json(['uuid' => $polygonGeometry->uuid], 200); diff --git a/app/Http/Controllers/V2/Terrafund/TerrafundEditGeometryController.php b/app/Http/Controllers/V2/Terrafund/TerrafundEditGeometryController.php index a5693c537..e62fb6ded 100644 --- a/app/Http/Controllers/V2/Terrafund/TerrafundEditGeometryController.php +++ b/app/Http/Controllers/V2/Terrafund/TerrafundEditGeometryController.php @@ -6,6 +6,7 @@ use App\Models\V2\PolygonGeometry; use App\Models\V2\Sites\SitePolygon; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; use Illuminate\Support\Str; @@ -109,9 +110,9 @@ public function createSitePolygon(string $uuid, Request $request) 'num_trees' => $validatedData['num_trees'], 'est_area' => $areaHectares, // Assign the calculated area 'target_sys' => $validatedData['target_sys'], + 'poly_id' => $uuid, + 'created_by' => Auth::user()?->id, ]); - $sitePolygon->poly_id = $uuid; - $sitePolygon->uuid = Str::uuid(); $sitePolygon->save(); return response()->json(['message' => 'Site polygon created successfully', 'uuid' => $sitePolygon, 'area' => $areaHectares], 201); diff --git a/app/Models/V2/PolygonGeometry.php b/app/Models/V2/PolygonGeometry.php index 7c2e03176..2737a2404 100644 --- a/app/Models/V2/PolygonGeometry.php +++ b/app/Models/V2/PolygonGeometry.php @@ -9,6 +9,7 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\SoftDeletes; /** @@ -24,7 +25,9 @@ class PolygonGeometry extends Model protected $table = 'polygon_geometry'; protected $fillable = [ - 'polygon_id', 'geom', + 'polygon_id', + 'geom', + 'created_by', ]; public function criteriaSite(): HasMany @@ -37,6 +40,11 @@ public function sitePolygon(): BelongsTo return $this->belongsTo(SitePolygon::class, 'uuid', 'poly_id'); } + public function createdBy(): HasOne + { + return $this->hasOne(User::class, 'id', 'created_by'); + } + public static function getGeoJson(string $uuid): ?array { $geojson_string = PolygonGeometry::isUuid($uuid) diff --git a/app/Models/V2/Sites/SitePolygon.php b/app/Models/V2/Sites/SitePolygon.php index 8c823d3d9..dd420a334 100644 --- a/app/Models/V2/Sites/SitePolygon.php +++ b/app/Models/V2/Sites/SitePolygon.php @@ -5,10 +5,12 @@ use App\Models\Traits\HasUuid; use App\Models\V2\PolygonGeometry; use App\Models\V2\Projects\Project; +use App\Models\V2\User; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\SoftDeletes; use Znck\Eloquent\Relations\BelongsToThrough; use Znck\Eloquent\Traits\BelongsToThrough as BelongsToThroughTrait; @@ -42,6 +44,7 @@ class SitePolygon extends Model 'num_trees', 'est_area', 'country', + 'created_by', ]; public function polygonGeometry(): BelongsTo @@ -63,4 +66,9 @@ public function project(): BelongsToThrough localKeyLookup: [Site::class => 'uuid'] ); } + + public function createdBy(): HasOne + { + return $this->hasOne(User::class, 'id', 'created_by'); + } } diff --git a/app/Services/PolygonService.php b/app/Services/PolygonService.php index 233320b6c..7145e0dc5 100644 --- a/app/Services/PolygonService.php +++ b/app/Services/PolygonService.php @@ -7,6 +7,7 @@ use App\Models\V2\Sites\SitePolygon; use App\Validators\SitePolygonValidator; use Illuminate\Support\Carbon; +use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; @@ -92,6 +93,7 @@ private function insertSinglePolygon(array $geometry): array $polygonGeometry = PolygonGeometry::create([ 'geom' => $geom, + 'created_by' => Auth::user()?->id, ]); return ['uuid' => $polygonGeometry->uuid, 'area' => $areaHectares]; @@ -141,6 +143,7 @@ private function insertSitePolygon(string $polygonUuid, array $properties, float $sitePolygon->distr = $properties['distr'] ?? null; $sitePolygon->num_trees = $properties['num_trees']; $sitePolygon->est_area = $area ?? null; + $sitePolygon->created_by = Auth::user()?->id; $sitePolygon->save(); return null; diff --git a/database/migrations/2024_05_10_215235_add_created_by_to_geometry_tables.php b/database/migrations/2024_05_10_215235_add_created_by_to_geometry_tables.php new file mode 100644 index 000000000..1f8492863 --- /dev/null +++ b/database/migrations/2024_05_10_215235_add_created_by_to_geometry_tables.php @@ -0,0 +1,39 @@ +dropColumn(['created_by']); + }); + Schema::table('site_polygon', function (Blueprint $table) { + $table->foreignIdFor(User::class, 'created_by')->nullable(); + }); + Schema::table('polygon_geometry', function (Blueprint $table) { + $table->foreignIdFor(User::class, 'created_by')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('site_polygon', function (Blueprint $table) { + $table->dropColumn(['created_by']); + }); + Schema::table('polygon_geometry', function (Blueprint $table) { + $table->dropColumn(['created_by']); + }); + } +}; From c5a0aa3e5f4bf7c95f5ef3df2fd5e8fd71086867 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Fri, 10 May 2024 15:38:17 -0700 Subject: [PATCH 38/64] [TM-801] Implement bulk delete of polygons for GH. --- .../V2/Geometry/GeometryController.php | 26 +++++++++++++++++++ app/Policies/V2/PolygonGeometryPolicy.php | 24 +++++++++++++++++ .../V2/paths/Geometry/delete-v2-geometry.yml | 18 +++++++++++++ openapi-src/V2/paths/_index.yml | 3 +++ resources/docs/swagger-v2.yml | 20 ++++++++++++++ routes/api_v2.php | 1 + 6 files changed, 92 insertions(+) create mode 100644 app/Policies/V2/PolygonGeometryPolicy.php create mode 100644 openapi-src/V2/paths/Geometry/delete-v2-geometry.yml diff --git a/app/Http/Controllers/V2/Geometry/GeometryController.php b/app/Http/Controllers/V2/Geometry/GeometryController.php index 24725d3e2..dc213f6d6 100644 --- a/app/Http/Controllers/V2/Geometry/GeometryController.php +++ b/app/Http/Controllers/V2/Geometry/GeometryController.php @@ -11,6 +11,8 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\App; use Illuminate\Validation\ValidationException; +use Spatie\FlareClient\Http\Exceptions\NotFound; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; class GeometryController extends Controller { @@ -138,4 +140,28 @@ function (array $errorItems, $field) use ($validation) { return response()->json(['errors' => $geometryErrors], 422); } } + + public function deleteGeometries(Request $request): JsonResponse + { + $uuids = $request->input('uuids'); + if (empty($uuids)) { + throw new NotFoundHttpException(); + } + + $polygons = PolygonGeometry::whereIn('uuid', $uuids)->get(); + if (count($polygons) != count($uuids)) { + throw new NotFoundHttpException(); + } + + foreach ($polygons as $polygon) { + $this->authorize('delete', $polygon); + } + + foreach ($polygons as $polygon) { + $polygon->sitePolygon()->delete(); + $polygon->delete(); + } + + return response()->json(['success' => 'geometries have been deleted'], 202); + } } diff --git a/app/Policies/V2/PolygonGeometryPolicy.php b/app/Policies/V2/PolygonGeometryPolicy.php new file mode 100644 index 000000000..27ae44d3e --- /dev/null +++ b/app/Policies/V2/PolygonGeometryPolicy.php @@ -0,0 +1,24 @@ +hasAnyPermission(['manage-own', 'polygons-manage'])) { + return false; + } + + return $this->isTheirs($user, $polygon); + } + + protected function isTheirs(User $user, PolygonGeometry $polygon): bool + { + return $user->id == $polygon->created_by; + } +} diff --git a/openapi-src/V2/paths/Geometry/delete-v2-geometry.yml b/openapi-src/V2/paths/Geometry/delete-v2-geometry.yml new file mode 100644 index 000000000..d2dab43a9 --- /dev/null +++ b/openapi-src/V2/paths/Geometry/delete-v2-geometry.yml @@ -0,0 +1,18 @@ +summary: Bulk delete geometries +operationId: delete-v2-geometry +tags: + - V2 Geometry +parameters: + - type: array + name: uuids[] + in: query + required: true + items: + type: string +responses: + '200': + description: OK + '404': + description: Some of the UUIDs were not found. Nothing was deleted + '403': + description: This account does not have permission to delete some of the geometries. Nothing was deleted. diff --git a/openapi-src/V2/paths/_index.yml b/openapi-src/V2/paths/_index.yml index 279d67c42..3ee6f6fd1 100644 --- a/openapi-src/V2/paths/_index.yml +++ b/openapi-src/V2/paths/_index.yml @@ -2517,3 +2517,6 @@ /v2/geometry/validate: post: $ref: './Geometry/post-v2-geometry-validate.yml' +/v2/geometry: + delete: + $ref: './Geometry/delete-v2-geometry.yml' diff --git a/resources/docs/swagger-v2.yml b/resources/docs/swagger-v2.yml index 102b4f76d..819591801 100644 --- a/resources/docs/swagger-v2.yml +++ b/resources/docs/swagger-v2.yml @@ -93858,3 +93858,23 @@ paths: field: type: string description: A path string indicating where the error occurred. + /v2/geometry: + delete: + summary: Bulk delete geometries + operationId: delete-v2-geometry + tags: + - V2 Geometry + parameters: + - type: array + name: 'uuids[]' + in: query + required: true + items: + type: string + responses: + '200': + description: OK + '403': + description: This account does not have permission to delete some of the geometries. Nothing was deleted. + '404': + description: Some of the UUIDs were not found. Nothing was deleted diff --git a/routes/api_v2.php b/routes/api_v2.php index 22088ae76..d931fd79c 100644 --- a/routes/api_v2.php +++ b/routes/api_v2.php @@ -560,6 +560,7 @@ Route::prefix('geometry')->group(function () { Route::post('/validate', [GeometryController::class, 'validateGeometries']); + Route::delete('', [GeometryController::class, 'deleteGeometries']); }); Route::prefix('project-monitorings')->group(function () { From 738cec924c77954b1e178b8c928ee211ab31c8d7 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Fri, 10 May 2024 16:36:06 -0700 Subject: [PATCH 39/64] [TM-801] Implement polygon update endpoint for GH --- .../V2/Geometry/GeometryController.php | 93 ++++++++----- app/Models/V2/PolygonGeometry.php | 5 + app/Models/V2/Sites/CriteriaSite.php | 2 +- app/Policies/V2/PolygonGeometryPolicy.php | 9 ++ app/Services/PolygonService.php | 128 +++++++++++------- .../paths/Geometry/put-v2-geometry-uuid.yml | 47 +++++++ openapi-src/V2/paths/_index.yml | 3 + resources/docs/swagger-v2.yml | 100 ++++++++++++++ routes/api_v2.php | 1 + 9 files changed, 304 insertions(+), 84 deletions(-) create mode 100644 openapi-src/V2/paths/Geometry/put-v2-geometry-uuid.yml diff --git a/app/Http/Controllers/V2/Geometry/GeometryController.php b/app/Http/Controllers/V2/Geometry/GeometryController.php index dc213f6d6..e87d2ce74 100644 --- a/app/Http/Controllers/V2/Geometry/GeometryController.php +++ b/app/Http/Controllers/V2/Geometry/GeometryController.php @@ -57,39 +57,9 @@ public function storeSiteGeometry(Request $request, Site $site): JsonResponse // for things like overlapping and estimated area. $polygonErrors = []; foreach ($polygonUuids as $polygonUuid) { - $data = ['geometry' => $polygonUuid]; - foreach (self::STORE_GEOMETRY_VALIDATIONS as $criteriaId => $validation) { - $valid = true; - - try { - SitePolygonValidator::validate($validation, $data); - } catch (ValidationException $exception) { - $valid = false; - $polygonErrors[$polygonUuid][] = json_decode($exception->errors()['geometry'][0]); - } - - $service->createCriteriaSite($polygonUuid, $criteriaId, $valid); - } - - // For these two, the createGeojsonModels already handled creating the site criteria, so we just need to - // report on them if not valid - $polygon = PolygonGeometry::isUuid($polygonUuid)->select('uuid')->first(); - $schemaCriteria = $polygon->criteriaSite()->forCriteria(PolygonService::SCHEMA_CRITERIA_ID)->first(); - if ($schemaCriteria != null && ! $schemaCriteria->valid) { - $polygonErrors[$polygonUuid][] = [ - 'key' => 'TABLE_SCHEMA', - 'message' => 'The properties for the geometry are missing some required values.', - ]; - } else { - // only report data validation if the schema was valid. When the schema is invalid, the data is - // always invalid as well. - $dataCriteria = $polygon->criteriaSite()->forCriteria(PolygonService::DATA_CRITERIA_ID)->first(); - if ($dataCriteria != null && ! $dataCriteria->valid) { - $polygonErrors[$polygonUuid][] = [ - 'key' => 'DATA_COMPLETED', - 'message' => 'The properties for the geometry have some invalid values.', - ]; - } + $errors = $this->runStoredGeometryValidations($polygonUuid); + if (!empty($errors)) { + $polygonErrors[$polygonUuid] = $errors; } } @@ -164,4 +134,61 @@ public function deleteGeometries(Request $request): JsonResponse return response()->json(['success' => 'geometries have been deleted'], 202); } + + public function updateGeometry(Request $request, PolygonGeometry $polygon): JsonResponse + { + $this->authorize('update', $polygon); + + $geometry = $request->input('geometry'); + /** @var PolygonService $service */ + $service = App::make(PolygonService::class); + $service->updateGeojsonModels($polygon, $geometry); + + $errors = $this->runStoredGeometryValidations($polygon->uuid); + + return response()->json(['errors' => $errors], 200); + } + + protected function runStoredGeometryValidations(string $polygonUuid): array + { + /** @var PolygonService $service */ + $service = App::make(PolygonService::class); + $data = ['geometry' => $polygonUuid]; + $errors = []; + foreach (self::STORE_GEOMETRY_VALIDATIONS as $criteriaId => $validation) { + $valid = true; + + try { + SitePolygonValidator::validate($validation, $data); + } catch (ValidationException $exception) { + $valid = false; + $errors[] = json_decode($exception->errors()['geometry'][0]); + } + + $service->createCriteriaSite($polygonUuid, $criteriaId, $valid); + } + + // For these two, the polygon service already handled creating the site criteria, so we just need to + // report on them if not valid + $polygon = PolygonGeometry::isUuid($polygonUuid)->select('uuid')->first(); + $schemaCriteria = $polygon->criteriaSite()->forCriteria(PolygonService::SCHEMA_CRITERIA_ID)->first(); + if ($schemaCriteria != null && ! $schemaCriteria->valid) { + $errors[] = [ + 'key' => 'TABLE_SCHEMA', + 'message' => 'The properties for the geometry are missing some required values.', + ]; + } else { + // only report data validation if the schema was valid. When the schema is invalid, the data is + // always invalid as well. + $dataCriteria = $polygon->criteriaSite()->forCriteria(PolygonService::DATA_CRITERIA_ID)->first(); + if ($dataCriteria != null && ! $dataCriteria->valid) { + $errors[] = [ + 'key' => 'DATA_COMPLETED', + 'message' => 'The properties for the geometry have some invalid values.', + ]; + } + } + + return $errors; + } } diff --git a/app/Models/V2/PolygonGeometry.php b/app/Models/V2/PolygonGeometry.php index 2737a2404..842ac30cf 100644 --- a/app/Models/V2/PolygonGeometry.php +++ b/app/Models/V2/PolygonGeometry.php @@ -30,6 +30,11 @@ class PolygonGeometry extends Model 'created_by', ]; + public function getRouteKeyName() + { + return 'uuid'; + } + public function criteriaSite(): HasMany { return $this->hasMany(CriteriaSite::class, 'polygon_id', 'uuid'); diff --git a/app/Models/V2/Sites/CriteriaSite.php b/app/Models/V2/Sites/CriteriaSite.php index 93c72f370..bb6bbac5b 100644 --- a/app/Models/V2/Sites/CriteriaSite.php +++ b/app/Models/V2/Sites/CriteriaSite.php @@ -42,6 +42,6 @@ class CriteriaSite extends Model public function scopeForCriteria($query, $criteriaId) { - return $query->where('criteria_id', $criteriaId); + return $query->where('criteria_id', $criteriaId)->latest(); } } diff --git a/app/Policies/V2/PolygonGeometryPolicy.php b/app/Policies/V2/PolygonGeometryPolicy.php index 27ae44d3e..4a0dabdf7 100644 --- a/app/Policies/V2/PolygonGeometryPolicy.php +++ b/app/Policies/V2/PolygonGeometryPolicy.php @@ -17,6 +17,15 @@ public function delete(User $user, PolygonGeometry $polygon): bool return $this->isTheirs($user, $polygon); } + public function update(User $user, PolygonGeometry $polygon): bool + { + if (!$user->hasAnyPermission(['manage-own', 'polygons-manage'])) { + return false; + } + + return $this->isTheirs($user, $polygon); + } + protected function isTheirs(User $user, PolygonGeometry $polygon): bool { return $user->id == $polygon->created_by; diff --git a/app/Services/PolygonService.php b/app/Services/PolygonService.php index 7145e0dc5..f2abedb9e 100644 --- a/app/Services/PolygonService.php +++ b/app/Services/PolygonService.php @@ -35,7 +35,6 @@ public function createGeojsonModels($geojson, $sitePolygonProperties = []): arra $returnSite = $this->insertSitePolygon( $data['uuid'], array_merge($sitePolygonProperties, $feature['properties']), - $data['area'] ); if ($returnSite) { Log::info($returnSite); @@ -48,7 +47,6 @@ public function createGeojsonModels($geojson, $sitePolygonProperties = []): arra $returnSite = $this->insertSitePolygon( $data['uuid'], array_merge($sitePolygonProperties, $feature['properties']), - $data['area'] ); if ($returnSite) { Log::info($returnSite); @@ -76,12 +74,27 @@ public function createCriteriaSite($polygonId, $criteriaId, $valid): bool|string } } - private function insertSinglePolygon(array $geometry): array + /** + * Note: At this time, this method assumes that the geometry is a single polygon. + */ + public function updateGeojsonModels(PolygonGeometry $polygonGeometry, array $geometry) + { + $dbGeometry = $this->getGeomAndArea(data_get($geometry, 'features.0')); + $polygonGeometry->update(['geom' => $dbGeometry['geom']]); + + $sitePolygon = $polygonGeometry->sitePolygon()->first(); + $sitePolygon->update($this->validateSitePolygonProperties( + $polygonGeometry->uuid, + array_merge(['area' => $dbGeometry['area']], data_get($geometry, 'features.0.properties', [])) + )); + } + + protected function getGeomAndArea(array $geometry): array { // Convert geometry to GeoJSON string $geojson = json_encode(['type' => 'Feature', 'geometry' => $geometry, 'crs' => ['type' => 'name', 'properties' => ['name' => 'EPSG:4326']]]); - // Insert GeoJSON data into the database + // Update GeoJSON data in the database $geom = DB::raw("ST_GeomFromGeoJSON('$geojson')"); $areaSqDegrees = DB::selectOne("SELECT ST_Area(ST_GeomFromGeoJSON('$geojson')) AS area")->area; $latitude = DB::selectOne("SELECT ST_Y(ST_Centroid(ST_GeomFromGeoJSON('$geojson'))) AS latitude")->latitude; @@ -91,64 +104,79 @@ private function insertSinglePolygon(array $geometry): array $areaHectares = $areaSqMeters / 10000; + return ['geom' => $geom, 'area' => $areaHectares]; + } + + protected function insertSinglePolygon(array $geometry): array + { + $dbGeometry = $this->getGeomAndArea($geometry); + $polygonGeometry = PolygonGeometry::create([ - 'geom' => $geom, + 'geom' => $dbGeometry['geom'], 'created_by' => Auth::user()?->id, ]); - return ['uuid' => $polygonGeometry->uuid, 'area' => $areaHectares]; + return ['uuid' => $polygonGeometry->uuid, 'area' => $dbGeometry['area']]; } - private function insertSitePolygon(string $polygonUuid, array $properties, float $area) + protected function insertSitePolygon(string $polygonUuid, array $properties) { try { - // Avoid trying to store an invalid date string or int in the DB, as that will throw an exception and prevent - // the site polygon from storing. With an invalid date, this will end up reporting schema invalid and data - // invalid, which isn't necessarily correct for the payload given, but it does reflect the status in the DB - try { - $properties['plantstart'] = empty($properties['plantstart']) ? null : Carbon::parse($properties['plantstart']); - } catch (\Exception $e) { - $properties['plantstart'] = null; - } - - try { - $properties['plantend'] = empty($properties['plantend']) ? null : Carbon::parse($properties['plantend']); - } catch (\Exception $e) { - $properties['plantend'] = null; - } - $properties['num_trees'] = is_int($properties['num_trees'] ?? null) ? $properties['num_trees'] : null; - - $validationGeojson = ['features' => [ - 'feature' => ['properties' => $properties], - ]]; - $validSchema = SitePolygonValidator::isValid('SCHEMA', $validationGeojson); - $validData = SitePolygonValidator::isValid('DATA', $validationGeojson); - $this->createCriteriaSite($polygonUuid, self::SCHEMA_CRITERIA_ID, $validSchema); - $this->createCriteriaSite($polygonUuid, self::DATA_CRITERIA_ID, $validData); - - $sitePolygon = new SitePolygon(); - $sitePolygon->project_id = $properties['project_id'] ?? null; - $sitePolygon->proj_name = $properties['proj_name'] ?? null; - $sitePolygon->org_name = $properties['org_name'] ?? null; - $sitePolygon->country = $properties['country'] ?? null; - $sitePolygon->poly_id = $polygonUuid ?? null; - $sitePolygon->poly_name = $properties['poly_name'] ?? null; - $sitePolygon->site_id = $properties['site_id'] ?? null; - $sitePolygon->site_name = $properties['site_name'] ?? null; - $sitePolygon->poly_label = $properties['poly_label'] ?? null; - $sitePolygon->plantstart = $properties['plantstart'] ?? null; - $sitePolygon->plantend = $properties['plantend']; - $sitePolygon->practice = $properties['practice']; - $sitePolygon->target_sys = $properties['target_sys'] ?? null; - $sitePolygon->distr = $properties['distr'] ?? null; - $sitePolygon->num_trees = $properties['num_trees']; - $sitePolygon->est_area = $area ?? null; - $sitePolygon->created_by = Auth::user()?->id; - $sitePolygon->save(); + SitePolygon::create(array_merge( + $this->validateSitePolygonProperties($polygonUuid, $properties), + [ + 'poly_id' => $polygonUuid ?? null, + 'created_by' => Auth::user()?->id, + ], + )); return null; } catch (\Exception $e) { return $e->getMessage(); } } + + protected function validateSitePolygonProperties(string $polygonUuid, array $properties) { + // Avoid trying to store an invalid date string or int in the DB, as that will throw an exception and prevent + // the site polygon from storing. With an invalid date, this will end up reporting schema invalid and data + // invalid, which isn't necessarily correct for the payload given, but it does reflect the status in the DB + try { + $properties['plantstart'] = empty($properties['plantstart']) ? null : Carbon::parse($properties['plantstart']); + } catch (\Exception $e) { + $properties['plantstart'] = null; + } + + try { + $properties['plantend'] = empty($properties['plantend']) ? null : Carbon::parse($properties['plantend']); + } catch (\Exception $e) { + $properties['plantend'] = null; + } + $properties['num_trees'] = is_int($properties['num_trees'] ?? null) ? $properties['num_trees'] : null; + + $validationGeojson = ['features' => [ + 'feature' => ['properties' => $properties], + ]]; + $validSchema = SitePolygonValidator::isValid('SCHEMA', $validationGeojson); + $validData = SitePolygonValidator::isValid('DATA', $validationGeojson); + $this->createCriteriaSite($polygonUuid, self::SCHEMA_CRITERIA_ID, $validSchema); + $this->createCriteriaSite($polygonUuid, self::DATA_CRITERIA_ID, $validData); + + return [ + 'project_id' => $properties['project_id'] ?? null, + 'proj_name' => $properties['proj_name'] ?? null, + 'org_name' => $properties['org_name'] ?? null, + 'country' => $properties['country'] ?? null, + 'poly_name' => $properties['poly_name'] ?? null, + 'site_id' => $properties['site_id'] ?? null, + 'site_name' => $properties['site_name'] ?? null, + 'poly_label' => $properties['poly_label'] ?? null, + 'plantstart' => $properties['plantstart'], + 'plantend' => $properties['plantend'], + 'practice' => $properties['practice'] ?? null, + 'target_sys' => $properties['target_sys'] ?? null, + 'distr' => $properties['distr'] ?? null, + 'num_trees' => $properties['num_trees'], + 'est_area' => $properties['area'] ?? null, + ]; + } } diff --git a/openapi-src/V2/paths/Geometry/put-v2-geometry-uuid.yml b/openapi-src/V2/paths/Geometry/put-v2-geometry-uuid.yml new file mode 100644 index 000000000..d274018aa --- /dev/null +++ b/openapi-src/V2/paths/Geometry/put-v2-geometry-uuid.yml @@ -0,0 +1,47 @@ +summary: Update a geometry +operationId: put-v2-geometry +tags: + - V2 Geometry +parameters: + - in: path + type: string + name: UUID + required: true + - in: body + name: body + schema: + type: object + properties: + geometry: + $ref: '../../definitions/_index.yml#/GeoJSON' +responses: + '404': + description: Geometry was not found. + '403': + description: This account does not have permission to update the polygon. + '200': + description: 'OK: Update was applied.' + schema: + type: object + properties: + errors: + type: array + items: + type: object + properties: + key: + type: string + enum: + - OVERLAPPING_POLYGON + - SELF_INTERSECTION + - COORDINATE_SYSTEM + - SIZE_LIMIT + - WITHIN_COUNTRY + - SPIKE + - GEOMETRY_TYPE + - TOTAL_AREA_EXPECTED + - TABLE_SCHEMA + - DATA_COMPLETED + message: + type: string + description: Human readable string in English to describe the error. diff --git a/openapi-src/V2/paths/_index.yml b/openapi-src/V2/paths/_index.yml index 3ee6f6fd1..e1e615422 100644 --- a/openapi-src/V2/paths/_index.yml +++ b/openapi-src/V2/paths/_index.yml @@ -2520,3 +2520,6 @@ /v2/geometry: delete: $ref: './Geometry/delete-v2-geometry.yml' +/v2/geometry/{UUID}: + put: + $ref: './Geometry/put-v2-geometry-uuid.yml' diff --git a/resources/docs/swagger-v2.yml b/resources/docs/swagger-v2.yml index 819591801..77a77bfa4 100644 --- a/resources/docs/swagger-v2.yml +++ b/resources/docs/swagger-v2.yml @@ -93878,3 +93878,103 @@ paths: description: This account does not have permission to delete some of the geometries. Nothing was deleted. '404': description: Some of the UUIDs were not found. Nothing was deleted + '/v2/geometry/{UUID}': + put: + summary: Update a geometry + operationId: put-v2-geometry + tags: + - V2 Geometry + parameters: + - in: path + type: string + name: UUID + required: true + - in: body + name: body + schema: + type: object + properties: + geometry: + title: GeoJSON + type: object + properties: + type: + type: string + enum: + - FeatureCollection + features: + type: array + items: + type: object + properties: + type: + type: string + enum: + - Feature + properties: + type: object + properties: + poly_name: + type: string + plantstart: + type: string + format: date + plantend: + type: string + format: date + practice: + type: string + target_sys: + type: string + distr: + type: string + num_trees: + type: number + site_id: + type: string + geometry: + type: object + properties: + type: + type: string + enum: + - Polygon + coordinates: + type: array + items: + type: array + items: + type: array + items: + type: number + responses: + '200': + description: 'OK: Update was applied.' + schema: + type: object + properties: + errors: + type: array + items: + type: object + properties: + key: + type: string + enum: + - OVERLAPPING_POLYGON + - SELF_INTERSECTION + - COORDINATE_SYSTEM + - SIZE_LIMIT + - WITHIN_COUNTRY + - SPIKE + - GEOMETRY_TYPE + - TOTAL_AREA_EXPECTED + - TABLE_SCHEMA + - DATA_COMPLETED + message: + type: string + description: Human readable string in English to describe the error. + '403': + description: This account does not have permission to update the polygon. + '404': + description: Geometry was not found. diff --git a/routes/api_v2.php b/routes/api_v2.php index d931fd79c..b304399a6 100644 --- a/routes/api_v2.php +++ b/routes/api_v2.php @@ -561,6 +561,7 @@ Route::prefix('geometry')->group(function () { Route::post('/validate', [GeometryController::class, 'validateGeometries']); Route::delete('', [GeometryController::class, 'deleteGeometries']); + Route::put('{polygon}', [GeometryController::class, 'updateGeometry']); }); Route::prefix('project-monitorings')->group(function () { From e59fc8988b787ecb5a501e8d66719893d71f09c3 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Mon, 13 May 2024 12:07:39 -0700 Subject: [PATCH 40/64] [TM-838] Regenerate docs post merge --- resources/docs/swagger-v2.yml | 131 +++++++++++++--------------------- 1 file changed, 48 insertions(+), 83 deletions(-) diff --git a/resources/docs/swagger-v2.yml b/resources/docs/swagger-v2.yml index 77a77bfa4..bfde29f4e 100644 --- a/resources/docs/swagger-v2.yml +++ b/resources/docs/swagger-v2.yml @@ -9254,65 +9254,28 @@ definitions: properties: uuid: type: string - amount: - type: integer collection: type: string - gender: - type: string - age: - type: string - ethnicity: - type: string - indigeneity: + readable_collection: type: string - V2WorkdaysPaginated: - type: object - properties: - data: + demographics: type: array items: - title: V2WorkdayRead + title: WorkdayDemographic type: object properties: - uuid: - type: string - amount: - type: integer - collection: - type: string - gender: - type: string - age: + type: type: string - ethnicity: + enum: + - gender + - age + - ethnicity + subtype: type: string - indigeneity: + name: type: string - links: - type: object - properties: - first: - type: string - last: - type: string - prev: - type: string - next: - type: string - meta: - type: object - properties: - current_page: - type: integer - from: - type: integer - last_page: - type: integer - next: - type: integer - unfiltered_total: - type: integer + amount: + type: integer V2DisturbanceRead: title: V2DisturbanceRead type: object @@ -43997,6 +43960,22 @@ definitions: properties: email_address: type: string + WorkdayDemographic: + title: WorkdayDemographic + type: object + properties: + type: + type: string + enum: + - gender + - age + - ethnicity + subtype: + type: string + name: + type: string + amount: + type: integer GeoJSON: title: GeoJSON type: object @@ -57663,7 +57642,7 @@ paths: name: ENTITY in: path required: true - description: allowed values project/site/nursery/project-reports/site-reports/nursery-reports + description: allowed values project-report/site-report - type: string name: UUID in: path @@ -57682,42 +57661,28 @@ paths: properties: uuid: type: string - amount: - type: integer collection: type: string - gender: - type: string - age: - type: string - ethnicity: - type: string - indigeneity: + readable_collection: type: string - links: - type: object - properties: - first: - type: string - last: - type: string - prev: - type: string - next: - type: string - meta: - type: object - properties: - current_page: - type: integer - from: - type: integer - last_page: - type: integer - next: - type: integer - unfiltered_total: - type: integer + demographics: + type: array + items: + title: WorkdayDemographic + type: object + properties: + type: + type: string + enum: + - gender + - age + - ethnicity + subtype: + type: string + name: + type: string + amount: + type: integer /v2/stratas: post: operationId: post-v2-stratas From 227b9f770918fc26049a1d0533cad8b1072aa7d3 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Mon, 13 May 2024 13:01:02 -0700 Subject: [PATCH 41/64] Get tests and linter passing on staging again --- app/Http/Controllers/V2/Geometry/GeometryController.php | 4 ++-- .../V2/Terrafund/TerrafundEditGeometryController.php | 1 - app/Policies/V2/PolygonGeometryPolicy.php | 4 ++-- app/Services/PolygonService.php | 3 ++- .../2024_05_10_215235_add_created_by_to_geometry_tables.php | 3 +-- openapi-src/V2/paths/Geometry/post-v2-geometry-validate.yml | 1 + resources/docs/swagger-v2.yml | 1 + .../Extensions/Polygons/TestFiles/data_fail.geojson | 2 +- 8 files changed, 10 insertions(+), 9 deletions(-) diff --git a/app/Http/Controllers/V2/Geometry/GeometryController.php b/app/Http/Controllers/V2/Geometry/GeometryController.php index e87d2ce74..3981d6596 100644 --- a/app/Http/Controllers/V2/Geometry/GeometryController.php +++ b/app/Http/Controllers/V2/Geometry/GeometryController.php @@ -11,7 +11,6 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\App; use Illuminate\Validation\ValidationException; -use Spatie\FlareClient\Http\Exceptions\NotFound; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; class GeometryController extends Controller @@ -58,7 +57,7 @@ public function storeSiteGeometry(Request $request, Site $site): JsonResponse $polygonErrors = []; foreach ($polygonUuids as $polygonUuid) { $errors = $this->runStoredGeometryValidations($polygonUuid); - if (!empty($errors)) { + if (! empty($errors)) { $polygonErrors[$polygonUuid] = $errors; } } @@ -86,6 +85,7 @@ function (array $errorItems, $field) use ($validation) { if (array_key_exists('key', $errorItem)) { // This is an error that came from one of our geometry validations $errorItem['field'] = $field; + return $errorItem; } else { // This is an error that came from the schema or data validations. The last item in the diff --git a/app/Http/Controllers/V2/Terrafund/TerrafundEditGeometryController.php b/app/Http/Controllers/V2/Terrafund/TerrafundEditGeometryController.php index e62fb6ded..1c601a421 100644 --- a/app/Http/Controllers/V2/Terrafund/TerrafundEditGeometryController.php +++ b/app/Http/Controllers/V2/Terrafund/TerrafundEditGeometryController.php @@ -8,7 +8,6 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; -use Illuminate\Support\Str; class TerrafundEditGeometryController extends Controller { diff --git a/app/Policies/V2/PolygonGeometryPolicy.php b/app/Policies/V2/PolygonGeometryPolicy.php index 4a0dabdf7..2be8cd69b 100644 --- a/app/Policies/V2/PolygonGeometryPolicy.php +++ b/app/Policies/V2/PolygonGeometryPolicy.php @@ -10,7 +10,7 @@ class PolygonGeometryPolicy extends Policy { public function delete(User $user, PolygonGeometry $polygon): bool { - if (!$user->hasAnyPermission(['manage-own', 'polygons-manage'])) { + if (! $user->hasAnyPermission(['manage-own', 'polygons-manage'])) { return false; } @@ -19,7 +19,7 @@ public function delete(User $user, PolygonGeometry $polygon): bool public function update(User $user, PolygonGeometry $polygon): bool { - if (!$user->hasAnyPermission(['manage-own', 'polygons-manage'])) { + if (! $user->hasAnyPermission(['manage-own', 'polygons-manage'])) { return false; } diff --git a/app/Services/PolygonService.php b/app/Services/PolygonService.php index f2abedb9e..89193f23e 100644 --- a/app/Services/PolygonService.php +++ b/app/Services/PolygonService.php @@ -136,7 +136,8 @@ protected function insertSitePolygon(string $polygonUuid, array $properties) } } - protected function validateSitePolygonProperties(string $polygonUuid, array $properties) { + protected function validateSitePolygonProperties(string $polygonUuid, array $properties) + { // Avoid trying to store an invalid date string or int in the DB, as that will throw an exception and prevent // the site polygon from storing. With an invalid date, this will end up reporting schema invalid and data // invalid, which isn't necessarily correct for the payload given, but it does reflect the status in the DB diff --git a/database/migrations/2024_05_10_215235_add_created_by_to_geometry_tables.php b/database/migrations/2024_05_10_215235_add_created_by_to_geometry_tables.php index 1f8492863..367d4ce1b 100644 --- a/database/migrations/2024_05_10_215235_add_created_by_to_geometry_tables.php +++ b/database/migrations/2024_05_10_215235_add_created_by_to_geometry_tables.php @@ -5,8 +5,7 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -return new class extends Migration -{ +return new class extends Migration { /** * Run the migrations. */ diff --git a/openapi-src/V2/paths/Geometry/post-v2-geometry-validate.yml b/openapi-src/V2/paths/Geometry/post-v2-geometry-validate.yml index b0a6865f7..df3dde513 100644 --- a/openapi-src/V2/paths/Geometry/post-v2-geometry-validate.yml +++ b/openapi-src/V2/paths/Geometry/post-v2-geometry-validate.yml @@ -20,6 +20,7 @@ responses: properties: errors: type: array + items: description: An empty array on the OK response is included for ease of parsing on the client side. '422': description: One or more errors was found with the supplied geometries diff --git a/resources/docs/swagger-v2.yml b/resources/docs/swagger-v2.yml index 77a77bfa4..a3a3f21ed 100644 --- a/resources/docs/swagger-v2.yml +++ b/resources/docs/swagger-v2.yml @@ -93828,6 +93828,7 @@ paths: properties: errors: type: array + items: null description: An empty array on the OK response is included for ease of parsing on the client side. '422': description: One or more errors was found with the supplied geometries diff --git a/tests/Unit/Validators/Extensions/Polygons/TestFiles/data_fail.geojson b/tests/Unit/Validators/Extensions/Polygons/TestFiles/data_fail.geojson index b34b1ac92..1bcae4f19 100644 --- a/tests/Unit/Validators/Extensions/Polygons/TestFiles/data_fail.geojson +++ b/tests/Unit/Validators/Extensions/Polygons/TestFiles/data_fail.geojson @@ -1 +1 @@ -{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[40.405701461490054,-12.96724571876176],[40.40517180334834,-12.965903759897898],[40.403529908559165,-12.964716647487307],[40.400669841338356,-12.964819870022112],[40.3990459167255,-12.966312776735407],[40.39862217229356,-12.968102907699532],[40.398939980922535,-12.970322277411398],[40.40010520549225,-12.972438397330677],[40.40258316565041,-12.973669721688609],[40.40473735406755,-12.973136499056011],[40.40688554181705,-12.972162411592251],[40.40704443522537,-12.969891409784722],[40.4044491492121,-12.970355942218049],[40.403972454664455,-12.971852704880703],[40.40275426315375,-12.970923669720435],[40.40333688067838,-12.96911721161139],[40.40503177248303,-12.969272054897885],[40.40614403547883,-12.969478504071148],[40.40677960559336,-12.968394601830951],[40.404978804273696,-12.968033308262562],[40.406832566272016,-12.967620388148362],[40.4064617987822,-12.966175188510476],[40.405701461490054,-12.96724571876176]]]},"properties":{"poly_name":"siteNameMz","plantstart":"2022-09-29","plantend":"2023-10-10","practice":"","target_sys":"","distr":"","num_trees":"","project_id":"652ba56f-2e75-4735-a0d1-aafebbd940c1","site_id":""},"id":0}]} \ No newline at end of file +{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[40.405701461490054,-12.96724571876176],[40.40517180334834,-12.965903759897898],[40.403529908559165,-12.964716647487307],[40.400669841338356,-12.964819870022112],[40.3990459167255,-12.966312776735407],[40.39862217229356,-12.968102907699532],[40.398939980922535,-12.970322277411398],[40.40010520549225,-12.972438397330677],[40.40258316565041,-12.973669721688609],[40.40473735406755,-12.973136499056011],[40.40688554181705,-12.972162411592251],[40.40704443522537,-12.969891409784722],[40.4044491492121,-12.970355942218049],[40.403972454664455,-12.971852704880703],[40.40275426315375,-12.970923669720435],[40.40333688067838,-12.96911721161139],[40.40503177248303,-12.969272054897885],[40.40614403547883,-12.969478504071148],[40.40677960559336,-12.968394601830951],[40.404978804273696,-12.968033308262562],[40.406832566272016,-12.967620388148362],[40.4064617987822,-12.966175188510476],[40.405701461490054,-12.96724571876176]]]},"properties":{"poly_name":"null","plantstart":"2022-09-29","plantend":"2023-10-10","practice":"","target_sys":"","distr":"","num_trees":"","project_id":"652ba56f-2e75-4735-a0d1-aafebbd940c1","site_id":""},"id":0}]} From c35fe72df355acd67c4dbd0918c7ee713a2072fe Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Mon, 13 May 2024 12:47:32 -0700 Subject: [PATCH 42/64] [TM-913] Add and sync a description text field to workday --- app/Models/V2/Workdays/Workday.php | 4 +++ ..._13_194243_add_description_to_workdays.php | 27 +++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 database/migrations/2024_05_13_194243_add_description_to_workdays.php diff --git a/app/Models/V2/Workdays/Workday.php b/app/Models/V2/Workdays/Workday.php index 39b62a48e..6ca5b5a2f 100644 --- a/app/Models/V2/Workdays/Workday.php +++ b/app/Models/V2/Workdays/Workday.php @@ -39,6 +39,7 @@ class Workday extends Model implements HandlesLinkedFieldSync 'ethnicity', 'indigeneity', 'migrated_to_demographics', + 'description', ]; public const COLLECTION_PROJECT_PAID_NURSERY_OPRERATIONS = 'paid-nursery-operations'; @@ -108,7 +109,10 @@ public static function syncRelation(EntityModel $entity, string $property, $data 'workdayable_type' => get_class($entity), 'workdayable_id' => $entity->id, 'collection' => $workdayData['collection'], + 'description' => $workdayData['description'], ]); + } else { + $workday->update(['description' => $workdayData['description']]); } $demographics = $workday->demographics; diff --git a/database/migrations/2024_05_13_194243_add_description_to_workdays.php b/database/migrations/2024_05_13_194243_add_description_to_workdays.php new file mode 100644 index 000000000..fd4a5b1b9 --- /dev/null +++ b/database/migrations/2024_05_13_194243_add_description_to_workdays.php @@ -0,0 +1,27 @@ +text('description')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('v2_workdays', function (Blueprint $table) { + $table->dropColumn('description'); + }); + } +}; From 86f30442a8644fc981289ed824a8721db8d975a8 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Tue, 14 May 2024 11:51:01 -0700 Subject: [PATCH 43/64] [TM-913] Implement description on workdays as a virtual property on SiteReport. --- app/Models/Traits/UsesLinkedFields.php | 11 +++++--- app/Models/V2/Sites/SiteReport.php | 35 ++++++++++++++++++++++++++ app/Models/V2/Workdays/Workday.php | 3 --- config/wri/linked-fields.php | 2 ++ 4 files changed, 44 insertions(+), 7 deletions(-) diff --git a/app/Models/Traits/UsesLinkedFields.php b/app/Models/Traits/UsesLinkedFields.php index fe30e485b..7a4d13b80 100644 --- a/app/Models/Traits/UsesLinkedFields.php +++ b/app/Models/Traits/UsesLinkedFields.php @@ -128,10 +128,13 @@ public function calculateCompletion(Form $form): int continue; } - if (! empty($answer['conditionalOn']) && - ! $answers[$answer['conditionalOn']]['value']) { - // don't count it if the question wasn't shown to the user because the parent conditional is false. - continue; + if (! empty($answers['conditionalOn'])) { + $conditional = $answers['conditional']; + if (empty($conditional) || ! $conditional['value']) { + // don't count it if the question wasn't shown to the user because the parent conditional is false + // or missing + continue; + } } $questionCount++; diff --git a/app/Models/V2/Sites/SiteReport.php b/app/Models/V2/Sites/SiteReport.php index 99dc5d110..a8cd39854 100644 --- a/app/Models/V2/Sites/SiteReport.php +++ b/app/Models/V2/Sites/SiteReport.php @@ -91,6 +91,9 @@ class SiteReport extends Model implements MediaModel, AuditableContract, ReportM 'polygon_status', 'answers', 'paid_other_activity_description', + + // virtual (see get/set accessor) + 'other_workdays_description', ]; public $fileConfiguration = [ @@ -316,6 +319,38 @@ public function getTaskUuidAttribute(): ?string return $this->task?->uuid ?? null; } + public function getOtherWorkdaysDescriptionAttribute(): ?string + { + $paid = $this->workdaysPaidOtherActivities()->first(); + $volunteer = $this->workdaysVolunteerOtherActivities()->first(); + + if ($paid == null || $volunteer == null || $paid->description == $volunteer->description) { + return $paid?->description ?? $volunteer?->description; + } else { + // They're both not null, but their description doesn't match. This is unexpected, but we'll + // return the most recently updated record's value + return $paid->updated_at->greatedthan($volunteer->updated_at) ? $paid->description : $volunteer->description; + } + } + + public function setOtherWorkdaysDescriptionAttribute(?string $value) + { + $collections = [Workday::COLLECTION_SITE_PAID_OTHER, Workday::COLLECTION_SITE_VOLUNTEER_OTHER]; + $workdaysQuery = $this->morphMany(Workday::class, 'workdayable'); + if (! empty($value)) { + foreach ($collections as $collection) { + if (! (clone $workdaysQuery)->where('collection', $collection)->exists()) { + Workday::create([ + 'workdayable_type' => get_class($this), + 'workdayable_id' => $this->id, + 'collection' => $collection, + ]); + } + } + } + $workdaysQuery->whereIn('collection', $collections)->update(['description' => $value]); + } + public function toSearchableArray() { return [ diff --git a/app/Models/V2/Workdays/Workday.php b/app/Models/V2/Workdays/Workday.php index 6ca5b5a2f..145fe38de 100644 --- a/app/Models/V2/Workdays/Workday.php +++ b/app/Models/V2/Workdays/Workday.php @@ -109,10 +109,7 @@ public static function syncRelation(EntityModel $entity, string $property, $data 'workdayable_type' => get_class($entity), 'workdayable_id' => $entity->id, 'collection' => $workdayData['collection'], - 'description' => $workdayData['description'], ]); - } else { - $workday->update(['description' => $workdayData['description']]); } $demographics = $workday->demographics; diff --git a/config/wri/linked-fields.php b/config/wri/linked-fields.php index e4a2484c8..50aecb461 100644 --- a/config/wri/linked-fields.php +++ b/config/wri/linked-fields.php @@ -574,7 +574,9 @@ 'site-rep-seeds-planted' => ['property' => 'seeds_planted', 'label' => 'Seeds planted', 'input_type' => 'number'], 'site-rep-workdays-volunteer' => ['property' => 'workdays_volunteer', 'label' => 'Workdays volunteer', 'input_type' => 'number'], 'site-rep-polygon-status' => ['property' => 'polygon_status', 'label' => 'Polygon status', 'input_type' => 'long-text'], + // TODO (TM-912) Deprecated, to be removed. 'site-rep-paid-other-activity-description' => ['property' => 'paid_other_activity_description', 'label' => 'Paid Other Activities Description', 'input_type' => 'long-text'], + 'site-rep-other-workdays-description' => ['property' => 'other_workdays_description', 'label' => 'Other Activities Description', 'input_type' => 'long-text'], ], 'file-collections' => [ 'site-rep-col-media' => ['property' => 'media', 'label' => 'Media', 'input_type' => 'file', 'multichoice' => true], From 850e92c250615f4af360f70e997f294f65549899 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Tue, 14 May 2024 14:04:25 -0700 Subject: [PATCH 44/64] [TM-913] Add the new virtual field to ProjectReport as well with a new trait. --- .../Commands/ReportWorkdayDiscrepancies.php | 4 +- .../GetWorkdaysForEntityController.php | 4 +- .../V2/Workdays/StoreWorkdayRequest.php | 28 ------ .../V2/Workdays/UpdateWorkdayRequest.php | 26 ------ app/Models/Traits/HasWorkdays.php | 56 +++++++++++ app/Models/V2/Projects/ProjectReport.php | 42 +++------ app/Models/V2/Sites/SiteReport.php | 93 ++----------------- app/Models/V2/Workdays/Workday.php | 20 ++-- config/wri/linked-fields.php | 2 + 9 files changed, 97 insertions(+), 178 deletions(-) delete mode 100644 app/Http/Requests/V2/Workdays/StoreWorkdayRequest.php delete mode 100644 app/Http/Requests/V2/Workdays/UpdateWorkdayRequest.php create mode 100644 app/Models/Traits/HasWorkdays.php diff --git a/app/Console/Commands/ReportWorkdayDiscrepancies.php b/app/Console/Commands/ReportWorkdayDiscrepancies.php index 56237974b..152b3fe0e 100644 --- a/app/Console/Commands/ReportWorkdayDiscrepancies.php +++ b/app/Console/Commands/ReportWorkdayDiscrepancies.php @@ -26,12 +26,12 @@ class ReportWorkdayDiscrepancies extends Command private const PROPERTIES = [ ProjectReport::class => [ 'paid' => [ - Workday::COLLECTION_PROJECT_PAID_NURSERY_OPRERATIONS, + Workday::COLLECTION_PROJECT_PAID_NURSERY_OPERATIONS, Workday::COLLECTION_PROJECT_PAID_PROJECT_MANAGEMENT, Workday::COLLECTION_PROJECT_PAID_OTHER, ], 'volunteer' => [ - Workday::COLLECTION_PROJECT_VOLUNTEER_NURSERY_OPRERATIONS, + Workday::COLLECTION_PROJECT_VOLUNTEER_NURSERY_OPERATIONS, Workday::COLLECTION_PROJECT_VOLUNTEER_PROJECT_MANAGEMENT, Workday::COLLECTION_PROJECT_VOLUNTEER_OTHER, ], diff --git a/app/Http/Controllers/V2/Workdays/GetWorkdaysForEntityController.php b/app/Http/Controllers/V2/Workdays/GetWorkdaysForEntityController.php index 087cdb144..3ba26e4ee 100644 --- a/app/Http/Controllers/V2/Workdays/GetWorkdaysForEntityController.php +++ b/app/Http/Controllers/V2/Workdays/GetWorkdaysForEntityController.php @@ -26,8 +26,8 @@ public function __invoke(Request $request, EntityModel $entity) ])->get(); $expectedCollections = match ($entity->shortName) { - 'site-report' => array_keys(Workday::$siteCollections), - 'project-report' => array_keys(Workday::$projectCollections), + 'site-report' => array_keys(Workday::SITE_COLLECTIONS), + 'project-report' => array_keys(Workday::PROJECT_COLLECTION), default => throw new NotFoundHttpException(), }; $collections = $workdays->pluck('collection'); diff --git a/app/Http/Requests/V2/Workdays/StoreWorkdayRequest.php b/app/Http/Requests/V2/Workdays/StoreWorkdayRequest.php deleted file mode 100644 index 480e379b4..000000000 --- a/app/Http/Requests/V2/Workdays/StoreWorkdayRequest.php +++ /dev/null @@ -1,28 +0,0 @@ - 'required|string|in:organisation,project-pitch,site,site-report,project,project-report,nursery,nursery-report', - 'model_uuid' => 'required|string', - 'amount' => 'sometimes|nullable|integer|between:0,2147483647', - 'collection' => 'sometimes|nullable|string|in:' . implode(',', array_keys(array_merge(Workday::$siteCollections, Workday::$projectCollections))), - 'gender' => 'sometimes|nullable|string|between:1,255', - 'ethnicity' => 'sometimes|nullable|string|between:1,255', - 'indigeneity' => 'sometimes|nullable|string|between:1,255', - 'age' => 'sometimes|nullable|string|between:1,255', - ]; - } -} diff --git a/app/Http/Requests/V2/Workdays/UpdateWorkdayRequest.php b/app/Http/Requests/V2/Workdays/UpdateWorkdayRequest.php deleted file mode 100644 index f2c962eec..000000000 --- a/app/Http/Requests/V2/Workdays/UpdateWorkdayRequest.php +++ /dev/null @@ -1,26 +0,0 @@ - 'sometimes|nullable|integer|between:0,2147483647', - 'collection' => 'sometimes|nullable|string|in:' . implode(',', array_keys(array_merge(Workday::$siteCollections, Workday::$projectCollections))), - 'gender' => 'sometimes|nullable|string|between:1,255', - 'ethnicity' => 'sometimes|nullable|string|between:1,255', - 'indigeneity' => 'sometimes|nullable|string|between:1,255', - 'age' => 'sometimes|nullable|string|between:1,255', - ]; - } -} diff --git a/app/Models/Traits/HasWorkdays.php b/app/Models/Traits/HasWorkdays.php new file mode 100644 index 000000000..69b70cc85 --- /dev/null +++ b/app/Models/Traits/HasWorkdays.php @@ -0,0 +1,56 @@ +workdays()->collection($collection); + } + ); + } + } + + public function workdays () + { + return $this->morphMany(Workday::class, 'workdayable'); + } + + public function getOtherWorkdaysDescriptionAttribute(): ?string + { + return $this + ->workdays() + ->whereIn('collection', self::OTHER_WORKDAY_COLLECTIONS) + ->orderBy('updated_at', 'desc') + ->select('description') + ->first() + ?->description; + } + + public function setOtherWorkdaysDescriptionAttribute(?string $value): void + { + $workdaysQuery = $this->morphMany(Workday::class, 'workdayable'); + if (! empty($value)) { + foreach (self::OTHER_WORKDAY_COLLECTIONS as $collection) { + if (! (clone $workdaysQuery)->where('collection', $collection)->exists()) { + Workday::create([ + 'workdayable_type' => get_class($this), + 'workdayable_id' => $this->id, + 'collection' => $collection, + ]); + } + } + } + $workdaysQuery + ->whereIn('collection', self::OTHER_WORKDAY_COLLECTIONS) + ->update(['description' => $value]); + } +} diff --git a/app/Models/V2/Projects/ProjectReport.php b/app/Models/V2/Projects/ProjectReport.php index 6bfbf8a86..52526197e 100644 --- a/app/Models/V2/Projects/ProjectReport.php +++ b/app/Models/V2/Projects/ProjectReport.php @@ -10,6 +10,7 @@ use App\Models\Traits\HasUpdateRequests; use App\Models\Traits\HasUuid; use App\Models\Traits\HasV2MediaCollections; +use App\Models\Traits\HasWorkdays; use App\Models\Traits\UsesLinkedFields; use App\Models\V2\MediaModel; use App\Models\V2\Nurseries\Nursery; @@ -52,6 +53,7 @@ class ProjectReport extends Model implements MediaModel, AuditableContract, Repo use HasUpdateRequests; use HasEntityResources; use BelongsToThroughTrait; + use HasWorkdays; protected $auditInclude = [ 'status', @@ -141,6 +143,9 @@ class ProjectReport extends Model implements MediaModel, AuditableContract, Repo 'local_engagement', 'site_addition', 'paid_other_activity_description', + + // virtual (see HasWorkdays trait) + 'other_workdays_description', ]; public $casts = [ @@ -173,6 +178,13 @@ class ProjectReport extends Model implements MediaModel, AuditableContract, Repo ], ]; + // Required by the HasWorkdays trait + public const WORKDAY_COLLECTIONS = Workday::PROJECT_COLLECTION; + public const OTHER_WORKDAY_COLLECTIONS = [ + Workday::COLLECTION_PROJECT_PAID_OTHER, + Workday::COLLECTION_PROJECT_VOLUNTEER_OTHER, + ]; + public function registerMediaConversions(Media $media = null): void { $this->addMediaConversion('thumbnail') @@ -239,36 +251,6 @@ public function treeSpecies() return $this->morphMany(TreeSpecies::class, 'speciesable'); } - public function workdaysPaidNurseryOperations() - { - return $this->morphMany(Workday::class, 'workdayable')->where('collection', Workday::COLLECTION_PROJECT_PAID_NURSERY_OPRERATIONS); - } - - public function workdaysPaidProjectManagement() - { - return $this->morphMany(Workday::class, 'workdayable')->where('collection', Workday::COLLECTION_PROJECT_PAID_PROJECT_MANAGEMENT); - } - - public function workdaysPaidOtherActivities() - { - return $this->morphMany(Workday::class, 'workdayable')->where('collection', Workday::COLLECTION_PROJECT_PAID_OTHER); - } - - public function workdaysVolunteerNurseryOperations() - { - return $this->morphMany(Workday::class, 'workdayable')->where('collection', Workday::COLLECTION_PROJECT_VOLUNTEER_NURSERY_OPRERATIONS); - } - - public function workdaysVolunteerProjectManagement() - { - return $this->morphMany(Workday::class, 'workdayable')->where('collection', Workday::COLLECTION_PROJECT_VOLUNTEER_PROJECT_MANAGEMENT); - } - - public function workdaysVolunteerOtherActivities() - { - return $this->morphMany(Workday::class, 'workdayable')->where('collection', Workday::COLLECTION_PROJECT_VOLUNTEER_OTHER); - } - /** Calculated Values */ public function getTaskUuidAttribute(): ?string { diff --git a/app/Models/V2/Sites/SiteReport.php b/app/Models/V2/Sites/SiteReport.php index a8cd39854..48e80e8a3 100644 --- a/app/Models/V2/Sites/SiteReport.php +++ b/app/Models/V2/Sites/SiteReport.php @@ -11,6 +11,7 @@ use App\Models\Traits\HasUpdateRequests; use App\Models\Traits\HasUuid; use App\Models\Traits\HasV2MediaCollections; +use App\Models\Traits\HasWorkdays; use App\Models\Traits\UsesLinkedFields; use App\Models\V2\Disturbance; use App\Models\V2\Invasive; @@ -56,6 +57,7 @@ class SiteReport extends Model implements MediaModel, AuditableContract, ReportM use HasUpdateRequests; use HasEntityResources; use BelongsToThroughTrait; + use HasWorkdays; protected $auditInclude = [ 'status', @@ -92,7 +94,7 @@ class SiteReport extends Model implements MediaModel, AuditableContract, ReportM 'answers', 'paid_other_activity_description', - // virtual (see get/set accessor) + // virtual (see HasWorkdays trait) 'other_workdays_description', ]; @@ -142,6 +144,13 @@ class SiteReport extends Model implements MediaModel, AuditableContract, ReportM 'answers' => 'array', ]; + // Required by the HasWorkdays trait + public const WORKDAY_COLLECTIONS = Workday::SITE_COLLECTIONS; + public const OTHER_WORKDAY_COLLECTIONS = [ + Workday::COLLECTION_SITE_PAID_OTHER, + Workday::COLLECTION_SITE_VOLUNTEER_OTHER, + ]; + public function registerMediaConversions(Media $media = null): void { $this->addMediaConversion('thumbnail') @@ -219,56 +228,6 @@ public function invasive() return $this->morphMany(Invasive::class, 'invasiveable'); } - public function workdaysPaidSiteEstablishment() - { - return $this->morphMany(Workday::class, 'workdayable')->where('collection', Workday::COLLECTION_SITE_PAID_SITE_ESTABLISHMENT); - } - - public function workdaysPaidPlanting() - { - return $this->morphMany(Workday::class, 'workdayable')->where('collection', Workday::COLLECTION_SITE_PAID_PLANTING); - } - - public function workdaysPaidSiteMaintenance() - { - return $this->morphMany(Workday::class, 'workdayable')->where('collection', Workday::COLLECTION_SITE_PAID_SITE_MAINTENANCE); - } - - public function workdaysPaidSiteMonitoring() - { - return $this->morphMany(Workday::class, 'workdayable')->where('collection', Workday::COLLECTION_SITE_PAID_SITE_MONITORING); - } - - public function workdaysPaidOtherActivities() - { - return $this->morphMany(Workday::class, 'workdayable')->where('collection', Workday::COLLECTION_SITE_PAID_OTHER); - } - - public function workdaysVolunteerSiteEstablishment() - { - return $this->morphMany(Workday::class, 'workdayable')->where('collection', Workday::COLLECTION_SITE_VOLUNTEER_SITE_ESTABLISHMENT); - } - - public function workdaysVolunteerPlanting() - { - return $this->morphMany(Workday::class, 'workdayable')->where('collection', Workday::COLLECTION_SITE_VOLUNTEER_PLANTING); - } - - public function workdaysVolunteerSiteMaintenance() - { - return $this->morphMany(Workday::class, 'workdayable')->where('collection', Workday::COLLECTION_SITE_VOLUNTEER_SITE_MAINTENANCE); - } - - public function workdaysVolunteerSiteMonitoring() - { - return $this->morphMany(Workday::class, 'workdayable')->where('collection', Workday::COLLECTION_SITE_VOLUNTEER_SITE_MONITORING); - } - - public function workdaysVolunteerOtherActivities() - { - return $this->morphMany(Workday::class, 'workdayable')->where('collection', Workday::COLLECTION_SITE_VOLUNTEER_OTHER); - } - public function approvedBy(): HasOne { return $this->hasOne(User::class, 'id', 'approved_by'); @@ -319,38 +278,6 @@ public function getTaskUuidAttribute(): ?string return $this->task?->uuid ?? null; } - public function getOtherWorkdaysDescriptionAttribute(): ?string - { - $paid = $this->workdaysPaidOtherActivities()->first(); - $volunteer = $this->workdaysVolunteerOtherActivities()->first(); - - if ($paid == null || $volunteer == null || $paid->description == $volunteer->description) { - return $paid?->description ?? $volunteer?->description; - } else { - // They're both not null, but their description doesn't match. This is unexpected, but we'll - // return the most recently updated record's value - return $paid->updated_at->greatedthan($volunteer->updated_at) ? $paid->description : $volunteer->description; - } - } - - public function setOtherWorkdaysDescriptionAttribute(?string $value) - { - $collections = [Workday::COLLECTION_SITE_PAID_OTHER, Workday::COLLECTION_SITE_VOLUNTEER_OTHER]; - $workdaysQuery = $this->morphMany(Workday::class, 'workdayable'); - if (! empty($value)) { - foreach ($collections as $collection) { - if (! (clone $workdaysQuery)->where('collection', $collection)->exists()) { - Workday::create([ - 'workdayable_type' => get_class($this), - 'workdayable_id' => $this->id, - 'collection' => $collection, - ]); - } - } - } - $workdaysQuery->whereIn('collection', $collections)->update(['description' => $value]); - } - public function toSearchableArray() { return [ diff --git a/app/Models/V2/Workdays/Workday.php b/app/Models/V2/Workdays/Workday.php index 145fe38de..547888cf6 100644 --- a/app/Models/V2/Workdays/Workday.php +++ b/app/Models/V2/Workdays/Workday.php @@ -6,6 +6,7 @@ use App\Models\Traits\HasTypes; use App\Models\Traits\HasUuid; use App\Models\V2\EntityModel; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -42,18 +43,18 @@ class Workday extends Model implements HandlesLinkedFieldSync 'description', ]; - public const COLLECTION_PROJECT_PAID_NURSERY_OPRERATIONS = 'paid-nursery-operations'; + public const COLLECTION_PROJECT_PAID_NURSERY_OPERATIONS = 'paid-nursery-operations'; public const COLLECTION_PROJECT_PAID_PROJECT_MANAGEMENT = 'paid-project-management'; public const COLLECTION_PROJECT_PAID_OTHER = 'paid-other-activities'; - public const COLLECTION_PROJECT_VOLUNTEER_NURSERY_OPRERATIONS = 'volunteer-nursery-operations'; + public const COLLECTION_PROJECT_VOLUNTEER_NURSERY_OPERATIONS = 'volunteer-nursery-operations'; public const COLLECTION_PROJECT_VOLUNTEER_PROJECT_MANAGEMENT = 'volunteer-project-management'; public const COLLECTION_PROJECT_VOLUNTEER_OTHER = 'volunteer-other-activities'; - public static $projectCollections = [ - self::COLLECTION_PROJECT_PAID_NURSERY_OPRERATIONS => 'Paid Nursery Operations', + public const PROJECT_COLLECTION = [ + self::COLLECTION_PROJECT_PAID_NURSERY_OPERATIONS => 'Paid Nursery Operations', self::COLLECTION_PROJECT_PAID_PROJECT_MANAGEMENT => 'Paid Project Management', self::COLLECTION_PROJECT_PAID_OTHER => 'Paid Other Activities', - self::COLLECTION_PROJECT_VOLUNTEER_NURSERY_OPRERATIONS => 'Volunteer Nursery Operations', + self::COLLECTION_PROJECT_VOLUNTEER_NURSERY_OPERATIONS => 'Volunteer Nursery Operations', self::COLLECTION_PROJECT_VOLUNTEER_PROJECT_MANAGEMENT => 'Volunteer Project Management', self::COLLECTION_PROJECT_VOLUNTEER_OTHER => 'Volunteer Other Activities', ]; @@ -69,7 +70,7 @@ class Workday extends Model implements HandlesLinkedFieldSync public const COLLECTION_SITE_VOLUNTEER_SITE_MONITORING = 'volunteer-site-monitoring'; public const COLLECTION_SITE_VOLUNTEER_OTHER = 'volunteer-other-activities'; - public static $siteCollections = [ + public const SITE_COLLECTIONS = [ self::COLLECTION_SITE_PAID_SITE_ESTABLISHMENT => 'Paid Site Establishment', self::COLLECTION_SITE_PAID_PLANTING => 'Paid Planting', self::COLLECTION_SITE_PAID_SITE_MAINTENANCE => 'Paid Site Maintenance', @@ -146,6 +147,11 @@ public function getRouteKeyName() return 'uuid'; } + public function scopeCollection (Builder $query, string $collection): Builder + { + return $query->where('collection', $collection); + } + public function demographics(): HasMany { return $this->hasMany(WorkdayDemographic::class); @@ -157,6 +163,6 @@ public function getReadableCollectionAttribute(): ?string return null; } - return data_get(array_merge(static::$projectCollections, static::$siteCollections), $this->collection, 'Unknown'); + return data_get(array_merge(static::PROJECT_COLLECTION, static::SITE_COLLECTIONS), $this->collection, 'Unknown'); } } diff --git a/config/wri/linked-fields.php b/config/wri/linked-fields.php index 50aecb461..e5d5aec2c 100644 --- a/config/wri/linked-fields.php +++ b/config/wri/linked-fields.php @@ -414,7 +414,9 @@ 'pro-rep-equitable-opportunities' => ['property' => 'equitable_opportunities', 'label' => 'Equitable Opportunities for Women + Youth', 'input_type' => 'long-text'], 'pro-rep-local-engagement' => ['property' => 'local_engagement', 'label' => 'Community Engagement Approach', 'input_type' => 'select', 'multichoice' => false, 'option_list_key' => 'local-engagement'], 'pro-rep-site-addition' => ['property' => 'site_addition', 'label' => 'Site Addition', 'input_type' => 'boolean'], + // TODO (TM-912) Deprecated, to be removed. 'pro-rep-paid-other-activity-description' => ['property' => 'paid_other_activity_description', 'label' => 'Paid Other Activities Description', 'input_type' => 'long-text'], + 'pro-rep-other-workdays-description' => ['property' => 'other_workdays_description', 'label' => 'Other Activities Description', 'input_type' => 'long-text'], ], 'relations' => [ 'pro-rep-rel-tree-species' => [ From 671217839e30fabc27c4a3af9eaa466700214862 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Tue, 14 May 2024 14:04:48 -0700 Subject: [PATCH 45/64] [TM-913] Lint fix --- app/Models/Traits/HasWorkdays.php | 2 +- app/Models/V2/Workdays/Workday.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Models/Traits/HasWorkdays.php b/app/Models/Traits/HasWorkdays.php index 69b70cc85..095563b69 100644 --- a/app/Models/Traits/HasWorkdays.php +++ b/app/Models/Traits/HasWorkdays.php @@ -19,7 +19,7 @@ function ($entity) use ($collection) { } } - public function workdays () + public function workdays() { return $this->morphMany(Workday::class, 'workdayable'); } diff --git a/app/Models/V2/Workdays/Workday.php b/app/Models/V2/Workdays/Workday.php index 547888cf6..038f1cf27 100644 --- a/app/Models/V2/Workdays/Workday.php +++ b/app/Models/V2/Workdays/Workday.php @@ -147,7 +147,7 @@ public function getRouteKeyName() return 'uuid'; } - public function scopeCollection (Builder $query, string $collection): Builder + public function scopeCollection(Builder $query, string $collection): Builder { return $query->where('collection', $collection); } From f42cc8b3b78498b9a4f25aac6650889029766e32 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Tue, 14 May 2024 14:15:58 -0700 Subject: [PATCH 46/64] [TM-913] Fix tests --- database/factories/V2/Workdays/WorkdayFactory.php | 2 +- tests/V2/Workdays/GetWorkdaysForEntityControllerTest.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/database/factories/V2/Workdays/WorkdayFactory.php b/database/factories/V2/Workdays/WorkdayFactory.php index dfe7cf23a..e6a19f119 100644 --- a/database/factories/V2/Workdays/WorkdayFactory.php +++ b/database/factories/V2/Workdays/WorkdayFactory.php @@ -19,7 +19,7 @@ public function definition() 'uuid' => $this->faker->uuid(), 'workdayable_type' => SiteReport::class, 'workdayable_id' => SiteReport::factory()->create(), - 'collection' => $this->faker->randomElement(array_keys(Workday::$siteCollections)), + 'collection' => $this->faker->randomElement(array_keys(Workday::SITE_COLLECTIONS)), ]; } } diff --git a/tests/V2/Workdays/GetWorkdaysForEntityControllerTest.php b/tests/V2/Workdays/GetWorkdaysForEntityControllerTest.php index 4b0393d10..9c79db07b 100644 --- a/tests/V2/Workdays/GetWorkdaysForEntityControllerTest.php +++ b/tests/V2/Workdays/GetWorkdaysForEntityControllerTest.php @@ -54,7 +54,7 @@ public function test_empty_workdays_response() $response = $this->actingAs($owner) ->getJson($uri) ->assertSuccessful() - ->assertJsonCount(count(Workday::$siteCollections), 'data') + ->assertJsonCount(count(Workday::SITE_COLLECTIONS), 'data') ->decodeResponseJson(); foreach ($response['data'] as $workday) { $this->assertCount(0, $workday['demographics']); @@ -115,7 +115,7 @@ public function test_populated_workdays() $response = $this->actingAs($owner) ->getJson($uri) ->assertSuccessful() - ->assertJsonCount(count(Workday::$siteCollections), 'data') + ->assertJsonCount(count(Workday::SITE_COLLECTIONS), 'data') ->decodeResponseJson(); $foundCollection = false; foreach ($response['data'] as $workdayData) { From c4f336e8121f1c3d1930166bcc977ad5316c3c95 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Tue, 14 May 2024 16:45:23 -0700 Subject: [PATCH 47/64] [TM-904] Add HBF as a framework permission and admin type. --- app/Console/Commands/Migration/RolesMigrationCommand.php | 7 ++++++- app/Policies/FrameworkPolicy.php | 2 +- app/Policies/V2/FundingProgrammePolicy.php | 2 +- app/Policies/V2/Nurseries/NurseryPolicy.php | 2 +- app/Policies/V2/Nurseries/NurseryReportPolicy.php | 2 +- app/Policies/V2/Projects/ProjectPolicy.php | 2 +- app/Policies/V2/Projects/ProjectReportPolicy.php | 2 +- app/Policies/V2/Sites/SitePolicy.php | 2 +- app/Policies/V2/Sites/SiteReportPolicy.php | 2 +- app/Policies/V2/Tasks/TaskPolicy.php | 2 +- app/Policies/V2/UpdateRequests/UpdateRequestPolicy.php | 2 +- config/wri/permissions.php | 1 + 12 files changed, 17 insertions(+), 11 deletions(-) diff --git a/app/Console/Commands/Migration/RolesMigrationCommand.php b/app/Console/Commands/Migration/RolesMigrationCommand.php index a0f1a4b41..9855a097c 100644 --- a/app/Console/Commands/Migration/RolesMigrationCommand.php +++ b/app/Console/Commands/Migration/RolesMigrationCommand.php @@ -52,7 +52,7 @@ public function handle() if (Role::where('name', 'admin-super')->count() === 0) { $role = Role::create(['name' => 'admin-super']); - $role->givePermissionTo(['framework-terrafund', 'framework-ppc', 'framework-terrafund-enterprises', 'custom-forms-manage', 'users-manage', 'monitoring-manage', 'reports-manage']); + $role->givePermissionTo(['framework-terrafund', 'framework-ppc', 'framework-terrafund-enterprises', 'framework-hbf', 'custom-forms-manage', 'users-manage', 'monitoring-manage', 'reports-manage']); } if (Role::where('name', 'admin-ppc')->count() === 0) { @@ -65,6 +65,11 @@ public function handle() $role->givePermissionTo(['framework-terrafund', 'framework-terrafund-enterprises', 'custom-forms-manage', 'users-manage', 'monitoring-manage', 'reports-manage']); } + if (Role::where('name', 'admin-hbf')->count() === 0) { + $role = Role::create(['name' => 'admin-hbf']); + $role->givePermissionTo(['framework-hbf', 'custom-forms-manage', 'users-manage', 'monitoring-manage', 'reports-manage']); + } + if (Role::where('name', 'project-developer')->count() === 0) { $role = Role::create(['name' => 'project-developer']); $role->givePermissionTo(['manage-own']); diff --git a/app/Policies/FrameworkPolicy.php b/app/Policies/FrameworkPolicy.php index 1bd9d1f48..977f26a3f 100644 --- a/app/Policies/FrameworkPolicy.php +++ b/app/Policies/FrameworkPolicy.php @@ -8,6 +8,6 @@ class FrameworkPolicy extends Policy { public function update(?UserModel $user): bool { - return $user->hasAnyPermission(['framework-terrafund', 'framework-ppc']); + return $user->hasAnyPermission(['framework-terrafund', 'framework-ppc', 'framework-hbf']); } } diff --git a/app/Policies/V2/FundingProgrammePolicy.php b/app/Policies/V2/FundingProgrammePolicy.php index d41deaa8d..28ce24739 100644 --- a/app/Policies/V2/FundingProgrammePolicy.php +++ b/app/Policies/V2/FundingProgrammePolicy.php @@ -10,6 +10,6 @@ class FundingProgrammePolicy extends Policy { public function uploadFiles(?User $user, ?FundingProgramme $model = null): bool { - return $this->isVerifiedAdmin($user) || $user->hasAnyPermission(['framework-terrafund', 'framework-ppc']); + return $this->isVerifiedAdmin($user) || $user->hasAnyPermission(['framework-terrafund', 'framework-ppc', 'framework-hbf']); } } diff --git a/app/Policies/V2/Nurseries/NurseryPolicy.php b/app/Policies/V2/Nurseries/NurseryPolicy.php index d036703b9..bb7a96a33 100644 --- a/app/Policies/V2/Nurseries/NurseryPolicy.php +++ b/app/Policies/V2/Nurseries/NurseryPolicy.php @@ -25,7 +25,7 @@ public function read(?User $user, ?Nursery $nursey = null): bool public function readAll(?User $user, ?Nursery $nursey = null): bool { - return $user->hasAnyPermission(['framework-terrafund', 'framework-ppc']); + return $user->hasAnyPermission(['framework-terrafund', 'framework-ppc', 'framework-hbf']); } public function update(?User $user, ?Nursery $nursey = null): bool diff --git a/app/Policies/V2/Nurseries/NurseryReportPolicy.php b/app/Policies/V2/Nurseries/NurseryReportPolicy.php index d0a7fe012..cdecf2454 100644 --- a/app/Policies/V2/Nurseries/NurseryReportPolicy.php +++ b/app/Policies/V2/Nurseries/NurseryReportPolicy.php @@ -25,7 +25,7 @@ public function read(?User $user, ?NurseryReport $report = null): bool public function readAll(?User $user, ?NurseryReport $report = null): bool { - return $user->hasAnyPermission(['framework-terrafund', 'framework-ppc']); + return $user->hasAnyPermission(['framework-terrafund', 'framework-ppc', 'framework-hbf']); } public function update(?User $user, ?NurseryReport $report = null): bool diff --git a/app/Policies/V2/Projects/ProjectPolicy.php b/app/Policies/V2/Projects/ProjectPolicy.php index 0418ac7c4..307c5e73f 100644 --- a/app/Policies/V2/Projects/ProjectPolicy.php +++ b/app/Policies/V2/Projects/ProjectPolicy.php @@ -30,7 +30,7 @@ public function read(?User $user, ?Project $project = null): bool public function readAll(?User $user, ?Project $project = null): bool { - return $user->hasAnyPermission(['framework-terrafund', 'framework-ppc']); + return $user->hasAnyPermission(['framework-terrafund', 'framework-ppc', 'framework-hbf']); } public function update(?User $user, ?Project $project = null): bool diff --git a/app/Policies/V2/Projects/ProjectReportPolicy.php b/app/Policies/V2/Projects/ProjectReportPolicy.php index 81b8b1e7e..cd83e87e4 100644 --- a/app/Policies/V2/Projects/ProjectReportPolicy.php +++ b/app/Policies/V2/Projects/ProjectReportPolicy.php @@ -25,7 +25,7 @@ public function read(?User $user, ?ProjectReport $report = null): bool public function readAll(?User $user, ?ProjectReport $report = null): bool { - return $user->hasAnyPermission(['framework-terrafund', 'framework-ppc']); + return $user->hasAnyPermission(['framework-terrafund', 'framework-ppc', 'framework-hbf']); } public function update(?User $user, ?ProjectReport $report = null): bool diff --git a/app/Policies/V2/Sites/SitePolicy.php b/app/Policies/V2/Sites/SitePolicy.php index 153f6c065..2b7b22a52 100644 --- a/app/Policies/V2/Sites/SitePolicy.php +++ b/app/Policies/V2/Sites/SitePolicy.php @@ -25,7 +25,7 @@ public function read(?User $user, ?Site $site = null): bool public function readAll(?User $user, ?Site $site = null): bool { - return $user->hasAnyPermission(['framework-terrafund', 'framework-ppc']); + return $user->hasAnyPermission(['framework-terrafund', 'framework-ppc', 'framework-hbf']); } public function update(?User $user, ?Site $site = null): bool diff --git a/app/Policies/V2/Sites/SiteReportPolicy.php b/app/Policies/V2/Sites/SiteReportPolicy.php index 17047eea0..74a2a2fdd 100644 --- a/app/Policies/V2/Sites/SiteReportPolicy.php +++ b/app/Policies/V2/Sites/SiteReportPolicy.php @@ -25,7 +25,7 @@ public function read(?User $user, ?SiteReport $report = null): bool public function readAll(?User $user, ?SiteReport $report = null): bool { - return $user->hasAnyPermission(['framework-terrafund', 'framework-ppc']); + return $user->hasAnyPermission(['framework-terrafund', 'framework-ppc', 'framework-hbf']); } public function update(?User $user, ?SiteReport $report = null): bool diff --git a/app/Policies/V2/Tasks/TaskPolicy.php b/app/Policies/V2/Tasks/TaskPolicy.php index 6029d8220..ccba44a67 100644 --- a/app/Policies/V2/Tasks/TaskPolicy.php +++ b/app/Policies/V2/Tasks/TaskPolicy.php @@ -24,7 +24,7 @@ public function read(?User $user, ?Task $task = null): bool public function readAll(?User $user, ?Task $task = null): bool { - return $user->hasAnyPermission(['framework-terrafund', 'framework-ppc']); + return $user->hasAnyPermission(['framework-terrafund', 'framework-ppc', 'framework-hbf']); } public function update(?User $user, ?Task $task = null): bool diff --git a/app/Policies/V2/UpdateRequests/UpdateRequestPolicy.php b/app/Policies/V2/UpdateRequests/UpdateRequestPolicy.php index 32eeb9608..227fa6c18 100644 --- a/app/Policies/V2/UpdateRequests/UpdateRequestPolicy.php +++ b/app/Policies/V2/UpdateRequests/UpdateRequestPolicy.php @@ -29,7 +29,7 @@ public function read(?User $user, ?UpdateRequest $updateRequest = null): bool public function readAll(?User $user, ?UpdateRequest $updateRequest = null): bool { - return $user->hasAnyPermission(['framework-terrafund', 'framework-ppc']); + return $user->hasAnyPermission(['framework-terrafund', 'framework-ppc', 'framework-hbf']); } public function update(?User $user, ?UpdateRequest $updateRequest = null): bool diff --git a/config/wri/permissions.php b/config/wri/permissions.php index 840b63edb..3e3f29c99 100644 --- a/config/wri/permissions.php +++ b/config/wri/permissions.php @@ -4,6 +4,7 @@ 'framework-ppc' => 'Framework PPC', 'framework-terrafund' => 'Framework Terrafund', 'framework-terrafund-enterprises' => 'Framework Terrafund Enterprises', + 'framework-hbf' => 'Framework Harit Bharat Fund', 'custom-forms-manage' => 'Manage custom forms', 'users-manage' => 'Manage users', 'monitoring-manage' => 'Manage monitoring', From 75d65417714a7cbe667cc070aabcbb5393873c4a Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Wed, 15 May 2024 07:54:46 -0700 Subject: [PATCH 48/64] [TM-675] Rejigger the old_id / old_model columns on sites and projects. --- ...05_15_055006_move_old_site_project_ids.php | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 database/migrations/2024_05_15_055006_move_old_site_project_ids.php diff --git a/database/migrations/2024_05_15_055006_move_old_site_project_ids.php b/database/migrations/2024_05_15_055006_move_old_site_project_ids.php new file mode 100644 index 000000000..22066d79a --- /dev/null +++ b/database/migrations/2024_05_15_055006_move_old_site_project_ids.php @@ -0,0 +1,98 @@ +where('old_model', 'App\Models\Terrafund\TerrafundSite') + ->update(['old_id' => null]); + DB::table('v2_projects') + ->where('old_model', 'App\Models\Terrafund\TerrafundProgramme') + ->update(['old_id' => null]); + + Schema::table('v2_sites', function (Blueprint $table) { + $table->renameColumn('old_id', 'ppc_external_id'); + $table->dropColumn('old_model'); + }); + Schema::table('v2_sites', function (Blueprint $table) { + $table->unsignedInteger('ppc_external_id')->unique()->change(); + + // These triggers ensure that we always get a unique ppc_external_id set for every ppc site + DB::unprepared(' + CREATE TRIGGER before_insert_v2_sites BEFORE INSERT ON v2_sites + FOR EACH ROW + BEGIN + IF (NEW.framework_key = \'ppc\') THEN + SET NEW.ppc_external_id = (SELECT max(ppc_external_id) + 1 FROM v2_sites); + END IF; + END; + '); + DB::unprepared(' + CREATE TRIGGER before_update_v2_sites BEFORE UPDATE ON v2_sites + FOR EACH ROW + BEGIN + IF (NEW.framework_key = \'ppc\' AND OLD.framework_key != \'pcc\' AND NEW.ppc_external_id IS NULL) THEN + SET NEW.ppc_external_id = (SELECT max(ppc_external_id) + 1 FROM v2_sites); + END IF; + END; + '); + }); + + Schema::table('v2_projects', function (Blueprint $table) { + $table->renameColumn('old_id', 'ppc_external_id'); + $table->dropColumn('old_model'); + }); + Schema::table('v2_projects', function (Blueprint $table) { + $table->unsignedInteger('ppc_external_id')->unique()->change(); + + // These triggers ensure that we always get a unique ppc_external_id set for every ppc project + DB::unprepared(' + CREATE TRIGGER before_insert_v2_projects BEFORE INSERT ON v2_projects + FOR EACH ROW + BEGIN + IF (NEW.framework_key = \'ppc\') THEN + SET NEW.ppc_external_id = (SELECT max(ppc_external_id) + 1 FROM v2_projects); + END IF; + END; + '); + DB::unprepared(' + CREATE TRIGGER before_update_v2_projects BEFORE UPDATE ON v2_projects + FOR EACH ROW + BEGIN + IF (NEW.framework_key = \'ppc\' AND OLD.framework_key != \'pcc\' AND NEW.ppc_external_id IS NULL) THEN + SET NEW.ppc_external_id = (SELECT max(ppc_external_id) + 1 FROM v2_projects); + END IF; + END; + '); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('v2_sites', function (Blueprint $table) { + $table->renameColumn('ppc_external_id', 'old_id'); + $table->unsignedInteger('ppc_external_id')->change(); + }); + Schema::table('v2_projects', function (Blueprint $table) { + $table->renameColumn('ppc_external_id', 'old_id'); + $table->unsignedInteger('ppc_external_id')->change(); + }); + DB::unprepared(' + DROP TRIGGER IF EXISTS before_insert_v2_sites; + DROP TRIGGER IF EXISTS before_update_v2_sites; + DROP TRIGGER IF EXISTS before_insert_v2_projects; + DROP TRIGGER IF EXISTS before_update_v2_projects; + '); + } +}; From 4f162039a054702381d0c92a00f7eef3231f8799 Mon Sep 17 00:00:00 2001 From: cesarLima1 Date: Wed, 15 May 2024 14:21:04 -0400 Subject: [PATCH 49/64] [TM-799] Corrected spelling error and verified existence of error attribute. --- .../V2/Terrafund/TerrafundCreateGeometryController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php b/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php index 0950c961a..f7893f39a 100644 --- a/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php +++ b/app/Http/Controllers/V2/Terrafund/TerrafundCreateGeometryController.php @@ -424,7 +424,7 @@ public function getAllCountryNames() private function handlePolygonValidation($polygonUuid, $response, $criteriaId): JsonResponse { - if ($response['error'] != null) { + if (isset($response['error']) && $response['error'] != null) { $status = $response['status']; unset($response['valid']); unset($response['status']); @@ -433,7 +433,7 @@ private function handlePolygonValidation($polygonUuid, $response, $criteriaId): } $response['insertion_success'] = App::make(PolygonService::class) - ->createCriteraSite($polygonUuid, $criteriaId, $response['valid']); + ->createCriteriaSite($polygonUuid, $criteriaId, $response['valid']); return response()->json($response); } From 06d2353d658f9a9e838f6fef2b6bbb7aeebb5bfb Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Wed, 15 May 2024 11:39:51 -0700 Subject: [PATCH 50/64] [TM-675] Use the ppc_external_id field. --- app/Exports/V2/EntityExport.php | 4 ++-- .../Controllers/V2/Sites/CreateSiteWithFormController.php | 4 ---- app/Models/V2/Projects/Project.php | 3 +-- app/Models/V2/Sites/Site.php | 3 +-- 4 files changed, 4 insertions(+), 10 deletions(-) diff --git a/app/Exports/V2/EntityExport.php b/app/Exports/V2/EntityExport.php index d5f29a76d..2e4dc1c8d 100644 --- a/app/Exports/V2/EntityExport.php +++ b/app/Exports/V2/EntityExport.php @@ -67,7 +67,7 @@ protected function getAttachedMappedForEntity($entity): array $organisation = $entity->organisation; $mapped = [ - $entity->old_id ?? ($entity->id ?? null), + $entity->ppc_external_id ?? $entity->old_id ?? $entity->id ?? null, $entity->uuid, $organisation->readable_type ?? null, $organisation->name ?? null, @@ -77,7 +77,7 @@ protected function getAttachedMappedForEntity($entity): array ]; if (in_array($this->form->type, ['nursery', 'nursery-report','site', 'site-report', 'project-report'])) { - $mapped[] = $entity->project->old_id ?? ($entity->project->id ?? null); + $mapped[] = $entity->project->ppc_external_id ?? $entity->project->id ?? null; } if ($this->form->type === 'project-report') { diff --git a/app/Http/Controllers/V2/Sites/CreateSiteWithFormController.php b/app/Http/Controllers/V2/Sites/CreateSiteWithFormController.php index b8e72e635..e3a78586f 100644 --- a/app/Http/Controllers/V2/Sites/CreateSiteWithFormController.php +++ b/app/Http/Controllers/V2/Sites/CreateSiteWithFormController.php @@ -23,14 +23,10 @@ public function __invoke(Form $form, CreateEntityFormRequest $formRequest) return new JsonResponse('No Project found for this site.', 404); } - $lastOldId = Site::orderByDesc('old_id') - ->value('old_id'); - $site = Site::create([ 'framework_key' => $project->framework_key, 'project_id' => $project->id, 'status' => EntityStatusStateMachine::STARTED, - 'old_id' => $lastOldId + 1, ]); return $site->createSchemaResource(); diff --git a/app/Models/V2/Projects/Project.php b/app/Models/V2/Projects/Project.php index d9d153bfc..b7b125870 100644 --- a/app/Models/V2/Projects/Project.php +++ b/app/Models/V2/Projects/Project.php @@ -98,8 +98,6 @@ class Project extends Model implements MediaModel, AuditableContract, EntityMode 'monitored_tree_cover', 'land_use_types', 'restoration_strategy', - 'old_model', - 'old_id', 'feedback', 'feedback_fields', 'organization_name', @@ -123,6 +121,7 @@ class Project extends Model implements MediaModel, AuditableContract, EntityMode 'pct_beneficiaries_youth', 'land_tenure_project_area', 'answers', + 'ppc_external_id', ]; public $fileConfiguration = [ diff --git a/app/Models/V2/Sites/Site.php b/app/Models/V2/Sites/Site.php index 4c9689eed..69b6c48fb 100644 --- a/app/Models/V2/Sites/Site.php +++ b/app/Models/V2/Sites/Site.php @@ -89,11 +89,10 @@ class Site extends Model implements MediaModel, AuditableContract, EntityModel 'siting_strategy', 'description_siting_strategy', 'framework_key', - 'old_id', - 'old_model', 'feedback', 'feedback_fields', 'answers', + 'ppc_external_id', ]; public $fileConfiguration = [ From 479e44150786a490992e957c231662be80554fe1 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Wed, 15 May 2024 12:10:14 -0700 Subject: [PATCH 51/64] [TM-838] Avoid breaking when an old update request is merged. --- app/Models/V2/Workdays/Workday.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Models/V2/Workdays/Workday.php b/app/Models/V2/Workdays/Workday.php index 038f1cf27..1a8274bec 100644 --- a/app/Models/V2/Workdays/Workday.php +++ b/app/Models/V2/Workdays/Workday.php @@ -115,7 +115,7 @@ public static function syncRelation(EntityModel $entity, string $property, $data $demographics = $workday->demographics; $represented = collect(); - foreach ($workdayData['demographics'] as $demographicData) { + foreach (($workdayData['demographics'] ?? []) as $demographicData) { $demographic = $demographics->firstWhere([ 'type' => data_get($demographicData, 'type'), 'subtype' => data_get($demographicData, 'subtype'), From 1cbdaceb4a39c4aa4d5d81bf465c528cee865a59 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Wed, 15 May 2024 13:16:43 -0700 Subject: [PATCH 52/64] [TM-838] Include the ppc_external_id on project/site resources. --- app/Http/Resources/V2/Projects/ProjectLiteResource.php | 1 + app/Http/Resources/V2/Projects/ProjectResource.php | 1 + app/Http/Resources/V2/Sites/SiteLiteResource.php | 1 + app/Http/Resources/V2/Sites/SiteResource.php | 1 + 4 files changed, 4 insertions(+) diff --git a/app/Http/Resources/V2/Projects/ProjectLiteResource.php b/app/Http/Resources/V2/Projects/ProjectLiteResource.php index a2bcb625d..23fd777be 100644 --- a/app/Http/Resources/V2/Projects/ProjectLiteResource.php +++ b/app/Http/Resources/V2/Projects/ProjectLiteResource.php @@ -11,6 +11,7 @@ public function toArray($request) { $data = [ 'uuid' => $this->uuid, + 'ppc_external_id' => $this->ppc_external_id ?? $this->id, 'framework_key' => $this->framework_key, 'framework_uuid' => $this->framework_uuid, 'status' => $this->status, diff --git a/app/Http/Resources/V2/Projects/ProjectResource.php b/app/Http/Resources/V2/Projects/ProjectResource.php index 723f551da..4417e7d5c 100644 --- a/app/Http/Resources/V2/Projects/ProjectResource.php +++ b/app/Http/Resources/V2/Projects/ProjectResource.php @@ -12,6 +12,7 @@ public function toArray($request) { $data = [ 'uuid' => $this->uuid, + 'ppc_external_id' => $this->ppc_external_id ?? $this->id, 'name' => $this->name, 'status' => $this->status, 'readable_status' => $this->readable_status, diff --git a/app/Http/Resources/V2/Sites/SiteLiteResource.php b/app/Http/Resources/V2/Sites/SiteLiteResource.php index aa4f1990f..1e286da0f 100644 --- a/app/Http/Resources/V2/Sites/SiteLiteResource.php +++ b/app/Http/Resources/V2/Sites/SiteLiteResource.php @@ -11,6 +11,7 @@ public function toArray($request) { $data = [ 'uuid' => $this->uuid, + 'ppc_external_id' => $this->ppc_external_id ?? $this->id, 'name' => $this->name, 'project' => new ProjectLiteResource($this->project), 'framework_key' => $this->framework_key, diff --git a/app/Http/Resources/V2/Sites/SiteResource.php b/app/Http/Resources/V2/Sites/SiteResource.php index fc835578d..6daba0c71 100644 --- a/app/Http/Resources/V2/Sites/SiteResource.php +++ b/app/Http/Resources/V2/Sites/SiteResource.php @@ -13,6 +13,7 @@ public function toArray($request) { $data = [ 'uuid' => $this->uuid, + 'ppc_external_id' => $this->ppc_external_id ?? $this->id, 'name' => $this->name, 'framework_key' => $this->framework_key, 'framework_uuid' => $this->framework_uuid, From 6bc1d62c8ae6b68e537041417b0381e8ff021957 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Wed, 15 May 2024 16:00:17 -0700 Subject: [PATCH 53/64] [TM-675] Fix up the migration after some testing with a fresh clone of prod. --- .../2024_05_15_055006_move_old_site_project_ids.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/database/migrations/2024_05_15_055006_move_old_site_project_ids.php b/database/migrations/2024_05_15_055006_move_old_site_project_ids.php index 22066d79a..6c3931bd6 100644 --- a/database/migrations/2024_05_15_055006_move_old_site_project_ids.php +++ b/database/migrations/2024_05_15_055006_move_old_site_project_ids.php @@ -12,10 +12,10 @@ public function up(): void { DB::table('v2_sites') - ->where('old_model', 'App\Models\Terrafund\TerrafundSite') + ->whereNot('framework_key', 'ppc') ->update(['old_id' => null]); DB::table('v2_projects') - ->where('old_model', 'App\Models\Terrafund\TerrafundProgramme') + ->whereNot('framework_key', 'ppc') ->update(['old_id' => null]); Schema::table('v2_sites', function (Blueprint $table) { @@ -30,7 +30,7 @@ public function up(): void CREATE TRIGGER before_insert_v2_sites BEFORE INSERT ON v2_sites FOR EACH ROW BEGIN - IF (NEW.framework_key = \'ppc\') THEN + IF (NEW.framework_key = \'ppc\' and NEW.ppc_external_id IS NULL) THEN SET NEW.ppc_external_id = (SELECT max(ppc_external_id) + 1 FROM v2_sites); END IF; END; @@ -58,7 +58,7 @@ public function up(): void CREATE TRIGGER before_insert_v2_projects BEFORE INSERT ON v2_projects FOR EACH ROW BEGIN - IF (NEW.framework_key = \'ppc\') THEN + IF (NEW.framework_key = \'ppc\' and NEW.ppc_external_id IS NULL) THEN SET NEW.ppc_external_id = (SELECT max(ppc_external_id) + 1 FROM v2_projects); END IF; END; From c48b6d70e8c139c75c204eb88a74225d20a4f6de Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Thu, 16 May 2024 11:23:19 -0700 Subject: [PATCH 54/64] [TM-930] Don't attempt to create a permission that already exists. --- ...dminCreateReportingFrameworkController.php | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/app/Http/Controllers/V2/ReportingFrameworks/AdminCreateReportingFrameworkController.php b/app/Http/Controllers/V2/ReportingFrameworks/AdminCreateReportingFrameworkController.php index f674930cb..c75363b5c 100644 --- a/app/Http/Controllers/V2/ReportingFrameworks/AdminCreateReportingFrameworkController.php +++ b/app/Http/Controllers/V2/ReportingFrameworks/AdminCreateReportingFrameworkController.php @@ -39,20 +39,23 @@ public function __invoke(CreateReportingFrameworkRequest $frameworkRequest): Rep 'updated_at' => now(), ]); - $PermissionAdded = Permission::create([ - 'name' => 'framework-' . Str::slug($frameworkRequest->name), - 'guard_name' => 'api', - 'created_at' => now(), - 'updated_at' => now(), - ]); - - $adminSuperRoleId = 1; - $adminTerrafund = 3; - - DB::table('role_has_permissions')->insert([ - ['permission_id' => $PermissionAdded->id, 'role_id' => $adminSuperRoleId], - ['permission_id' => $PermissionAdded->id, 'role_id' => $adminTerrafund], - ]); + $permissionName = 'framework-' . Str::slug($frameworkRequest->name); + if (! Permission::where('name', $permissionName)->exists()) { + $PermissionAdded = Permission::create([ + 'name' => 'framework-' . Str::slug($frameworkRequest->name), + 'guard_name' => 'api', + 'created_at' => now(), + 'updated_at' => now(), + ]); + + $adminSuperRoleId = 1; + $adminTerrafund = 3; + + DB::table('role_has_permissions')->insert([ + ['permission_id' => $PermissionAdded->id, 'role_id' => $adminSuperRoleId], + ['permission_id' => $PermissionAdded->id, 'role_id' => $adminTerrafund], + ]); + } Form::isUuid($frameworkRequest->project_form_uuid)->update([ 'framework_key' => $framework->slug, From e2e45220026350fd229776b27d8bd60cf4de2914 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Thu, 16 May 2024 11:47:01 -0700 Subject: [PATCH 55/64] [TM-931] Use the ppc_external_id for site id on site report export. --- app/Exports/V2/EntityExport.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Exports/V2/EntityExport.php b/app/Exports/V2/EntityExport.php index 2e4dc1c8d..db157c250 100644 --- a/app/Exports/V2/EntityExport.php +++ b/app/Exports/V2/EntityExport.php @@ -92,7 +92,7 @@ protected function getAttachedMappedForEntity($entity): array } if ($this->form->type === 'site-report') { - $mapped[] = $entity->site->old_id ?? ($entity->site->id ?? null); + $mapped[] = $entity->site->ppc_external_id ?? $entity->site->id ?? null; $mapped[] = $entity->site->name ?? null; $sumTreeSPecies = $entity->treeSpecies()->sum('amount'); $mapped[] = $sumTreeSPecies > 0 ? $sumTreeSPecies : null; From 04990997d517c51bf6e7b5f9fd5a81f3c186c082 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Thu, 16 May 2024 12:15:04 -0700 Subject: [PATCH 56/64] [TM-931] A migration to copy over the old "other" workdays description to the new virtual field. --- .../OneOff/MigrateWorkdayOtherDescription.php | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 app/Console/Commands/OneOff/MigrateWorkdayOtherDescription.php diff --git a/app/Console/Commands/OneOff/MigrateWorkdayOtherDescription.php b/app/Console/Commands/OneOff/MigrateWorkdayOtherDescription.php new file mode 100644 index 000000000..5b5d79b0d --- /dev/null +++ b/app/Console/Commands/OneOff/MigrateWorkdayOtherDescription.php @@ -0,0 +1,41 @@ +count() . "\n"; + + foreach ($query->get() as $report) { + $report->update(['other_workdays_description' => $report->paid_other_activity_description]); + } + } + + echo "Migration complete!\n"; + } +} From f3768bbea8775fc05098ba2496725fc9ff2f208b Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Thu, 16 May 2024 15:08:26 -0700 Subject: [PATCH 57/64] [TM-931] Calculate workday total amounts rather than relying on the report columns. --- app/Models/Traits/HasWorkdays.php | 53 +++++++++++++------ app/Models/V2/Projects/ProjectReport.php | 65 +++++++++++++----------- app/Models/V2/Sites/SiteReport.php | 25 ++++++--- app/Models/V2/Workdays/Workday.php | 5 ++ 4 files changed, 97 insertions(+), 51 deletions(-) diff --git a/app/Models/Traits/HasWorkdays.php b/app/Models/Traits/HasWorkdays.php index 095563b69..1869f0a09 100644 --- a/app/Models/Traits/HasWorkdays.php +++ b/app/Models/Traits/HasWorkdays.php @@ -3,20 +3,28 @@ namespace App\Models\Traits; use App\Models\V2\Workdays\Workday; +use App\Models\V2\Workdays\WorkdayDemographic; use Illuminate\Support\Str; +/** + * @property int workdays_paid + * @property int workdays_volunteer + * @property string other_workdays_description + */ trait HasWorkdays { public static function bootHasWorkdays() { - foreach (array_keys(static::WORKDAY_COLLECTIONS) as $collection) { - self::resolveRelationUsing( - 'workdays' . Str::studly($collection), - function ($entity) use ($collection) { - return $entity->workdays()->collection($collection); - } - ); - } + collect([static::WORKDAY_COLLECTIONS['paid'], static::WORKDAY_COLLECTIONS['volunteer']]) + ->flatten() + ->each(function ($collection) { + self::resolveRelationUsing( + 'workdays' . Str::studly($collection), + function ($entity) use ($collection) { + return $entity->workdays()->collection($collection); + } + ); + }); } public function workdays() @@ -24,11 +32,21 @@ public function workdays() return $this->morphMany(Workday::class, 'workdayable'); } + public function getWorkdaysPaidAttribute(): int + { + return $this->sumTotalWorkdaysAmounts(self::WORKDAY_COLLECTIONS['paid']); + } + + public function getWorkdaysVolunteerAttribute(): int + { + return $this->sumTotalWorkdaysAmounts(self::WORKDAY_COLLECTIONS['volunteer']); + } + public function getOtherWorkdaysDescriptionAttribute(): ?string { return $this ->workdays() - ->whereIn('collection', self::OTHER_WORKDAY_COLLECTIONS) + ->collections(self::WORKDAY_COLLECTIONS['other']) ->orderBy('updated_at', 'desc') ->select('description') ->first() @@ -37,10 +55,9 @@ public function getOtherWorkdaysDescriptionAttribute(): ?string public function setOtherWorkdaysDescriptionAttribute(?string $value): void { - $workdaysQuery = $this->morphMany(Workday::class, 'workdayable'); if (! empty($value)) { - foreach (self::OTHER_WORKDAY_COLLECTIONS as $collection) { - if (! (clone $workdaysQuery)->where('collection', $collection)->exists()) { + foreach (self::WORKDAY_COLLECTIONS['other'] as $collection) { + if (! $this->workdays()->collection($collection)->exists()) { Workday::create([ 'workdayable_type' => get_class($this), 'workdayable_id' => $this->id, @@ -49,8 +66,14 @@ public function setOtherWorkdaysDescriptionAttribute(?string $value): void } } } - $workdaysQuery - ->whereIn('collection', self::OTHER_WORKDAY_COLLECTIONS) - ->update(['description' => $value]); + + $this->workdays()->collections(self::WORKDAY_COLLECTIONS['other'])->update(['description' => $value]); + } + + protected function sumTotalWorkdaysAmounts(array $collections): int + { + // Assume that the types are balanced, and just return the value from `gender` + return WorkdayDemographic::whereIn('workday_id', $this->workdays()->collections($collections)->select('id')) + ->gender()->sum('amount'); } } diff --git a/app/Models/V2/Projects/ProjectReport.php b/app/Models/V2/Projects/ProjectReport.php index 52526197e..f5c72d169 100644 --- a/app/Models/V2/Projects/ProjectReport.php +++ b/app/Models/V2/Projects/ProjectReport.php @@ -24,6 +24,7 @@ use App\Models\V2\TreeSpecies\TreeSpecies; use App\Models\V2\User; use App\Models\V2\Workdays\Workday; +use App\Models\V2\Workdays\WorkdayDemographic; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; @@ -79,8 +80,6 @@ class ProjectReport extends Model implements MediaModel, AuditableContract, Repo 'completion', 'planted_trees', 'title', - 'workdays_paid', - 'workdays_volunteer', 'technical_narrative', 'public_narrative', 'landscape_community_contribution', @@ -179,10 +178,21 @@ class ProjectReport extends Model implements MediaModel, AuditableContract, Repo ]; // Required by the HasWorkdays trait - public const WORKDAY_COLLECTIONS = Workday::PROJECT_COLLECTION; - public const OTHER_WORKDAY_COLLECTIONS = [ - Workday::COLLECTION_PROJECT_PAID_OTHER, - Workday::COLLECTION_PROJECT_VOLUNTEER_OTHER, + public const WORKDAY_COLLECTIONS = [ + 'paid' => [ + Workday::COLLECTION_PROJECT_PAID_NURSERY_OPERATIONS, + Workday::COLLECTION_PROJECT_PAID_PROJECT_MANAGEMENT, + Workday::COLLECTION_PROJECT_PAID_OTHER, + ], + 'volunteer' => [ + Workday::COLLECTION_PROJECT_VOLUNTEER_NURSERY_OPERATIONS, + Workday::COLLECTION_PROJECT_VOLUNTEER_PROJECT_MANAGEMENT, + Workday::COLLECTION_PROJECT_VOLUNTEER_OTHER, + ], + 'other' => [ + Workday::COLLECTION_PROJECT_PAID_OTHER, + Workday::COLLECTION_PROJECT_VOLUNTEER_OTHER, + ] ]; public function registerMediaConversions(Media $media = null): void @@ -345,32 +355,27 @@ public function getTotalJobsCreatedAttribute(): int public function getWorkdaysTotalAttribute(): int { - $paid = $this->workdays_paid ?? 0; - $volunteer = $this->workdays_volunteer ?? 0; + $projectReportTotal = $this->workdays_paid + $this->workdays_volunteer; - if (empty($this->due_at)) { - return $paid + $volunteer; - } else { - $siteIds = $this->project->sites()->pluck('project_id')->toArray(); - $month = $this->due_at->month; - $year = $this->due_at->year; - - $sitePaid = SiteReport::whereIn('id', $siteIds) - ->where('due_at', '<', now()) - ->hasBeenSubmitted() - ->whereMonth('due_at', $month) - ->whereYear('due_at', $year) - ->sum('workdays_paid'); - - $siteVolunteer = SiteReport::whereIn('id', $siteIds) - ->where('due_at', '<', now()) - ->hasBeenSubmitted() - ->whereMonth('due_at', $month) - ->whereYear('due_at', $year) - ->sum('workdays_volunteer'); - - return $paid + $volunteer + $sitePaid + $siteVolunteer; + if (empty($this->task_id)) { + return $projectReportTotal; } + + // Assume that the types are balanced and just return the value from 'gender' + $sitePaid = WorkdayDemographic::whereIn('workday_id', + Workday::where('workdayable_type', SiteReport::class) + ->whereIn('workdayable_id', $this->task->siteReports()->select('id')) + ->collections(SiteReport::WORKDAY_COLLECTIONS['paid']) + ->select('id') + )->gender()->sum('amount'); + $siteVolunteer = WorkdayDemographic::whereIn('workday_id', + Workday::where('workdayable_type', SiteReport::class) + ->whereIn('workdayable_id', $this->task->siteReports()->select('id')) + ->collections(SiteReport::WORKDAY_COLLECTIONS['volunteer']) + ->select('id') + )->gender()->sum('amount'); + + return $projectReportTotal + $sitePaid + $siteVolunteer; } public function getSiteReportsCountAttribute(): int diff --git a/app/Models/V2/Sites/SiteReport.php b/app/Models/V2/Sites/SiteReport.php index 48e80e8a3..d864d3ad9 100644 --- a/app/Models/V2/Sites/SiteReport.php +++ b/app/Models/V2/Sites/SiteReport.php @@ -73,8 +73,6 @@ class SiteReport extends Model implements MediaModel, AuditableContract, ReportM 'approved_by', 'created_by', 'submitted_at', - 'workdays_paid', - 'workdays_volunteer', 'technical_narrative', 'public_narrative', 'disturbance_details', @@ -145,10 +143,25 @@ class SiteReport extends Model implements MediaModel, AuditableContract, ReportM ]; // Required by the HasWorkdays trait - public const WORKDAY_COLLECTIONS = Workday::SITE_COLLECTIONS; - public const OTHER_WORKDAY_COLLECTIONS = [ - Workday::COLLECTION_SITE_PAID_OTHER, - Workday::COLLECTION_SITE_VOLUNTEER_OTHER, + public const WORKDAY_COLLECTIONS = [ + 'paid' => [ + Workday::COLLECTION_SITE_PAID_SITE_ESTABLISHMENT, + WORKDAY::COLLECTION_SITE_PAID_PLANTING, + Workday::COLLECTION_SITE_PAID_SITE_MAINTENANCE, + Workday::COLLECTION_SITE_PAID_SITE_MONITORING, + Workday::COLLECTION_SITE_PAID_OTHER, + ], + 'volunteer' => [ + Workday::COLLECTION_SITE_VOLUNTEER_SITE_ESTABLISHMENT, + WORKDAY::COLLECTION_SITE_VOLUNTEER_PLANTING, + Workday::COLLECTION_SITE_VOLUNTEER_SITE_MAINTENANCE, + Workday::COLLECTION_SITE_VOLUNTEER_SITE_MONITORING, + Workday::COLLECTION_SITE_VOLUNTEER_OTHER, + ], + 'other' => [ + Workday::COLLECTION_SITE_PAID_OTHER, + Workday::COLLECTION_SITE_VOLUNTEER_OTHER, + ] ]; public function registerMediaConversions(Media $media = null): void diff --git a/app/Models/V2/Workdays/Workday.php b/app/Models/V2/Workdays/Workday.php index 1a8274bec..cc7928c11 100644 --- a/app/Models/V2/Workdays/Workday.php +++ b/app/Models/V2/Workdays/Workday.php @@ -152,6 +152,11 @@ public function scopeCollection(Builder $query, string $collection): Builder return $query->where('collection', $collection); } + public function scopeCollections(Builder $query, array $collections): Builder + { + return $query->whereIn('collection', $collections); + } + public function demographics(): HasMany { return $this->hasMany(WorkdayDemographic::class); From d72739808561a0bed694ce6372e358b70a535ef9 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Thu, 16 May 2024 15:13:47 -0700 Subject: [PATCH 58/64] [TM-931] Dry up totals calculation. --- app/Models/V2/Projects/ProjectReport.php | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/app/Models/V2/Projects/ProjectReport.php b/app/Models/V2/Projects/ProjectReport.php index f5c72d169..ca4b59baf 100644 --- a/app/Models/V2/Projects/ProjectReport.php +++ b/app/Models/V2/Projects/ProjectReport.php @@ -362,20 +362,14 @@ public function getWorkdaysTotalAttribute(): int } // Assume that the types are balanced and just return the value from 'gender' - $sitePaid = WorkdayDemographic::whereIn('workday_id', + $sumTotals = fn ($collectionType) => WorkdayDemographic::whereIn('workday_id', Workday::where('workdayable_type', SiteReport::class) ->whereIn('workdayable_id', $this->task->siteReports()->select('id')) - ->collections(SiteReport::WORKDAY_COLLECTIONS['paid']) - ->select('id') - )->gender()->sum('amount'); - $siteVolunteer = WorkdayDemographic::whereIn('workday_id', - Workday::where('workdayable_type', SiteReport::class) - ->whereIn('workdayable_id', $this->task->siteReports()->select('id')) - ->collections(SiteReport::WORKDAY_COLLECTIONS['volunteer']) + ->collections(SiteReport::WORKDAY_COLLECTIONS[$collectionType]) ->select('id') )->gender()->sum('amount'); - return $projectReportTotal + $sitePaid + $siteVolunteer; + return $projectReportTotal + $sumTotals('paid') + $sumTotals('volunteer'); } public function getSiteReportsCountAttribute(): int From 24175467d7e6dd0c16d6b4619894c656effd106e Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Thu, 16 May 2024 15:15:29 -0700 Subject: [PATCH 59/64] [TM-931] Bring back the hasBeenSubmitted constraint --- app/Models/V2/Projects/ProjectReport.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Models/V2/Projects/ProjectReport.php b/app/Models/V2/Projects/ProjectReport.php index ca4b59baf..ade3fada1 100644 --- a/app/Models/V2/Projects/ProjectReport.php +++ b/app/Models/V2/Projects/ProjectReport.php @@ -364,7 +364,7 @@ public function getWorkdaysTotalAttribute(): int // Assume that the types are balanced and just return the value from 'gender' $sumTotals = fn ($collectionType) => WorkdayDemographic::whereIn('workday_id', Workday::where('workdayable_type', SiteReport::class) - ->whereIn('workdayable_id', $this->task->siteReports()->select('id')) + ->whereIn('workdayable_id', $this->task->siteReports()->hasBeenSubmitted()->select('id')) ->collections(SiteReport::WORKDAY_COLLECTIONS[$collectionType]) ->select('id') )->gender()->sum('amount'); From c57b4644f3821a158d41d879f1a1f037ff1bfecb Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Thu, 16 May 2024 16:03:10 -0700 Subject: [PATCH 60/64] [TM-931] Include the new workdays format in entity exports. --- app/Exports/V2/BaseExportFormSubmission.php | 25 ++++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/app/Exports/V2/BaseExportFormSubmission.php b/app/Exports/V2/BaseExportFormSubmission.php index 94219b524..cdffec825 100644 --- a/app/Exports/V2/BaseExportFormSubmission.php +++ b/app/Exports/V2/BaseExportFormSubmission.php @@ -47,11 +47,7 @@ protected function getAnswer(array $field, array $answers): string if (is_array($answer)) { $list = []; foreach ($answer as $item) { - if (is_array($item)) { - $list[] = $item['amount'].':'.$item['gender'].':'.$item['age'].':'.$item['ethnicity'].':'.$item['indigeneity']; - } else { - $list[] = $item; - } + $list[] = data_get($field, 'input_type') . '??' . $item; } return implode('|', $list); @@ -72,7 +68,24 @@ protected function getAnswer(array $field, array $answers): string return $this->stringifyModel($answer, ['name', 'amount']); case 'workdays': - return $this->stringifyModel($answer, ['amount', 'gender', 'age', 'ethnicity', 'indigeneity']); + $list = []; + $workday = $answer->first(); + if ($workday == null) { + return ''; + } + + $types = ['gender' => [], 'age' => [], 'ethnicity' => []]; + foreach ($workday->demographics as $demographic) { + $value = match ($demographic->type) { + 'ethnicity' => [$demographic->amount, $demographic->subtype, $demographic->name], + default => [$demographic->amount, $demographic->name], + }; + $types[$demographic['type']][] = implode(':', $value); + } + $list[] = 'gender:(' . implode(')(', $types['gender']) . ')'; + $list[] = 'age:(' . implode(')(', $types['age']) . ')'; + $list[] = 'ethnicity:(' . implode(')(', $types['ethnicity']) . ')'; + return implode('|', $list); case 'leadershipTeam': return $this->stringifyModel($answer, ['first_name', 'last_name', 'position', 'gender', 'age',]); From 98fd9c38502ccaf218a14cdc8b012c32c9f633e3 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Thu, 16 May 2024 16:57:52 -0700 Subject: [PATCH 61/64] [TM-674] Allow searching by ppc external id in the admin sites index. --- .../Controllers/V2/Sites/AdminIndexSitesController.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/V2/Sites/AdminIndexSitesController.php b/app/Http/Controllers/V2/Sites/AdminIndexSitesController.php index d4d5685d5..399dbd708 100644 --- a/app/Http/Controllers/V2/Sites/AdminIndexSitesController.php +++ b/app/Http/Controllers/V2/Sites/AdminIndexSitesController.php @@ -51,8 +51,13 @@ public function __invoke(Request $request): V2SitesCollection ]); if (! empty($request->query('search'))) { - $ids = Site::search(trim($request->query('search')))->get()->pluck('id')->toArray(); - $query->whereIn('v2_sites.id', $ids); + $search = $request->query('search'); + if (is_numeric($search)) { + $query->where('v2_sites.ppc_external_id', $search); + } else { + $ids = Site::search(trim($search))->get()->pluck('id')->toArray(); + $query->whereIn('v2_sites.id', $ids); + } } $this->isolateAuthorizedFrameworks($query, 'v2_sites'); From 9d23dcd98ed6fb29d051b5f2de7bda281699e79c Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Thu, 16 May 2024 21:01:45 -0700 Subject: [PATCH 62/64] [TM-674] Allow searching by ppc external id in the project sites view. --- .../Projects/ViewProjectSitesController.php | 19 ++++++++++++------- .../V2/Sites/AdminIndexSitesController.php | 4 ++-- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/app/Http/Controllers/V2/Projects/ViewProjectSitesController.php b/app/Http/Controllers/V2/Projects/ViewProjectSitesController.php index aaf1e955d..198d774fe 100644 --- a/app/Http/Controllers/V2/Projects/ViewProjectSitesController.php +++ b/app/Http/Controllers/V2/Projects/ViewProjectSitesController.php @@ -48,15 +48,20 @@ public function __invoke(Request $request, Project $project) } if (! empty($request->query('search'))) { - $ids = Site::search(trim($request->query('search'))) - ->get() - ->pluck('id') - ->toArray(); + $search = trim($request->query('search')); + if (is_numeric($search)) { + $qry->where('v2_sites.ppc_external_id', $search); + } else { + $ids = Site::search(trim($request->query('search'))) + ->get() + ->pluck('id') + ->toArray(); - if (empty($ids)) { - return V2SitesCollection::collection(collect()); + if (empty($ids)) { + return V2SitesCollection::collection(collect()); + } + $qry->whereIn('id', $ids); } - $qry->whereIn('id', $ids); } $collection = $qry->paginate($perPage) diff --git a/app/Http/Controllers/V2/Sites/AdminIndexSitesController.php b/app/Http/Controllers/V2/Sites/AdminIndexSitesController.php index 399dbd708..32b446d6f 100644 --- a/app/Http/Controllers/V2/Sites/AdminIndexSitesController.php +++ b/app/Http/Controllers/V2/Sites/AdminIndexSitesController.php @@ -51,11 +51,11 @@ public function __invoke(Request $request): V2SitesCollection ]); if (! empty($request->query('search'))) { - $search = $request->query('search'); + $search = trim($request->query('search')); if (is_numeric($search)) { $query->where('v2_sites.ppc_external_id', $search); } else { - $ids = Site::search(trim($search))->get()->pluck('id')->toArray(); + $ids = Site::search($search)->get()->pluck('id')->toArray(); $query->whereIn('v2_sites.id', $ids); } } From 4b53fa4a6d987126d85647f92dd31c2f682a92a6 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Thu, 16 May 2024 21:04:54 -0700 Subject: [PATCH 63/64] [TM-931] Lint fix --- app/Exports/V2/BaseExportFormSubmission.php | 1 + app/Models/V2/Projects/ProjectReport.php | 5 +++-- app/Models/V2/Sites/SiteReport.php | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/Exports/V2/BaseExportFormSubmission.php b/app/Exports/V2/BaseExportFormSubmission.php index cdffec825..7749fde20 100644 --- a/app/Exports/V2/BaseExportFormSubmission.php +++ b/app/Exports/V2/BaseExportFormSubmission.php @@ -85,6 +85,7 @@ protected function getAnswer(array $field, array $answers): string $list[] = 'gender:(' . implode(')(', $types['gender']) . ')'; $list[] = 'age:(' . implode(')(', $types['age']) . ')'; $list[] = 'ethnicity:(' . implode(')(', $types['ethnicity']) . ')'; + return implode('|', $list); case 'leadershipTeam': diff --git a/app/Models/V2/Projects/ProjectReport.php b/app/Models/V2/Projects/ProjectReport.php index ade3fada1..a25f34874 100644 --- a/app/Models/V2/Projects/ProjectReport.php +++ b/app/Models/V2/Projects/ProjectReport.php @@ -192,7 +192,7 @@ class ProjectReport extends Model implements MediaModel, AuditableContract, Repo 'other' => [ Workday::COLLECTION_PROJECT_PAID_OTHER, Workday::COLLECTION_PROJECT_VOLUNTEER_OTHER, - ] + ], ]; public function registerMediaConversions(Media $media = null): void @@ -362,7 +362,8 @@ public function getWorkdaysTotalAttribute(): int } // Assume that the types are balanced and just return the value from 'gender' - $sumTotals = fn ($collectionType) => WorkdayDemographic::whereIn('workday_id', + $sumTotals = fn ($collectionType) => WorkdayDemographic::whereIn( + 'workday_id', Workday::where('workdayable_type', SiteReport::class) ->whereIn('workdayable_id', $this->task->siteReports()->hasBeenSubmitted()->select('id')) ->collections(SiteReport::WORKDAY_COLLECTIONS[$collectionType]) diff --git a/app/Models/V2/Sites/SiteReport.php b/app/Models/V2/Sites/SiteReport.php index d864d3ad9..cbae89591 100644 --- a/app/Models/V2/Sites/SiteReport.php +++ b/app/Models/V2/Sites/SiteReport.php @@ -161,7 +161,7 @@ class SiteReport extends Model implements MediaModel, AuditableContract, ReportM 'other' => [ Workday::COLLECTION_SITE_PAID_OTHER, Workday::COLLECTION_SITE_VOLUNTEER_OTHER, - ] + ], ]; public function registerMediaConversions(Media $media = null): void From b797f7e534e8fbf4655544c6436d61e8a1ce8eaa Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Thu, 16 May 2024 21:38:13 -0700 Subject: [PATCH 64/64] [TM-931] Fix unit test that's covering the updated workdays_paid attribute getter. --- app/Helpers/CustomFormHelper.php | 30 ++++++++++++------- ...ateProjectReportWithFormControllerTest.php | 22 ++++++++++++-- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/app/Helpers/CustomFormHelper.php b/app/Helpers/CustomFormHelper.php index 1622a0ae9..a089fee7d 100644 --- a/app/Helpers/CustomFormHelper.php +++ b/app/Helpers/CustomFormHelper.php @@ -21,7 +21,7 @@ class CustomFormHelper * @param string $framework [ppc,terrafund] * @return Form */ - public static function generateFakeForm(string $type, string $framework): Form + public static function generateFakeForm(string $type, string $framework, bool $withRelations = false): Form { switch ($type) { case 'project': @@ -59,16 +59,24 @@ public static function generateFakeForm(string $type, string $framework): Form $form = Form::factory()->create(['framework_key' => $framework, 'model' => $model]); $section = FormSection::factory()->create(['form_id' => $form->uuid]); - foreach (config('wri.linked-fields.models.' . $type . '.fields') as $key => $fieldCfg) { - FormQuestion::factory()->create( - [ - 'input_type' => data_get($fieldCfg, 'input_type'), - 'label' => data_get($fieldCfg, 'label'), - 'name' => data_get($fieldCfg, 'label'), - 'form_section_id' => $section->id, - 'linked_field_key' => $key, - ] - ); + + $generateQuestions = function ($subtype) use ($type, $section) { + foreach (config("wri.linked-fields.models.$type.$subtype") as $key => $fieldCfg) { + FormQuestion::factory()->create( + [ + 'input_type' => data_get($fieldCfg, 'input_type'), + 'label' => data_get($fieldCfg, 'label'), + 'name' => data_get($fieldCfg, 'label'), + 'form_section_id' => $section->id, + 'linked_field_key' => $key, + ] + ); + } + }; + + $generateQuestions('fields'); + if ($withRelations) { + $generateQuestions('relations'); } $conditional = FormQuestion::factory()->conditionalField()->create(['form_section_id' => $section->id]); diff --git a/tests/V2/ProjectReports/UpdateProjectReportWithFormControllerTest.php b/tests/V2/ProjectReports/UpdateProjectReportWithFormControllerTest.php index 18e139e50..bee899891 100644 --- a/tests/V2/ProjectReports/UpdateProjectReportWithFormControllerTest.php +++ b/tests/V2/ProjectReports/UpdateProjectReportWithFormControllerTest.php @@ -44,7 +44,7 @@ public function test_invoke_action() 'status' => EntityStatusStateMachine::STARTED, ]); - $form = CustomFormHelper::generateFakeForm('project-report', 'ppc'); + $form = CustomFormHelper::generateFakeForm('project-report', 'ppc', true); $answers = []; @@ -59,8 +59,24 @@ public function test_invoke_action() if ($question->linked_field_key == 'pro-rep-title') { $answers[$question->uuid] = '* testing title updated *'; } - if ($question->linked_field_key == 'pro-rep-workdays-paid') { - $answers[$question->uuid] = 24; + if ($question->linked_field_key == 'pro-rep-rel-paid-project-management') { + $answers[$question->uuid] = [[ + 'collection' => 'paid-project-management', + 'demographics' => [[ + 'type' => 'gender', + 'name' => 'male', + 'amount' => 24, + ], [ + 'type' => 'age', + 'name' => 'youth', + 'amount' => 24, + ], [ + 'type' => 'ethnicity', + 'subtype' => 'indigenous', + 'name' => 'Ohlone', + 'amount' => 24, + ]], + ]]; } }