Skip to content

Commit

Permalink
Merge pull request #633 from wri/merge/main-staging-post-uu
Browse files Browse the repository at this point in the history
[MERGE] main -> staging post uu
  • Loading branch information
roguenet authored Dec 20, 2024
2 parents 0a50883 + 8ea4821 commit f938d49
Show file tree
Hide file tree
Showing 25 changed files with 852 additions and 78 deletions.
43 changes: 43 additions & 0 deletions app/Console/Commands/OneOff/AssociateExactMatchTrees.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

namespace App\Console\Commands\OneOff;

use App\Models\V2\TreeSpecies\TreeSpecies;
use Illuminate\Console\Command;

class AssociateExactMatchTrees extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'one-off:associate-exact-match-trees';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Update tree species rows without a taxon_id but that do have an exact match in the backbone.';

/**
* Execute the console command.
*/
public function handle()
{
TreeSpecies::withoutTimestamps(function () {
$query = TreeSpecies::withTrashed()
->join('tree_species_research', 'v2_tree_species.name', '=', 'tree_species_research.scientific_name')
->where('v2_tree_species.taxon_id', null);
$this->withProgressBar((clone $query)->count(), function ($progressBar) use ($query) {
$query->chunkById(100, function ($trees) use ($progressBar) {
foreach ($trees as $tree) {
TreeSpecies::where('id', $tree->id)->update(['taxon_id' => $tree->taxon_id]);
$progressBar->advance();
}
});
});
});
}
}
94 changes: 94 additions & 0 deletions app/Console/Commands/RecalculatePolygonAreas.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php

namespace App\Console\Commands;

use App\Models\V2\PolygonGeometry;
use App\Models\V2\Sites\SitePolygon;
use App\Services\AreaCalculationService;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;

class RecalculatePolygonAreas extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'polygons:recalculate-areas
{--batch-size=100 : Number of records to process in each batch}
{--only-active : Process only active polygons}';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Recalculate areas for all site polygons';

/**
* Execute the console command.
*/
public function handle(AreaCalculationService $areaService)
{
DB::beginTransaction();

try {
$query = SitePolygon::query();

if ($this->option('only-active')) {
$query->active();
}

$sitePolygons = $query->cursor();

$processedCount = 0;
$errorCount = 0;

$this->info('Starting polygon area recalculation...');
$progressBar = $this->output->createProgressBar();
$progressBar->start();

foreach ($sitePolygons as $sitePolygon) {
try {
$polygonGeometry = PolygonGeometry::where('uuid', $sitePolygon->poly_id)
->select('uuid', DB::raw('ST_AsGeoJSON(geom) AS geojsonGeometry'))
->first();
if (! $polygonGeometry) {
$this->error("No geometry found for poly_id: {$sitePolygon->poly_id}");
$errorCount++;

continue;
}
$geometry = json_decode($polygonGeometry->geojsonGeometry, true);

$calculatedArea = $areaService->getArea($geometry);

$sitePolygon->calc_area = $calculatedArea;
$sitePolygon->save();

$processedCount++;
$progressBar->advance();
} catch (\Exception $e) {
$this->error("Error processing polygon {$sitePolygon->id}: " . $e->getMessage());
$errorCount++;
}
}

DB::commit();

$progressBar->finish();
$this->info("\n\nRecalculation complete!");
$this->info("Processed: {$processedCount} polygons");
$this->info("Errors: {$errorCount}");

} catch (\Exception $e) {
DB::rollBack();
$this->error('Recalculation failed: ' . $e->getMessage());

return self::FAILURE;
}

return self::SUCCESS;
}
}
215 changes: 215 additions & 0 deletions app/Console/Commands/UpdateValuesForIndicatorsCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
<?php

namespace App\Console\Commands;

use App\Helpers\GeometryHelper;
use App\Models\V2\MonitoredData\IndicatorHectares;
use App\Models\V2\MonitoredData\IndicatorTreeCoverLoss;
use App\Services\PythonService;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;

class UpdateValuesForIndicatorsCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'update-values-indicators';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Update values for indicators';

public function handle(): int
{
$slugMappings = [
'treeCoverLoss' => [
'sql' => 'SELECT umd_tree_cover_loss__year, SUM(area__ha) FROM results GROUP BY umd_tree_cover_loss__year',
'query_url' => '/dataset/umd_tree_cover_loss/latest/query',
'indicator' => 'umd_tree_cover_loss',
'model' => IndicatorTreeCoverLoss::class,
'table_name' => 'indicator_output_tree_cover_loss',
],
'treeCoverLossFires' => [
'sql' => 'SELECT umd_tree_cover_loss_from_fires__year, SUM(area__ha) FROM results GROUP BY umd_tree_cover_loss_from_fires__year',
'query_url' => '/dataset/umd_tree_cover_loss_from_fires/latest/query',
'indicator' => 'umd_tree_cover_loss_from_fires',
'model' => IndicatorTreeCoverLoss::class,
'table_name' => 'indicator_output_tree_cover_loss',
],
'restorationByEcoRegion' => [
'indicator' => 'wwf_terrestrial_ecoregions',
'model' => IndicatorHectares::class,
'table_name' => 'indicator_output_hectares',
],
'restorationByStrategy' => [
'indicator' => 'restoration_practice',
'model' => IndicatorHectares::class,
'table_name' => 'indicator_output_hectares',
],
'restorationByLandUse' => [
'indicator' => 'target_system',
'model' => IndicatorHectares::class,
'table_name' => 'indicator_output_hectares',
],
];

foreach ($slugMappings as $slug => $slugMapping) {
$uuids = [];
$processedCount = 0;
$errorCount = 0;
$this->info('Processing ' . $slug . '...');
$progressBar = $this->output->createProgressBar();
$progressBar->start();
$data = $slugMapping['model']::with('sitePolygon')
->where('indicator_slug', $slug)
->select('id', 'site_polygon_id')->get();
$uuids = $data->map(function ($item) {
return $item->sitePolygon ? $item->sitePolygon->poly_id : null;
})->filter()->toArray();

foreach ($uuids as $uuid) {
try {
$polygonGeometry = $this->getGeometry($uuid);
$registerExist = DB::table($slugMappings[$slug]['table_name'].' as i')
->where('i.site_polygon_id', $polygonGeometry['site_polygon_id'])
->where('i.indicator_slug', $slug)
->where('i.year_of_analysis', Carbon::now()->year)
->exists();

if (! $registerExist) {
continue;
}

if (str_contains($slug, 'restorationBy')) {
$geojson = GeometryHelper::getPolygonGeojson($uuid);
$indicatorRestorationResponse = App::make(PythonService::class)->IndicatorPolygon($geojson, $slugMappings[$slug]['indicator'], getenv('GFW_SECRET_KEY'));

if ($slug == 'restorationByEcoRegion') {
$value = json_encode($indicatorRestorationResponse['area'][$slugMappings[$slug]['indicator']]);
} else {
$value = $this->formatKeysValues($indicatorRestorationResponse['area'][$slugMappings[$slug]['indicator']]);
}
$data = [
'value' => $value,
];
$slugMappings[$slug]['model']::where('site_polygon_id', $polygonGeometry['site_polygon_id'])
->where('indicator_slug', $slug)
->where('year_of_analysis', Carbon::now()->year)
->update($data);

$processedCount++;
$progressBar->advance();

continue;
}

$response = $this->sendApiRequestIndicator(getenv('GFW_SECRET_KEY'), $slugMappings[$slug]['query_url'], $slugMappings[$slug]['sql'], $polygonGeometry['geo']);
if (str_contains($slug, 'treeCoverLoss')) {
$processedTreeCoverLossValue = $this->processTreeCoverLossValue($response->json()['data'], $slugMappings[$slug]['indicator']);
}

if ($response->successful()) {
if (str_contains($slug, 'treeCoverLoss')) {
$data = $this->generateTreeCoverLossData($processedTreeCoverLossValue);
} else {
$data = [
'value' => json_encode($response->json()['data']),
];
}

$slugMappings[$slug]['model']::where('site_polygon_id', $polygonGeometry['site_polygon_id'])
->where('indicator_slug', $slug)
->where('year_of_analysis', Carbon::now()->year)
->update($data);
$processedCount++;
$progressBar->advance();
} else {
Log::error('A problem occurred during the analysis of the geometry for the polygon: ' . $uuid);
}
} catch (\Exception $e) {
Log::error('Error in the analysis: ' . $e->getMessage());
$errorCount++;
}
}
$progressBar->finish();
$this->info("\n\n{$slug} updated successfully.");
$this->info("Processed: {$processedCount} polygons");
$this->info("Errors: {$errorCount}");
}

return 0;
}

public function getGeometry($polygonUuid)
{
$geojson = GeometryHelper::getMonitoredPolygonsGeojson($polygonUuid);
$geoJsonObject = json_decode($geojson['geometry']->geojsonGeometry, true);

return [
'geo' => [
'type' => 'Polygon',
'coordinates' => $geoJsonObject['coordinates'],
],
'site_polygon_id' => $geojson['site_polygon_id'],
];
}

public function sendApiRequestIndicator($secret_key, $query_url, $query_sql, $geometry)
{
return Http::withHeaders([
'content-type' => 'application/json',
'x-api-key' => $secret_key,
])->post('https://data-api.globalforestwatch.org' . $query_url, [
'sql' => $query_sql,
'geometry' => $geometry,
]);
}

public function processTreeCoverLossValue($data, $indicator)
{
$processedTreeCoverLossValue = [];
foreach ($data as $i) {
$processedTreeCoverLossValue[$i[$indicator . '__year']] = $i['area__ha'];
}

return $processedTreeCoverLossValue;
}

public function generateTreeCoverLossData($processedTreeCoverLossValue)
{
$yearsOfAnalysis = [2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024];
$responseData = [];
foreach ($yearsOfAnalysis as $year) {
if (isset($processedTreeCoverLossValue[$year])) {
$responseData[$year] = $processedTreeCoverLossValue[$year];
} else {
$responseData[$year] = 0.0;
}
}

return [
'value' => json_encode($responseData),
];
}

public function formatKeysValues($data)
{
$formattedData = [];
foreach ($data as $key => $value) {
$formattedKey = strtolower(str_replace(' ', '-', $key));
$formattedData[$formattedKey] = $value;
}

return json_encode($formattedData);
}
}
10 changes: 3 additions & 7 deletions app/Helpers/PolygonGeometryHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use App\Models\V2\Projects\Project;
use App\Models\V2\Sites\Site;
use App\Models\V2\Sites\SitePolygon;
use Illuminate\Support\Facades\DB;
use App\Services\AreaCalculationService;
use Illuminate\Support\Facades\Log;

class PolygonGeometryHelper
Expand All @@ -16,12 +16,8 @@ public static function updateEstAreainSitePolygon($polygonGeometry, $geometry)
$sitePolygon = SitePolygon::where('poly_id', $polygonGeometry->uuid)->first();

if ($sitePolygon) {
$geojson = json_encode($geometry);
$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;
$unitLatitude = 111320;
$areaSqMeters = $areaSqDegrees * pow($unitLatitude * cos(deg2rad($latitude)), 2);
$areaHectares = $areaSqMeters / 10000;
$areaCalculationService = app(AreaCalculationService::class);
$areaHectares = $areaCalculationService->getArea((array) $geometry->geometry);

$sitePolygon->calc_area = $areaHectares;
$sitePolygon->save();
Expand Down
Loading

0 comments on commit f938d49

Please sign in to comment.