This commit is contained in:
Martin Folkerts 2024-08-20 17:01:58 +02:00
parent fcd900075b
commit 64a576d638
18 changed files with 378 additions and 880 deletions

View file

@ -24,7 +24,7 @@ done
# Check if required arguments are set
if [ -z "$filename" ] || [ -z "$report_date" ]|| [ -z "$mail_day" ] || [ -z "$data_dir" ]; then
echo "Missing arguments. Use: build_reports.sh --filename=hello.txt --report_date=2020-01-01 --data_dir=chemba"
echo "Missing arguments. Use: build_reports.sh --filename=hello.txt --report_date=2020-01-01 --mail_day=Friday --data_dir=chemba"
exit 1
fi

View file

@ -11,6 +11,7 @@
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Carbon;
use Symfony\Component\Process\Process;
class ProjectReportGeneratorJob implements ShouldQueue
@ -21,13 +22,16 @@ class ProjectReportGeneratorJob implements ShouldQueue
private ProjectReport $projectReport;
private bool $sendMail;
private bool $isTestReport;
/**
* Create a new job instance.
*/
public function __construct(ProjectReport $projectReport, $sendMail = false)
public function __construct(ProjectReport $projectReport, $sendMail = false, $isTestReport = false)
{
$this->projectReport = $projectReport;
$this->sendMail = $sendMail;
$this->isTestReport = $isTestReport;
}
/**
@ -42,7 +46,7 @@ public function handle()
sprintf('%sbuild_report.sh', $projectFolder),
sprintf('--filename=%s', $this->projectReport->getFullPathName()),
sprintf('--report_date=%s', $this->projectReport->getReportDate()),
sprintf('--mail_day=%s',$this->projectReport->project->mail_day),
sprintf('--mail_day=%s', $this->isTestReport? Carbon::yesterday()->dayName: $this->projectReport->project->mail_day),
sprintf('--data_dir=%s', $this->projectReport->project->download_path),
];
logger('command:'. print_r($command, true));
@ -54,7 +58,6 @@ public function handle()
$currentPath = '/usr/bin:/usr/gnu/bin:/usr/local/bin:/bin:/usr/bin/Users/mfolkerts/anaconda3/bin:/Library/Apple/usr/bin';
$process->setEnv(['PATH' => $currentPath.':/usr/local/Cellar/pandoc/3.1.8/bin/pandoc']);
// $process->setTimeout(36000); // stel een geschikte timeout in
$process->start();
try {
$myOutput = [];

View file

@ -2,12 +2,10 @@
namespace App\Livewire\Projects\Tabs;
use App\Jobs\ProjectMosiacGeneratorJob;
use App\Jobs\ProjectReportGeneratorJob;
use App\Jobs\ProjectTestReportGeneratorJob;
use App\Models\Project;
use App\Models\ProjectMosaic;
use App\Models\ProjectReport;
use App\Rules\AllMosaicsPresentRule;
use Carbon\Carbon;
use Illuminate\Support\Str;
use Livewire\Attributes\Reactive;
@ -28,6 +26,7 @@ class Report extends Component
public $showReportModal = false;
public $showTestReportModal = false;
public function mount()
{
@ -39,6 +38,13 @@ public function openCreateReportModal()
$this->resetFormData();
$this->showReportModal = true;
}
public function openCreateTestReportModal()
{
$this->showTestReportModal = true;
}
private function resetFormData()
{
$this->formData['end_date'] = Carbon::yesterday()->toDateString();
@ -68,6 +74,16 @@ public function saveProjectReport()
}
public function createProjectTestReport()
{
$this->project->scheduleTestReport();
$this->dispatch('refresh');
$this->showTestReportModal = false;
}
public function getDateRangeProperty()
{
if (!$this->formData['end_date'] || !$this->formData['offset']) {

View file

@ -309,6 +309,14 @@ public function scheduleReport(?Carbon $endDate = null, ?int $offset = null)
$endDate ??= Carbon::yesterday();
$offset ??= 7;
for ($step = 0; $step<=3; $step++) {
$latestMosaicToDelete = ProjectMosaic::projectMosaicNameFormat($endDate->clone(), $offset*$step);
logger('Deleting mosaic: '.$latestMosaicToDelete);
$this->mosaics()->where('name', $latestMosaicToDelete)->first()?->delete();
}
logger('Scheduling report for '.$endDate->format('d-m-Y').' with offset '.$offset.' days');
Bus::chain([
Bus::batch($this->getFileDownloadsFor($endDate->clone(), $offset)),
@ -323,6 +331,33 @@ public function scheduleReport(?Carbon $endDate = null, ?int $offset = null)
return "done";
}
public function scheduleTestReport()
{
$endDate = Carbon::yesterday();
$offset = 7;
for ($step = 0; $step<=3; $step++) {
$latestMosaicToDelete = ProjectMosaic::projectMosaicNameFormat($endDate->clone(), $offset*$step);
logger('Deleting mosaic: '.$latestMosaicToDelete);
$this->mosaics()->where('name', $latestMosaicToDelete)->first()?->delete();
}
logger('Scheduling test report for '.$endDate->format('d-m-Y').' with offset '.$offset.' days');
Bus::chain([
Bus::batch($this->getFileDownloadsFor($endDate->clone(), $offset)),
Bus::batch($this->getTestMosaicsFor($endDate->clone())),
Bus::batch(
[
new ProjectInterpolateGrowthModelJob($this),
$this->getTestReportFor($endDate->clone(), $offset, true)
]),
])
->dispatch();
return "done";
}
public function getReportFor(Carbon $endDate, int $offset = 7, $sendMail = false): ProjectReportGeneratorJob
{
$report = $this->reports()->create([
@ -335,6 +370,18 @@ public function getReportFor(Carbon $endDate, int $offset = 7, $sendMail = false
return (new ProjectReportGeneratorJob($report, $sendMail));
}
public function getTestReportFor(Carbon $endDate, int $offset = 7, $sendMail = false): ProjectReportGeneratorJob
{
$report = $this->reports()->create([
'name' => 'Test Report of the '.$endDate->format('d-m-Y').' from the past '.$offset.' days',
'end_date' => $endDate,
'offset' => $offset,
'path' => 'reports/'.ProjectReport::getFileName($endDate, $offset).'.docx',
]);
return (new ProjectReportGeneratorJob($report, $sendMail, true));
}
public function getFileDownloadsFor(Carbon $endDate, int $offset = 7): array
{
$startOfRange = (clone $endDate)->subdays(4 * $offset - 1);
@ -363,6 +410,19 @@ public function getMosaicsFor(Carbon $endDate, int $offset = 7): array
->toArray();
}
public function getTestMosaicsFor(Carbon $endDate)
{
return collect(range(0, 3))
->map(function () use ($endDate) {
$currentEndDate = $endDate->clone();
$endDate->subWeek();
return ProjectMosiacGeneratorJob::handleFor($this, $currentEndDate, 7);
})
->filter()
->toArray();
}
public function handleMissingDownloads()
{
$this->getMissingDownloads()
@ -414,7 +474,9 @@ public function newDownloadsUploaded()
dispatch_sync(new ProjectDownloadRDSJob($this, Carbon::yesterday(), $offset));
dispatch_sync(new ProjectInterpolateGrowthModelJob($this));
}
public function getRdsAsDownload() {
public function getRdsAsDownload()
{
$path = $this->download_path.'/Data/extracted_ci/cumulative_vals/combined_CI_data.rds';
@ -423,7 +485,8 @@ public function getRdsAsDownload() {
);
}
public function getTifsAsZip(Carbon $startDate, Carbon $endDate) {
public function getTifsAsZip(Carbon $startDate, Carbon $endDate)
{
$path = $this->download_path.'/merged_final_tif';
$files = collect(Storage::files($path))
->filter(fn($file) => Str::endsWith($file, '.tif'))
@ -436,12 +499,13 @@ public function getTifsAsZip(Carbon $startDate, Carbon $endDate) {
return $this->createZipArchiveAndReturn($files);
}
public function getMosaicsAsZip(Carbon $startDate, Carbon $endDate) {
public function getMosaicsAsZip(Carbon $startDate, Carbon $endDate)
{
$path = $this->download_path.'/weekly_mosaic';
// create a collection of all week numbers and years for given date range
$allowedFiles = collect(CarbonPeriod::create($startDate, $endDate)->toArray())
->map(fn($date) => sprintf('week_%s_%s.tif',$date->weekOfYear, $date->year))
->map(fn($date) => sprintf('week_%s_%s.tif', $date->weekOfYear, $date->year))
->unique();
@ -492,12 +556,12 @@ public function createDownloadRecordsInDatabaseAndUpdateStatusForAllDownloadedIm
public function createAllMosaicsInDatabaseAndUpdateStatusForAllDownloadedImages()
{
$list_of_desired_mosaics = $this->getMergedTiffList()
->map(fn($file) => str_replace('.tif', '', basename($file) ))
->map(function($date) {
$carbon = Carbon::parse($date);
return sprintf('week_%s_%s', $carbon->format('W'), $carbon->year);
})
->unique();
$list_of_desired_mosaics = $this->getMergedTiffList()
->map(fn($file) => str_replace('.tif', '', basename($file)))
->map(function ($date) {
$carbon = Carbon::parse($date);
return sprintf('week_%s_%s', $carbon->format('W'), $carbon->year);
})
->unique();
}
}

View file

@ -34,6 +34,7 @@ public static function getFilenameByPeriod(Carbon $endDate, int $offset)
public static function projectMosaicNameFormat(Carbon $endDate, int $offset): string
{
$paddedWeek = str_pad($endDate->clone()->subDays($offset)->week, 2, '0', STR_PAD_LEFT);
return sprintf('Week_%s_%s',

View file

@ -44,7 +44,7 @@
"src": "node_modules/leaflet/dist/images/marker-icon.png"
},
"resources/css/app.css": {
"file": "assets/app-d3f21131.css",
"file": "assets/app-36af0ee7.css",
"isEntry": true,
"src": "resources/css/app.css"
},

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-5 w-5">
<path fill-rule="evenodd" d="M8.75 1A2.75 2.75 0 0 0 6 3.75v.443c-.795.077-1.584.176-2.365.298a.75.75 0 1 0 .23 1.482l.149-.022.841 10.518A2.75 2.75 0 0 0 7.596 19h4.807a2.75 2.75 0 0 0 2.742-2.53l.841-10.52.149.023a.75.75 0 0 0 .23-1.482A41.03 41.03 0 0 0 14 4.193V3.75A2.75 2.75 0 0 0 11.25 1h-2.5ZM10 4c.84 0 1.673.025 2.5.075V3.75c0-.69-.56-1.25-1.25-1.25h-2.5c-.69 0-1.25.56-1.25 1.25v.325C8.327 4.025 9.16 4 10 4ZM8.58 7.72a.75.75 0 0 0-1.5.06l.3 7.5a.75.75 0 1 0 1.5-.06l-.3-7.5Zm4.34.06a.75.75 0 1 0-1.5-.06l-.3 7.5a.75.75 0 1 0 1.5.06l.3-7.5Z" clip-rule="evenodd" />
</svg>

After

Width:  |  Height:  |  Size: 684 B

View file

@ -29,19 +29,35 @@
<x-button class=" h-full" wire:click="createProject">Create</x-button>
</div>
</div>
<div class="grid grid-cols-3 gap-3">
<div class="text-sm font-semibold text-gray-900">
Name
</div>
<div class="text-sm font-semibold text-gray-900">
Mail Scheduled
</div>
<div class="text-sm font-semibold text-gray-900">
Actions
</div>
@foreach ($projectManager->projects->sortBy('name') as $project)
<div class="flex items-center justify-between">
<div class="break-all">
<div>
<a href="{!! route('project.show', [$project->name]) !!}">{{ $project->name }}</a>
</div>
<div class="flex items-center ml-2">
<button class="cursor-pointer ml-6 text-sm text-red-500"
<div @class([
'text-green-500' => $project->mail_scheduled,
'text-red-500' => !$project->mail_scheduled,
])">
{!! $project->mail_scheduled ? 'Yes' : 'No' !!}
</div>
<div>
<button class="cursor-pointer text-sm text-red-500"
wire:click="confirmReportDeletion({{ $project->id }})">
{{ __('Delete') }}
<x-icon.trash />
</button>
</div>
</div>
@endforeach
</div>
</div>
</x-slot>
</x-action-section>

View file

@ -0,0 +1,29 @@
<x-modal wire:model.live="showTestReportModal" {{ $attributes }} >
<x-form-modal submit="createProjectTestReport" wire:loading.class="opacity-50">
<x-slot name="title">
{{ __('Project') }}
</x-slot>
<x-slot name="description">
{{ __('Report generator for generating reports') }}
</x-slot>
<x-slot name="form">
<div class="">
When you create a test report It will remove existing mosaics files and generate new ones for the last 4 weeks. This process may take a while.
</div>
</x-slot>
<x-slot name="actions">
<x-secondary-button class="mr-3"
type="button"
x-on:click="$wire.showTestReportModal = false"
>
{{ __('Cancel') }}
</x-secondary-button>
<x-button wire:loading.disabled>
{{ __('Save') }}
</x-button>
</x-slot>
</x-form-modal>
</x-modal>

View file

@ -7,10 +7,17 @@
</div>
<div class="flex flex-col md:flex-row mt-4 items-center md:justify-between w-full gap-2">
<x-search></x-search>
<div class="flex items-end space-x-1">
<button wire:click="openCreateTestReportModal"
class="inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50">
Create Test Report
</button>
<x-button wire:click="openCreateReportModal"
class="flex justify-center rounded-md bg-indigo-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">
Create Report
</x-button>
</div>
</div>
</div>
<div class="mt-8 flow-root">
@ -53,5 +60,6 @@ class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6 lg
</div>
</div>
<x-report-manager-properties-modal :reportManager="$this"/>
<x-report-manager-test-report-modal :reportManager="$this"/>
</div>

View file

@ -1,5 +1,4 @@
<div>
<x-form-section submit="createReport">
<x-slot name="title">
{{ __('Create report') }}

File diff suppressed because one or more lines are too long

View file

@ -72,10 +72,12 @@ source(here("r_app", "parameters_project.R"))
```{r week, message=FALSE, warning=FALSE, include=FALSE}
Sys.setlocale("LC_TIME", "C")
today <- as.character(report_date)
mail_day_as_character <- as.character(mail_day)
report_date_as_week_day = weekdays(as.Date(today))
report_date_as_week_day <- weekdays(ymd(today))
days_of_week <- c("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday")
#als de index of report_date_as_week_day groter dan de index van de mail_day dan moet de week + 1
week <- week(today)
@ -294,7 +296,7 @@ ci_plot <- function(pivotName){
tst <- tmap_arrange(CImap_m2, CImap_m1, CImap,CI_max_abs_last_week, CI_max_abs_three_week, nrow = 1)
cat(paste("## Pivot", pivotName, "-", age, "weeks after planting/harvest", "\n"))
cat(paste("## Field", pivotName, "-", age, "weeks after planting/harvest", "\n"))
# cat("\n")
# cat('<h2> Pivot', pivotName, '- week', week, '-', age$Age, 'weeks after planting/harvest <h2>')
# cat(paste("# Pivot",pivots$pivot[i],"\n"))

Binary file not shown.

View file

@ -92,7 +92,7 @@ raster_files <- list.files(planet_tif_folder,full.names = T, pattern = ".tif")
filtered_files <- map(dates$days_filter, ~ raster_files[grepl(pattern = .x, x = raster_files)]) %>%
compact() %>%
flatten_chr()
head(filtered_files)
# filtered_files <- raster_files #for first CI extraction
# create_mask_and_crop <- function(file, field_boundaries) {
@ -129,13 +129,11 @@ for (file in filtered_files) {
emtpy_or_full <- global(v_crop, "notNA")
vrt_file <- here(daily_vrt, paste0(tools::file_path_sans_ext(basename(file)), ".vrt"))
if(emtpy_or_full[1,] > 10000){
if(emtpy_or_full[1,] > 100){
vrt_list[vrt_file] <- vrt_file
}else{
file.remove(vrt_file)
}
message(file, " processed")

View file

@ -101,68 +101,79 @@ vrt_list <- map(dates$days_filter, ~ vrt_files[grepl(pattern = .x, x = vrt_file
compact() %>%
flatten_chr()
total_pix_area <- rast(vrt_list[1]) %>% terra::subset(1) %>% setValues(1) %>%
crop(field_boundaries, mask = TRUE) %>%
global(., fun="notNA") #%>%
raster_files_final <- list.files(merged_final,full.names = T, pattern = ".tif")
if (length(vrt_list) > 0 ){
print("vrt list made, preparing mosaic creation by counting cloud cover")
layer_5_list <- purrr::map(vrt_list, function(vrt_list) {
rast(vrt_list[1]) %>% terra::subset(1)
}) %>% rast()
total_pix_area <- rast(vrt_list[1]) %>% terra::subset(1) %>% setValues(1) %>%
crop(field_boundaries, mask = TRUE) %>%
global(., fun="notNA") #%>%
missing_pixels_count <- layer_5_list %>% global(., fun="notNA") %>%
mutate(
total_pixels = total_pix_area$notNA,
missing_pixels_percentage = round(100 -((notNA/total_pix_area$notNA)*100)),
thres_5perc = as.integer(missing_pixels_percentage < 5),
thres_40perc = as.integer(missing_pixels_percentage < 45)
)
layer_5_list <- purrr::map(vrt_list, function(vrt_list) {
rast(vrt_list[1]) %>% terra::subset(1)
}) %>% rast()
index_5perc <- which(missing_pixels_count$thres_5perc == max(missing_pixels_count$thres_5perc) )
index_40perc <- which(missing_pixels_count$thres_40perc == max(missing_pixels_count$thres_40perc))
missing_pixels_count <- layer_5_list %>% global(., fun="notNA") %>%
mutate(
total_pixels = total_pix_area$notNA,
missing_pixels_percentage = round(100 -((notNA/total_pix_area$notNA)*100)),
thres_5perc = as.integer(missing_pixels_percentage < 5),
thres_40perc = as.integer(missing_pixels_percentage < 45)
)
## Create mosaic
index_5perc <- which(missing_pixels_count$thres_5perc == max(missing_pixels_count$thres_5perc) )
index_40perc <- which(missing_pixels_count$thres_40perc == max(missing_pixels_count$thres_40perc))
## Create mosaic
if(sum(missing_pixels_count$thres_5perc)>1){
message("More than 1 raster without clouds (<5%), max composite made")
cloudy_rasters_list <- vrt_list[index_5perc]
rsrc <- sprc(cloudy_rasters_list)
x <- terra::mosaic(rsrc, fun = "max")
names(x) <- c("Red", "Green", "Blue", "NIR", "CI")
}else if(sum(missing_pixels_count$thres_5perc)==1){
message("Only 1 raster without clouds (<5%)")
x <- rast(vrt_list[index_5perc[1]])
names(x) <- c("Red", "Green", "Blue", "NIR", "CI")
}else if(sum(missing_pixels_count$thres_40perc)>1){
message("More than 1 image contains clouds, composite made of <40% cloud cover images")
cloudy_rasters_list <- vrt_list[index_40perc]
rsrc <- sprc(cloudy_rasters_list)
x <- mosaic(rsrc, fun = "max")
names(x) <- c("Red", "Green", "Blue", "NIR", "CI")
}else if(sum(missing_pixels_count$thres_40perc)==1){
message("Only 1 image available but contains clouds")
x <- rast(vrt_list[index_40perc[1]])
names(x) <- c("Red", "Green", "Blue", "NIR", "CI")
}else if(sum(missing_pixels_count$thres_40perc)==0){
message("No cloud free images available, all images combined")
rsrc <- sprc(vrt_list)
x <- mosaic(rsrc, fun = "max")
names(x) <- c("Red", "Green", "Blue", "NIR", "CI")
}
} else{
message("No images available this week, empty mosaic created")
x <- rast(raster_files_final[1]) %>% setValues(0) %>%
crop(field_boundaries, mask = TRUE)
if(sum(missing_pixels_count$thres_5perc)>1){
message("More than 1 raster without clouds (<5%), max composite made")
cloudy_rasters_list <- vrt_list[index_5perc]
rsrc <- sprc(cloudy_rasters_list)
x <- terra::mosaic(rsrc, fun = "max")
# names(x) <- "CI"
names(x) <- c("Red", "Green", "Blue", "NIR", "CI")
}else if(sum(missing_pixels_count$thres_5perc)==1){
message("Only 1 raster without clouds (<5%)")
x <- rast(vrt_list[index_5perc[1]])
# names(x) <- c("CI")
names(x) <- c("Red", "Green", "Blue", "NIR", "CI")
}else if(sum(missing_pixels_count$thres_40perc)>1){
message("More than 1 image contains clouds, composite made of <40% cloud cover images")
cloudy_rasters_list <- vrt_list[index_40perc]
rsrc <- sprc(cloudy_rasters_list)
x <- terra::mosaic(rsrc, fun = "max")
# names(x) <- "CI"
names(x) <- c("Red", "Green", "Blue", "NIR", "CI")
}else if(sum(missing_pixels_count$thres_40perc)==1){
message("Only 1 image available but contains clouds")
x <- rast(vrt_list[index_40perc[1]])
# names(x) <- c("CI")
names(x) <- c("Red", "Green", "Blue", "NIR", "CI")
}else{
message("No cloud free images available, all images combined")
message(vrt_list)
rsrc <- sprc(vrt_list)
x <- terra::mosaic(rsrc, fun = "max")
# x <- rast(vrt_list[1]) %>% setValues(NA)
# names(x) <- c("CI")
names(x) <- c("Red", "Green", "Blue", "NIR", "CI")
}
plot(x$CI, main = paste("CI map ", dates$week))
plotRGB(x, main = paste("RGB map ", dates$week))

View file

@ -91,14 +91,20 @@ if(project_dir == "chemba"){
field_boundaries_sf <- st_read(here(data_dir, "pivot.geojson"))
names(field_boundaries_sf) <- c("field", "sub_field", "geometry")
field_boundaries <- field_boundaries_sf %>% vect()
harvesting_data <- read_excel(here(data_dir, "harvest.xlsx"),
col_types = c("text", "text", "numeric",
"date", "date", "numeric", "text",
"numeric", "numeric")) %>%
mutate(season_end = case_when(
season_end > Sys.Date() ~ Sys.Date(),
TRUE ~ season_end),
age = round(as.numeric(season_end - season_start)/7),1)
harvesting_data <- read_excel(here(data_dir, "harvest.xlsx")) %>%
dplyr::select(c("field", "sub_field", "year", "season_start", "season_end", "age", "sub_area", "tonnage_ha")) %>%
mutate(field = as.character(field),
sub_field = as.character(sub_field),
year = as.numeric(year),
season_start = as.Date(season_start),
season_end = as.Date(season_end),
age = as.numeric(age),
sub_area = as.character(sub_area),
tonnage_ha = as.numeric(tonnage_ha)
) %>%
mutate(season_end = case_when(
season_end > Sys.Date() ~ Sys.Date(),
TRUE ~ season_end),
age = round(as.numeric(season_end - season_start)/7,0))
}