Skip to content

Commit

Permalink
Aggregate weather for entries when importing
Browse files Browse the repository at this point in the history
  • Loading branch information
thekid committed Dec 31, 2024
1 parent 3f68637 commit 7def09d
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 1 deletion.
1 change: 1 addition & 0 deletions src/main/handlebars/feed.handlebars
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
{{/each}}
</ul>
{{count views '' '(Eine Ansicht)' '(# Ansichten)'}}
{{> partials/weather in=.}}
</div>

{{> partials/images in=. first=@first}}
Expand Down
1 change: 1 addition & 0 deletions src/main/handlebars/journey.handlebars
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ parent: feed
{{/each}}
</ul>
{{count views '' '(Eine Ansicht)' '(# Ansichten)'}}
{{> partials/weather in=.}}
</div>
{{> partials/images in=.}}

Expand Down
16 changes: 15 additions & 1 deletion src/main/handlebars/layout.handlebars
Original file line number Diff line number Diff line change
Expand Up @@ -460,8 +460,22 @@
}
}
.meta, .meta a {
.meta {
margin-block: .25rem -.5rem;
color: var(--meta-color);
a {
color: var(--meta-color);
}
.weather {
display: flex;
img {
width: 1lh;
height: 1lh;
}
}
}
ul.locations {
Expand Down
7 changes: 7 additions & 0 deletions src/main/php/de/thekid/dialog/Helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ public function helpers() {
if ($time > $until) return 'passed';
return 'current';
};
yield 'temperature' => function($node, $context, $options) {
$diff= abs($options[0] - $options[1]);
return $diff <= ($options['tolerance'] ?? 1)
? sprintf('%.1f', ($options[0] + $options[1]) / 2)
: $options[0].''.$options[1]
;
};
yield 'size-class' => function($node, $context, $options) {
$s= (int)$options[0];
return match {
Expand Down
44 changes: 44 additions & 0 deletions src/main/php/de/thekid/dialog/OpenMeteo.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php namespace de\thekid\dialog;

use lang\IllegalArgumentException;
use util\{Date, TimeZone, URI};
use webservices\rest\Endpoint;

/**
* Open-Meteo is an open-source weather API and offers free access for non-commercial use.
*
* @see https://github.com/open-meteo/open-meteo
*/
class OpenMeteo {
private $base;
private $auth= [];
private $endpoints= [];

public function __construct(string|URI $base) {
$this->base= $base instanceof URI ? $base : new URI($base);
}

/** Returns a given API endpoint */
protected function endpoint(string $kind): Endpoint {
return $this->endpoints[$kind]??= new Endpoint($this->base->using()
->host($kind.'.'.$this->base->host())
->create()
);
}

public function lookup(string|float $lat, string|float $lon, Date $start, ?Date $end= null, TimeZone $tz= null): array<string, mixed> {
$params= $this->auth + [
'latitude' => $lat,
'longitude' => $lon,
'start_date' => $start->toString('Y-m-d'),
'end_date' => ($end ?? $start)->toString('Y-m-d'),
'timezone' => ($tz ?? $start->getTimeZone())->name(),
'daily' => ['sunrise', 'sunset'],
'hourly' => ['weather_code', 'apparent_temperature'],
];
return $this->endpoint('archive-api')->resource('archive')->get($params)->match([
200 => fn($r) => $r->value(),
400 => fn($r) => throw new IllegalArgumentException($r->content()),
]);
}
}
58 changes: 58 additions & 0 deletions src/main/php/de/thekid/dialog/import/LookupWeather.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php namespace de\thekid\dialog\import;

use de\thekid\dialog\OpenMeteo;
use lang\FormatException;
use util\{Date, Dates, TimeZone, TimeInterval};
use webservices\rest\Endpoint;

/** Aggregates weather for entries using OpenMeteo */
class LookupWeather extends Task {
private $weather= new OpenMeteo('https://open-meteo.com/v1');

public function __construct(private array<string, mixed> $entry, private array<mixed> $images) { }

public function execute(Endpoint $api) {
$weather= [];
$min= $max= null;
foreach ($this->entry['locations'] as $location) {
$tz= new TimeZone($location['timezone']);

// Infer date range from first and last images
$dates= [];
foreach ($this->images as $image) {
$dates[]= new Date(strtr($image['meta']['dateTime'], ['+00:00' => '']), $tz);
}
usort($dates, fn($a, $b) => $b->compareTo($a));
$first= current($dates);
$last= end($dates);

// Filter hourly weather for the duration of the images
$result= $this->weather->lookup($location['lat'], $location['lon'], $first, $last, $tz);
$start= array_search(Dates::truncate($first, TimeInterval::$HOURS)->toString('Y-m-d\TH:i'), $result['hourly']['time']);
$end= array_search(Dates::truncate($last, TimeInterval::$HOURS)->toString('Y-m-d\TH:i'), $result['hourly']['time']);

// Determine most common weather codes and temperature range
$codes= array_count_values(array_slice($result['hourly']['weather_code'], $min, 1 + ($end - $start)));
$temp= array_slice($result['hourly']['apparent_temperature'], $start, 1 + ($end - $start));
$min= null === $min ? min($temp) : min($min, min($temp));
$max= null === $max ? max($temp) : max($max, max($temp));

arsort($codes);
foreach ($codes as $code => $count) {
$weather[$code]??= 0;
$weather[$code]+= $count;
}

yield $location['name'] => sprintf('#%02d @ %.1f-%.1f °C', key($codes), min($temp), max($temp));
}

arsort($weather);
return [
'code' => sprintf('%02d', key($weather)),
'min' => $min,
'max' => $max,
];
}

public function description(): string { return 'Looking up weather'; }
}
1 change: 1 addition & 0 deletions src/main/php/de/thekid/dialog/import/Source.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ public function synchronize(Files $files) {

if (isset($updated)) {
$changes['locations']= yield new LookupLocationInfos($changes);
$changes['weather']= yield new LookupWeather($changes, $this->entry['images'] ?? []);
$changes['published']= time();
yield new PublishEntry($this->entry['slug'], $changes);
}
Expand Down

0 comments on commit 7def09d

Please sign in to comment.