-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Aggregate weather for entries when importing
- Loading branch information
Showing
7 changed files
with
127 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()), | ||
]); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters