Browse Source

Change str methods, more variation optios

Joachim M. Giæver 4 years ago
parent
commit
cdd123fdb9

+ 52 - 58
index.php

@@ -6,6 +6,7 @@ error_reporting(E_ALL);
 
 use App\Yr\Forecast;
 use App\Yr\Forecast\Tabular;
+use App\Yr\Forecast\Tabular\Time\AbstractUnit;
 use App\Yr\Forecast\Tabular\Time\Symbol;
 use App\Yr\Forecast\Tabular\Time\Temperature;
 use App\Yr\Forecast\Tabular\Time\WindDirection;
@@ -14,6 +15,7 @@ use App\Yr\Forecast\Tabular\Time\WindSpeed;
 require __DIR__ . '/vendor/autoload.php';
 
 class TextForcast {
+    const HOUR_FORMAT = 'H (h a)';
 
     private $forecast;
 
@@ -25,63 +27,53 @@ class TextForcast {
      * «Tonight Southeasterly light breeze, cloudy, temperature -8 to  -9 degrees»
      * @author Helge Tangen
      */
-    public function start(): string {
+    public function json(): string {
         $time = $this->forecast->getVariations()->getTime();
-        return sprintf(
-            "%s from %s (%s) %s",
-            $this->when($time->getFrom()), 
-            $time->getFrom()->format('H'),
-            $time->getFrom()->format('h a'),
-            (function() use ($time){
-                $str = [];
-
-                $prev = null;
-                foreach ($time as $data) {
-                    switch (get_class($data)) {
-                    case Symbol::class:
-                        $str[] = ($prev == null ? ' ' : ', ') . $data->getName();
-                        break;
-                    case WindSpeed::class:
-                        $str[] = ($prev == null || WindSpeed::class ? " " : ", ") . $data->getName();
-                        break;
-                    case WindDirection::class:
-                        $str[] = ($prev == null ? ' ' : ', ') . $data->getName();
-                        break;
-                    case Temperature::class:
-                        $data = $data->convertTo(Temperature::UNIT_KELVIN);
-                        $str[] = ($prev == null ? ' ' : ', ') . sprintf('%d %s', $data->getValue(), $data->getDegree());
-                        break;
-                    default:
-                        //$str []= '``' .  get_class($data) . '`` missing';
-                    }
-                    $prev = $data;
-                }
-
-                $last = array_pop($str);
-
-                if (substr($last, 0, 1) == ',')
-                    $last = substr($last, 1);
-
-                if (sizeof($str) == 0)
-                    return strtolower($last);
-
-                return strtolower(sprintf(
-                    "%s and %s", 
-                    join($str), $last
-                ));
-            })()
-        );
+
+        $arr = [
+            "from"      => $this->forecast->getFrom()->format('Y-m-d H:i'),
+            "until"     => $this->forecast->getUntil()->format("Y-m-d H:i"),
+            "initial"   => [],
+            "changes"   => [],
+            "credit"    => $this->forecast->getForecast()->getCredit()
+        ];
+        foreach ($time as $ent) {
+            $arr['initial'][] = (string)$ent;
+        }
+
+        $vars = $this->forecast->getVariations()->filter(function($e, $p, $n) {
+            return ($p == null ? true : $e->thresholdDiff($p->getEntity(get_class($e))));
+        });
+        
+        foreach ($vars as $var) {
+            $changes = [
+                "at" => [
+                    "time" => $var->getTime()->getFrom()->format('Y-m-d H:i'),
+                    "period" => $this->when($var->getTime()->getFrom())
+                ],
+                "to" => [],
+            ];
+            $var->operate(function($e, $p, $n) use (&$changes) {
+                $changes['to'][] = (string)$e;
+            });
+
+            $arr['changes'][] = $changes;
+        }
+
+        return json_encode($arr);
     }
 
     public function when(\DateTimeInterface $d): string {
-        $diff = $d->diff(new \DateTime());
+        $a = DateTime::createFromFormat('d/m/Y', $d->format('d/m/Y'));
+        $b = DateTime::createFromFormat('d/m/Y', (new \DateTime())->format('d/m/Y'));
+        $diff = $b->diff($a);
 
         if ($diff->invert && $diff->days == 1)
-            return sprintf("yesterday %s", $this->period($d));
+            return $this->period($d, "yesterday");
         elseif (!$diff->invert && $diff->days == 1)
-            return sprintf("tomorrow %s", $this->period($d));
+            return $this->period($d, "tomorrow");
         elseif ($diff->days == 0)
-            return sprintf("This %s", $this->period($d));
+            return $this->period($d, "today");
 
         return sprintf("the %s at %s of %s %y ",
             $this->period($d),
@@ -89,20 +81,20 @@ class TextForcast {
         );
     }
 
-    public function period(\DateTimeInterface $d): string {
+    public function period(\DateTimeInterface $d, ?string $when = null): string {
         $hour = (int)$d->format('H');
         if ($hour >= 6 && $hour <= 11)
-            return "morning";
+            return ($when == "today" ? "this" : $when ) . " morning";
         elseif ($hour == 12)
-            return "noon";
+            return "at noon";
         elseif ($hour > 12 && $hour < 18)
-            return "afternoon";
+            return ($when == "today" ? "this" : $when) . " afternoon";
         elseif ($hour >= 18 && $hour < 24)
-            return "evening";
-        elseif ($hour == 12)
-            return "midning";
+            return ($when == "today" ? "tonight" : $when . " evening");
+        elseif ($hour == 0)
+            return ($when == "tomorrow" ? "at" : ($when == "today" ? "last" : $when)) . " midning";
         else
-            return "night";
+            return ($when == "tomorrow" ? "this" : ($when == "today" ? "last" : $when)) . " night";
     }
 
     public function since(\DateTimeInterface $d): string {
@@ -112,11 +104,13 @@ class TextForcast {
 
 $url = 'https://www.yr.no/place/Norway/Troms/Tromsø/Tromsø/forecast_hour_by_hour.xml';
 
+echo '<pre>';
 $forecast = new Forecast($url);
 
 $range = $forecast->getTabular()->getBetween(
     $forecast->getSunset(), $forecast->getSunrise()->add(new \DateInterval('P1D'))
 );
 
-echo (new TextForcast($range))->start();
+$tf = new TextForcast($range);
+echo $tf->json();
 ?>

+ 1 - 1
src/Yr/Forecast.php

@@ -116,7 +116,7 @@ final class Forecast {
      * @return Tabular
      */
     public function getTabular(): Tabular {
-        return new Tabular($this->xml->forecast->tabular);
+        return new Tabular($this, $this->xml->forecast->tabular);
     }
 
 }

+ 12 - 3
src/Yr/Forecast/Tabular.php

@@ -2,6 +2,7 @@
 
 namespace App\Yr\Forecast;
 
+use App\Yr\Forecast;
 use App\Yr\Forecast\Tabular\Statistics;
 use App\Yr\Forecast\Tabular\Time;
 use App\Yr\Forecast\Tabular\Variations;
@@ -22,11 +23,13 @@ class Tabular implements \IteratorAggregate {
     private $time = [];
     private $stats;
     private $variations;
+    private $forecast;
 
     /**
      * @param \SimpleXMLElement $xml The xml part holding the time objects, can be null
      */
-    public function __construct(?\SimpleXMLElement $xml) {
+    public function __construct($forecast, ?\SimpleXMLElement $xml) {
+        $this->forecast = $forecast;
         $this->stats = new Statistics();
 
         if ($xml != null) {
@@ -38,6 +41,10 @@ class Tabular implements \IteratorAggregate {
         }
     }
 
+    public function getForecast(): Forecast {
+        return $this->forecast;
+    }
+
     /**
      * Add a Time-object to the tabular
      *
@@ -90,7 +97,7 @@ class Tabular implements \IteratorAggregate {
      * @return Tabular with new collection
      */
     public function getBetween(\DateTimeInterface $from, \DateTimeInterface $until): self {
-        $n = new Tabular(null);
+        $n = new Tabular($this->forecast, null);
 
         foreach ($this as $time)
             if ($time->getFrom() >= $from && $time->getUntil() <= $until)
@@ -121,8 +128,10 @@ class Tabular implements \IteratorAggregate {
     /**
      * {@inheritDoc}
      */
-    public function getIterator(): \Generator {
+    public function getIterator(): ?\Generator {
         foreach ($this->time as $time)
             yield $time;
+
+        return null;
     }
 }

+ 3 - 3
src/Yr/Forecast/Tabular/Statistics.php

@@ -91,7 +91,7 @@ class Statistics {
 
     public function getAverageSymbols(): array {
         if ($s = current($this->symbol))
-           if ($s != null && $s['avg'] != 0)
+           if ($s != null && $s['avg'] instanceof CustomUnit)
                return $this->symbol;
 
         array_walk($this->symbol, function(array &$symbol) {
@@ -119,10 +119,10 @@ class Statistics {
         else
             return $this;
 
-        if ($unit['low'] == null || $au->getValue() < $unit['low']->getValue())
+        if ($unit['low'] == null || $au->diff($unit['low']) <= 0)
             $unit['low'] = $au;
 
-        if ($unit['high'] == null || $au->getValue() < $unit['high']->getValue())
+        if ($unit['high'] == null || $au->diff($unit['high']) >= 0)
             $unit['high'] = $au;
 
         return $this;

+ 6 - 4
src/Yr/Forecast/Tabular/Time/AbstractUnit.php

@@ -127,20 +127,22 @@ abstract class AbstractUnit implements DiffInterface {
     }
 
     /**
-     * Note! Working on floats, so operates on 100-times value.
+     * Note! Working on floats, so operates on 100-times values,
+     * so result should be divided with 100 to get the actual
+     * difference.
      *
      * {@inheritDoc}
      */
     public function diff(DiffInterface $d): int {
-        if ($this instanceof $d)
-            return (int)(($this->getValue() * 100) - ($d->getValue() * 100));
+        if ($this->canOperate($d, false))
+            return (int)($this->sub($d)->getValue() * 100);
 
         return 0;
     }
 
     public function __toString(): string {
         return sprintf(
-            "%s: %f %s", basename(str_replace(
+            "%s: %01.1f %s", basename(str_replace(
                 '\\', DIRECTORY_SEPARATOR, get_class($this)
             )), $this->value, $this->unit);
     }

+ 3 - 0
src/Yr/Forecast/Tabular/Time/CustomUnit.php

@@ -10,6 +10,9 @@ namespace App\Yr\Forecast\Tabular\Time;
  */
 class CustomUnit extends AbstractUnit {
     // Nothing extra here
+    function thresholdDiff(DiffInterface $e): bool {
+        return $this->diff($e) == 0;
+    }
 }
 
 ?>

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

@@ -14,5 +14,6 @@ interface DiffInterface {
      * @param DiffInterface $e The object to check agains
      */
     public function diff(DiffInterface $e): int;
+    public function thresholdDiff(DiffInterface $e): bool;
 }
 

+ 2 - 8
src/Yr/Forecast/Tabular/Time/Pressure.php

@@ -39,14 +39,8 @@ class Pressure extends AbstractUnit {
         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;
+    public function thresholdDiff(DiffInterface $e): bool {
+        return $this->isLowPressure() != $e->isLowPressure();
     }
 }
 

+ 9 - 4
src/Yr/Forecast/Tabular/Time/Symbol.php

@@ -39,7 +39,7 @@ class Symbol implements DiffInterface {
      * @return string
      */
     public function getName(): string {
-        return $this->name;
+        return strtolower($this->name);
     }
 
     /**
@@ -61,11 +61,16 @@ class Symbol implements DiffInterface {
         return 0;
     }
 
+    public function thresholdDiff(DiffInterface $e): bool {
+        return $this->diff($e) != 0;
+    }
+
     public function __toString(): string {
         return sprintf(
-            "%s (%d, %d, %s)", $this->name,
-            $this->number, $this->numberEx,
-            $this->var
+            "%s: %s (%d)", 
+            basename(str_replace("\\", DIRECTORY_SEPARATOR, get_class($this))),
+            $this->name,
+            $this->number
         );
     }
 }

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

@@ -41,11 +41,24 @@ class Temperature extends AbstractUnit implements ConvertableInterface {
         }
     }
 
+    public function thresholdDiff(DiffInterface $e): bool {
+        return abs($this->diff($e)/100) > 1;
+    }
+
     /**
      * Return the ⁰X symbol
      */
     public function getDegree(): string {
         return sprintf("⁰%s", strtoupper(substr($this->getUnit(), 0, 1)));
     }
+
+    public function __toString(): string {
+        return sprintf(
+            "%s: %d %s",
+            basename(str_replace("\\", DIRECTORY_SEPARATOR, get_class($this))),
+            $this->getValue(),
+            $this->getDegree()
+        );
+    }
 }
 

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

@@ -30,7 +30,7 @@ class WindDirection extends AbstractUnit{
      * @return string
      */
     public function getName(): string {
-        return $this->name;
+        return strtolower($this->name);
     }
 
     /**
@@ -61,4 +61,14 @@ class WindDirection extends AbstractUnit{
         return 0;
     }
 
+    public function thresholdDiff(DiffInterface $e): bool {
+        return $this->diff($e) != 0;       
+    }
+
+    public function __toString(): string {
+        return sprintf("%s: %s (%01.1f⁰)",
+            basename(str_replace("\\", DIRECTORY_SEPARATOR, get_class($this))),
+            $this->getName(), $this->getValue()
+        );
+    }
 }

+ 8 - 9
src/Yr/Forecast/Tabular/Time/WindSpeed.php

@@ -47,7 +47,7 @@ class WindSpeed extends AbstractUnit implements ConvertableInterface {
      * Returns the wind name, e.g «light breeze»
      */
     public function getName(): string {
-        return $this->name;
+        return strtolower($this->name);
     }
 
     /**
@@ -58,18 +58,17 @@ class WindSpeed extends AbstractUnit implements ConvertableInterface {
         return $this;
     }
 
-    /**
-     * {@inheritDoc}
-     */
-    public function diff(DiffInterface $e): int {
-        if ($diff = parent::diff($e))
-            return $this->getName() == $e->getName() ? 0 : $diff;
-        return 0;
+    public function thresholdDiff(DiffInterface $e): bool {
+        return $this->getName() != $e->getName();
     }
 
     public function __toString(): string {
         return sprintf(
-            '%s (%s)', parent::__toString(), $this->name
+            '%s: %s (%01.1f %s)',
+            basename(str_replace('\\', DIRECTORY_SEPARATOR, get_class($this))),
+            $this->getName(),
+            $this->getValue(),
+            $this->getUnit()
         );
     }
 }

+ 96 - 9
src/Yr/Forecast/Tabular/Variation/Variation.php

@@ -12,21 +12,40 @@ if (!function_exists('array_key_last')) {
     }
 }
 
+class VariationFn {
+    private $fn, $v;
+    public function __construct(callable $fn, Variation $v) {
+        $this->fn = $fn;
+        $this->v = $v;
+    }
+    public function callable(DiffInterface $e): ?bool {
+        $callable = $this->fn;
+        return $callable(
+            $e, 
+            $this->v->getIntersect($e), 
+            $this->v->getIntersected($e)
+        );
+    }
+}
+
 class Variation implements \IteratorAggregate {
 
     private $time;
     private $entities = [];
+    private $intersected = [];
     private $intersects = [];
 
     public function __construct(Time $t) {
         $this->time = $t;
     }
 
-    public function addEntity(DiffInterface $entity, ?DiffInterface $intersects): self {
+    public function addEntity(DiffInterface $entity, ?Variation $intersects): self {
         $this->entities[] = $entity;
 
-        if ($intersects != null)
+        if ($intersects != null) {
             $this->intersects[array_key_last($this->entities)] = $intersects;
+            $intersects->addIntersected($entity, $this);
+        }
 
         return $this;
     }
@@ -35,26 +54,94 @@ class Variation implements \IteratorAggregate {
         return $this->time;
     }
 
-    public function getIntersection(DiffInterface $entity): ?DiffInterface {
-        $key = (function() use ($entity) : int {
+    private function getEntityKey(DiffInterface $entity): ?int {
+        $key = (function() use ($entity) : ?int {
             foreach ($this->entities as $key => $ent)
-                if ($ent == $entity)
+                if ($ent instanceof $entity)
                     return $key;
+            return null;
         })();
 
-        if (isset($this->intersects[$key]))
-            return $this->intersects[$key];
+        return $key;
+    }
 
-        return null;
+    public function getIntersect(DiffInterface $entity): ?Variation {
+        $key = $this->getEntityKey($entity);
+
+        if ($key === null || !isset($this->intersects[$key]))
+            return null;
+
+        return $this->intersects[$key];
+    }
+
+    public function getIntersected(DiffInterface $entity): ?Variation {
+        $key = $this->getEntityKey($entity);
+
+        if ($key === null || !isset($this->intersected[$key]))
+            return null;
+
+        foreach ($this->intersected[$key] as $var)
+            if ($var->getEntity(get_class($entity)) != null)
+                return $var;
+    }
+
+    protected function addIntersected(DiffInterface $entity, Variation $var): self {
+        $key = $this->getEntityKey($entity);
+
+        if ($key === null)
+            return $this;
+
+        if (!isset($this->intersected[$key]))
+            $this->intersected[$key] = [$var];
+        else
+            $this->intersected[$key][] = $var;
+
+        return $this;
     }
 
     public function isEmpty(): bool {
         return empty($this->entities);
     }
 
-    public function getIterator(): \Generator {
+    public function filter(callable $filterFn): ?Variation {
+
+        $entities = array_filter($this->entities, [
+            new VariationFn($filterFn, $this), 'callable',
+        ]);
+
+        if (count($entities) == 0)
+            return null;
+
+        $var = new Variation($this->time);
+
+        foreach ($entities as $entity)
+            $var->addEntity($entity, $this->getIntersect($entity));
+
+        return $var;
+    }
+
+    public function operate(callable $opFn): void {
+        array_walk($this->entities, [
+            new VariationFn($opFn, $this), 'callable'
+        ]);
+    }
+
+    public function numEntities(): int {
+        return sizeof($this->entities);
+    }
+
+    public function getEntity(string $class): ?DiffInterface { 
+        foreach ($this as $entity)
+            if ($entity instanceof $class)
+                return $entity;
+        return null;
+    }
+
+    public function getIterator(): ?\Generator {
         foreach ($this->entities as $entity)
             yield $entity;
+
+        return null;
     }
 
 }

+ 23 - 7
src/Yr/Forecast/Tabular/Variations.php

@@ -11,7 +11,7 @@ use App\Yr\Forecast\Tabular\Variation\Variation;
  *
  * @author Joachim M. Giæver (joachim[]giaever.org)
  */
-class Variations {
+class Variations implements \IteratorAggregate {
 
     private $time;
     private $data = [];
@@ -52,7 +52,7 @@ class Variations {
             foreach ($data as $dentity) {
                 if ($entity instanceof $dentity) {
                     if ($entity->diff($dentity))
-                        $var->addEntity($entity, $dentity);
+                        $var->addEntity($entity, $data == null ? "NULL" : $data);
                     return;
                 }
             }
@@ -64,12 +64,28 @@ class Variations {
     }
 
     /**
-     * Returns the changes in the forecast
+     * Filter on types, example usage
+     * ```
+     * $variations->filter(function ($entity) {
+     *      return $entity instanceof Temperature;
+     * );
+     * ```
      *
-     * @todo implement filter function
-     * @return array 
+     * @return \Generator
      */
-    public function getData(callable $filterFn = null): array {
-        return $this->data;
+    public function filter(callable $filterFn): \Generator {
+        foreach ($this as $data)
+            if (($match = $data->filter($filterFn)) != null)
+                yield $match;
+
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getIterator(): \Generator {
+        foreach ($this->data as $data)
+            yield $data;
     }
 }