You will only see the public (readable) repos here until you login.
Browse Source

Add forecast data, filtering and variation

Joachim M. Giæver 1 year ago
parent
commit
680204ab8f

+ 3 - 0
.gitmodules

@@ -0,0 +1,3 @@
+[submodule "sami-markdown"]
+	path = sami-markdown
+	url = git.giaever.org@git.giaever.org:joachimmg/sami-markdown.git

File diff suppressed because it is too large
+ 1133 - 2
README.md


+ 12 - 3
index.php

@@ -7,9 +7,18 @@ $url = 'https://www.yr.no/place/Norway/Troms/Tromsø/Tromsø/forecast_hour_by_ho
 $forecast = new App\Yr\Forecast($url);
 
 echo '<pre>';
-var_dump($forecast->getLocation());
-foreach ($forecast->getTabular()->getBetween(
+$range = $forecast->getTabular()->getBetween(
     $forecast->getSunset(), $forecast->getSunrise()->add(new \DateInterval('P1D'))
-) as $time)
+);
+
+foreach ($range as $time)
     echo $time . "\n";
+
+echo "\n<hr />\n";
+foreach ($range->getVariations()->getData() as $data) 
+    echo sprintf("%s: %s\n", $data['time']->getFrom()->format("H:i"), join(", ", $data['entities']));
+
+echo "\n";
+foreach ($forecast->getCredit() as $c)
+    echo $c . "\n";
 ?>

+ 1 - 0
sami-markdown

@@ -0,0 +1 @@
+Subproject commit ea769268e2a2cc9598f83cf8f46484b7ec87d67f

+ 56 - 4
src/Yr/Forecast.php

@@ -5,19 +5,41 @@ namespace App\Yr;
 use App\Yr\Forecast\Location;
 use App\Yr\Forecast\Tabular;
 
+/**
+ * Read forecast data from Yr.no for a specific location.
+ *
+ * Disclaimer: To use this package you are required
+ * to print the credits from the method ```getCredit()```
+ *
+ * @author Joachim M. Giæver (joachim[]giaever.org)
+ * @version 1.0
+ */
 final class Forecast {
     private $xml;
     private $location;
 
+    /**
+     * @param string $url The XML url to load data from
+     */
     public function __construct(string $url) {
         $this->xml = simplexml_load_file($url);
         $this->location = new Location($this->xml->location);
     }
 
+    /**
+     * Returns the location data for the forecast
+     *
+     * @return App\Forecast\Location
+     */
     public function getLocation(): Location {
         return $this->location;
     }
 
+    /**
+     * Return the credit to Yr.no, Meterogical Institute and NRK
+     *
+     * @return array
+     */
     final public function getCredit(): array {
         return [
             'text' => (string)$this->xml->credit->link->attributes()['text'],
@@ -25,14 +47,29 @@ final class Forecast {
         ];
     }
 
+    /**
+     * Returns the time when the sun rise for the location
+     *
+     * @return \DateTimeImmutable
+     */
     public function getSunrise(): \DateTimeImmutable {
-        return (new \DateTimeImmutable($this->xml->sun['rise']));
+        return new \DateTimeImmutable((string)$this->xml->sun['rise']);
     }
 
+    /**
+     * Returns the time when the sun sets for the location
+     *
+     * @return DateTimeImmutable
+     */
     public function getSunset(): \DateTimeImmutable {
-        return (new \DateTimeImmutable($this->xml->sun['seẗ́']));
+        return new \DateTimeImmutable((string)$this->xml->sun['set']);
     }
 
+    /**
+     * Returns links for forecasts in other formats
+     *
+     * @return \Generator
+     */
     public function getLinks(): \Generator {
         foreach ($this->xml->links->children() as $link)
             yield [
@@ -41,14 +78,29 @@ final class Forecast {
             ];
     }
 
+    /**
+     * Return the time when this forecast was last update
+     *
+     * @return \DateTimeImmutable
+     */
     public function getLastUpdate(): \DateTimeImmutable {
-        return new DateTimeImmutable($this->xml->meta->lastupdate);
+        return new DateTimeImmutable((string)$this->xml->meta->lastupdate);
     }
 
+    /**
+     * Return the time when this forecast will update next
+     *
+     * @return \DateTimeImmutable
+     */
     public function getNextUpdate(): \DateTimeImmutable {
-        return new DateTimeImmutable($this->xml->meta->nextupdate);
+        return new DateTimeImmutable((string)$this->xml->meta->nextupdate);
     }
 
+    /**
+     * Get the forecast data
+     *
+     * @return Tabular
+     */
     public function getTabular(): Tabular {
         return new Tabular($this->xml->forecast->tabular);
     }

+ 59 - 0
src/Yr/Forecast/Location.php

@@ -2,6 +2,12 @@
 
 namespace App\Yr\Forecast;
 
+/**
+ * Get information about the location,
+ * such as name, timezone and geodata
+ *
+ * @author Joachim M. Giæver (joachim[]giaever.org)
+ */
 final class Location {
     private $name;
     private $type;
@@ -9,6 +15,9 @@ final class Location {
     private $tz;
     private $loc = [];
 
+    /**
+     * @param \SimpleXMLElement $xml The xml-part containing the location data
+     */
     public function __construct(\SimpleXMLElement $xml) {
         $this->name = (string)$xml->name;
         $this->type = (string)$xml->type;
@@ -20,6 +29,56 @@ final class Location {
             'alt' => (float)$xml->location['altitude']
         ];
     }
+
+    /**
+     * Get the location name, e.g «Tromsø»
+     *
+     * @return string
+     */
+    public function getName(): string {
+        return $this->name;
+    }
+
+    /**
+     * Get the location type, e.g «City - large town»
+     *
+     * @return string
+     */
+    public function getType(): string {
+        return $this->type;
+    }
+
+    /**
+     * Get the location country
+     *
+     * @return string
+     */
+    public function getCountry(): string {
+        return $this->country;
+    }
+
+    /**
+     * Get the location timezone
+     *
+     * @return \DateTimeZone
+     */
+    public function getTimeZone(): \DateTimeZone {
+        return new \DateTimeZone($this->tz);
+    }
+
+    /**
+     * Returns the geodata, in the format
+     * ```[
+     *      'lat' => (float),
+     *      'lng' => (float),
+     *      'alt' => (float),
+     * ]```
+     *
+     * @return array
+     */
+    public function getGeoData(): array {
+        return $this->loc;
+    }
 }
 
 ?>

+ 54 - 1
src/Yr/Forecast/Tabular.php

@@ -4,12 +4,27 @@ namespace App\Yr\Forecast;
 
 use App\Yr\Forecast\Tabular\Statistics;
 use App\Yr\Forecast\Tabular\Time;
+use App\Yr\Forecast\Tabular\Variations;
 
+/**
+ * Holds the forecast data in Time-objects. 
+ *
+ * Use the ```getBetween``` option to limit the results.
+ *
+ * Class also makes a simple statistic on the forecast
+ * for the periode and a variation that will exclude
+ * repetitive forecast data.
+ *
+ * @author Joachim M. Giæver (joachim[]giaver.org)
+ */
 class Tabular implements \IteratorAggregate {
 
     private $time = [];
     private $stats;
 
+    /**
+     * @param \SimpleXMLElement $xml The xml part holding the time objects, can be null
+     */
     public function __construct(?\SimpleXMLElement $xml) {
         $this->stats = new Statistics();
 
@@ -18,16 +33,51 @@ class Tabular implements \IteratorAggregate {
                 $this->addTime(new Time($time));
     }
 
+    /**
+     * Add a Time-object to the tabular
+     *
+     * @return Tabular
+     */
     protected function addTime(Time $time): self {
         $this->time[] = $time;
         $this->stats->analyse($time);
         return $this;
     }
 
-    public function getStatistics(): array {
+    /**
+     * Get statistics for the Time-object collection
+     *
+     * @return Statistics
+     */
+    public function getStatistics(): Statistics {
         return $this->stats;
     }
 
+    /**
+     * Remove superfluous weather data.
+     *
+     * Checks if the data in the Time-object differs from
+     * the current Time-object and returns the unique data
+     *
+     * @return Variations
+     */
+    public function getVariations(): Variations {
+        return new Variations($this->time);
+    }
+
+    /**
+     * Filter data between a certain periode, e.g
+     * ```
+     * $forcast->between(
+     *  $forcast->getSunset(),
+     *  $forecast->getSunrise()->add(
+     *      new \DateInterval('P1D')
+     *  )
+     * );```
+     * to only show from sunset today unti sunrise tomorrow
+     *
+     * @return Tabular with new collection
+     */
     public function getBetween(\DateTimeInterface $from, \DateTimeInterface $until): self {
         $n = new Tabular(null);
 
@@ -38,6 +88,9 @@ class Tabular implements \IteratorAggregate {
         return $n;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     public function getIterator(): \Generator {
         foreach ($this->time as $time)
             yield $time;

+ 41 - 1
src/Yr/Forecast/Tabular/Statistics.php

@@ -2,6 +2,17 @@
 
 namespace App\Yr\Forecast\Tabular;
 
+use App\Yr\Forecast\Tabular\Time\AbstractUnit;
+use App\Yr\Forecast\Tabular\Time\Temperature;
+use App\Yr\Forecast\Tabular\Time\WindSpeed;
+
+/**
+ * Make simple statistic on analysed time objects,
+ * such as highest/lowest wind speed and temperature,
+ * average wind speed and temperature etc.
+ *
+ * @author Joachim M. Giæver (joachim[]giaever.org)
+ */
 class Statistics {
     private $temp = [];
     private $wind = [];
@@ -15,8 +26,37 @@ class Statistics {
         ];
     }
 
-    public function analyse(Time $t) {
+    /**
+     * Analyse a single Time-object
+     *
+     * @param Time $t The time object.
+     * @return Statistics
+     */
+    public function analyse(Time $t): self {
+        $this->analyseHihgLow($t->getTemperature());
+        $this->analyseHihgLow($t->getWindSpeed());
+        $this->temp['mean'] += $t->getTemperature()->getValue();
+        $this->wind['mean'] += $t->getWindSpeed()->getValue();
+        return $this;
+    }
+
+    private function analyseHihgLow(AbstractUnit $au): self {
+        $unit = null;
+
+        if ($au instanceof Temperature)
+            $unit = &$this->temp;
+        elseif ($au instanceof WindSpeed)
+            $unit = &$this->wind;
+        else
+            return $this;
+
+        if ($unit['low'] == null || $au->getValue() < $unit['low']->getValue())
+            $unit['low'] = $au;
+
+        if ($unit['high'] == null || $au->getValue() < $unit['high']->getValue())
+            $unit['high'] = $au;
 
+        return $this;
     }
 }
 ?>

+ 52 - 1
src/Yr/Forecast/Tabular/Time.php

@@ -8,6 +8,12 @@ use App\Yr\Forecast\Tabular\Time\Temperature;
 use App\Yr\Forecast\Tabular\Time\WindDirection;
 use App\Yr\Forecast\Tabular\Time\WindSpeed;
 
+/**
+ * Forecast data witin a time period
+ *
+ * @author Joachim M. Giæver (joachim[]giaever.org)
+ */
+
 final class Time implements \IteratorAggregate {
 
     private $from;
@@ -16,6 +22,9 @@ final class Time implements \IteratorAggregate {
     private $symbol;
     private $windDirection;
 
+    /**
+     * @param \SimpleXMLElement $xml The xml holding the time-object
+     */
     public function __construct(\SimpleXMLElement $xml) {
         $this->from = new \DateTimeImmutable($xml['from']);
         $this->until = new \DateTimeImmutable($xml['to']);
@@ -27,38 +36,80 @@ final class Time implements \IteratorAggregate {
         $this->pressure = new Pressure($xml->pressure);
     }
 
+    /**
+     * Returns the which index in the forecast
+     * its for. 
+     */
     public function getPeriod(): int {
         return $this->period;
     }
 
+    /**
+     * Returns the time this forecast is from
+     *
+     * @return \DateTimeImmutable
+     */
     public function getFrom(): \DateTimeImmutable {
         return $this->from;
     }
 
+    /**
+     * Returns the time this forecast is to/until.
+     *
+     * @return \DateTimeImmutable
+     */
     public function getUntil(): \DateTimeImmutable {
         return $this->until;
     }
 
+    /**
+     * Returns the «symbol» (e.g Clody etc)
+     *
+     * @return Symbol
+     */
     public function getSymbol(): Symbol {
         return $this->symbol;
     }
 
+    /**
+     * Returns the wind direction
+     *
+     * @return WindDirection
+     */
     public function getWindDirection(): WindDirection {
-        return $this->getWindDirection();
+        return $this->windDirection;
     }
 
+    /**
+     * Returns the wind speed
+     *
+     * @return WindSpeed
+     */
     public function getWindSpeed(): WindSpeed {
         return $this->windSpeed;
     }
 
+    /**
+     * Returns the temperature
+     *
+     * @return Temperature
+     */
     public function getTemperature(): Temperature {
         return $this->temperature;
     }
 
+    /**
+     * Returns the pressure
+     *
+     * @return Pressure
+     */
     public function getPressure(): Pressure {
         return $this->pressure;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     public function getIterator(): \Generator {
         foreach ([
             $this->getSymbol(),

+ 23 - 7
src/Yr/Forecast/Tabular/Time/AbstractUnit.php

@@ -2,34 +2,50 @@
 
 namespace App\Yr\Forecast\Tabular\Time;
 
+/**
+ * Time-object entity should inherit this
+ */
 abstract class AbstractUnit implements DiffInterface {
     const DEFAULT_VARIANCE = 2;
 
     private $value;
     private $unit;
 
+    /**
+     * @param float The value
+     * @param float The unit
+     */
     public function __construct(float $value, string $unit) {
         $this->value = $value;
         $this->unit = $unit;
     }
 
+    /**
+     * Get the value
+     *
+     * @return float
+     */
     public function getValue(): float {
         return $this->value;
     }
 
+    /**
+     * Return the unit (e.g «degree»)
+     *
+     * @return string
+     */
     public function getUnit(): string {
         return $this->unit;
     }
 
-    public function setThresholdValue(int $t): self {
-        $this->threshold = $t;
-    }
-
-    public function diff(DiffInterface $d): bool {
+    /**
+     * {@inheritDoc}
+     */
+    public function diff(DiffInterface $d): int {
         if ($d instanceof $this)
-            return abs($this->value - $d->getValue()) > static::DEFAULT_VARIANCE;
+            return $this->value - $d->getValue();
 
-        return false;
+        return 0;
     }
 
     public function __toString(): string {

+ 0 - 25
src/Yr/Forecast/Tabular/Time/DiffEntity.php

@@ -1,25 +0,0 @@
-<?php
-
-namespace App\Yr\Forecast\Tabular\Time;
-
-class DiffEntity implements DiffInterface {
-
-    private $time;
-    private $entity;
-
-    public function __construct(Time $t, DiffInterface $d) {
-        $this->time = $t;
-        $this->entity = $d;
-    }
-
-    public function getOccuringTime(): \DateTimeInterface {
-        return $time->getFrom();
-    }
-
-    public function diff(DiffInterface $d): bool {
-        if ($d instanceof get_class($this->entity))
-            return $this->entity->diff($d);
-
-        return false;
-    }
-}

+ 11 - 1
src/Yr/Forecast/Tabular/Time/DiffInterface.php

@@ -2,7 +2,17 @@
 
 namespace App\Yr\Forecast\Tabular\Time;
 
+/**
+ * Defines that an entity can be checked for differences
+ * agains another entity.
+ */
 interface DiffInterface {
-    public function diff(DiffInterface $e): bool;
+    /**
+     * Check for differences with another DiffInterface.
+     * The method should check that the objects is the same.
+     *
+     * @param DiffInterface $e The object to check agains
+     */
+    public function diff(DiffInterface $e): int;
 }
 

+ 38 - 1
src/Yr/Forecast/Tabular/Time/Pressure.php

@@ -2,13 +2,50 @@
 
 namespace App\Yr\Forecast\Tabular\Time;
 
-
+/**
+ * Airpressure
+ *
+ * @author Joachim M. Giæver (joachim[]giaever.org)
+ */
 class Pressure extends AbstractUnit {
+    const NORMAL_PRESSURE = 1015.0;
+
+    /**
+     * @param \SimpleXMLElement $xml XML containing the pressure
+     */
     public function __construct(\SimpleXMLElement $xml) {
         parent::__construct(
             (float)$xml['value'],
             (string)$xml['unit']
         );
     }
+
+    /**
+     * Check if the pressure is below normal pressure
+     *
+     * @return bool
+     */
+    public function isLowPressure(): bool {
+        return $this->getValue() < self::NORMAL_PRESSURE;
+    }
+
+    /**
+     * Check if the pressure is above normal pressure
+     *
+     * @return bool
+     */
+    public function isHighPressure(): bool {
+        return $this->getValue() > self::NORMAL_PRESSURE;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function diff(DiffInterface $d): int {
+        if ($diff = parent::diff($d))
+            return $this->isLowPressure() == $d->isLowPressure() ? 0 : 1;
+
+        return 0;
+    }
 }
 

+ 29 - 3
src/Yr/Forecast/Tabular/Time/Symbol.php

@@ -2,6 +2,11 @@
 
 namespace App\Yr\Forecast\Tabular\Time;
 
+/**
+ * Contains the sky data, e.g «Clody» etc
+ *
+ * @author Joachim M. Giæver (joachim[]giaever.org)
+ */
 class Symbol implements DiffInterface {
 
     private $number;
@@ -9,6 +14,9 @@ class Symbol implements DiffInterface {
     private $name;
     private $var;
 
+    /**
+     * @param \SimpleXMLElement $xml XML containing the symbol data
+     */
     public function __construct(\SimpleXMLElement $xml) {
         $this->number = (int)$xml['number'];
         $this->numberEx = (int)$xml['numberEx'];
@@ -16,23 +24,41 @@ class Symbol implements DiffInterface {
         $this->var = (string)$xml['var'];
     }
 
+    /**
+     * Retuns the type identifier
+     *
+     * @return int
+     */
     public function getNumber(): int {
         return $this->number;
     }
 
+    /**
+     * Returns the name, e.g «clody».
+     *
+     * @return string
+     */
     public function getName(): string {
         return $this->name;
     }
 
+    /**
+     * Return the var-variable
+     *
+     * @return string
+     */
     public function getVar(): string {
         return $this->var;
     }
 
-    public function diff(DiffInterface $s): bool {
+    /**
+     * {@inheritDoc}
+     */
+    public function diff(DiffInterface $s): int {
         if ($s instanceof Symbol)
-            return $this->number != $s->getNumber();
+            return $this->number - $s->getNumber();
 
-        return false;
+        return 0;
     }
 
     public function __toString(): string {

+ 10 - 0
src/Yr/Forecast/Tabular/Time/Temperature.php

@@ -2,7 +2,17 @@
 
 namespace App\Yr\Forecast\Tabular\Time;
 
+/**
+ * Temperature
+ *
+ * @author Joachim M. Giæver (joachim[]giaever.org)
+ */
 class Temperature extends AbstractUnit {
+    const DEFAULT_VARIANCE = 2;
+
+    /**
+     * @param \SimpleXMLElement $xml XML containing the temperature
+     */
     public function __construct(\SimpleXMLElement $xml) {
         parent::__construct(
             (float)$xml['value'],

+ 24 - 6
src/Yr/Forecast/Tabular/Time/WindDirection.php

@@ -2,9 +2,18 @@
 
 namespace App\Yr\Forecast\Tabular\Time;
 
+/**
+ * Wind direction
+ *
+ * @author Joachim M. Giæver (joachim[]giaever.org)
+ */
 class WindDirection extends AbstractUnit{
+    const DEFAULT_VARIANCE = 22.5;
     private $name;
 
+    /**
+     * @param \SimpleXMLElement $xml XML element containing the wind direction
+     */
     public function __construct(\SimpleXMLElement $xml) {
         parent::__construct(
             (float)$xml['deg'],
@@ -14,14 +23,23 @@ class WindDirection extends AbstractUnit{
         $this->name = (string)$xml['name'];
     }
 
-    public function diff(DiffInterface $d): bool {
-        if (parent::diff($d))
-            return $this->getUnit() != $f->getUnit();
-        return false;
-    }
-
+    /**
+     * Returns the wind direction in full,
+     * e.g «Northeast».
+     *
+     * @return string
+     */
     public function getName(): string {
         return $this->name;
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    public function diff(DiffInterface $d): int {
+        if ($diff = parent::diff($d))
+            return $diff > static::DEFAULT_VARIANCE ? 1 : 0;
+        return 0;
+    }
+
 }

+ 25 - 0
src/Yr/Forecast/Tabular/Time/WindSpeed.php

@@ -2,6 +2,11 @@
 
 namespace App\Yr\Forecast\Tabular\Time;
 
+/**
+ * Wind speed
+ *
+ * @author Joachim M. Giæver (joachim[]giaever.org)
+ */
 class WindSpeed extends AbstractUnit {
     const DEFAULT_VARIANCE = (4.9 / 3.5);
 
@@ -12,6 +17,9 @@ class WindSpeed extends AbstractUnit {
 
     private $name;
 
+    /**
+     * @param \SimpleXMLElement $xml XML containing the wind spedd
+     */
     public function __construct(\SimpleXMLElement $xml){
         parent::__construct(
             (float)$xml['mps'], self::UNIT_MPS
@@ -20,6 +28,11 @@ class WindSpeed extends AbstractUnit {
         $this->name = (string)$xml['name'];
     }
 
+    /**
+     * Convert the wind speed to a different unit
+     *
+     * @param string $unit The unit to convert to, eg UNIT_FTS
+     */
     public function convertTo(string $unit): int {
         switch ($unit) {
         case self::UNIT_KNOTS:
@@ -31,10 +44,22 @@ class WindSpeed extends AbstractUnit {
         }
     }
 
+    /**
+     * Returns the wind name, e.g «light breeze»
+     */
     public function getName(): string {
         return $this->name;
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    public function diff(DiffInterface $e): int {
+        if (parent::diff($e))
+            return ($this->getName() != $e->getName()) ? 1 : 0;
+        return 0;
+    }
+
     public function __toString(): string {
         return sprintf(
             '%s (%s)', parent::__toString(), $this->name

+ 55 - 0
src/Yr/Forecast/Tabular/Variations.php

@@ -0,0 +1,55 @@
+<?php
+
+namespace App\Yr\Forecast\Tabular;
+
+/**
+ * Removes superfluous forecast data in an Time-object
+ * only storing changes.
+ *
+ * @author Joachim M. Giæver (joachim[]giaever.org)
+ */
+class Variations {
+
+    private $data = [];
+
+    /**
+     * @param array $t Array of Time-objects
+     */
+    public function __construct(array $t) {
+        if (!current($t) instanceof Time)
+            return;
+
+        foreach ($t as $time)
+            $this->determineVariance($time);
+    }
+
+    private function determineVariance(Time $t) {
+        $var = [
+            'time' => $t,
+            'entities' => iterator_to_array($t->getIterator())
+        ];
+
+        if (empty($this->data))
+            $this->data[] = $var;
+        else {
+            foreach (array_reverse($this->data) as $entry) {
+                foreach ($entry['entities'] as $entity) {
+                    foreach ($var['entities'] as $key => $ventity)
+                        if ($ventity instanceof $entity && !$ventity->diff($entity))
+                            unset($var['entities'][$key]);
+                }
+            }
+            if (!empty($var['entities']))
+                $this->data[] = $var;
+        }
+    }
+
+    /**
+     * Returns the changes in the forecast
+     *
+     * @return array 
+     */
+    public function getData(callable $filterFn = null): array {
+        return $this->data;
+    }
+}