- Working version

This commit is contained in:
Dave M. 2026-07-01 14:37:46 -04:00
parent c2e7628b22
commit 9c70225a77
7 changed files with 173 additions and 81 deletions

View File

@ -7,7 +7,8 @@ return [
'adapter' => getenv("WEATHER_API_ADAPTER"),
'url' => getenv("WEATHER_API_URL"),
'auth' => getenv('WEATHER_API_AUTH') ?: 'token',
'key' => getenv("WEATHER_API_KEY"),
'header' => getenv("WEATHER_API_HEADER"),
'secret' => getenv("WEATHER_API_SECRET"),
'options' => [],
],
]

View File

@ -1,8 +1,10 @@
<?php
return [
Ulmus\Api\Weather\ApiHandler::class => DI\create(Ulmus\Api\Weather\ApiHandler::class)->constructor(getenv('WEATHER_API_UNIT_SCALE')),
'api.weather' => function($c) {
$adapter = new \Ulmus\Api\ConnectionAdapter($c->create(Ulmus\Api\Weather\ApiHandler::class)->constructor(getenv('WEATHER_API_UNIT_SCALE')), 'api-weather', $c->get('config')['ulmus'], false);
$adapter = new \Ulmus\Api\ConnectionAdapter($c->get(Ulmus\Api\Weather\ApiHandler::class), 'api-weather', $c->get('config')['ulmus'], false);
$adapter->resolveConfiguration();
return $adapter;

View File

@ -10,6 +10,7 @@ use Ulmus\Api\Common\Uri;
use Ulmus\Api\Response\JsonResponse;
use Ulmus\Api\Stream\JsonStream;
use Ulmus\Api\Stream\Stream;
use Ulmus\Repository\RepositoryInterface;
class ApiHandler implements ApiHandlerInterface
{
@ -17,6 +18,11 @@ class ApiHandler implements ApiHandlerInterface
protected ? string $unitScale,
) {}
public function executingRequest(RepositoryInterface $repository, \Ulmus\Api\Attribute\Obj\Api\ApiAction $attribute) : void
{
$repository->bindUrl('apiBase', $repository->entityClass::API_BASE_URL);
}
public function handleRequest(RequestInterface $request, \Ulmus\Api\Attribute\Obj\Api\ApiAction $attribute) : RequestInterface
{
if ($this->unitScale) {
@ -24,7 +30,7 @@ class ApiHandler implements ApiHandlerInterface
}
if ($attribute->fields) {
$uriParams['fields'] = implode(',', $attribute->fields) ;
$uriParams['fields'] = implode(',', $attribute->fields);
}
if ($uriParams ?? false) {
@ -42,15 +48,35 @@ class ApiHandler implements ApiHandlerInterface
$json = $response->getParsedBody();
if (! empty($json['forecast']) ) {
foreach($json['forecast'] as &$forecast) {
$forecast['date'] = $this->adjustTimezone($forecast['date']);
}
if ($attribute instanceof Collection) {
$response = $response->withParsedBody($json['forecast']);
}
}
if (! empty($json['nowcast'])) {
$json['nowcast']['timestamp'] = new \DateTime($json['nowcast']['timestamp'], new \DateTimeZone('UTC'))->setTimezone(new \DateTimeZone('America/Toronto'))->format('Y-m-d H:i:s');
if ($attribute instanceof Read) {
$response = $response->withParsedBody($json['nowcast']);
}
}
}
catch(\Throwable $e) {
throw new \Exception(sprintf("WeatherSource error: '%s'", $json['errorMessage']), $json['errorCode']);
$msg = $json['errorMessage'] ?? $e->getMessage();
$code = $json['errorCode'] ?? $e->getCode();
throw new \Exception(sprintf("WeatherSource error: '%s'", $msg), $code);
}
return JsonResponse::fromResponse($response);
}
protected function adjustTimezone(string $datetime) : string
{
return new \DateTime("@" . strtotime($datetime))->format('Y-m-d H:i:s');
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace Ulmus\Api\Weather\Attribute\Obj\Api;
use Ulmus\Api\Common\MethodEnum;
#[\Attribute(\Attribute::TARGET_CLASS)]
class Read extends \Ulmus\Api\Attribute\Obj\Api\Read {
public function __construct(
public string $url,
public MethodEnum $method = MethodEnum::Get,
public int $timeout = 15,
public array $fields = [ "all" ],
public MethodEnum $searchMethod = MethodEnum::Get,
) {}
}

View File

@ -12,25 +12,28 @@ use Ulmus\{Api\Attribute\Property\Entity,
SearchRequest\SearchRequestInterface,
SearchRequest\SearchRequestPaginationTrait};
use Ulmus\Api\Attribute\Obj\Api;
use Ulmus\Api\Weather\Attribute;
# Daily forecast response for a Latitude/Longitude point.
#[Api(adapter: "api-weather")]
#[Api\Collection(url: "/points/{latitude},{longitude}/days")]
class ForecastDay implements EntityInterface
#[Attribute\Obj\Api\Collection(url: "/points/{latitude},{longitude}/days")]
class ForecastDay implements EntityInterface, \JsonSerializable
{
use EntityTrait;
const API_BASE_URL = "forecast";
# Average Cloud Cover as a percent value.
#[Field(name: "cldCvrAvg")]
public int $cloudCoverAverage;
public ? int $cloudCoverAverage;
# Minimum Cloud Cover as a percent value.
#[Field(name: "cldCvrMax")]
public int $cloudCoverMaximum;
public ? int $cloudCoverMaximum;
# Maximum Cloud Cover as a percent value.
#[Field(name: "cldCoverMin")]
public int $cloudCoverMinimum;
public ? int $cloudCoverMinimum;
# date formatted as an RFC3339 date value.
#[Field]
@ -38,113 +41,113 @@ class ForecastDay implements EntityInterface
# Average Dew Point in Degrees Fahrenheit (imperial), Celsius (metric), or Kelvin (si).
#[Field(name: "dewPtAvg")]
public float $dewPointAverage;
public ? float $dewPointAverage;
# Minimum Dew Point in Degrees Fahrenheit (imperial), Celsius (metric), or Kelvin (si).
#[Field(name: "dewPtMin")]
public float $dewPointMinimum;
public ? float $dewPointMinimum;
# Maxumum Dew Point in Degrees Fahrenheit (imperial), Celsius (metric), or Kelvin (si).
#[Field(name: "dewPtMax")]
public float $dewPointMaximum;
public ? float $dewPointMaximum;
#[Field(name: "feelsLikeAvg")]
public float $feelsLikeAverage;
public ? float $feelsLikeAverage;
#[Field(name: "feelsLikeMin")]
public float $feelsLikeMinimum;
public ? float $feelsLikeMinimum;
#[Field(name: "feelsLikeMax")]
public float $feelsLikeMaximum;
public ? float $feelsLikeMaximum;
#[Field(name: "heatIndexAvg")]
public float $heatIndexAverage;
public ? float $heatIndexAverage;
#[Field(name: "heatIndexMin")]
public float $heatIndexMinimum;
public ? float $heatIndexMinimum;
#[Field(name: "heatIndexMax")]
public float $heatIndexMaximum;
public ? float $heatIndexMaximum;
# Average Mean Sea Level Pressure in millibars.
#[Field(name: "mslPresAvg")]
public float $meanSeaLevelPressureAverage;
public ? float $meanSeaLevelPressureAverage;
# Minimum Mean Sea Level Pressure in millibars.
#[Field(name: "mslPresMin")]
public float $meanSeaLevelPressureMinimum;
public ? float $meanSeaLevelPressureMinimum;
# Maximum Mean Sea Level Pressure in millibars.
#[Field(name: "mslPresMax")]
public float $meanSeaLevelPressureMaximum;
public ? float $meanSeaLevelPressureMaximum;
# Total precipitation in inches (imperial) or centimeters (metric, si).
#[Field(name: "precip")]
public float $precipitation;
public ? float $precipitation;
# Probability of precipitation as a percent value.
#[Field(name: "precipProb")]
public float $precipitationProbability;
public ? float $precipitationProbability;
# Average Solar Radiation in watts per square meter.
#[Field(name: "radSolarAvg")]
public float $solarRadiationAverage;
public ? float $solarRadiationAverage;
# Minimum Solar Radiation in watts per square meter.
#[Field(name: "radSolarMin")]
public float $solarRadiationMinimum;
public ? float $solarRadiationMinimum;
# Maximum Solar Radiation in watts per square meter.
#[Field(name: "radSolarMax")]
public float $solarRadiationMaximum;
public ? float $solarRadiationMaximum;
# Total Solar Radiation in watts per square meter.
#[Field(name: "radSolarTot")]
public float $solarRadiationTotal;
public ? float $solarRadiationTotal;
# Average Relative humidity as a percent value.
#[Field(name: "relHumAvg")]
public float $relativeHumidityAverage;
public ? float $relativeHumidityAverage;
# Minimum Relative humidity as a percent value.
#[Field(name: "relHumMin")]
public float $relativeHumidityMinimum;
public ? float $relativeHumidityMinimum;
# Maximum Relative humidity as a percent value.
#[Field(name: "relHumMax")]
public float $relativeHumidityMaximum;
public ? float $relativeHumidityMaximum;
# Total snowfall in inches (imperial) or centimeters (metric, si).
#[Field]
public float $snowfall;
public ? float $snowfall;
# Probability of snowfall as a percent value.
#[Field(name: "snowfallProb")]
public float $snowfallProbability;
public ? float $snowfallProbability;
# Average Surface pressure in millibars.
#[Field(name: "sfcPresAvg")]
public float $surfacePressureAverage;
public ? float $surfacePressureAverage;
# Minimum Surface pressure in millibars.
#[Field(name: "sfcPresMin")]
public float $surfacePressureMinimum;
public ? float $surfacePressureMinimum;
# Maximum Surface pressure in millibars.
#[Field(name: "sfcPresMax")]
public float $surfacePressureMaximum;
public ? float $surfacePressureMaximum;
# Average Temperature in Degrees Fahrenheit (imperial), Celsius (metric), or Kelvin (si).
#[Field(name: "tempAvg")]
public float $temperatureAverage;
public ? float $temperatureAverage;
# Minimum Temperature in Degrees Fahrenheit (imperial), Celsius (metric), or Kelvin (si).
#[Field(name: "tempMin")]
public float $temperatureMinimum;
public ? float $temperatureMinimum;
# Maximum Temperature in Degrees Fahrenheit (imperial), Celsius (metric), or Kelvin (si).
#[Field(name: "tempMax")]
public float $temperatureMaximum;
public ? float $temperatureMaximum;
# The model initialization timestamp formatted as an RFC3339 date-time value.
#[Field(name: "timestampInit")]
@ -152,73 +155,73 @@ class ForecastDay implements EntityInterface
# Average Wet Bulb Temperature in Degrees Fahrenheit (imperial), Celsius (metric), or Kelvin (si).
#[Field(name: "wetBulbAvg")]
public float $wetBulbeAverage;
public ? float $wetBulbeAverage;
# Minimum Wet Bulb Temperature in Degrees Fahrenheit (imperial), Celsius (metric), or Kelvin (si).
#[Field(name: "wetBulbMin")]
public float $wetBulbeMinimum;
public ? float $wetBulbeMinimum;
# Maximum Wet Bulb Temperature in Degrees Fahrenheit (imperial), Celsius (metric), or Kelvin (si).
#[Field(name: "wetBulbMax")]
public float $wetBulbeMaximum;
public ? float $wetBulbeMaximum;
# Average Wind Chill in Degrees Fahrenheit (imperial), Celsius (metric), or Kelvin (si).
#[Field(name: "windChillAvg")]
public float $windChillAverage;
public ? float $windChillAverage;
# Minimum Wind Chill in Degrees Fahrenheit (imperial), Celsius (metric), or Kelvin (si).
#[Field(name: "windChillMin")]
public float $windChillMinimum;
public ? float $windChillMinimum;
# Maximum Wind Chill in Degrees Fahrenheit (imperial), Celsius (metric), or Kelvin (si).
#[Field(name: "windChillMax")]
public float $windChillMaximum;
public ? float $windChillMaximum;
# Average Wind direction at 10 meters in Degrees (0 or 360 = North, 90 = East, South = 180, West = 270).
#[Field(name: "windDirAvg")]
public float $windDirectionAverage;
public ? float $windDirectionAverage;
# Average Wind direction at 80 meters in Degrees (0 or 360 = North, 90 = East, South = 180, West = 270).
#[Field(name: "windDir80mAvg")]
public float $windDirection80mAvg;
public ? float $windDirection80mAvg;
# Average Wind direction at 80 meters in Degrees (0 or 360 = North, 90 = East, South = 180, West = 270).
#[Field(name: "windDir100mAvg")]
public float $windDirection100mAvg;
public ? float $windDirection100mAvg;
# Average Wind speed at 10 meters in miles per hour (imperial) or km/hour (metric, si).
#[Field(name: "windSpdAvg")]
public float $windSpeedAverage;
public ? float $windSpeedAverage;
# Minimum Wind speed at 10 meters in miles per hour (imperial) or km/hour (metric, si).
#[Field(name: "windSpdMin")]
public float $windSpeedMinimum;
public ? float $windSpeedMinimum;
# Maximum Wind speed at 10 meters in miles per hour (imperial) or km/hour (metric, si).
#[Field(name: "windSpdMax")]
public float $windSpeedMaximum;
public ? float $windSpeedMaximum;
# Average Wind speed at 80 meters in miles per hour (imperial) or km/hour (metric, si).
#[Field(name: "windSpd80mAvg")]
public float $windSpeed80mAverage;
public ? float $windSpeed80mAverage;
# Minimum Wind speed at 80 meters in miles per hour (imperial) or km/hour (metric, si).
#[Field(name: "windSpd80mMin")]
public float $windSpeed80mMinimum;
public ? float $windSpeed80mMinimum;
# Maximum Wind speed at 80 meters in miles per hour (imperial) or km/hour (metric, si).
#[Field(name: "windSpd80mMax")]
public float $windSpeed80mMaximum;
public ? float $windSpeed80mMaximum;
# Average Wind speed at 100 meters in miles per hour (imperial) or km/hour (metric, si).
#[Field(name: "windSpd100mAvg")]
public float $windSpeed100mAverage;
public ? float $windSpeed100mAverage;
# Minimum Wind speed at 100 meters in miles per hour (imperial) or km/hour (metric, si).
#[Field(name: "windSpd100mMin")]
public float $windSpeed100mMinimum;
public ? float $windSpeed100mMinimum;
# Maximum Wind speed at 100 meters in miles per hour (imperial) or km/hour (metric, si).
#[Field(name: "windSpd100mMax")]
public float $windSpeed100mMaximum;
public ? float $windSpeed100mMaximum;
}

View File

@ -12,71 +12,74 @@ use Ulmus\{Api\Attribute\Property\Entity,
SearchRequest\SearchRequestInterface,
SearchRequest\SearchRequestPaginationTrait};
use Ulmus\Api\Attribute\Obj\Api;
use Ulmus\Api\Weather\Attribute;
# An hourly weather forecast object.
#[Api(adapter: "api-weather")]
#[Api\Collection(url: "/points/{latitude},{longitude}/hours")]
class ForecastHour implements EntityInterface
#[Attribute\Obj\Api\Collection(url: "/points/{latitude},{longitude}/hours")]
class ForecastHour implements EntityInterface, \JsonSerializable
{
use EntityTrait;
const API_BASE_URL = "forecast";
# Cloud Cover as a percent value.
#[Field(name: "cldCvr")]
public int $cloudCover;
public ? int $cloudCover;
# Dew Point in Degrees Fahrenheit (imperial), Celsius (metric), or Kelvin (si).
#[Field(name: "dewPt")]
public float $dewPoint;
public ? float $dewPoint;
#[Field]
public float $feelsLike;
public ? float $feelsLike;
#[Field]
public float $heatIndex;
public ? float $heatIndex;
# Mean Sea Level Pressure in millibars.
#[Field(name: "mslPres")]
public float $meanSeaLevelPressure;
public ? float $meanSeaLevelPressure;
# Total precipitation in inches (imperial) or centimeters (metric, si).
#[Field(name: "precip")]
public float $precipitation;
public ? float $precipitation;
# Probability of precipitation as a percent value.
#[Field(name: "precipProb")]
public float $precipitationProbability;
public ? float $precipitationProbability;
# Average Solar Radiation in watts per square meter.
#[Field(name: "radSolar")]
public float $solarRadiation;
public ? float $solarRadiation;
# Average Relative humidity as a percent value.
#[Field(name: "relHum")]
public float $relativeHumidity;
public ? float $relativeHumidity;
# Average Surface pressure in millibars.
#[Field(name: "sfcPres")]
public float $surfacePressure;
public ? float $surfacePressure;
# Total snowfall in inches (imperial) or centimeters (metric, si).
#[Field]
public float $snowfall;
public ? float $snowfall;
# Probability of snowfall as a percent value.
#[Field(name: "snowfallProb")]
public float $snowfallProbability;
public ? float $snowfallProbability;
# Specific humidity in grams per kilograms.
#[Field(name: "spcHum")]
public float $specificHumidity;
public ? float $specificHumidity;
# Temperature in Degrees Fahrenheit (imperial), Celsius (metric), or Kelvin (si).
#[Field(name: "temp")]
public float $temperature;
public ? float $temperature;
# The model initialization timestamp formatted as an RFC3339 date-time value.
#[Field(name: "timestampInit")]
public Datetime $timestampInitialization;
public ? Datetime $timestampInitialization;
# The model initialization timestamp formatted as an RFC3339 date-time value.
#[Field]
@ -84,33 +87,33 @@ class ForecastHour implements EntityInterface
# Wet Bulb Temperature in Degrees Fahrenheit (imperial), Celsius (metric), or Kelvin (si).
#[Field(name: "wetBulb")]
public float $wetBulbe;
public ? float $wetBulbe;
# Wind Chill in Degrees Fahrenheit (imperial), Celsius (metric), or Kelvin (si).
#[Field]
public float $windChill;
public ? float $windChill;
# Wind direction at 10 meters in Degrees (0 or 360 = North, 90 = East, South = 180, West = 270).
#[Field(name: "windDir")]
public float $windDirection;
public ? float $windDirection;
# Average Wind direction at 80 meters in Degrees (0 or 360 = North, 90 = East, South = 180, West = 270).
#[Field(name: "windDir80m")]
public float $windDirection80m;
public ? float $windDirection80m;
# Average Wind direction at 80 meters in Degrees (0 or 360 = North, 90 = East, South = 180, West = 270).
#[Field(name: "windDir100m")]
public float $windDirection100m;
public ? float $windDirection100m;
# Average Wind speed at 10 meters in miles per hour (imperial) or km/hour (metric, si).
#[Field(name: "windSpd")]
public float $windSpeed;
public ? float $windSpeed;
# Average Wind speed at 80 meters in miles per hour (imperial) or km/hour (metric, si).
#[Field(name: "windSpd80m")]
public float $windSpeed80m;
public ? float $windSpeed80m;
# Average Wind speed at 100 meters in miles per hour (imperial) or km/hour (metric, si).
#[Field(name: "windSpd100m")]
public float $windSpeed100m;
public ? float $windSpeed100m;
}

41
src/Entity/Nowcast.php Normal file
View File

@ -0,0 +1,41 @@
<?php
namespace Ulmus\Api\Weather\Entity;
use Notes\Attribute\Ignore;
use Ulmus\{
Attribute\Property\Field,
Entity\EntityInterface,
Entity\Field\Date,
Entity\Field\Datetime,
EntityTrait,
Repository,
SearchRequest,};
use Ulmus\Api\Attribute\Obj\Api;
use Ulmus\Api\Weather\Attribute;
# Nowcast response for a Latitude/Longitude point.
#[Api(adapter: "api-weather")]
#[Attribute\Obj\Api\Read(url: "/points/{latitude},{longitude}")]
class Nowcast extends ForecastHour implements EntityInterface
{
use EntityTrait;
const API_BASE_URL = "nowcast";
# Current Precipitation
#[Field(name: "precipFlag")]
public ? bool $precipitationFlag;
# Current Precipitation
#[Field(name: "precipIntensity")]
public ? float $precipitationIntensity;
# Current Snowfall
#[Field]
public ? bool $snowfallFlag;
# Current Precipitation
#[Field]
public ? float $snowfallIntensity;
}