Browse Source

Creating GIT Repo

Daniel 3 years ago
commit
298f6e6759
10 changed files with 585 additions and 0 deletions
  1. 10 0
      FRAShuttle.jpg.php
  2. 27 0
      README.md
  3. 265 0
      Schedule.php
  4. 10 0
      Tuesday.jpg.php
  5. BIN
      UbuntuMono-Regular.ttf
  6. 10 0
      Woche.jpg.php
  7. 10 0
      Woche2.jpg.php
  8. 128 0
      config.php
  9. 34 0
      darkmode.php
  10. 91 0
      test.jpg.php

+ 10 - 0
FRAShuttle.jpg.php

@@ -0,0 +1,10 @@
+<?php
+
+require_once "Schedule.php";
+require_once "config.php";
+
+$sched = new Schedule(Schedule::EveryXWeeksFromStartdate("2021-10-08", 4, 2), $stations_berlin, $optional, $wanted, $darkmode);
+$image = $sched->createImage();
+
+header("Content-Type: image/jpg");
+imagejpeg($image);

+ 27 - 0
README.md

@@ -0,0 +1,27 @@
+VATSIM PHP Scheduler, using VATSIM GERMANY API.
+
+It is recommended to set up a separate PHP file for each overview you would like to create. Alternatively one file can be created that outputs images to files using a cronjob.
+If multiple overviews use the same stations, optional stations or wanted stations, a central config file might be useful.
+
+A new Overview is created by making a new instance of the Schedule class:
+
+```php
+$schedule = new Schedule($displaydates, $stations, $optstations, $wantedstations, $darkmode);
+```
+
+* `$displaydates` is an Array that should be created with one of the helper functions:
+    * `Schedule::EveryXDaysFromStartdate (String startdate, int x, int numberofdays = 1)` - Returns an array of DateTime objects. For regularly recurring events. Define a startdate and an interval in days for the event. numberofdays defines how many upcoming days will be returned.
+    * `EveryXWeeksFromStartdate (String startdate, int x, int numberofdays = 1)` - Returns an array of DateTime objects. For regularly recurring events. Define a startdate and an interval in weeks for the event. numberofdays defines how many upcoming days will be returned. This is just an alias for EveryXDaysFromStardate with the input for x being x*7.
+    * `EveryWeekday (String weekday, int numberofdays)` - Returns a list of datetime objects. This can be used for weekly events. Define a day of the week (such as "Wednesday") and the number of days to be displayed.
+    * `WholeWeek (int shift = 0)` - Returns a list of 7 datetime objects for consecutive days. Will start with today + shift days.
+* `$stations` - an array of the stations that are to be displayed (including optional ones!). The String `--` can be used to display a visual seperator.
+* `$optstations` - an array of the stations that are only to be displayed when there is a booking for that station. Defaults to `[]`.
+* `$wantedstations` - A 2-dimensional array of stations and dates that should show "Wanted!" instead of "Open" when no booking is made. Defaults to `[]`. A helper function to create this array is provided:
+    * `Schedule::processWantedDays(array stations, array dates, ...)`: In pairs of stations and dates each, provide as many pairs as needed. Stations should just be a plain array of stations, dates should once again be an array of DateTime objects, ideally using the helper functions described above.
+* `$darkmode` - Whether to display a dark or light background. Defaults to `false`. The attached `darkmode.php` provides a way to set a cookie for the darkmode which can be used to feed this value.
+
+The image will be creted calling the `createImage` method.
+```php
+    $image = $schedule->createImage();
+    imagejpg($image);
+```

+ 265 - 0
Schedule.php

@@ -0,0 +1,265 @@
+<?php
+
+class Schedule {
+    /** @var string[] $abbreviations */
+    private $abbreviations;
+    
+    /** @var DateTime[] $displaydates */
+    private $displaydates;
+
+    /** @var string[] $stations */
+    private $stations;
+
+    /** @var string[] $stations */
+    private $optstations;
+
+    /** @var array[] $stations */
+    private $wantedstations;
+
+    /** @var array $improps */
+    private $improps;
+
+    /** @var GDIMAGE $this->image */
+    private $image;
+
+    /** @var boolean $darkmode */
+    private $darkmode;
+
+    /** @var boolean $debug */
+    private $debug;
+
+    public function __construct(array $displaydates, array $stations, array $optstations = [], $wantedstations = [], $darkmode = false) {
+        $this->abbreviations = [];
+        $this->training = $this->event = false;
+        $this->displaydates = $displaydates;
+        $this->stations = $stations;
+        $this->optstations = $optstations;
+        $this->wantedstations = $wantedstations;
+        $this->improps = [];
+        $this->darkmode = $darkmode;
+    }
+    
+    public function createImage() {
+        $rawData = $this->fetchData();
+
+        $relevantBookings = [];
+        foreach($this->stations as $station) {
+            $relevantBookings[$station] = [];
+        }
+
+        foreach($rawData as $item) {
+            if(in_array($item['station']['ident'], $this->stations)) {
+                $relevantBookings[$item['station']['ident']][] = [
+                    'starts_at' => new DateTime($item['starts_at']),
+                    'ends_at' => new DateTime($item['ends_at']),
+                    'lastname' => $item['controller']['lastname'],
+                    'firstname' => $item['controller']['firstname'],
+                    'training' => $item['training'],
+                    'event' => $item['event']
+                ];
+            }
+        }
+
+        putenv('GDFONTPATH=' . realpath('.'));
+        $this->improps['font'] = "UbuntuMono-Regular";
+        // $this->improps['font'] = "EuroScope";
+        $this->improps['width'] = 100 * count($this->displaydates) + 100;
+        $this->improps['height'] = 25 * count($this->stations) + 600;
+
+        if(!$this->darkmode) {
+            $this->image = imagecreatetruecolor($this->improps['width'], $this->improps['height']);
+            $this->improps['bg'] = imagecolorallocate($this->image, 255, 255, 255);
+            $this->improps['black'] = imagecolorexact($this->image, 0, 0, 0);
+            $this->improps['open'] = imagecolorexact($this->image, 155, 155, 155);
+            $this->improps['wanted'] = imagecolorexact($this->image, 255, 0, 0);
+            $this->improps['event'] = imagecolorexact($this->image, 0, 255, 0);
+            $this->improps['training'] = imagecolorexact($this->image, 50, 100, 255);
+            $this->improps['eventtraining'] = imagecolorexact($this->image, 0, 255, 255);
+        } else {
+            $this->image = imagecreatetruecolor($this->improps['width'], $this->improps['height']);
+            $this->improps['bg'] = imagecolorallocate($this->image, 0, 0, 0);
+            $this->improps['black'] = imagecolorexact($this->image, 255, 255, 255);
+            $this->improps['open'] = imagecolorexact($this->image, 155, 155, 155);
+            $this->improps['wanted'] = imagecolorexact($this->image, 255, 0, 0);
+            $this->improps['event'] = imagecolorexact($this->image, 0, 255, 0);
+            $this->improps['training'] = imagecolorexact($this->image, 50, 100, 255);
+            $this->improps['eventtraining'] = imagecolorexact($this->image, 0, 255, 255);
+        }
+        imagefill($this->image, 0, 0, $this->improps['bg']);
+
+        $this->improps['lineheight'] = 20;
+        $sepheight = 25;
+
+        for($i = 0; $i < count($this->displaydates); $i++) {
+            imagefttext($this->image, 11, 0, ($i) * 100 + 100, 25, $this->improps['black'], $this->improps['font'], $this->displaydates[$i]->format("D d.m."));
+        }
+
+        $height = 40;
+        foreach($this->stations as $station) {
+            if($this->debug)  echo "\n". $station . "\th$height";
+            imageline($this->image, 0, $height + 5, $this->improps['width'], $height + 5, $this->improps['open']);
+            if($station == '--') {
+                $height += $sepheight;
+                continue;
+            }
+            $height += $this->improps['lineheight'];
+            
+            $lines = 0;
+            for($i = 0; $i < count($this->displaydates); $i++) {
+                $l = $this->addBookingData($station, $this->displaydates[$i], $relevantBookings[$station], $i * 100 + 100, $height);
+                $lines = $l > $lines ? $l : $lines;
+                if($this->debug) echo "\t" . $this->displaydates[$i]->format("m-d") . ": " . $lines;
+            }
+            if(!in_array($station, $this->optstations) && $lines < 1)  $lines = 1;
+            if($this->debug) echo "\tl$lines";
+            if($lines > 0) {
+                imagefttext($this->image, 11, 0, 10, $height, $this->improps['black'], $this->improps['font'], $station);
+                imageline($this->image, 0, $height + 5, $this->improps['width'], $height + 5, $this->improps['open']);
+            }
+            $height += ($lines - 1) * $this->improps['lineheight'];
+            
+        }
+
+        for($i = 0; $i < count($this->displaydates); $i++) {
+            imageline($this->image, 100 * $i + 92, 0, 100 * $i + 92, $height + 5, $this->improps['open']);
+        }
+
+        $height += $this->improps['lineheight'] + 5;
+        imagefttext($this->image, 11, 0, 10, $height, $this->improps['wanted'], $this->improps['font'], 'Wanted');
+        imagefttext($this->image, 11, 0, 100, $height, $this->improps['training'], $this->improps['font'], 'Training');
+        imagefttext($this->image, 11, 0, 200, $height, $this->improps['event'], $this->improps['font'], 'Event');
+        imagefttext($this->image, 11, 0, 300, $height, $this->improps['eventtraining'], $this->improps['font'], 'Event + Training');
+
+        $height += $this->improps['lineheight'] * 2;
+        $namelength = 0;
+        asort($this->abbreviations);
+        foreach($this->abbreviations as $name) {
+            $namelength = strlen($name) > $namelength ? strlen($name) : $namelength;
+        }
+        $collength = ($namelength + 10) * 8;
+        $cols = floor( ($this->improps['width'] - 10) / $collength);
+        $col = 0;
+        $row = 0;
+        foreach($this->abbreviations as $abbrv => $name) {
+            imagefttext($this->image, 11, 0, $col * $collength + 10, $height + $row * $this->improps['lineheight'], $this->improps['black'], $this->improps['font'], $abbrv . ": " . $name);
+            $row = ($col + 1) >= $cols ? $row + 1 : $row;
+            $col = ($col + 1) >= $cols ? 0 : $col + 1;
+        }
+
+        $height += $this->improps['lineheight'] * ($row +2);
+        imagefttext($this->image, 11, 0, 10, $height, $this->improps['open'], $this->improps['font'], "Generated " . (new DateTime("now", new DateTimeZone('UTC')))->format('d.m.Y H:i:s') . "z" );
+        $this->image = imagecrop($this->image, ['x' => 0, 'y' => 0, 'width' => $this->improps['width'], 'height' => $height+5]);
+
+        return $this->image;
+
+    }
+    
+    private function addBookingData($station, $date, $bookings, $x, $height) {
+        $count = 0;
+        foreach($bookings as $booking) {
+            if($booking['starts_at']->format('Y-m-d') == $date->format('Y-m-d')) {
+                $abbrv = substr($booking['firstname'], 0, 3) . substr($booking['lastname'], 0, 3); //TODO
+                $this->abbreviations[$abbrv] = $booking['firstname'] . " " . $booking['lastname'];
+                if($booking['training'] && $booking['event']) $color = $this->improps['eventtraining'];
+                elseif($booking['training']) $color = $this->improps['training'];
+                elseif($booking['event']) $color = $this->improps['event'];
+                else $color = $this->improps['black'];
+                imagefttext($this->image, 11, 0, $x, $height + $count * $this->improps['lineheight'], $color, 
+                    $this->improps['font'], $abbrv . " " . $booking['starts_at']->format('H') . "-" . $booking['ends_at']->format('H'));
+                $count++;
+            }
+        }
+        if($count == 0 && !in_array($station, $this->optstations)) {
+            if(!$this->dayIsWanted($station, $date))
+                imagefttext($this->image, 10, 0, $x, $height, $this->improps['open'], $this->improps['font'], "Open");
+            else
+                imagefttext($this->image, 10, 0, $x, $height, $this->improps['wanted'], $this->improps['font'], "WANTED!");
+        }
+        return $count;
+    }
+
+    private function fetchData() {
+        $url = sprintf("https://vatsim-germany.org/api/booking/atc/daterange/%s/%s", $this->displaydates[0]->format("d.m.Y"), $this->displaydates[count($this->displaydates) -1]->format("d.m.Y"));
+        $curl = curl_init();
+        curl_setopt($curl, CURLOPT_URL, $url ) ;
+        curl_setopt($curl, CURLOPT_HTTPHEADER, ["X-Requested-With: XMLHttpRequest"]);
+        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
+        
+        // curl_setopt($curl, CURLOPT_VERBOSE, true);
+        // $verbose = fopen('php://temp', 'w+');
+        // curl_setopt($curl, CURLOPT_STDERR, $verbose);
+        
+        $result = curl_exec($curl);
+        
+        // if ($result === FALSE) {
+        //     printf("cUrl error (#%d): %s<br>\n", curl_errno($curl),
+        //            htmlspecialchars(curl_error($curl)));
+        // }
+        // rewind($verbose);
+        // $verboseLog = stream_get_contents($verbose);
+        // echo "Verbose information:\n<pre>", htmlspecialchars($verboseLog), "</pre>\n";
+        
+        curl_close($curl);
+        return json_decode($result, true);
+    }
+
+
+
+    public static function EveryXDaysFromStartdate($startdate, $x = 1, $numberofdates = 1) {
+        $startdate = new DateTime($startdate, new DateTimeZone('UTC'));
+        $days = [];
+        while(count($days) < $numberofdates) {
+            if(new DateTime('today', new DateTimeZone('UTC')) <= $startdate) {
+                $days[] = clone($startdate);
+            }
+            $startdate->add(new DateInterval(sprintf("P%dD", $x)));
+        }
+        return $days;
+    }
+
+    public static function EveryXWeeksFromStartdate($startdate, $x = 1, $numberofdates = 1) {
+        return self::EveryXDaysFromStartdate($startdate, $x * 7, $numberofdates);
+    }
+
+    public static function EveryWeekday($dayoftheweek, $numberofdates = 1) {
+        $startdate = new DateTime($dayoftheweek, new DateTimeZone('UTC'));
+        $days = [];
+        while(count($days) < $numberofdates) {
+            $days[] = clone($startdate);
+            $startdate->add(new DateInterval("P7D"));
+        }
+        return $days;
+    }
+
+    public static function WholeWeek($startdate = "today") {
+        $days = [];
+        $startdate = new DateTime($startdate, new DateTimeZone('UTC'));
+        for($i = 0; $i < 7; $i++) {
+            $days[] = clone($startdate);
+            $startdate->add(new DateInterval("P1D"));
+        }
+        return $days;
+    }
+
+    public static function processWantedDays(...$args) {
+        if(count($args) % 2 == 1) throw new Exception("Even number of arguments required.");
+        $r = [];
+        for($i = 0; $i < count($args); $i += 2) {
+            foreach($args[$i] as $station) {
+                if(!isset($r[$station])) $r[$station] = [];
+                foreach($args[$i + 1] as $date) {
+                    $r[$station][] = $date;
+                }
+            }
+        }
+        return $r;
+    }
+
+    private function dayIsWanted($station, $date) {
+        if(!isset($this->wantedstations[$station])) return false;
+        foreach($this->wantedstations[$station] as $target) {
+            if($target->format("Y-m-d") == $date->format("Y-m-d")) return true;
+        }
+        return false;
+    }
+}

+ 10 - 0
Tuesday.jpg.php

@@ -0,0 +1,10 @@
+<?php
+
+require_once "Schedule.php";
+require_once "config.php";
+
+$sched = new Schedule(Schedule::EveryWeekday("Tuesday", 2), $stations_berlin, $optional, $wanted, $darkmode);
+$image = $sched->createImage();
+
+header("Content-Type: image/jpg");
+imagejpeg($image);

BIN
UbuntuMono-Regular.ttf


+ 10 - 0
Woche.jpg.php

@@ -0,0 +1,10 @@
+<?php
+
+require_once "Schedule.php";
+require_once "config.php";
+
+$sched = new Schedule(Schedule::WholeWeek("today"), $stations, $optional, $wanted, $darkmode);
+$image = $sched->createImage();
+
+header("Content-Type: image/jpg");
+imagejpeg($image);

+ 10 - 0
Woche2.jpg.php

@@ -0,0 +1,10 @@
+<?php
+
+require_once "Schedule.php";
+require_once "config.php";
+
+$sched = new Schedule(Schedule::WholeWeek("+1 week"), $stations, $optional, $wanted, $darkmode);
+$image = $sched->createImage();
+
+header("Content-Type: image/jpg");
+imagejpeg($image);

+ 128 - 0
config.php

@@ -0,0 +1,128 @@
+<?php
+
+require_once 'Schedule.php';
+
+$stations = [
+    "EDWW_CTR",
+    "EDWW_A_CTR",
+    "EDWW_B_CTR",
+    "EDWW_M_CTR",
+    "EDWW_K_CTR",
+    "EDWW_F_CTR",
+    "EDMM_M_CTR",
+    "EDMM_G_CTR",
+    "EDUU_O_CTR",
+    "EDUU_E_CTR",
+    "--",
+    "EDBB_S_APP",
+    "EDBB_N_APP",
+    "EDBB_U_APP",
+    "EDBB_F_APP",
+    "EDBB_S_DEP",
+    "EDBB_N_DEP",
+    "--",
+    "EDDB_N_TWR",
+    "EDDB_S_TWR",
+    "EDDB_A_GND",
+    "EDDB_E_GND",
+    "EDDB_N_GND",
+    "EDDB_S_GND",
+    "EDDB_DEL",
+    "--",
+    "EDDP_S_APP",
+    "EDDP_F_APP",
+    "EDDP_N_APP",
+    "EDDP_N_TWR",
+    "EDDP_S_TWR",
+    "EDDP_GND",
+    "EDDP_DEL",
+    "--",
+    "EDDC_APP",
+    "EDDC_TWR",
+    "EDDC_GND",
+    "EDDC_A_GND",
+    "--",
+    "EDDE_TWR",
+    "EDDE_GND",
+    "EDDE_A_GND"
+];
+
+$stations_berlin = [
+    "EDWW_CTR",
+    "EDWW_A_CTR",
+    "EDWW_B_CTR",
+    "EDWW_M_CTR",
+    "EDWW_K_CTR",
+    "EDWW_F_CTR",
+    "EDMM_M_CTR",
+    "EDMM_G_CTR",
+    "EDUU_O_CTR",
+    "EDUU_E_CTR",
+    "--",
+    "EDBB_S_APP",
+    "EDBB_N_APP",
+    "EDBB_U_APP",
+    "EDBB_F_APP",
+    "EDBB_S_DEP",
+    "EDBB_N_DEP",
+    "--",
+    "EDDB_N_TWR",
+    "EDDB_S_TWR",
+    "EDDB_A_GND",
+    "EDDB_E_GND",
+    "EDDB_N_GND",
+    "EDDB_S_GND",
+    "EDDB_DEL",
+
+];
+
+$optional = [
+    "EDWW_CTR",
+    "EDWW_A_CTR",
+    "EDWW_M_CTR",
+    "EDWW_K_CTR",
+    "EDWW_F_CTR",
+    "EDMM_M_CTR",
+    "EDUU_O_CTR",
+    "EDUU_E_CTR",
+    "EDBB_N_APP",
+    "EDBB_F_APP",
+    "EDBB_N_DEP",
+    "EDDB_S_TWR",
+    "EDDB_E_GND",
+    "EDDB_S_GND",
+    "EDDP_F_APP",
+    "EDDP_N_APP",
+    "EDDP_S_TWR",
+    "EDDP_GND",
+    "EDDP_DEL",
+    "EDDC_APP",
+    "EDDC_GND",
+    "EDDC_A_GND",
+    "EDDE_GND",
+    "EDDE_A_GND"
+];
+
+$wantedstns = [
+    'EDWW_B_CTR', 
+    'EDMM_G_CTR', 
+    'EDBB_S_APP', 
+    'EDBB_U_APP', 
+    'EDBB_S_DEP', 
+    'EDDB_N_TWR', 
+    'EDDB_A_GND', 
+    'EDDB_DEL', 
+    'EDDB_N_GND'
+];
+
+$wanted = Schedule::processWantedDays(
+    $wantedstns, 
+    Schedule::EveryWeekday("Tuesday", 5), 
+    $wantedstns, 
+    Schedule::EveryXWeeksFromStartdate("2021-10-08", 4, 2));
+
+if(isset($_COOKIE['darkmode']) && $_COOKIE['darkmode'] == 'On') {
+    $darkmode = true;
+} else {
+    $darkmode = false;
+}

+ 34 - 0
darkmode.php

@@ -0,0 +1,34 @@
+<?php
+// Diese PHP-Seite setzt ein Cookie, das einen Darkmode aktiviert oder deaktiviert.
+
+    if(isset($_COOKIE['darkmode'])) {
+        $darkmode = $_COOKIE['darkmode'];
+    } else {
+        $darkmode = 'Off';
+    }
+
+    if(isset($_GET['darkmode']) && $_GET['darkmode'] == "Einschalten") {
+        setcookie('darkmode', 'On', time() + 20 * 365 * 24 * 60 * 60);
+        $darkmode = 'On';
+    } elseif(isset($_GET['darkmode']) && $_GET['darkmode'] == "Ausschalten") {
+        setcookie('darkmode', 'Off', 1);
+        $darkmode = 'Off';
+    }
+?>
+<!DOCTYPE html>
+<html lang="de">
+<head>
+    <meta charset="UTF-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>PHP Vatsim Scheduler: Darkmode</title>
+</head>
+<body>
+    <h1>PHP Vatsim Scheduler: Darkmode</h1>
+    <div>Darkmode ist <b><?php echo $darkmode == "On" ? "eingeschaltet" : "ausgeschaltet" ?></b></div>
+    <form action="" method="GET">
+        <input type="submit" name="darkmode" value="Einschalten">
+        <input type="submit" name="darkmode" value="Ausschalten">
+    </form>
+</body>
+</html>

+ 91 - 0
test.jpg.php

@@ -0,0 +1,91 @@
+<?php
+
+include "Schedule.php";
+
+$stations = [
+    "EDWW_CTR",
+    "EDWW_A_CTR",
+    "EDWW_B_CTR",
+    "EDWW_M_CTR",
+    "EDWW_K_CTR",
+    "EDWW_F_CTR",
+    "EDMM_M_CTR",
+    "EDMM_G_CTR",
+    "EDUU_O_CTR",
+    "EDUU_E_CTR",
+    "--",
+    "EDBB_S_APP",
+    "EDBB_N_APP",
+    "EDBB_U_APP",
+    "EDBB_F_APP",
+    "EDBB_S_DEP",
+    "EDBB_N_DEP",
+    "--",
+    "EDDB_N_TWR",
+    "EDDB_S_TWR",
+    "EDDB_A_GND",
+    "EDDB_E_GND",
+    "EDDB_N_GND",
+    "EDDB_S_GND",
+    "EDDB_DEL",
+    "--",
+    "EDDP_S_APP",
+    "EDDP_F_APP",
+    "EDDP_N_APP",
+    "EDDP_N_TWR",
+    "EDDP_S_TWR",
+    "EDDP_GND",
+    "EDDP_DEL",
+    "--",
+    "EDDC_APP",
+    "EDDC_TWR",
+    "EDDC_GND",
+    "EDDC_A_GND",
+    "--",
+    "EDDE_TWR",
+    "EDDE_GND",
+    "EDDE_A_GND"
+];
+
+$optional = [
+    "EDWW_CTR",
+    "EDWW_A_CTR",
+    "EDWW_M_CTR",
+    "EDWW_K_CTR",
+    "EDWW_F_CTR",
+    "EDMM_M_CTR",
+    "EDUU_O_CTR",
+    "EDUU_E_CTR",
+    "EDBB_N_APP",
+    "EDBB_F_APP",
+    "EDBB_N_DEP",
+    "EDDB_S_TWR",
+    "EDDB_E_GND",
+    "EDDB_S_GND",
+    "EDDP_F_APP",
+    "EDDP_N_APP",
+    "EDDP_S_TWR",
+    "EDDP_GND",
+    "EDDP_DEL",
+    "EDDC_APP",
+    "EDDC_GND",
+    "EDDC_A_GND",
+    "EDDE_GND",
+    "EDDE_A_GND"
+];
+
+$wantedstns = ['EDWW_B_CTR', 'EDMM_G_CTR', 'EDBB_S_APP', 'EDBB_U_APP', 'EDBB_S_DEP', 'EDDB_N_TWR', 'EDDB_A_GND', 'EDDB_DEL', 'EDDB_N_GND'];
+//echo "<pre>";
+
+//$sched = new Schedule(Schedule::EveryXDaysFromStartdate("26.02.2021", 4, 2), $stations);
+$wanted = Schedule::processWantedDays($wantedstns, Schedule::EveryWeekday("Tuesday", 5), $wantedstns, Schedule::EveryXWeeksFromStartdate("2021-10-08", 4, 2));
+//var_dump($wanted);
+//exit;
+$sched = new Schedule(Schedule::WholeWeek("today"), $stations, $optional, $wanted, false);
+$image = $sched->createImage();
+
+header("Content-Type: image/jpg");
+imagejpeg($image);
+
+//imagejpeg($image, __DIR__ . "/test.jpg");
+//?/> <br><img src="test.jpg">