src/Service/MeteomaticsWeatherService.php line 4920

Open in your IDE?
  1. <?php
  2. namespace App\Service;
  3. use PDO;
  4. use DateTime;
  5. use GuzzleHttp;
  6. use GuzzleHttp\Client;
  7. use GuzzleHttp\Exception;
  8. use App\Lib\ExcelGenerator;
  9. use App\Service\RedisCache;
  10. use Pimcore\Model\DataObject\Report;
  11. use GuzzleHttp\Exception\RequestException;
  12. use Symfony\Component\HttpFoundation\Response;
  13. use DateTimeZone;
  14. class MeteomaticsWeatherService
  15. {
  16.     private $apiBaseUrlOG MATEOMATICS_API_URL_OG;
  17.     private $apiBaseUrl MATEOMATICS_API_URL;
  18.     private $username;
  19.     private $password;
  20.     private $redisCache;
  21.     public function __construct(RedisCache $redisCache)
  22.     {
  23.         $this->username MATEOMATICS_API_USERNAME;
  24.         $this->password MATEOMATICS_API_PASSWORD;
  25.         $this->redisCache $redisCache;
  26.     }
  27.     /**
  28.      * Query Meteomatics API for time series data and return the parsed response
  29.      *
  30.      * @param DateTime $startDate The start date of the time series data
  31.      * @param DateTime $endDate The end date of the time series data
  32.      * @param string $resolution The time resolution of the data (e.g. PT1H for hourly data)
  33.      * @param float $lat The latitude of the location to query data for
  34.      * @param float $lon The longitude of the location to query data for
  35.      * @param int $hour The number of hours ahead to forecast (e.g. 1 for one hour ahead)
  36.      * @param string $format The format to request the data in (e.g. json)
  37.      * @return array The parsed response data
  38.      */
  39.     public function timeSeriesQueryMeteocache(DateTime $startDateDateTime $endDate$resolution$lat$lon$hour$format)
  40.     {
  41.         try {
  42.             $startDateStr $startDate->format(DateTime::ISO8601);
  43.             $endDateStr $endDate->format(DateTime::ISO8601);
  44.             $parameters = [
  45.                 'wind_speed_10m:kmh',
  46.                 'wind_dir_10m:d',
  47.                 't_2m:C',
  48.                 'precip_3h:mm',
  49.                 'weather_symbol_' $hour 'h:idx',
  50.                 'precip_type:idx',
  51.                 'sunrise:sql',
  52.                 'wind_speed_10m:kn'
  53.             ];
  54.             $parametersStr implode(','$parameters);
  55.             // Create unique Redis key
  56.             $keyParams = [
  57.                 $startDate->format(DateTime::ISO8601),
  58.                 $endDate->format(DateTime::ISO8601),
  59.                 $resolution,
  60.                 $lat,
  61.                 $lon,
  62.                 $hour,
  63.                 $format
  64.             ];
  65.             $redisKey hash('sha256'implode('_'$keyParams));
  66.             // Try to get the weather forecast data from Redis cache
  67.             $data $this->redisCache->get($redisKey);
  68.             if (!$data) {
  69.                 $url "{$this->apiBaseUrl}/{$startDateStr}--{$endDateStr}:{$resolution}/{$parametersStr}/{$lat},{$lon}/" $format "?use_decluttered=true";
  70.                 //echo $url;exit;
  71.                 $client = new Client(['verify' => false]);
  72.                 $response $client->request('GET'$url, [
  73.                     'auth' => [$this->username$this->password],
  74.                     'connect_timeout' => 2,
  75.                     'headers' => [
  76.                         'User-Agent' => 'Meteomatics PHP connector (Guzzle)'
  77.                     ]
  78.                 ]);
  79.                 $statusCode $response->getStatusCode();
  80.                 $data json_decode($response->getBody(), true);
  81.                 if ($statusCode != 200) {
  82.                     return $this->createErrorResponse($statusCode);
  83.                 }
  84.                 $parsedData = array();
  85.                 if (isset($data['data']) && $format == "json") {
  86.                     foreach ($data['data'] as $item) {
  87.                         $parameter $item["parameter"];
  88.                         $coordinates $item["coordinates"];
  89.                         $lat $coordinates[0]["lat"];
  90.                         $lon $coordinates[0]["lon"];
  91.                         $dates $coordinates[0]["dates"];
  92.                         $groupedDates = array();
  93.                         foreach ($dates as $date) {
  94.                             $dateTime = new DateTime($date["date"]);
  95.                             $dateString $dateTime->format("Y-m-d");
  96.                             if (!array_key_exists($dateString$groupedDates)) {
  97.                                 $groupedDates[$dateString] = array();
  98.                             }
  99.                             $groupedDates[$dateString][] = $date["value"];
  100.                         }
  101.                         $parsedData[$parameter] = array("coordinates" => array("lat" => $lat"lon" => $lon), "dates" => $groupedDates);
  102.                     }
  103.                 } else {
  104.                     $parsedData $data;
  105.                 }
  106.                 if ($parsedData) {
  107.                     $this->redisCache->set($redisKey$parsedData86400);
  108.                 }
  109.             } else {
  110.                 $parsedData $data;
  111.             }
  112.             return $parsedData;
  113.         } catch (Exception\RequestException $e) {
  114.             return throw new \Exception($e->getMessage());
  115.         } catch (\Exception $e) {
  116.             return throw new \Exception($e->getMessage());
  117.         }
  118.     }
  119.     public function createErrorResponse(int $http_code): Response
  120.     {
  121.         switch ($http_code) {
  122.             case 100:
  123.                 $text 'Continue';
  124.                 break;
  125.             case 101:
  126.                 $text 'Switching Protocols';
  127.                 break;
  128.             case 200:
  129.                 $text 'OK';
  130.                 break;
  131.             case 201:
  132.                 $text 'Created';
  133.                 break;
  134.             case 202:
  135.                 $text 'Accepted';
  136.                 break;
  137.             case 203:
  138.                 $text 'Non-Authoritative Information';
  139.                 break;
  140.             case 204:
  141.                 $text 'No Content';
  142.                 break;
  143.             case 205:
  144.                 $text 'Reset Content';
  145.                 break;
  146.             case 206:
  147.                 $text 'Partial Content';
  148.                 break;
  149.             case 300:
  150.                 $text 'Multiple Choices';
  151.                 break;
  152.             case 301:
  153.                 $text 'Moved Permanently';
  154.                 break;
  155.             case 302:
  156.                 $text 'Moved Temporarily';
  157.                 break;
  158.             case 303:
  159.                 $text 'See Other';
  160.                 break;
  161.             case 304:
  162.                 $text 'Not Modified';
  163.                 break;
  164.             case 305:
  165.                 $text 'Use Proxy';
  166.                 break;
  167.             case 400:
  168.                 $text 'Bad Request';
  169.                 break;
  170.             case 401:
  171.                 $text 'Unauthorized';
  172.                 break;
  173.             case 402:
  174.                 $text 'Payment Required';
  175.                 break;
  176.             case 403:
  177.                 $text 'Forbidden';
  178.                 break;
  179.             case 404:
  180.                 $text 'Not Found';
  181.                 break;
  182.             case 405:
  183.                 $text 'Method Not Allowed';
  184.                 break;
  185.             case 406:
  186.                 $text 'Not Acceptable';
  187.                 break;
  188.             case 407:
  189.                 $text 'Proxy Authentication Required';
  190.                 break;
  191.             case 408:
  192.                 $text 'Request Time-out';
  193.                 break;
  194.             case 409:
  195.                 $text 'Conflict';
  196.                 break;
  197.             case 410:
  198.                 $text 'Gone';
  199.                 break;
  200.             case 411:
  201.                 $text 'Length Required';
  202.                 break;
  203.             case 412:
  204.                 $text 'Precondition Failed';
  205.                 break;
  206.             case 413:
  207.                 $text 'Request Entities Too Large';
  208.                 break;
  209.             case 414:
  210.                 $text 'Request-URI Too Large';
  211.                 break;
  212.             case 415:
  213.                 $text 'Unsupported Media Type';
  214.                 break;
  215.             case 500:
  216.                 $text 'Internal Server Error';
  217.                 break;
  218.             case 501:
  219.                 $text 'Not Implemented';
  220.                 break;
  221.             case 502:
  222.                 $text 'Bad Gateway';
  223.                 break;
  224.             case 503:
  225.                 $text 'Service Unavailable';
  226.                 break;
  227.             case 504:
  228.                 $text 'Gateway Time-out';
  229.                 break;
  230.             case 505:
  231.                 $text 'HTTP Version not supported';
  232.                 break;
  233.             default:
  234.                 $text 'Unknown http status code';
  235.                 break;
  236.         }
  237.         $response = new Response();
  238.         $response->setContent($text);
  239.         $response->setStatusCode($http_code);
  240.         $response->headers->set('Content-Type''text/plain');
  241.         return $response;
  242.     }
  243.     /**
  244.      * Fetches the weather forecast data for a given latitude, longitude, and for selected data range
  245.      *
  246.      * @param array $coordinates An array of coordinates in the format [[lat1, long1], [lat2, long2], ...]
  247.      * @param int $days The number of days for which forecast is required
  248.      * @return array|null The weather forecast data in JSON format, or null if there was an error
  249.      */
  250.     public function getTempratureByParams(array $coordinatesstring $startDatestring $endDatestring $parametersStrint $hour 1)
  251.     {
  252.         try {
  253.             if (count($coordinates) > 1) {
  254.                 // Validate the input parameters
  255.                 foreach ($coordinates as $coordinate) {
  256.                     if (count($coordinate) < || !is_numeric($coordinate[0]) || !is_numeric($coordinate[1])) {
  257.                         throw new \InvalidArgumentException('Invalid coordinates');
  258.                     }
  259.                 }
  260.                 if (empty($startDate) || empty($endDate)) {
  261.                     throw new \InvalidArgumentException('Invalid dates');
  262.                 }
  263.                 $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  264.                 $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  265.                 // Create a Redis key for the cache
  266.                 $cacheKey sprintf('custom_noti_%s_%s_%s'md5(implode('_'array_map(function ($coordinate) {
  267.                     return implode('_'$coordinate);
  268.                 }, $coordinates))), ($startDate '-' $endDate), $parametersStr $hour);
  269.                 // Try to get the data from Redis cache
  270.                 $cachedData $this->redisCache->get($cacheKey);
  271.                 if ($cachedData !== null) {
  272.                     // Return the cached data if available
  273.                     return $cachedData;
  274.                 }
  275.                 // If cache is empty, get the data from the API
  276.                 $client = new Client(['verify' => false]);
  277.                 // $coordinateString = implode('+', array_map(fn($coords) => implode(',', $coords), $coordinates));
  278.                 $coordinateString implode('+'array_map(fn($coords) => implode(','$coords), $coordinates));
  279.                 //   p_r($coordinateString); // Debug
  280.                 $url sprintf(
  281.                     '%s/%s--%s:PT%sH/%s/%s/json?use_decluttered=true',
  282.                     $this->apiBaseUrl,
  283.                     $startDate,
  284.                     $endDate,
  285.                     $hour,
  286.                     $parametersStr,
  287.                     $coordinateString
  288.                 );
  289.             } else {
  290.                 //p_R($coordinates);
  291.                 if ($coordinates) {
  292.                     $latitude $coordinates[0][0];
  293.                     $longitude $coordinates[0][1];
  294.                 }
  295.                 // Validate the input parameters
  296.                 if (!preg_match('/^[-]?[0-9]{1,2}\.[0-9]+/'$latitude)) {
  297.                     throw new \InvalidArgumentException('Invalid latitude');
  298.                 }
  299.                 if (!preg_match('/^[-]?[0-9]{1,3}\.[0-9]+/'$longitude)) {
  300.                     throw new \InvalidArgumentException('Invalid longitude');
  301.                 }
  302.                 if (empty($startDate) || empty($endDate)) {
  303.                     throw new \InvalidArgumentException('Invalid dates');
  304.                 }
  305.                 // // $startDate = date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  306.                 // // $endDate = date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  307.                 // Create a Redis key for the cache            
  308.                 $cacheKey sprintf('custom_noti_%s_%s_%s'md5(implode('_'array_map(function ($coordinate) {
  309.                     return implode('_'$coordinate);
  310.                 }, $coordinates))), ($startDate '-' $endDate), $parametersStr $hour);
  311.                 // Try to get the data from Redis cache
  312.                 $cachedData $this->redisCache->get($cacheKey);
  313.                 if ($cachedData !== null) {
  314.                     // Return the cached data if available
  315.                     //return $cachedData;
  316.                 }
  317.                 
  318.                 // If cache is empty, get the data from the API
  319.                 $client = new Client(['verify' => false]);
  320.                 $url sprintf(
  321.                     '%s/%s--%s:PT%sH/%s/%s,%s/json?use_decluttered=true',
  322.                     $this->apiBaseUrl,
  323.                     $startDate,
  324.                     $endDate,
  325.                     $hour,
  326.                     $parametersStr,
  327.                     $latitude,
  328.                     $longitude
  329.                 );
  330.                 //p_r($url);
  331.             }
  332.             $response $client->request('GET'$url, [
  333.                 'auth' => [$this->username$this->password],
  334.             ]);
  335.             $statusCode $response->getStatusCode();
  336.             $data json_decode($response->getBody(), true);
  337.             // Set the data to Redis cache
  338.             $this->redisCache->set($cacheKey$data);
  339.             return $data;
  340.         } catch (GuzzleHttp\Exception\RequestException $e) {
  341.             return throw new \Exception($e->getMessage());
  342.         } catch (\Exception $e) {
  343.             return throw new \Exception($e->getMessage());
  344.         }
  345.     }
  346.     /**
  347.      * Fetches the weather forecast data for a given latitude, longitude, and for selected data range
  348.      *
  349.      * @param array $coordinates An array of coordinates in the format [[lat1, long1], [lat2, long2], ...]
  350.      * @param int $days The number of days for which forecast is required
  351.      * @return array|null The weather forecast data in JSON format, or null if there was an error
  352.      */
  353.     public function getForecastData(array $coordinatesstring $startDatestring $endDateint $hoursstring $model "ksancm-wrf-48"$translator)
  354.     {
  355.         try {
  356.             // Set timezone to Saudi (UTC+3)
  357.             $timezone = new \DateTimeZone('Asia/Riyadh');
  358.             if (count($coordinates) > 1) {
  359.                 // Validate the input parameters
  360.                 foreach ($coordinates as $coordinate) {
  361.                     if (count($coordinate) < || !is_numeric($coordinate[0]) || !is_numeric($coordinate[1])) {
  362.                         // throw new \InvalidArgumentException('Invalid coordinates');
  363.                         return ["success" => false"message" => $translator->trans("invalid_coordinates")];
  364.                     }
  365.                 }
  366.                 if (empty($startDate) || empty($endDate)) {
  367.                     return ["success" => false"message" => $translator->trans("invalid_dates")];
  368.                     // throw new \InvalidArgumentException('Invalid dates');
  369.                 }
  370.                 if ($hours and $hours 24) {
  371.                     throw new \InvalidArgumentException('Invalid hour');
  372.                 }
  373.                 // $startDate = date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  374.                 // $endDate = date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  375.                 // // Adjust the date range based on the hours parameter
  376.                 // if ($hours == 24) {
  377.                 //     $startDateNew = date('Y-m-d\TH:i:s.v\Z', strtotime($startDate . ' +1 day'));
  378.                 //    $endDateNew = date('Y-m-d\TH:i:s.v\Z', strtotime($endDate . ' +1 day'));
  379.                 // } elseif ($hours == 1) {
  380.                 //    $startDateNew = date('Y-m-d\TH:i:s.v\Z', strtotime($startDate . ' +1 hour'));
  381.                 //    $endDateNew = date('Y-m-d\TH:i:s.v\Z', strtotime($endDate . ' +1 hour'));
  382.                 // }
  383.                 // Convert dates to Saudi timezone
  384.                 $startDateObj = new \DateTime($startDate$timezone);
  385.                 $endDateObj = new \DateTime($endDate$timezone);
  386.                 // Subtract 3 hours from each date
  387.                 // $startDateObj->modify('-3 hours');
  388.                 // $endDateObj->modify('-3 hours');
  389.                 $startDate $startDateObj->format('Y-m-d\TH:i:s.v\Z');
  390.                 $endDate $endDateObj->format('Y-m-d\TH:i:s.v\Z');
  391.                 // Adjust date range based on the hours parameter
  392.                 $startDateNew = clone $startDateObj;
  393.                 $endDateNew = clone $endDateObj;
  394.                 if ($hours == 24) {
  395.                     $startDateNew->modify('+1 day');
  396.                     $endDateNew->modify('+1 day');
  397.                 } elseif ($hours == 1) {
  398.                     $startDateNew->modify('+1 hour');
  399.                     $endDateNew->modify('+1 hour');
  400.                 }
  401.                 $startDateNew $startDateNew->format('Y-m-d\TH:i:s.v\Z');
  402.                 $endDateNew $endDateNew->format('Y-m-d\TH:i:s.v\Z');
  403.                 // Create a Redis key for the cache
  404.                 $cacheKey sprintf('daily_forecast_%s_%s'implode('_'array_map(function ($coordinate) {
  405.                     return implode('_'$coordinate);
  406.                 }, $coordinates)), (($startDate) . '-' . ($endDate)) . $model $hours);
  407.                 // Try to get the data from Redis cache
  408.                 $cachedData $this->redisCache->get($cacheKey);
  409.                 if ($cachedData !== null) {
  410.                     // Return the cached data if available
  411.                     return $cachedData;
  412.                 }
  413.                 // If cache is empty, get the data from the API
  414.                 $client = new Client(['verify' => false]);
  415.                 $coordinateString implode('+'array_map(fn($coords) => implode(','$coords), $coordinates));
  416.                 $url1 sprintf(
  417.                     '%s/%s--%s:PT%sH/t_max_2m_%sh:C,t_min_2m_%sh:C,t_apparent_min_%sh:C,t_apparent_max_%sh:C/%s/json?model=' $model '&use_decluttered=true',
  418.                     $this->apiBaseUrl,
  419.                     $startDateNew,
  420.                     $endDateNew,
  421.                     $hours,
  422.                     $hours,
  423.                     $hours,
  424.                     $hours,
  425.                     $hours,
  426.                     $coordinateString
  427.                 );
  428.                 $url2 sprintf(
  429.                     '%s/%s--%s:PT%sH/wind_speed_mean_10m_%sh:kmh,wind_dir_mean_10m_%sh:d,prob_precip_%sh:p,precip_%sh:mm,relative_humidity_mean_2m_%sh:p,effective_cloud_cover_mean_%sh:octas,dew_point_mean_2m_%sh:C,wind_gusts_10m_%sh:kmh/%s/json?model=' $model '&use_decluttered=true',
  430.                     $this->apiBaseUrl,
  431.                     $startDateNew,
  432.                     $endDateNew,
  433.                     $hours,
  434.                     $hours,
  435.                     $hours,
  436.                     $hours,
  437.                     $hours,
  438.                     $hours,
  439.                     $hours,
  440.                     $hours,
  441.                     $hours,
  442.                     $coordinateString
  443.                 );
  444.                 $url3 sprintf(
  445.                     '%s/%s--%s:PT%sH/t_2m:C,visibility:km,wind_gusts_10m_%sh:kn,wind_speed_mean_10m_%sh:kn/%s/json?model=mix&use_decluttered=true',
  446.                     $this->apiBaseUrl,
  447.                     $startDateNew,
  448.                     $endDateNew,
  449.                     $hours,
  450.                     $hours,
  451.                     $hours,
  452.                     $coordinateString
  453.                 );
  454.                 $url4 sprintf(
  455.                     '%s/%s--%s:PT10M/t_2m:C/%s/json?model=' $model '&use_decluttered=true',
  456.                     $this->apiBaseUrl,
  457.                     $startDateNew,
  458.                     $endDateNew,
  459.                     $coordinateString
  460.                 );
  461.                 $url5 sprintf(
  462.                     '%s/%s--%s:PT3H/precip_3h:mm,prob_precip_3h:p/%s/json?model=' $model '&use_decluttered=true',
  463.                     $this->apiBaseUrl,
  464.                     $startDateNew,
  465.                     $endDateNew,
  466.                     $coordinateString
  467.                 );
  468.                 $url6 sprintf(
  469.                     '%s/%s--%s:PT6H/precip_6h:mm,prob_precip_6h:p,wind_speed_mean_100m_%sh:kmh,wind_speed_mean_200m_%sh:kmh,wind_speed_mean_300m_%sh:kmh/%s/json?model=' $model '&use_decluttered=true',
  470.                     $this->apiBaseUrl,
  471.                     $startDateNew,
  472.                     $endDateNew,
  473.                     $hours,
  474.                     $hours,
  475.                     $hours,
  476.                     $coordinateString
  477.                 );
  478.             } else {
  479.                 if ($coordinates) {
  480.                     $latitude $coordinates[0][0];
  481.                     $longitude $coordinates[0][1];
  482.                 }
  483.                 // Validate the input parameters
  484.                 if (!preg_match('/^[-]?[0-9]{1,2}\.[0-9]+/'$latitude)) {
  485.                     // throw new \InvalidArgumentException('Invalid latitude');
  486.                     return ["success" => false"message" => $translator->trans("invalid_latitude")];
  487.                 }
  488.                 if (!preg_match('/^[-]?[0-9]{1,3}\.[0-9]+/'$longitude)) {
  489.                     // throw new \InvalidArgumentException('Invalid longitude');
  490.                     return ["success" => false"message" => $translator->trans("invalid_longitude")];
  491.                 }
  492.                 if (empty($startDate) || empty($endDate)) {
  493.                     return ["success" => false"message" => $translator->trans("invalid_dates")];
  494.                 }
  495.                 if ($hours and $hours 24) {
  496.                     throw new \InvalidArgumentException('Invalid hour');
  497.                 }
  498.                 // $startDate = date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  499.                 // $endDate = date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  500.                 // // Adjust the date range based on the hours parameter
  501.                 // if ($hours == 24) {
  502.                 //     $startDateNew = date('Y-m-d\TH:i:s.v\Z', strtotime($startDate . ' +1 day'));
  503.                 //     $endDateNew = date('Y-m-d\TH:i:s.v\Z', strtotime($endDate . ' +1 day'));
  504.                 //  } elseif ($hours == 1) {
  505.                 //     $startDateNew = date('Y-m-d\TH:i:s.v\Z', strtotime($startDate . ' +1 hour'));
  506.                 //     $endDateNew = date('Y-m-d\TH:i:s.v\Z', strtotime($endDate . ' +1 hour'));
  507.                 // }
  508.                 // Convert dates to Saudi timezone
  509.                 $startDateObj = new \DateTime($startDate$timezone);
  510.                 $endDateObj = new \DateTime($endDate$timezone);
  511.                 // Subtract 3 hours from each date
  512.                 // $startDateObj->modify('-3 hours');
  513.                 // $endDateObj->modify('-3 hours');
  514.                 $startDate $startDateObj->format('Y-m-d\TH:i:s.v\Z');
  515.                 $endDate $endDateObj->format('Y-m-d\TH:i:s.v\Z');
  516.                 // Adjust date range based on the hours parameter
  517.                 $startDateNew = clone $startDateObj;
  518.                 $endDateNew = clone $endDateObj;
  519.                 if ($hours == 24) {
  520.                     $startDateNew->modify('+1 day');
  521.                     $endDateNew->modify('+1 day');
  522.                 } elseif ($hours == 1) {
  523.                     $startDateNew->modify('+1 hour');
  524.                     $endDateNew->modify('+1 hour');
  525.                 }
  526.                 $startDateNew $startDateNew->format('Y-m-d\TH:i:s.v\Z');
  527.                 $endDateNew $endDateNew->format('Y-m-d\TH:i:s.v\Z');
  528.                 // Create a Redis key for the cache            
  529.                 $cacheKey sprintf('daily_forecast_%s_%s'implode('_'array_map(function ($coordinate) {
  530.                     return implode('_'$coordinate);
  531.                 }, $coordinates)), ($startDate '-' $endDate) . $model $hours);
  532.                 // Try to get the data from Redis cache
  533.                 $cachedData $this->redisCache->get($cacheKey);
  534.                 if ($cachedData !== null) {
  535.                     // Return the cached data if available
  536.                     return $cachedData;
  537.                 }
  538.                 // If cache is empty, get the data from the API
  539.                 $client = new Client(['verify' => false]);
  540.                 $url1 sprintf(
  541.                     '%s/%s--%s:PT%sH/t_max_2m_%sh:C,t_min_2m_%sh:C,t_apparent_min_%sh:C,t_apparent_max_%sh:C/%s,%s/json?model=' $model '&use_decluttered=true',
  542.                     $this->apiBaseUrl,
  543.                     $startDateNew,
  544.                     $endDateNew,
  545.                     $hours,
  546.                     $hours,
  547.                     $hours,
  548.                     $hours,
  549.                     $hours,
  550.                     $latitude,
  551.                     $longitude
  552.                 );
  553.                 // print_r($url1);exit;
  554.                 $url2 sprintf(
  555.                     '%s/%s--%s:PT%sH/wind_speed_mean_10m_%sh:kmh,wind_dir_mean_10m_%sh:d,prob_precip_%sh:p,precip_%sh:mm,relative_humidity_mean_2m_%sh:p,effective_cloud_cover_mean_%sh:octas,dew_point_mean_2m_%sh:C,wind_gusts_10m_%sh:kmh/%s,%s/json?model=' $model '&use_decluttered=true',
  556.                     $this->apiBaseUrl,
  557.                     $startDateNew,
  558.                     $endDateNew,
  559.                     $hours,
  560.                     $hours,
  561.                     $hours,
  562.                     $hours,
  563.                     $hours,
  564.                     $hours,
  565.                     $hours,
  566.                     $hours,
  567.                     $hours,
  568.                     $latitude,
  569.                     $longitude
  570.                 );
  571.                 $url3 sprintf(
  572.                     '%s/%s--%s:PT%sH/t_2m:C,visibility:km,wind_gusts_10m_%sh:kn,wind_speed_mean_10m_%sh:kn/%s,%s/json?model=mix&use_decluttered=true',
  573.                     $this->apiBaseUrl,
  574.                     $startDateNew,
  575.                     $endDateNew,
  576.                     $hours,
  577.                     $hours,
  578.                     $hours,
  579.                     $latitude,
  580.                     $longitude
  581.                 );
  582.                 $url4 sprintf(
  583.                     '%s/%s--%s:PT10M/t_2m:C/%s,%s/json?model=' $model '&use_decluttered=true',
  584.                     $this->apiBaseUrl,
  585.                     $startDateNew,
  586.                     $endDateNew,
  587.                     $latitude,
  588.                     $longitude
  589.                 );
  590.                 $url5 sprintf(
  591.                     '%s/%s--%s:PT3H/precip_3h:mm,prob_precip_3h:p/%s,%s/json?model=' $model '&use_decluttered=true',
  592.                     $this->apiBaseUrl,
  593.                     $startDateNew,
  594.                     $endDateNew,
  595.                     $latitude,
  596.                     $longitude
  597.                 );
  598.                 $url6 sprintf(
  599.                     '%s/%s--%s:PT6H/precip_6h:mm,prob_precip_6h:p,wind_speed_mean_100m_%sh:kmh,wind_speed_mean_200m_%sh:kmh,wind_speed_mean_300m_%sh:kmh/%s,%s/json?model=' $model '&use_decluttered=true',
  600.                     $this->apiBaseUrl,
  601.                     $startDateNew,
  602.                     $endDateNew,
  603.                     $hours,
  604.                     $hours,
  605.                     $hours,
  606.                     $latitude,
  607.                     $longitude
  608.                 );
  609.             }
  610.             $response $client->request('GET'$url1, [
  611.                 'auth' => [$this->username$this->password],
  612.             ]);
  613.             $statusCode $response->getStatusCode();
  614.             $data1 json_decode($response->getBody(), true);
  615.             // Adjust the dates in the response data
  616.             $this->adjustResponseDates($data1['data'], $hours);
  617.             $response $client->request('GET'$url2, [
  618.                 'auth' => [$this->username$this->password],
  619.             ]);
  620.             $statusCode $response->getStatusCode();
  621.             $data2 json_decode($response->getBody(), true);
  622.             $this->adjustResponseDates($data2['data'], $hours);
  623.             $response $client->request('GET'$url3, [
  624.                 'auth' => [$this->username$this->password],
  625.             ]);
  626.             $statusCode $response->getStatusCode();
  627.             $data3 json_decode($response->getBody(), true);
  628.             $this->adjustResponseDates($data3['data'], $hours);
  629.             $response $client->request('GET'$url4, [
  630.                 'auth' => [$this->username$this->password],
  631.             ]);
  632.             $statusCode $response->getStatusCode();
  633.             $data4 json_decode($response->getBody(), true);
  634.             $this->adjustResponseDates($data4['data'], $hours);
  635.             $response $client->request('GET'$url5, [
  636.                 'auth' => [$this->username$this->password],
  637.             ]);
  638.             $statusCode $response->getStatusCode();
  639.             $data5 json_decode($response->getBody(), true);
  640.             $this->adjustResponseDates($data5['data'], $hours);
  641.             $response $client->request('GET'$url6, [
  642.                 'auth' => [$this->username$this->password],
  643.             ]);
  644.             $statusCode $response->getStatusCode();
  645.             $data6 json_decode($response->getBody(), true);
  646.             $this->adjustResponseDates($data6['data'], $hours);
  647.             $dataFinal $data1;
  648.             $dataFinal['data'] = array_merge($data6['data'], $data5['data'], $data4['data'], $data1['data'], $data2['data'], $data3['data']);
  649.             $weatherSymbols $this->getWeatherSymbols($coordinates$startDate$endDate'PT' $hours 'H'$hours 'h'true);
  650.             if (isset($weatherSymbols['data'][0]['parameter'])) {
  651.                 $dataFinal['symbols'] = $weatherSymbols['data'][0];
  652.             }
  653.             // Set the data to Redis cache
  654.             $this->redisCache->set($cacheKey$dataFinal);
  655.             return $dataFinal;
  656.         } catch (GuzzleHttp\Exception\RequestException $e) {
  657.             return throw new \Exception($e->getMessage());
  658.         } catch (\Exception $e) {
  659.             return throw new \Exception($e->getMessage());
  660.         }
  661.     }
  662.     /**
  663.      * Fetches the hourly weather forecast data for a given latitude, longitude, and hour
  664.      *
  665.      * @param array $coordinates An array of coordinates in the format [[lat1, long1], [lat2, long2], ...]
  666.      * @param string $hourly The hour for which forecast is required in 24-hour format
  667.      * @return array The hourly weather forecast data in JSON format
  668.      */
  669.     public function getHourlyForecastData(array $coordinatesstring $startDatestring $endDateint $hourstring $model$translator)
  670.     {
  671.         try {
  672.             if (count($coordinates) > 1) {
  673.                 // Validate the input parameters
  674.                 foreach ($coordinates as $coordinate) {
  675.                     if (count($coordinate) < || !is_numeric($coordinate[0]) || !is_numeric($coordinate[1])) {
  676.                         return ["success" => false"message" => $translator->trans("invalid_coordinates")];
  677.                     }
  678.                 }
  679.                 if (empty($startDate) || empty($endDate)) {
  680.                     // throw new \InvalidArgumentException('Invalid dates');
  681.                     return ["success" => false"message" => $translator->trans("invalid_dates")];
  682.                 }
  683.                 $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  684.                 $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  685.                 // Create a Redis key for the cache
  686.                 $cacheKey sprintf('hourly_forecast_%s_%s_%s'implode('_'array_map(function ($coordinate) {
  687.                     return implode('_'$coordinate);
  688.                 }, $coordinates)), (($startDate) . '-' . ($endDate)) . $model$hour);
  689.                 // Try to get the data from Redis cache
  690.                 $cachedData $this->redisCache->get($cacheKey);
  691.                 if ($cachedData !== null) {
  692.                     // Return the cached data if available
  693.                     return $cachedData;
  694.                 }
  695.                 // If cache is empty, get the data from the API
  696.                 $client = new Client(['verify' => false]);
  697.                 $coordinateString implode('+'array_map(fn($coords) => implode(','$coords), $coordinates));
  698.                 $url1 sprintf(
  699.                     '%s/%s--%s:PT%sH/t_max_2m_1h:C,t_min_2m_1h:C,t_apparent_min_1h:C,t_apparent_max_1h:C,wind_speed_mean_10m_1h:kn,wind_gusts_10m_1h:kn/%+%/json?model=' $model '&use_decluttered=true',
  700.                     $this->apiBaseUrl,
  701.                     $startDate,
  702.                     $endDate,
  703.                     $hour,
  704.                     $coordinateString,
  705.                     $coordinateString
  706.                 );
  707.                 $url2 sprintf(
  708.                     '%s/%s--%s:PT%sH/wind_speed_mean_10m_1h:kmh,wind_dir_mean_10m_1h:d,prob_precip_1h:p,precip_1h:mm,relative_humidity_mean_2m_1h:p,visibility:km,effective_cloud_cover_mean_1h:octas,dew_point_mean_2m_1h:C,wind_gusts_10m_1h:kmh/%+%/json?model=' $model '&use_decluttered=true',
  709.                     $this->apiBaseUrl,
  710.                     $startDate,
  711.                     $endDate,
  712.                     $hour,
  713.                     $coordinateString,
  714.                     $coordinateString
  715.                 );
  716.             } else {
  717.                 if ($coordinates) {
  718.                     $latitude $coordinates[0][0];
  719.                     $longitude $coordinates[0][1];
  720.                 }
  721.                 // Validate the input parameters
  722.                 if (!preg_match('/^[-]?[0-9]{1,2}\.[0-9]+/'$latitude)) {
  723.                     // throw new \InvalidArgumentException('Invalid latitude');
  724.                     return ["success" => false"message" => $translator->trans("invalid_latitude")];
  725.                 }
  726.                 if (!preg_match('/^[-]?[0-9]{1,3}\.[0-9]+/'$longitude)) {
  727.                     // throw new \InvalidArgumentException('Invalid longitude');
  728.                     return ["success" => false"message" => $translator->trans("invalid_longitude")];
  729.                 }
  730.                 if (empty($startDate) || empty($endDate)) {
  731.                     // throw new \InvalidArgumentException('Invalid dates');
  732.                     return ["success" => false"message" => $translator->trans("invalid_dates")];
  733.                 }
  734.                 $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  735.                 $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  736.                 // Create a Redis key for the cache            
  737.                 $cacheKey sprintf('hourly_forecast_%s_%s_%s'implode('_'array_map(function ($coordinate) {
  738.                     return implode('_'$coordinate);
  739.                 }, $coordinates)), ($startDate '-' $endDate) . $model$hour);
  740.                 // Try to get the data from Redis cache
  741.                 $cachedData $this->redisCache->get($cacheKey);
  742.                 if ($cachedData !== null) {
  743.                     // Return the cached data if available
  744.                     return $cachedData;
  745.                 }
  746.                 // If cache is empty, get the data from the API
  747.                 $client = new Client(['verify' => false]);
  748.                 $url1 sprintf(
  749.                     '%s/%s--%s:PT%sH/t_max_2m_1h:C,t_min_2m_1h:C,t_apparent_min_1h:C,t_apparent_max_1h:C,wind_speed_mean_10m_1h:kn,wind_gusts_10m_1h:kn/%s,%s/json?model=' $model '&use_decluttered=true',
  750.                     $this->apiBaseUrl,
  751.                     $startDate,
  752.                     $endDate,
  753.                     $hour,
  754.                     $latitude,
  755.                     $longitude
  756.                 );
  757.                 $url2 sprintf(
  758.                     '%s/%s--%s:PT%sH/wind_speed_mean_10m_1h:kmh,wind_dir_mean_10m_1h:d,prob_precip_1h:p,precip_1h:mm,relative_humidity_mean_2m_1h:p,visibility:km,effective_cloud_cover_mean_1h:octas,dew_point_mean_2m_1h:C,wind_gusts_10m_1h:kmh/%s,%s/json?model=' $model '&use_decluttered=true',
  759.                     $this->apiBaseUrl,
  760.                     $startDate,
  761.                     $endDate,
  762.                     $hour,
  763.                     $latitude,
  764.                     $longitude
  765.                 );
  766.             }
  767.             $response $client->request('GET'$url1, [
  768.                 'auth' => [$this->username$this->password],
  769.             ]);
  770.             $statusCode $response->getStatusCode();
  771.             $data1 json_decode($response->getBody(), true);
  772.             $response $client->request('GET'$url2, [
  773.                 'auth' => [$this->username$this->password],
  774.             ]);
  775.             $statusCode $response->getStatusCode();
  776.             $data2 json_decode($response->getBody(), true);
  777.             $data3 $data1;
  778.             $data3['data'] = array_merge($data1['data'], $data2['data']);
  779.             $weatherSymbols $this->getWeatherSymbols($coordinates$startDate$endDate'PT1H''1h');
  780.             if (isset($weatherSymbols['data'][0]['parameter'])) {
  781.                 $data3['symbols'] = $weatherSymbols['data'][0];
  782.             }
  783.             // Set the data to Redis cache
  784.             $this->redisCache->set($cacheKey$data3);
  785.             return $data3;
  786.         } catch (GuzzleHttp\Exception\RequestException $e) {
  787.             return throw new \Exception($e->getMessage());
  788.         } catch (\Exception $e) {
  789.             return throw new \Exception($e->getMessage());
  790.         }
  791.     }
  792.     /**
  793.      * Fetches the hourly weather forecast data for a given latitude, longitude, and hour
  794.      *
  795.      * @param array $coordinates An array of coordinates in the format [[lat1, long1], [lat2, long2], ...]
  796.      * @param string $hourly The hour for which forecast is required in 24-hour format
  797.      * @return array The hourly weather forecast data in JSON format
  798.      */
  799.     public function getForecastDataHistoryHourly(array $coordinatesstring $startDatestring $endDate$translatorint $hour 1)
  800.     {
  801.         try {
  802.             if (count($coordinates) > 1) {
  803.                 // Validate the input parameters
  804.                 foreach ($coordinates as $coordinate) {
  805.                     if (count($coordinate) < || !is_numeric($coordinate[0]) || !is_numeric($coordinate[1])) {
  806.                         // throw new \InvalidArgumentException('Invalid coordinates');
  807.                         return ["success" => false"message" => $translator->trans("invalid_coordinates")];
  808.                     }
  809.                 }
  810.                 if (empty($startDate) || empty($endDate)) {
  811.                     // throw new \InvalidArgumentException('Invalid dates');
  812.                     return ["success" => false"message" => $translator->trans("invalid_dates")];
  813.                 }
  814.                 $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  815.                 $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  816.                 // Create a Redis key for the cache
  817.                 $cacheKey sprintf('historical_hourly_forecast_%s_%s_%s'implode('_'array_map(function ($coordinate) {
  818.                     return implode('_'$coordinate);
  819.                 }, $coordinates)), (($startDate) . '-' . ($endDate)), $hour);
  820.                 // Try to get the data from Redis cache
  821.                 $cachedData $this->redisCache->get($cacheKey);
  822.                 if ($cachedData !== null) {
  823.                     // Return the cached data if available
  824.                     return $cachedData;
  825.                 }
  826.                 // If cache is empty, get the data from the API
  827.                 $client = new Client(['verify' => false]);
  828.                 $coordinateString implode('+'array_map(fn($coords) => implode(','$coords), $coordinates));
  829.                 $url1 sprintf(
  830.                     '%s/%s--%s:PT%sH/sfc_pressure:hPa,msl_pressure:hPa,dew_point_1000hPa:C,relative_humidity_2m:p,t_max_2m_1h:C,t_min_2m_1h:C,wind_speed_10m:kmh,wind_dir_10m:d,precip_1h:mm/%s+%s/json?use_decluttered=true',
  831.                     $this->apiBaseUrl,
  832.                     $startDate,
  833.                     $endDate,
  834.                     $hour,
  835.                     $coordinateString,
  836.                     $coordinateString
  837.                 );
  838.                 $url2 sprintf(
  839.                     '%s/%s--%s:PT%sH/effective_cloud_cover:octas,dew_point_2m:C,wind_speed_mean_10m_1h:ms,wind_speed_10m:kn/%s+%s/json?use_decluttered=true',
  840.                     $this->apiBaseUrl,
  841.                     $startDate,
  842.                     $endDate,
  843.                     $hour,
  844.                     $coordinateString,
  845.                     $coordinateString
  846.                 );
  847.             } else {
  848.                 if ($coordinates) {
  849.                     $latitude $coordinates[0][0];
  850.                     $longitude $coordinates[0][1];
  851.                 }
  852.                 // Validate the input parameters
  853.                 if (!preg_match('/^[-]?[0-9]{1,2}\.[0-9]+/'$latitude)) {
  854.                     // throw new \InvalidArgumentException('Invalid latitude');
  855.                     return ["success" => false"message" => $translator->trans("invalid_latitude")];
  856.                 }
  857.                 if (!preg_match('/^[-]?[0-9]{1,3}\.[0-9]+/'$longitude)) {
  858.                     // throw new \InvalidArgumentException('Invalid longitude');
  859.                     return ["success" => false"message" => $translator->trans("invalid_longitude")];
  860.                 }
  861.                 if (empty($startDate) || empty($endDate)) {
  862.                     // throw new \InvalidArgumentException('Invalid dates');
  863.                     return ["success" => false"message" => $translator->trans("invalid_dates")];
  864.                 }
  865.                 // 2023-04-06T00:00:00.000Z--2023-04-07T00:00:00.000Z
  866.                 $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  867.                 $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  868.                 // Create a Redis key for the cache            
  869.                 $cacheKey sprintf('historical_hourly_forecast_%s_%s_%s'implode('_'array_map(function ($coordinate) {
  870.                     return implode('_'$coordinate);
  871.                 }, $coordinates)), (($startDate) . '-' . ($endDate)), $hour);
  872.                 // Try to get the data from Redis cache
  873.                 $cachedData $this->redisCache->get($cacheKey);
  874.                 if ($cachedData !== null) {
  875.                     // Return the cached data if available
  876.                     return $cachedData;
  877.                 }
  878.                 // If cache is empty, get the data from the API
  879.                 $client = new Client(['verify' => false]);
  880.                 $url1 sprintf(
  881.                     '%s/%s--%s:PT%sH/sfc_pressure:hPa,msl_pressure:hPa,dew_point_1000hPa:C,relative_humidity_2m:p,t_max_2m_1h:C,t_min_2m_1h:C,wind_speed_10m:kmh,wind_dir_10m:d,precip_1h:mm/%s,%s/json?use_decluttered=true',
  882.                     $this->apiBaseUrl,
  883.                     $startDate,
  884.                     $endDate,
  885.                     $hour,
  886.                     $latitude,
  887.                     $longitude
  888.                 );
  889.                 $url2 sprintf(
  890.                     '%s/%s--%s:PT%sH/sfc_pressure:hPa,msl_pressure:hPa,dew_point_1000hPa:C,relative_humidity_2m:p,t_max_2m_1h:C,t_min_2m_1h:C,wind_speed_10m:kmh,wind_dir_10m:d,precip_1h:mm,wind_speed_10m:kn/%s,%s/json?use_decluttered=true',
  891.                     $this->apiBaseUrl,
  892.                     $startDate,
  893.                     $endDate,
  894.                     $hour,
  895.                     $latitude,
  896.                     $longitude
  897.                 );
  898.                 //echo $url;exit;
  899.             }
  900.             $response $client->request('GET'$url1, [
  901.                 'auth' => [$this->username$this->password],
  902.             ]);
  903.             $statusCode $response->getStatusCode();
  904.             $data1 json_decode($response->getBody(), true);
  905.             $response $client->request('GET'$url2, [
  906.                 'auth' => [$this->username$this->password],
  907.             ]);
  908.             $statusCode $response->getStatusCode();
  909.             $data2 json_decode($response->getBody(), true);
  910.             array_push($data1['data'], $data2['data']);
  911.             $weatherSymbols $this->getWeatherSymbols($coordinates$startDate$endDate'PT1H'$hour 'h');
  912.             if (isset($weatherSymbols['data'][0]['parameter'])) {
  913.                 $data1['symbols'] = $weatherSymbols['data'][0];
  914.             }
  915.             // Set the data to Redis cache
  916.             $this->redisCache->set($cacheKey$data1);
  917.             return $data1;
  918.         } catch (GuzzleHttp\Exception\RequestException $e) {
  919.             //p_r($e->getMessage());exit;
  920.             return throw new \Exception($e->getMessage());
  921.         } catch (\Exception $e) {
  922.             //p_r($e->getMessage());exit;
  923.             return throw new \Exception($e->getMessage());
  924.         }
  925.     }
  926.     /**
  927.      * Compare weather based on mateomatics models
  928.      *
  929.      * @param array $coordinates An array of coordinates in the format [[lat1, long1], [lat2, long2], ...]
  930.      * @param string $hourly The hour for which forecast is required in 24-hour format
  931.      * @return array The hourly weather forecast data in JSON format
  932.      */
  933.     public function getCompareParameters(array $coordinatesstring $startDatestring $endDatestring $modelstring $tempParamstring $timeDuration "P1H"$translator)
  934.     {
  935.         try {
  936.             if (count($coordinates) > 1) {
  937.                 // Validate the input parameters
  938.                 foreach ($coordinates as $coordinate) {
  939.                     if (count($coordinate) < || !is_numeric($coordinate[0]) || !is_numeric($coordinate[1])) {
  940.                         // throw new \InvalidArgumentException('Invalid coordinates');
  941.                         return ["success" => false"message" => $translator->trans("invalid_coordinates")];
  942.                     }
  943.                 }
  944.                 if (empty($startDate) || empty($endDate)) {
  945.                     // throw new \InvalidArgumentException('Invalid dates');
  946.                     return ["success" => false"message" => $translator->trans("invalid_dates")];
  947.                 }
  948.                 $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  949.                 $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  950.                 // Create a Redis key for the cache
  951.                 $cacheKey sprintf('compare_model_%s_%s_' $model '_' $tempParamimplode('_'array_map(function ($coordinate) {
  952.                     return implode('_'$coordinate);
  953.                 }, $coordinates)), (($startDate) . '-' . ($endDate)) . $timeDuration);
  954.                 // Try to get the data from Redis cache
  955.                 $cachedData $this->redisCache->get($cacheKey);
  956.                 if ($cachedData !== null) {
  957.                     // Return the cached data if available
  958.                     return $cachedData;
  959.                 }
  960.                 // If cache is empty, get the data from the API
  961.                 $client = new Client(['verify' => false]);
  962.                 $coordinateString implode('+'array_map(fn($coords) => implode(','$coords), $coordinates));
  963.                 $url sprintf(
  964.                     '%s/%s--%s:%s/%s/%+%/json?model=' $model '&use_decluttered=true',
  965.                     $this->apiBaseUrl,
  966.                     $startDate,
  967.                     $endDate,
  968.                     $timeDuration,
  969.                     $tempParam,
  970.                     $coordinateString,
  971.                     $coordinateString
  972.                 );
  973.             } else {
  974.                 if ($coordinates) {
  975.                     $latitude $coordinates[0][0];
  976.                     $longitude $coordinates[0][1];
  977.                 }
  978.                 // Validate the input parameters
  979.                 if (!preg_match('/^[-]?[0-9]{1,2}\.[0-9]+/'$latitude)) {
  980.                     // throw new \InvalidArgumentException('Invalid latitude');
  981.                     return ["success" => true"message" => $translator->trans("invalid_latitude")];
  982.                 }
  983.                 if (!preg_match('/^[-]?[0-9]{1,3}\.[0-9]+/'$longitude)) {
  984.                     // throw new \InvalidArgumentException('Invalid longitude');
  985.                     return ["success" => true"message" => $translator->trans("invalid_longitude")];
  986.                 }
  987.                 if (empty($startDate) || empty($endDate)) {
  988.                     // throw new \InvalidArgumentException('Invalid dates');
  989.                     return ["success" => true"message" => $translator->trans("invalid_dates")];
  990.                 }
  991.                 $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  992.                 $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  993.                 // Create a Redis key for the cache            
  994.                 $cacheKey sprintf('compare_model_%s_%s_' $model '_' $tempParamimplode('_'array_map(function ($coordinate) {
  995.                     return implode('_'$coordinate);
  996.                 }, $coordinates)), (($startDate) . '-' . ($endDate)) . $timeDuration);
  997.                 // Try to get the data from Redis cache
  998.                 $cachedData $this->redisCache->get($cacheKey);
  999.                 if ($cachedData !== null) {
  1000.                     // Return the cached data if available
  1001.                     return $cachedData;
  1002.                 }
  1003.                 // If cache is empty, get the data from the API
  1004.                 $client = new Client(['verify' => false]);
  1005.                 $url sprintf(
  1006.                     '%s/%s--%s:%s/%s/%s,%s/json?model=' $model '&use_decluttered=true',
  1007.                     $this->apiBaseUrl,
  1008.                     $startDate,
  1009.                     $endDate,
  1010.                     $timeDuration,
  1011.                     $tempParam,
  1012.                     $latitude,
  1013.                     $longitude
  1014.                 );
  1015.             }
  1016.             $response $client->request('GET'$url, [
  1017.                 'auth' => [$this->username$this->password],
  1018.             ]);
  1019.             $statusCode $response->getStatusCode();
  1020.             $data json_decode($response->getBody(), true);
  1021.             // Set the data to Redis cache
  1022.             $this->redisCache->set($cacheKey$data);
  1023.             return $data;
  1024.         } catch (GuzzleHttp\Exception\RequestException $e) {
  1025.             return throw new \Exception($e->getMessage());
  1026.         } catch (\Exception $e) {
  1027.             return throw new \Exception($e->getMessage());
  1028.         }
  1029.     }
  1030.     /**
  1031.      * Get forecast data in daily dashboard display for multiple locations
  1032.      *
  1033.      * @param array $coordinates An array of coordinates in the format [[lat1, long1], [lat2, long2], ...]
  1034.      * @param int $days The number of days for which forecast is required
  1035.      * @return array The model forecast data
  1036.      */
  1037.     public function getDashboardDailyForecast(array $coordinatesstring $startDatestring $endDate)
  1038.     {
  1039.         try {
  1040.             if (count($coordinates) > 1) {
  1041.                 // Validate the input parameters
  1042.                 foreach ($coordinates as $coordinate) {
  1043.                     if (count($coordinate) < || !is_numeric($coordinate[0]) || !is_numeric($coordinate[1])) {
  1044.                         throw new \InvalidArgumentException('Invalid coordinates');
  1045.                     }
  1046.                 }
  1047.                 if (empty($startDate) || empty($endDate)) {
  1048.                     throw new \InvalidArgumentException('Invalid dates');
  1049.                 }
  1050.                 $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  1051.                 $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  1052.                 // Create a Redis key for the cache
  1053.                 $cacheKey sprintf('dashboard_%s_%s'implode('_'array_map(function ($coordinate) {
  1054.                     return implode('_'$coordinate);
  1055.                 }, $coordinates)), (($startDate) . '-' . ($endDate)));
  1056.                 // Try to get the data from Redis cache
  1057.                 $cachedData $this->redisCache->get($cacheKey);
  1058.                 if ($cachedData !== null) {
  1059.                     // Return the cached data if available
  1060.                     return $cachedData;
  1061.                 }
  1062.                 // If cache is empty, get the data from the API
  1063.                 $client = new Client(['verify' => false]);
  1064.                 $coordinateString implode('+'array_map(fn($coords) => implode(','$coords), $coordinates));
  1065.                 $url sprintf(
  1066.                     '%s/%s--%s:P%sD/t_max_2m_24h:C,t_apparent:C,wind_speed_10m:kmh,wind_dir_mean_10m_24h:d,prob_precip_24h:p,precip_24h:mm,sunrise:sql,visibility:km,wind_speed_10m:kn/%+%/json?use_decluttered=true',
  1067.                     $this->apiBaseUrl,
  1068.                     $endDate,
  1069.                     $startDate,
  1070.                     1,
  1071.                     $coordinateString,
  1072.                     $coordinateString
  1073.                 );
  1074.             } else {
  1075.                 if ($coordinates) {
  1076.                     $latitude $coordinates[0][0];
  1077.                     $longitude $coordinates[0][1];
  1078.                 }
  1079.                 // Validate the input parameters
  1080.                 if (!preg_match('/^[-]?[0-9]{1,2}\.[0-9]+/'$latitude)) {
  1081.                     throw new \InvalidArgumentException('Invalid latitude');
  1082.                 }
  1083.                 if (!preg_match('/^[-]?[0-9]{1,3}\.[0-9]+/'$longitude)) {
  1084.                     throw new \InvalidArgumentException('Invalid longitude');
  1085.                 }
  1086.                 if (empty($startDate) || empty($endDate)) {
  1087.                     throw new \InvalidArgumentException('Invalid dates');
  1088.                 }
  1089.                 $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  1090.                 $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  1091.                 // Create a Redis key for the cache            
  1092.                 $cacheKey sprintf('dashboard_%s_%s'implode('_'array_map(function ($coordinate) {
  1093.                     return implode('_'$coordinate);
  1094.                 }, $coordinates)), (($startDate) . '-' . ($endDate)));
  1095.                 // Try to get the data from Redis cache
  1096.                 $cachedData $this->redisCache->get($cacheKey);
  1097.                 if ($cachedData !== null) {
  1098.                     // Return the cached data if available
  1099.                     return $cachedData;
  1100.                 }
  1101.                 // If cache is empty, get the data from the API
  1102.                 $client = new Client(['verify' => false]);
  1103.                 $url sprintf(
  1104.                     '%s/%s--%s:P%sD/t_max_2m_24h:C,t_apparent:C,wind_speed_10m:kmh,wind_dir_mean_10m_24h:d,prob_precip_24h:p,precip_24h:mm,sunrise:sql,visibility:km,wind_speed_10m:kn/%s,%s/json?use_decluttered=true',
  1105.                     $this->apiBaseUrl,
  1106.                     $endDate,
  1107.                     $startDate,
  1108.                     1,
  1109.                     $latitude,
  1110.                     $longitude
  1111.                 );
  1112.             }
  1113.             $response $client->request('GET'$url, [
  1114.                 'auth' => [$this->username$this->password],
  1115.             ]);
  1116.             $statusCode $response->getStatusCode();
  1117.             $data json_decode($response->getBody(), true);
  1118.             // Set the data to Redis cache
  1119.             $this->redisCache->set($cacheKey$data);
  1120.             return $data;
  1121.         } catch (GuzzleHttp\Exception\RequestException $e) {
  1122.             return throw new \Exception($e->getMessage());
  1123.         } catch (\Exception $e) {
  1124.             return throw new \Exception($e->getMessage());
  1125.         }
  1126.     }
  1127.     /**
  1128.      * Get forecast data for the provided coordinates
  1129.      *
  1130.      * @param array $coordinates An array of coordinates in the format [[lat1, long1], [lat2, long2], ...]
  1131.      * @param string $timestamp The timestamp for which forecast is required in the format "YYYY-MM-DDTHHZ"
  1132.      * @param string $duration The duration for which forecast is required in ISO 8601 format "P1D" for 1 day, "PT1H" for 1 hour
  1133.      * @param string $parameters The weather parameters to fetch separated by comma, e.g. "t_2m:C,sfc_pressure:hPa,wind_speed_10m:ms"
  1134.      * @param string $aggregations The aggregations to apply to the fetched parameters separated by comma, e.g. "mean,sum"
  1135.      * @param string $format The format in which to receive the data, either "json" or "xml"
  1136.      *
  1137.      * @return array The forecast data for the provided coordinates
  1138.      */
  1139.     public function getForecastForCoordinates(
  1140.         array $coordinates,
  1141.         string $timestamp,
  1142.         string $duration,
  1143.         string $parameters,
  1144.         string $aggregations,
  1145.         string $format
  1146.     ) {
  1147.         try {
  1148.             // Validate the input parameters
  1149.             foreach ($coordinates as $coordinate) {
  1150.                 if (!is_array($coordinate) || count($coordinate) !== || !is_numeric($coordinate[0]) || !is_numeric($coordinate[1])) {
  1151.                     throw new \InvalidArgumentException('Invalid coordinates');
  1152.                 }
  1153.             }
  1154.             if (!preg_match('/^\d{4}-\d{2}-\d{2}T\d{2}Z$/'$timestamp)) {
  1155.                 throw new \InvalidArgumentException('Invalid timestamp');
  1156.             }
  1157.             if (!in_array($duration, ['PT1H''P1D'])) {
  1158.                 throw new \InvalidArgumentException('Invalid duration');
  1159.             }
  1160.             if (!is_string($parameters) || empty($parameters)) {
  1161.                 throw new \InvalidArgumentException('Invalid parameters');
  1162.             }
  1163.             if (!is_string($aggregations) || empty($aggregations)) {
  1164.                 throw new \InvalidArgumentException('Invalid aggregations');
  1165.             }
  1166.             if (!in_array($format, ['json''xml'])) {
  1167.                 throw new \InvalidArgumentException('Invalid format');
  1168.             }
  1169.             // Convert the coordinates array into a string
  1170.             $coordinatesString implode('_'array_map(function ($coordinate) {
  1171.                 return implode(','$coordinate);
  1172.             }, $coordinates));
  1173.             // Build the URL for the API call
  1174.             $url sprintf(
  1175.                 '%s/%s/%s/%s/%s/%s/%s.%s?use_decluttered=true',
  1176.                 $this->apiBaseUrl,
  1177.                 $timestamp,
  1178.                 $duration,
  1179.                 $parameters,
  1180.                 $coordinatesString,
  1181.                 $aggregations,
  1182.                 $format
  1183.             );
  1184.             // Make the API call
  1185.             $client = new Client(['verify' => false]);
  1186.             $response $client->request('GET'$url, [
  1187.                 'auth' => [$this->username$this->password],
  1188.             ]);
  1189.             $statusCode $response->getStatusCode();
  1190.             $data json_decode($response->getBody(), true);
  1191.             return $data;
  1192.         } catch (GuzzleHttp\Exception\RequestException $e) {
  1193.             return throw new \Exception($e->getMessage());
  1194.         } catch (\Exception $e) {
  1195.             return throw new \Exception($e->getMessage());
  1196.         }
  1197.     }
  1198.     /**
  1199.      * Get weather symbols
  1200.      *
  1201.      * @param array $coordinates An array of coordinates in the format [[lat1, long1], [lat2, long2], ...]
  1202.      * @param int $days The number of days for which forecast is required
  1203.      * @return array The model forecast data
  1204.      */
  1205.     public function getWeatherSymbols(array $coordinatesstring $startDatestring $endDatestring $weatherFirstParam 'PT1H'string $weatherSecondParam '1h'$adjustDate false)
  1206.     {
  1207.         try {
  1208.             if ($coordinates) {
  1209.                 $latitude $coordinates[0][0];
  1210.                 $longitude $coordinates[0][1];
  1211.             }
  1212.             // Validate the input parameters
  1213.             if (!preg_match('/^[-]?[0-9]{1,2}\.[0-9]+/'$latitude)) {
  1214.                 throw new \InvalidArgumentException('Invalid latitude');
  1215.             }
  1216.             if (!preg_match('/^[-]?[0-9]{1,3}\.[0-9]+/'$longitude)) {
  1217.                 throw new \InvalidArgumentException('Invalid longitude');
  1218.             }
  1219.             if (empty($startDate) || empty($endDate)) {
  1220.                 throw new \InvalidArgumentException('Invalid dates');
  1221.             }
  1222.             $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  1223.             $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  1224.             if ($adjustDate) {
  1225.                 // Adjust the date range based on the hours parameter
  1226.                 if ($weatherSecondParam == '24h') {
  1227.                     $startDate date('Y-m-d\TH:i:s.v\Z'strtotime($startDate ' +1 day'));
  1228.                     $endDate date('Y-m-d\TH:i:s.v\Z'strtotime($endDate ' +1 day'));
  1229.                 } elseif ($weatherSecondParam == '1h') {
  1230.                     $startDate date('Y-m-d\TH:i:s.v\Z'strtotime($startDate ' +1 hour'));
  1231.                     $endDate date('Y-m-d\TH:i:s.v\Z'strtotime($endDate ' +1 hour'));
  1232.                 } elseif ($weatherSecondParam == '12h') {
  1233.                     $startDate date('Y-m-d\TH:i:s.v\Z'strtotime($startDate ' +12 hour'));
  1234.                     $endDate date('Y-m-d\TH:i:s.v\Z'strtotime($endDate ' +12 hour'));
  1235.                 }
  1236.             }
  1237.             // Create a Redis key for the cache            
  1238.             $cacheKey sprintf('get_symbols_%s_%s'implode('_'array_map(function ($coordinate) {
  1239.                 return implode('_'$coordinate);
  1240.             }, $coordinates)), (($startDate) . '-' . ($endDate)) . $weatherFirstParam);
  1241.             // Try to get the data from Redis cache
  1242.             $cachedData $this->redisCache->get($cacheKey);
  1243.             if ($cachedData !== null) {
  1244.                 // Return the cached data if available
  1245.                 return $cachedData;
  1246.             }
  1247.             // If cache is empty, get the data from the API
  1248.             $client = new Client(['verify' => false]);
  1249.             $url sprintf(
  1250.                 '%s/%s--%s:%s/weather_symbol_%s:idx/%s,%s/json?use_decluttered=true',
  1251.                 $this->apiBaseUrl,
  1252.                 $startDate,
  1253.                 $endDate,
  1254.                 $weatherFirstParam,
  1255.                 $weatherSecondParam,
  1256.                 $latitude,
  1257.                 $longitude
  1258.             );
  1259.             $response $client->request('GET'$url, [
  1260.                 'auth' => [$this->username$this->password],
  1261.             ]);
  1262.             $statusCode $response->getStatusCode();
  1263.             $data json_decode($response->getBody(), true);
  1264.             if ($adjustDate) {
  1265.                 // Define your adjustment ('-1 day' or '-1 hour' or '-12 hour')
  1266.                 if ($weatherSecondParam == '24h') {
  1267.                     $timeAdjustment 24;
  1268.                 } elseif ($weatherSecondParam == '1h') {
  1269.                     $timeAdjustment 1;
  1270.                 } elseif ($weatherSecondParam == '12h') {
  1271.                     $timeAdjustment 12;
  1272.                 }
  1273.                 // Adjust the dates in the response data
  1274.                 $this->adjustResponseDates($data['data'], $timeAdjustment);
  1275.             }
  1276.             // Set the data to Redis cache
  1277.             $this->redisCache->set($cacheKey$data);
  1278.             return $data;
  1279.         } catch (GuzzleHttp\Exception\RequestException $e) {
  1280.             return throw new \Exception($e->getMessage());
  1281.         } catch (\Exception $e) {
  1282.             return throw new \Exception($e->getMessage());
  1283.         }
  1284.     }
  1285.     /**
  1286.      * Weather Map
  1287.      *
  1288.      * @param string $version The version of the API (e.g., '1.3.0')
  1289.      * @param string $request The type of request (e.g., 'GetMap')
  1290.      * @param string $layers The layers to include in the map
  1291.      * @param string $crs The coordinate reference system (e.g., 'EPSG:3857')
  1292.      * @param string $bBox The bounding box in the format 'minX,minY,maxX,maxY'
  1293.      * @param string $format The format of the map image (e.g., 'image/png')
  1294.      * @param int $width The width of the map image
  1295.      * @param int $height The height of the map image
  1296.      * @param bool $tiled Whether to use tiled rendering (default: true)
  1297.      * @return array The model forecast data
  1298.      * @throws \InvalidArgumentException If any parameter has an invalid data type
  1299.      */
  1300.     public function getWeatherMap(
  1301.         string $version,
  1302.         string $request,
  1303.         string $layers,
  1304.         string $crs,
  1305.         string $bBox,
  1306.         string $format,
  1307.         int $width,
  1308.         int $height,
  1309.         bool $tiled true
  1310.     ) {
  1311.         // Validate data types of input parameters
  1312.         if (!is_string($version)) {
  1313.             throw new \InvalidArgumentException('Invalid data type for $version. Expected string.');
  1314.         }
  1315.         if (!is_string($request)) {
  1316.             throw new \InvalidArgumentException('Invalid data type for $request. Expected string.');
  1317.         }
  1318.         if (!is_string($layers)) {
  1319.             throw new \InvalidArgumentException('Invalid data type for $layers. Expected string.');
  1320.         }
  1321.         if (!is_string($crs)) {
  1322.             throw new \InvalidArgumentException('Invalid data type for $crs. Expected string.');
  1323.         }
  1324.         if (!is_string($bBox)) {
  1325.             throw new \InvalidArgumentException('Invalid data type for $bBox. Expected string.');
  1326.         }
  1327.         if (!is_string($format)) {
  1328.             throw new \InvalidArgumentException('Invalid data type for $format. Expected string.');
  1329.         }
  1330.         if (!is_int($width)) {
  1331.             throw new \InvalidArgumentException('Invalid data type for $width. Expected int.');
  1332.         }
  1333.         if (!is_int($height)) {
  1334.             throw new \InvalidArgumentException('Invalid data type for $height. Expected int.');
  1335.         }
  1336.         if (!is_bool($tiled)) {
  1337.             throw new \InvalidArgumentException('Invalid data type for $tiled. Expected bool.');
  1338.         }
  1339.         try {
  1340.             // If cache is empty, get the data from the API
  1341.             $client = new Client(['verify' => false]);
  1342.             $url sprintf(
  1343.                 '%s/wms?VERSION=%s&REQUEST=%s&LAYERS=%s&CRS=%s&BBOX=%s&FORMAT=%s&WIDTH=%s&HEIGHT=%s&TILED=%s&use_decluttered=true',
  1344.                 $this->apiBaseUrl,
  1345.                 $version,
  1346.                 $request,
  1347.                 $layers,
  1348.                 $crs,
  1349.                 $bBox,
  1350.                 $format,
  1351.                 $width,
  1352.                 $height,
  1353.                 $tiled
  1354.             );
  1355.             $response $client->request('GET'$url, [
  1356.                 'auth' => [$this->username$this->password],
  1357.             ]);
  1358.             return $response->getBody();
  1359.         } catch (GuzzleHttp\Exception\RequestException $e) {
  1360.             return throw new \Exception($e->getMessage());
  1361.         } catch (\Exception $e) {
  1362.             return throw new \Exception($e->getMessage());
  1363.         }
  1364.     }
  1365.     /**
  1366.      * Weather Warnings
  1367.      *
  1368.      * @param array $coordinates An array of coordinates in the format [[lat1, long1], [lat2, long2], ...]
  1369.      * @param string $startDate The start date for the forecast
  1370.      * @param string $endDate The end date for the forecast
  1371.      * @param string $duration The duration for the forecast (default: '24')
  1372.      * @param string $warningParam The type of weather warning parameter (default: 'frost_warning')
  1373.      * @param string $format The format of the forecast data (default: 'json')
  1374.      * @return array The model forecast data
  1375.      * @throws \InvalidArgumentException If any of the parameters are invalid or missing
  1376.      */
  1377.     public function getWeatherWarnings(array $coordinatesstring $startDatestring $endDatestring $duration '24'string $warningParam 'frost_warning'string $format "json"$translator)
  1378.     {
  1379.         try {
  1380.             // Validate the input parameters
  1381.             if (empty($coordinates)) {
  1382.                 // throw new \InvalidArgumentException('Invalid coordinates');
  1383.                 return ["success" => false"message" => $translator->trans("invalid_coordinates")];
  1384.             }
  1385.             if (!is_array($coordinates) || count($coordinates) < 1) {
  1386.                 // throw new \InvalidArgumentException('Coordinates should be a non-empty array');
  1387.                 return ["success" => false"message" => $translator->trans("coordinates_should_be_a_non_empty_array")];
  1388.             }
  1389.             $latitude $coordinates[0][0] ?? null;
  1390.             $longitude $coordinates[0][1] ?? null;
  1391.             if (!is_numeric($latitude) || $latitude < -90 || $latitude 90) {
  1392.                 // throw new \InvalidArgumentException('Invalid latitude');
  1393.                 return ["success" => false"message" => $translator->trans("invalid_latitude")];
  1394.             }
  1395.             if (!is_numeric($longitude) || $longitude < -180 || $longitude 180) {
  1396.                 // throw new \InvalidArgumentException('Invalid longitude');
  1397.                 return ["success" => false"message" => $translator->trans("invalid_longitude")];
  1398.             }
  1399.             if (empty($startDate) || empty($endDate)) {
  1400.                 // throw new \InvalidArgumentException('Invalid dates');
  1401.                 return ["success" => false"message" => $translator->trans("invalid_dates")];
  1402.             }
  1403.             if (!is_numeric($duration)) {
  1404.                 // throw new \InvalidArgumentException('Duration should be numeric');
  1405.                 return ["success" => false"message" => $translator->trans("duration_should_be _numeric")];
  1406.             }
  1407.             $weatherWarning = new \Pimcore\Model\DataObject\MMWarningConfig\Listing();
  1408.             $weatherWarning->setCondition("WarningKey = ? AND (hourEnd >= ? AND hourStart <= ?)", [$warningParam$duration$duration]);
  1409.             $weatherWarning $weatherWarning->load();
  1410.             if (!$weatherWarning) {
  1411.                 throw new \Exception('Weather configuration is missing for key.' $warningParam);
  1412.             }
  1413.             $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  1414.             $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  1415.             // Create a Redis key for the cache            
  1416.             $cacheKey sprintf('weather_warning_%s_%s_%s'$warningParam $formatimplode('_'array_map(function ($coordinate) {
  1417.                 return implode('_'$coordinate);
  1418.             }, $coordinates)), (($startDate) . '-' . ($endDate)));
  1419.             // Try to get the data from Redis cache
  1420.             $cachedData $this->redisCache->get($cacheKey);
  1421.             if ($cachedData !== null) {
  1422.                 // Return the cached data if available
  1423.                 return $cachedData;
  1424.             }
  1425.             // If cache is empty, get the data from the API
  1426.             $client = new Client(['verify' => false]);
  1427.             $url sprintf(
  1428.                 '%s/%s--%s:PT%sH//%s_%sh:idx/%s,%s/json?use_decluttered=true',
  1429.                 $this->apiBaseUrl,
  1430.                 $startDate,
  1431.                 $endDate,
  1432.                 $duration,
  1433.                 $warningParam,
  1434.                 $duration,
  1435.                 $latitude,
  1436.                 $longitude
  1437.             );
  1438.             $response $client->request('GET'$url, [
  1439.                 'auth' => [$this->username$this->password],
  1440.             ]);
  1441.             $statusCode $response->getStatusCode();
  1442.             $data json_decode($response->getBody(), true);
  1443.             $modifiedParams = [];
  1444.             if (isset($data['data'][0]['coordinates'][0]['dates'])) {
  1445.                 foreach ($data['data'][0]['coordinates'][0]['dates'] as $weather) {
  1446.                     $data \App\Lib\Utility::getWeatherTypeDescription($weatherWarning[0]->getParams(), $weather);
  1447.                     $modifiedParams[] = $data;
  1448.                 }
  1449.             }
  1450.             $data['data'] = $modifiedParams;
  1451.             // Set the data to Redis cache
  1452.             $this->redisCache->set($cacheKey$data);
  1453.             return $data;
  1454.         } catch (GuzzleHttp\Exception\RequestException $e) {
  1455.             return throw new \Exception($e->getMessage());
  1456.         } catch (\Exception $e) {
  1457.             return throw new \Exception($e->getMessage());
  1458.         }
  1459.     }
  1460.     /**
  1461.      * Weather Warnings
  1462.      *
  1463.      * @param array $coordinates An array of coordinates in the format [[lat1, long1], [lat2, long2], ...]
  1464.      * @param string $startDate The start date for the forecast
  1465.      * @param string $endDate The end date for the forecast
  1466.      * @param string $duration The duration for the forecast (default: '24')
  1467.      * @param string $warningParam The type of weather warning parameter (default: 'frost_warning')
  1468.      * @param string $format The format of the forecast data (default: 'json')
  1469.      * @return array The model forecast data
  1470.      * @throws \InvalidArgumentException If any of the parameters are invalid or missing
  1471.      */
  1472.     public function getPrecipitationType(array $coordinatesstring $startDatestring $endDatestring $duration '24'string $format "json"$translator)
  1473.     {
  1474.         try {
  1475.             // Validate the input parameters
  1476.             if (empty($coordinates)) {
  1477.                 // throw new \InvalidArgumentException('Invalid coordinates');
  1478.                 return ["success" => false"message" => $translator->trans("invalid_coordinates")];
  1479.             }
  1480.             if (!is_array($coordinates) || count($coordinates) < 1) {
  1481.                 // throw new \InvalidArgumentException('Coordinates should be a non-empty array');
  1482.                 return ["success" => false"message" => $translator->trans("coordinates_should_be_a_non_empty_array")];
  1483.             }
  1484.             $latitude $coordinates[0][0] ?? null;
  1485.             $longitude $coordinates[0][1] ?? null;
  1486.             if (!is_numeric($latitude) || $latitude < -90 || $latitude 90) {
  1487.                 // throw new \InvalidArgumentException('Invalid latitude');
  1488.                 return ["success" => false"message" => $translator->trans("invalid_latitude")];
  1489.             }
  1490.             if (!is_numeric($longitude) || $longitude < -180 || $longitude 180) {
  1491.                 // throw new \InvalidArgumentException('Invalid longitude');
  1492.                 return ["success" => false"message" => $translator->trans("invalid_longitude")];
  1493.             }
  1494.             if (empty($startDate) || empty($endDate)) {
  1495.                 // throw new \InvalidArgumentException('Invalid dates');
  1496.                 return ["success" => false"message" => $translator->trans("invalid_dates")];
  1497.             }
  1498.             if (!is_numeric($duration)) {
  1499.                 // throw new \InvalidArgumentException('Duration should be numeric');
  1500.                 return ["success" => false"message" => $translator->trans("duration_should_be_numeric")];
  1501.             }
  1502.             $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  1503.             $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  1504.             // Create a Redis key for the cache            
  1505.             $cacheKey sprintf('weather_precitipitation_%s_%s'implode('_'array_map(function ($coordinate) {
  1506.                 return implode('_'$coordinate);
  1507.             }, $coordinates)), (($startDate) . '-' . ($endDate)));
  1508.             // Try to get the data from Redis cache
  1509.             $cachedData $this->redisCache->get($cacheKey);
  1510.             if ($cachedData !== null) {
  1511.                 // Return the cached data if available
  1512.                 // return $cachedData;
  1513.             }
  1514.             // If cache is empty, get the data from the API
  1515.             $client = new Client(['verify' => false]);
  1516.             $url sprintf(
  1517.                 '%s/%s--%s:PT%sM/precip_type:idx/%s,%s/json?use_decluttered=true',
  1518.                 $this->apiBaseUrl,
  1519.                 $startDate,
  1520.                 $endDate,
  1521.                 $duration,
  1522.                 $duration,
  1523.                 $latitude,
  1524.                 $longitude
  1525.             );
  1526.             $response $client->request('GET'$url, [
  1527.                 'auth' => [$this->username$this->password],
  1528.             ]);
  1529.             $statusCode $response->getStatusCode();
  1530.             $data json_decode($response->getBody(), true);
  1531.             $modifiedParams = [];
  1532.             if (isset($data['data'][0]['coordinates'][0]['dates'])) {
  1533.                 foreach ($data['data'][0]['coordinates'][0]['dates'] as $prepData) {
  1534.                     $data \App\Lib\Utility::getPrecipitationTypeDescription($prepData);
  1535.                     $modifiedParams[] = $data;
  1536.                 }
  1537.             } else {
  1538.                 throw new \Exception("Data not available");
  1539.             }
  1540.             $data['data'][0]['coordinates'][0]['dates'] = $modifiedParams;
  1541.             // Set the data to Redis cache
  1542.             $this->redisCache->set($cacheKey$data);
  1543.             return $data;
  1544.         } catch (GuzzleHttp\Exception\RequestException $e) {
  1545.             return throw new \Exception($e->getMessage());
  1546.         } catch (\Exception $e) {
  1547.             return throw new \Exception($e->getMessage());
  1548.         }
  1549.     }
  1550.     /**
  1551.      * Weather Warnings
  1552.      *
  1553.      * @param array $coordinates An array of coordinates in the format [[lat1, long1], [lat2, long2], ...]
  1554.      * @param string $startDate The start date for the forecast
  1555.      * @param string $endDate The end date for the forecast
  1556.      * @param string $duration The duration for the forecast (default: '24')
  1557.      * @param string $intervalType type of interval (default: 'min')
  1558.      * @param string $format The format of the forecast data (default: 'json')
  1559.      * @return array The model forecast data
  1560.      * @throws \InvalidArgumentException If any of the parameters are invalid or missing
  1561.      */
  1562.     public function getHailIndex(array $coordinatesstring $startDatestring $endDatestring $duration '24'string $intervalType "min"string $format "json"$translator)
  1563.     {
  1564.         try {
  1565.             // Validate the input parameters
  1566.             if (count($coordinates) > 1) {
  1567.                 // Validate the input parameters
  1568.                 foreach ($coordinates as $coordinate) {
  1569.                     if (count($coordinate) < || !is_numeric($coordinate[0]) || !is_numeric($coordinate[1])) {
  1570.                         throw new \InvalidArgumentException('Invalid coordinates');
  1571.                     }
  1572.                 }
  1573.                 if (empty($startDate) || empty($endDate)) {
  1574.                     // throw new \InvalidArgumentException('Invalid dates');
  1575.                     return ["success" => false"message" => $translator->trans("duration_dates")];
  1576.                 }
  1577.                 if (!is_numeric($duration)) {
  1578.                     // throw new \InvalidArgumentException('Duration should be numeric');
  1579.                     return ["success" => false"message" => $translator->trans("duration_should_be_numeric")];
  1580.                 }
  1581.                 $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  1582.                 $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  1583.                 // Create a Redis key for the cache            
  1584.                 $cacheKey sprintf('weather_hail_%s_%s'implode('_'array_map(function ($coordinate) {
  1585.                     return implode('_'$coordinate);
  1586.                 }, $coordinates)), (($startDate) . '-' . ($endDate) . $duration $intervalType));
  1587.                 // Try to get the data from Redis cache
  1588.                 $cachedData $this->redisCache->get($cacheKey);
  1589.                 if ($cachedData !== null) {
  1590.                     // Return the cached data if available
  1591.                     return $cachedData;
  1592.                 }
  1593.                 // If cache is empty, get the data from the API
  1594.                 $coordinateString implode('+'array_map(fn($coords) => implode(','$coords), $coordinates));
  1595.                 //10min, 20min, 30min, 1h, 3h, 6h, 12h, 24h
  1596.                 // If cache is empty, get the data from the API
  1597.                 if ($intervalType == "min") {
  1598.                     $url sprintf(
  1599.                         '%s/%s--%s:PT%sM/hail_%smin:cm/%s/json?use_decluttered=true',
  1600.                         $this->apiBaseUrl,
  1601.                         $startDate,
  1602.                         $endDate,
  1603.                         $duration,
  1604.                         $duration,
  1605.                         $coordinateString
  1606.                     );
  1607.                 } else if ($intervalType == "hour") {
  1608.                     $url sprintf(
  1609.                         '%s/%s--%s:PT%sH/hail_%sh:cm/%s/json?use_decluttered=true',
  1610.                         $this->apiBaseUrl,
  1611.                         $startDate,
  1612.                         $endDate,
  1613.                         $duration,
  1614.                         $duration,
  1615.                         $coordinateString
  1616.                     );
  1617.                 }
  1618.                 $client = new Client(['verify' => false]);
  1619.                 $response $client->request('GET'$url, [
  1620.                     'auth' => [$this->username$this->password],
  1621.                 ]);
  1622.                 $statusCode $response->getStatusCode();
  1623.                 $data json_decode($response->getBody(), true);
  1624.                 // Set the data to Redis cache
  1625.                 $this->redisCache->set($cacheKey$data);
  1626.                 return $data;
  1627.             } else {
  1628.                 $latitude $coordinates[0][0] ?? null;
  1629.                 $longitude $coordinates[0][1] ?? null;
  1630.                 if (!is_numeric($latitude) || $latitude < -90 || $latitude 90) {
  1631.                     // throw new \InvalidArgumentException('Invalid latitude');
  1632.                     return ["success" => false"message" => $translator->trans("duration_latitude")];
  1633.                 }
  1634.                 if (!is_numeric($longitude) || $longitude < -180 || $longitude 180) {
  1635.                     // throw new \InvalidArgumentException('Invalid longitude');
  1636.                     return ["success" => false"message" => $translator->trans("duration_longitude")];
  1637.                 }
  1638.                 if (empty($startDate) || empty($endDate)) {
  1639.                     // throw new \InvalidArgumentException('Invalid dates');
  1640.                     return ["success" => false"message" => $translator->trans("duration_dates")];
  1641.                 }
  1642.                 if (!is_numeric($duration)) {
  1643.                     // throw new \InvalidArgumentException('Duration should be numeric');
  1644.                     return ["success" => false"message" => $translator->trans("duration_should_be_numeric")];
  1645.                 }
  1646.                 $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  1647.                 $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  1648.                 // Create a Redis key for the cache            
  1649.                 $cacheKey sprintf('weather_hail_%s_%s'implode('_'array_map(function ($coordinate) {
  1650.                     return implode('_'$coordinate);
  1651.                 }, $coordinates)), (($startDate) . '-' . ($endDate) . $duration $intervalType));
  1652.                 // Try to get the data from Redis cache
  1653.                 $cachedData $this->redisCache->get($cacheKey);
  1654.                 if ($cachedData !== null) {
  1655.                     // Return the cached data if available
  1656.                     return $cachedData;
  1657.                 }
  1658.                 //10min, 20min, 30min, 1h, 3h, 6h, 12h, 24h
  1659.                 // If cache is empty, get the data from the API
  1660.                 if ($intervalType == "min") {
  1661.                     $url sprintf(
  1662.                         '%s/%s--%s:PT%sM/hail_%smin:cm/%s,%s/json?use_decluttered=true',
  1663.                         $this->apiBaseUrl,
  1664.                         $startDate,
  1665.                         $endDate,
  1666.                         $duration,
  1667.                         $duration,
  1668.                         $latitude,
  1669.                         $longitude
  1670.                     );
  1671.                 } else if ($intervalType == "hour") {
  1672.                     $url sprintf(
  1673.                         '%s/%s--%s:PT%sH/hail_%sh:cm/%s,%s/json?use_decluttered=true',
  1674.                         $this->apiBaseUrl,
  1675.                         $startDate,
  1676.                         $endDate,
  1677.                         $duration,
  1678.                         $duration,
  1679.                         $latitude,
  1680.                         $longitude
  1681.                     );
  1682.                 }
  1683.                 $client = new Client(['verify' => false]);
  1684.                 $response $client->request('GET'$url, [
  1685.                     'auth' => [$this->username$this->password],
  1686.                 ]);
  1687.                 $statusCode $response->getStatusCode();
  1688.                 $data json_decode($response->getBody(), true);
  1689.                 // Set the data to Redis cache
  1690.                 $this->redisCache->set($cacheKey$data);
  1691.                 return $data;
  1692.             }
  1693.         } catch (GuzzleHttp\Exception\RequestException $e) {
  1694.             return throw new \Exception($e->getMessage());
  1695.         } catch (\Exception $e) {
  1696.             return throw new \Exception($e->getMessage());
  1697.         }
  1698.     }
  1699.     /**
  1700.      * Temprature
  1701.      *
  1702.      * @param array $coordinates An array of coordinates in the format [[lat1, long1], [lat2, long2], ...]
  1703.      * @param string $startDate The start date for the forecast
  1704.      * @param string $endDate The end date for the forecast
  1705.      * @param string $duration The duration for the forecast (default: '24')
  1706.      * @param string $intervalType type of interval (default: 'hour')
  1707.      * @param string $format The format of the forecast data (default: 'json')
  1708.      * @return array The model forecast data
  1709.      * @throws \InvalidArgumentException If any of the parameters are invalid or missing
  1710.      */
  1711.     //https://api.meteomatics.com/2023-07-11T00:00:00Z--2023-07-14T00:00:00Z:PT1H/t_2m:C/48.8566,2.3522/json
  1712.     public function getTemprature(array $coordinatesstring $startDatestring $endDatestring $duration '24'string $intervalType "hour"string $format "json"$translator)
  1713.     {
  1714.         try {
  1715.             // Validate the input parameters
  1716.             if (empty($coordinates)) {
  1717.                 // throw new \InvalidArgumentException('Invalid coordinates');
  1718.                 return ["success" => false"message" => $translator->trans("invalid_coordinate")];
  1719.             }
  1720.             if (!is_array($coordinates) || count($coordinates) < 1) {
  1721.                 // throw new \InvalidArgumentException('Coordinates should be a non-empty array');
  1722.                 return ["success" => false"message" => $translator->trans("coordinates_should_be_a_non_empty_array")];
  1723.             }
  1724.             $latitude $coordinates[0][0] ?? null;
  1725.             $longitude $coordinates[0][1] ?? null;
  1726.             if (!is_numeric($latitude) || $latitude < -90 || $latitude 90) {
  1727.                 // throw new \InvalidArgumentException('Invalid latitude');
  1728.                 return ["success" => false"message" => $translator->trans("invalid_coordinate")];
  1729.             }
  1730.             if (!is_numeric($longitude) || $longitude < -180 || $longitude 180) {
  1731.                 // throw new \InvalidArgumentException('Invalid longitude');
  1732.                 return ["success" => false"message" => $translator->trans("invalid_longitude")];
  1733.             }
  1734.             if (empty($startDate) || empty($endDate)) {
  1735.                 // throw new \InvalidArgumentException('Invalid dates');
  1736.                 return ["success" => false"message" => $translator->trans("invalid_dates")];
  1737.             }
  1738.             if (!is_numeric($duration)) {
  1739.                 // throw new \InvalidArgumentException('Duration should be numeric');
  1740.                 return ["success" => false"message" => $translator->trans("duration_should_be_numeric")];
  1741.             }
  1742.             $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  1743.             $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  1744.             // Create a Redis key for the cache            
  1745.             $cacheKey sprintf('temprature_%s_%s'implode('_'array_map(function ($coordinate) {
  1746.                 return implode('_'$coordinate);
  1747.             }, $coordinates)), (($startDate) . '-' . ($endDate) . $duration $intervalType));
  1748.             // Try to get the data from Redis cache
  1749.             $cachedData $this->redisCache->get($cacheKey);
  1750.             if ($cachedData !== null) {
  1751.                 // Return the cached data if available
  1752.                 return $cachedData;
  1753.             }
  1754.             // If cache is empty, get the data from the API
  1755.             if ($intervalType == "hour") {
  1756.                 $url sprintf(
  1757.                     '%s/%s--%s:PT%sM/t_2m:C/%s,%s/json?use_decluttered=true',
  1758.                     $this->apiBaseUrl,
  1759.                     $startDate,
  1760.                     $endDate,
  1761.                     $duration,
  1762.                     $latitude,
  1763.                     $longitude
  1764.                 );
  1765.             } else if ($intervalType == "day") {
  1766.                 $url sprintf(
  1767.                     '%s/%s--%s:P%sD/t_2m:C/%s,%s/json?use_decluttered=true',
  1768.                     $this->apiBaseUrl,
  1769.                     $startDate,
  1770.                     $endDate,
  1771.                     $duration,
  1772.                     $latitude,
  1773.                     $longitude
  1774.                 );
  1775.             }
  1776.             $client = new Client(['verify' => false]);
  1777.             $response $client->request('GET'$url, [
  1778.                 'auth' => [$this->username$this->password],
  1779.             ]);
  1780.             $statusCode $response->getStatusCode();
  1781.             $data json_decode($response->getBody(), true);
  1782.             // Set the data to Redis cache
  1783.             $this->redisCache->set($cacheKey$data);
  1784.             return $data;
  1785.         } catch (GuzzleHttp\Exception\RequestException $e) {
  1786.             return throw new \Exception($e->getMessage());
  1787.         } catch (\Exception $e) {
  1788.             return throw new \Exception($e->getMessage());
  1789.         }
  1790.     }
  1791.     /**
  1792.      * Precipitation Probability
  1793.      *
  1794.      * @param array $coordinates An array of coordinates in the format [[lat1, long1], [lat2, long2], ...]
  1795.      * @param string $startDate The start date for the forecast
  1796.      * @param string $endDate The end date for the forecast
  1797.      * @param string $duration The duration for the forecast (default: '24')
  1798.      * @param string $intervalType type of interval (default: 'hour')
  1799.      * @param string $format The format of the forecast data (default: 'json')
  1800.      * @return array The model forecast data
  1801.      * @throws \InvalidArgumentException If any of the parameters are invalid or missing
  1802.      */
  1803.     //https://api.meteomatics.com/2023-07-11T00:00:00Z--2023-07-14T00:00:00Z:PT1H/t_2m:C/48.8566,2.3522/json
  1804.     public function getPrecipitationProbability(array $coordinatesstring $startDatestring $endDatestring $duration '24'string $format "json"$translator)
  1805.     {
  1806.         try {
  1807.             // Validate the input parameters
  1808.             if (empty($coordinates)) {
  1809.                 return ["success" => false"message" => $translator->trans("invalid_coordinate")];
  1810.             }
  1811.             if (!is_array($coordinates) || count($coordinates) < 1) {
  1812.                 return ["success" => false"message" => $translator->trans("coordinates_should_be_a_non_empty_array")];
  1813.             }
  1814.             $latitude $coordinates[0][0] ?? null;
  1815.             $longitude $coordinates[0][1] ?? null;
  1816.             if (!is_numeric($latitude) || $latitude < -90 || $latitude 90) {
  1817.                 return ["success" => false"message" => $translator->trans("invalid_latitude")];
  1818.             }
  1819.             if (!is_numeric($longitude) || $longitude < -180 || $longitude 180) {
  1820.                 return ["success" => false"message" => $translator->trans("invalid_longitude")];
  1821.             }
  1822.             if (empty($startDate) || empty($endDate)) {
  1823.                 return ["success" => false"message" => $translator->trans("invalid_dates")];
  1824.             }
  1825.             if (!is_numeric($duration)) {
  1826.                 // throw new \InvalidArgumentException('Duration should be numeric');
  1827.                 return ["success" => false"message" => $translator->trans("duration_should_be_numeric")];
  1828.             }
  1829.             $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  1830.             $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  1831.             // Create a Redis key for the cache            
  1832.             $cacheKey sprintf('precep_prob_%s_%s'implode('_'array_map(function ($coordinate) {
  1833.                 return implode('_'$coordinate);
  1834.             }, $coordinates)), (($startDate) . '-' . ($endDate) . $duration));
  1835.             // Try to get the data from Redis cache
  1836.             $cachedData $this->redisCache->get($cacheKey);
  1837.             if ($cachedData !== null) {
  1838.                 // Return the cached data if available
  1839.                 return $cachedData;
  1840.             }
  1841.             // If cache is empty, get the data from the API
  1842.             $url sprintf(
  1843.                 '%s/%s--%s:PT%sH/prob_precip_%sh:p/%s,%s/json?use_decluttered=true',
  1844.                 $this->apiBaseUrl,
  1845.                 $startDate,
  1846.                 $endDate,
  1847.                 $duration,
  1848.                 $duration,
  1849.                 $latitude,
  1850.                 $longitude
  1851.             );
  1852.             $client = new Client(['verify' => false]);
  1853.             $response $client->request('GET'$url, [
  1854.                 'auth' => [$this->username$this->password],
  1855.             ]);
  1856.             $statusCode $response->getStatusCode();
  1857.             $data json_decode($response->getBody(), true);
  1858.             // Set the data to Redis cache
  1859.             $this->redisCache->set($cacheKey$data);
  1860.             return $data;
  1861.         } catch (GuzzleHttp\Exception\RequestException $e) {
  1862.             return throw new \Exception($e->getMessage());
  1863.         } catch (\Exception $e) {
  1864.             return throw new \Exception($e->getMessage());
  1865.         }
  1866.     }
  1867.     /**
  1868.      * Visibility
  1869.      *
  1870.      * @param array $coordinates An array of coordinates in the format [[lat1, long1], [lat2, long2], ...]
  1871.      * @param string $startDate The start date for the forecast
  1872.      * @param string $endDate The end date for the forecast
  1873.      * @param string $duration The duration for the forecast (default: '24')
  1874.      * @param string $intervalType type of interval (default: 'hour')
  1875.      * @param string $format The format of the forecast data (default: 'json')
  1876.      * @return array The model forecast data
  1877.      * @throws \InvalidArgumentException If any of the parameters are invalid or missing
  1878.      */
  1879.     public function getVisibility(array $coordinatesstring $startDatestring $endDatestring $duration '24'$unit 'km'string $format "json"$translator)
  1880.     {
  1881.         try {
  1882.             // Validate the input parameters
  1883.             if (empty($coordinates)) {
  1884.                 // throw new \InvalidArgumentException('Invalid coordinates');
  1885.                 return ["success" => false"message" => $translator->trans("invalid_coordinates")];
  1886.             }
  1887.             if (!is_array($coordinates) || count($coordinates) < 1) {
  1888.                 // throw new \InvalidArgumentException('Coordinates should be a non-empty array');
  1889.                 return ["success" => false"message" => $translator->trans("coordinates_should_be_a_non_empty_array")];
  1890.             }
  1891.             $latitude $coordinates[0][0] ?? null;
  1892.             $longitude $coordinates[0][1] ?? null;
  1893.             if (!is_numeric($latitude) || $latitude < -90 || $latitude 90) {
  1894.                 // throw new \InvalidArgumentException('Invalid latitude');
  1895.                 return ["success" => false"message" => $translator->trans("invalid_latitude")];
  1896.             }
  1897.             if (!is_numeric($longitude) || $longitude < -180 || $longitude 180) {
  1898.                 // throw new \InvalidArgumentException('Invalid longitude');
  1899.                 return ["success" => false"message" => $translator->trans("invalid_longitude")];
  1900.             }
  1901.             if (empty($startDate) || empty($endDate)) {
  1902.                 // throw new \InvalidArgumentException('Invalid dates');
  1903.                 return ["success" => false"message" => $translator->trans("invalid_dates")];
  1904.             }
  1905.             if (!is_numeric($duration)) {
  1906.                 // throw new \InvalidArgumentException('Duration should be numeric');
  1907.                 return ["success" => false"message" => $translator->trans("duration_should_be_numeric")];
  1908.             }
  1909.             if (!in_array($unit, ['m''km''ft''nmi'])) {
  1910.                 // throw new \InvalidArgumentException('Invalid unit');
  1911.                 return ["success" => false"message" => $translator->trans("invalid_unit")];
  1912.             }
  1913.             $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  1914.             $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  1915.             // Create a Redis key for the cache            
  1916.             $cacheKey sprintf('visibility_%s_%s'implode('_'array_map(function ($coordinate) {
  1917.                 return implode('_'$coordinate);
  1918.             }, $coordinates)), (($startDate) . '-' . ($endDate) . $duration));
  1919.             // Try to get the data from Redis cache
  1920.             $cachedData $this->redisCache->get($cacheKey);
  1921.             if ($cachedData !== null) {
  1922.                 // Return the cached data if available
  1923.                 return $cachedData;
  1924.             }
  1925.             // If cache is empty, get the data from the API
  1926.             $url sprintf(
  1927.                 '%s/%s--%s:PT%sH/visibility:%s/%s,%s/json?use_decluttered=true',
  1928.                 $this->apiBaseUrl,
  1929.                 $startDate,
  1930.                 $endDate,
  1931.                 $duration,
  1932.                 $unit,
  1933.                 $latitude,
  1934.                 $longitude
  1935.             );
  1936.             $client = new Client(['verify' => false]);
  1937.             $response $client->request('GET'$url, [
  1938.                 'auth' => [$this->username$this->password],
  1939.             ]);
  1940.             $statusCode $response->getStatusCode();
  1941.             $data json_decode($response->getBody(), true);
  1942.             // Set the data to Redis cache
  1943.             $this->redisCache->set($cacheKey$data);
  1944.             return $data;
  1945.         } catch (GuzzleHttp\Exception\RequestException $e) {
  1946.             return throw new \Exception($e->getMessage());
  1947.         } catch (\Exception $e) {
  1948.             return throw new \Exception($e->getMessage());
  1949.         }
  1950.     }
  1951.     /**
  1952.      * Wind Direction
  1953.      *
  1954.      * @param array $coordinates An array of coordinates in the format [[lat1, long1], [lat2, long2], ...]
  1955.      * @param string $startDate The start date for the forecast
  1956.      * @param string $endDate The end date for the forecast
  1957.      * @param string $duration The duration for the forecast (default: '24')
  1958.      * @param string $intervalType type of interval (default: 'hour')
  1959.      * @param string $format The format of the forecast data (default: 'json')
  1960.      * @return array The model forecast data
  1961.      * @throws \InvalidArgumentException If any of the parameters are invalid or missing
  1962.      */
  1963.     //https://api.meteomatics.com/2023-07-11T00:00:00ZP2D:PT3H/wind_dir_10m:d,wind_dir_700hPa:d/47.412164,9.340652/csv
  1964.     public function getWindDirection(array $coordinatesstring $startDatestring $endDatestring $duration '24'$height '10m'$level '700hPa'string $format "json"$translator)
  1965.     {
  1966.         try {
  1967.             // Validate the input parameters
  1968.             if (empty($coordinates)) {
  1969.                 return ["success" => false"message" => $translator->trans("invalid_coordinates")];
  1970.             }
  1971.             if (!is_array($coordinates) || count($coordinates) < 1) {
  1972.                 return ["success" => false"message" => $translator->trans("coordinates_should_be_a_non_empty_array")];
  1973.             }
  1974.             $latitude $coordinates[0][0] ?? null;
  1975.             $longitude $coordinates[0][1] ?? null;
  1976.             if (!is_numeric($latitude) || $latitude < -90 || $latitude 90) {
  1977.                 return ["success" => false"message" => $translator->trans("invalid_latitude")];
  1978.             }
  1979.             if (!is_numeric($longitude) || $longitude < -180 || $longitude 180) {
  1980.                 return ["success" => false"message" => $translator->trans("invalid_longitude")];
  1981.             }
  1982.             if (empty($startDate) || empty($endDate)) {
  1983.                 return ["success" => false"message" => $translator->trans("invalid_dates")];
  1984.             }
  1985.             if (!is_numeric($duration)) {
  1986.                 return ["success" => false"message" => $translator->trans("duration_should_be_numeric")];
  1987.             }
  1988.             if (!in_array($level, ['1000hPa''950hPa''925hPa''900hPa''850hPa''800hPa''700hPa''500hPa''300hPa''250hPa''200hPa''150hPa''100hPa''70hPa''50hPa''10hPa'])) {
  1989.                 return ["success" => false"message" => $translator->trans("invalid_level")];
  1990.             }
  1991.             $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  1992.             $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  1993.             // Create a Redis key for the cache            
  1994.             $cacheKey sprintf('wind_direction_%s_%s'implode('_'array_map(function ($coordinate) {
  1995.                 return implode('_'$coordinate);
  1996.             }, $coordinates)), (($startDate) . '-' . ($endDate) . $duration $level));
  1997.             // Try to get the data from Redis cache
  1998.             $cachedData $this->redisCache->get($cacheKey);
  1999.             if ($cachedData !== null) {
  2000.                 // Return the cached data if available
  2001.                 return $cachedData;
  2002.             }
  2003.             // If cache is empty, get the data from the API
  2004.             $url sprintf(
  2005.                 '%s/%s--%s:PT%sH/wind_dir_%s:d,wind_dir_%s:d/%s,%s/json?use_decluttered=true',
  2006.                 $this->apiBaseUrl,
  2007.                 $startDate,
  2008.                 $endDate,
  2009.                 $duration,
  2010.                 $height,
  2011.                 $level,
  2012.                 $latitude,
  2013.                 $longitude
  2014.             );
  2015.             $client = new Client(['verify' => false]);
  2016.             $response $client->request('GET'$url, [
  2017.                 'auth' => [$this->username$this->password],
  2018.             ]);
  2019.             $statusCode $response->getStatusCode();
  2020.             $data json_decode($response->getBody(), true);
  2021.             // Set the data to Redis cache
  2022.             $this->redisCache->set($cacheKey$data);
  2023.             return $data;
  2024.         } catch (GuzzleHttp\Exception\RequestException $e) {
  2025.             return throw new \Exception($e->getMessage());
  2026.         } catch (\Exception $e) {
  2027.             return throw new \Exception($e->getMessage());
  2028.         }
  2029.     }
  2030.     /**
  2031.      * Wind Speed
  2032.      *
  2033.      * @param array $coordinates An array of coordinates in the format [[lat1, long1], [lat2, long2], ...]
  2034.      * @param string $startDate The start date for the forecast
  2035.      * @param string $endDate The end date for the forecast
  2036.      * @param string $duration The duration for the forecast (default: '24')
  2037.      * @param string $intervalType type of interval (default: 'hour')
  2038.      * @param string $format The format of the forecast data (default: 'json')
  2039.      * @return array The model forecast data
  2040.      * @throws \InvalidArgumentException If any of the parameters are invalid or missing
  2041.      */
  2042.     public function getWindSpeed(array $coordinatesstring $startDatestring $endDatestring $duration '24'$unit 'ft'$level '700hPa'string $format "json"$translator)
  2043.     {
  2044.         try {
  2045.             // Validate the input parameters
  2046.             if (empty($coordinates)) {
  2047.                 return ["success" => false"message" => $translator->trans("invalid_coordinates")];
  2048.             }
  2049.             if (!is_array($coordinates) || count($coordinates) < 1) {
  2050.                 return ["success" => false"message" => $translator->trans("coordinates_should_be_a_non_empty_array")];
  2051.             }
  2052.             $latitude $coordinates[0][0] ?? null;
  2053.             $longitude $coordinates[0][1] ?? null;
  2054.             if (!is_numeric($latitude) || $latitude < -90 || $latitude 90) {
  2055.                 return ["success" => false"message" => $translator->trans("invalid_latitude")];
  2056.             }
  2057.             if (!is_numeric($longitude) || $longitude < -180 || $longitude 180) {
  2058.                 return ["success" => false"message" => $translator->trans("invalid_longitude")];
  2059.             }
  2060.             if (empty($startDate) || empty($endDate)) {
  2061.                 return ["success" => false"message" => $translator->trans("invalid_dates")];
  2062.             }
  2063.             if (!is_numeric($duration)) {
  2064.                 return ["success" => false"message" => $translator->trans("duration_should_be_numeric")];
  2065.             }
  2066.             if (!in_array($level, ['1000hPa''950hPa''925hPa''900hPa''850hPa''800hPa''700hPa''500hPa''300hPa''250hPa''200hPa''150hPa''100hPa''70hPa''50hPa''10hPa'])) {
  2067.                 return ["success" => false"message" => $translator->trans("invalid_level")];
  2068.             }
  2069.             if (!in_array($unit, ['ms''kmh''mph''kn''bft'])) {
  2070.                 return ["success" => false"message" => $translator->trans("invalid_unit")];
  2071.             }
  2072.             $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  2073.             $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  2074.             // Create a Redis key for the cache            
  2075.             $cacheKey sprintf('wind_speed_%s_%s'implode('_'array_map(function ($coordinate) {
  2076.                 return implode('_'$coordinate);
  2077.             }, $coordinates)), (($startDate) . '-' . ($endDate) . $duration $unit $level));
  2078.             // Try to get the data from Redis cache
  2079.             $cachedData $this->redisCache->get($cacheKey);
  2080.             if ($cachedData !== null) {
  2081.                 // Return the cached data if available
  2082.                 return $cachedData;
  2083.             }
  2084.             // If cache is empty, get the data from the API
  2085.             $url sprintf(
  2086.                 '%s/%s--%s:PT%sH/wind_speed_%s:%s/%s,%s/json?use_decluttered=true',
  2087.                 $this->apiBaseUrl,
  2088.                 $startDate,
  2089.                 $endDate,
  2090.                 $duration,
  2091.                 $level,
  2092.                 $unit,
  2093.                 $latitude,
  2094.                 $longitude
  2095.             );
  2096.             $client = new Client(['verify' => false]);
  2097.             $response $client->request('GET'$url, [
  2098.                 'auth' => [$this->username$this->password],
  2099.             ]);
  2100.             $statusCode $response->getStatusCode();
  2101.             $data json_decode($response->getBody(), true);
  2102.             // Set the data to Redis cache
  2103.             $this->redisCache->set($cacheKey$data);
  2104.             return $data;
  2105.         } catch (GuzzleHttp\Exception\RequestException $e) {
  2106.             return throw new \Exception($e->getMessage());
  2107.         } catch (\Exception $e) {
  2108.             return throw new \Exception($e->getMessage());
  2109.         }
  2110.     }
  2111.     /**
  2112.      * Slippery Road
  2113.      *
  2114.      * @param array $coordinates An array of coordinates in the format [[lat1, long1], [lat2, long2], ...]
  2115.      * @param string $startDate The start date for the forecast
  2116.      * @param string $endDate The end date for the forecast
  2117.      * @param string $duration The duration for the forecast (default: '24')
  2118.      * @param string $intervalType type of interval (default: 'hour')
  2119.      * @param string $format The format of the forecast data (default: 'json')
  2120.      * @return array The model forecast data
  2121.      * @throws \InvalidArgumentException If any of the parameters are invalid or missing
  2122.      */
  2123.     public function getSlipperyRoad(array $coordinatesstring $startDatestring $endDatestring $duration '24'string $format "json"$translator)
  2124.     {
  2125.         try {
  2126.             // Validate the input parameters
  2127.             if (empty($coordinates)) {
  2128.                 return ["success" => false"message" => $translator->trans("invalid_coordinates")];
  2129.             }
  2130.             if (!is_array($coordinates) || count($coordinates) < 1) {
  2131.                 return ["success" => false"message" => $translator->trans("coordinates_should_be_a_non_empty_array")];
  2132.             }
  2133.             $latitude $coordinates[0][0] ?? null;
  2134.             $longitude $coordinates[0][1] ?? null;
  2135.             if (!is_numeric($latitude) || $latitude < -90 || $latitude 90) {
  2136.                 return ["success" => false"message" => $translator->trans("invalid_latitude")];
  2137.             }
  2138.             if (!is_numeric($longitude) || $longitude < -180 || $longitude 180) {
  2139.                 return ["success" => false"message" => $translator->trans("invalid_longitude")];
  2140.             }
  2141.             if (empty($startDate) || empty($endDate)) {
  2142.                 return ["success" => false"message" => $translator->trans("invalid_dates")];
  2143.             }
  2144.             if (!is_numeric($duration)) {
  2145.                 return ["success" => false"message" => $translator->trans("duration_should_be_numeric")];
  2146.             }
  2147.             $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  2148.             $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  2149.             // Create a Redis key for the cache            
  2150.             $cacheKey sprintf('slipary_road_%s_%s'implode('_'array_map(function ($coordinate) {
  2151.                 return implode('_'$coordinate);
  2152.             }, $coordinates)), (($startDate) . '-' . ($endDate) . $duration));
  2153.             // Try to get the data from Redis cache
  2154.             $cachedData $this->redisCache->get($cacheKey);
  2155.             if ($cachedData !== null) {
  2156.                 // Return the cached data if available
  2157.                 return $cachedData;
  2158.             }
  2159.             // If cache is empty, get the data from the API
  2160.             $url sprintf(
  2161.                 '%s/%s--%s:PT%sH/prob_slippery_road_%sh:p,is_slippery_road_%sh:idx/%s,%s/json?use_decluttered=true',
  2162.                 $this->apiBaseUrl,
  2163.                 $startDate,
  2164.                 $endDate,
  2165.                 $duration,
  2166.                 $duration,
  2167.                 $duration,
  2168.                 $latitude,
  2169.                 $longitude
  2170.             );
  2171.             $client = new Client(['verify' => false]);
  2172.             $response $client->request('GET'$url, [
  2173.                 'auth' => [$this->username$this->password],
  2174.             ]);
  2175.             $statusCode $response->getStatusCode();
  2176.             $data json_decode($response->getBody(), true);
  2177.             // Set the data to Redis cache
  2178.             $this->redisCache->set($cacheKey$data);
  2179.             return $data;
  2180.         } catch (GuzzleHttp\Exception\RequestException $e) {
  2181.             return throw new \Exception($e->getMessage());
  2182.         } catch (\Exception $e) {
  2183.             return throw new \Exception($e->getMessage());
  2184.         }
  2185.     }
  2186.     /**
  2187.      * Solar Radiation
  2188.      *
  2189.      * @param array $coordinates An array of coordinates in the format [[lat1, long1], [lat2, long2], ...]
  2190.      * @param string $startDate The start date for the forecast
  2191.      * @param string $endDate The end date for the forecast     
  2192.      * @throws \InvalidArgumentException If any of the parameters are invalid or missing
  2193.      */
  2194.     public function getSolarRadiation(array $coordinatesstring $startDatestring $endDate$translatorstring $format 'png',)
  2195.     {
  2196.         try {
  2197.             // Validate the input parameters
  2198.             if (empty($coordinates)) {
  2199.                 return ["success" => false"message" => $translator->trans("invalid_coordinates")];
  2200.             }
  2201.             if (!is_array($coordinates) || count($coordinates) < 1) {
  2202.                 return ["success" => false"message" => $translator->trans("coordinates_should_be_a_non_empty_array")];
  2203.             }
  2204.             $latitude1 $coordinates[0][0] ?? null;
  2205.             $longitude1 $coordinates[0][1] ?? null;
  2206.             $latitude2 $coordinates[1][0] ?? null;
  2207.             $longitude2 $coordinates[1][1] ?? null;
  2208.             if (!is_numeric($latitude1) || $latitude1 < -90 || $latitude1 90) {
  2209.                 return ["success" => false"message" => $translator->trans("invalid_latitude")];
  2210.             }
  2211.             if (!is_numeric($longitude1) || $longitude1 < -180 || $longitude1 180) {
  2212.                 return ["success" => false"message" => $translator->trans("invalid_longitude")];
  2213.             }
  2214.             if (!is_numeric($latitude2) || $latitude2 < -90 || $latitude2 90) {
  2215.                 return ["success" => false"message" => $translator->trans("invalid_latitude")];
  2216.             }
  2217.             if (
  2218.                 !is_numeric($longitude2) || $longitude2 < -180 || $longitude2 180
  2219.             ) {
  2220.                 return ["success" => false"message" => $translator->trans("invalid_longitude")];
  2221.             }
  2222.             if (empty($startDate) || empty($endDate)) {
  2223.                 return ["success" => false"message" => $translator->trans("invalid_dates")];
  2224.             }
  2225.             $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  2226.             $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  2227.             $url sprintf(
  2228.                 '%s/%s/global_rad:W/%s,%s_%s,%s:600x400/%s?use_decluttered=true',
  2229.                 $this->apiBaseUrl,
  2230.                 $startDate,
  2231.                 $latitude1,
  2232.                 $longitude1,
  2233.                 $latitude2,
  2234.                 $longitude2,
  2235.                 $format
  2236.             );
  2237.             // echo $url;
  2238.             // exit;
  2239.             $client = new Client(['verify' => false]);
  2240.             $response $client->request('GET'$url, [
  2241.                 'auth' => [$this->username$this->password],
  2242.             ]);
  2243.             $statusCode $response->getStatusCode();
  2244.             return $response->getBody();
  2245.         } catch (GuzzleHttp\Exception\RequestException $e) {
  2246.             return throw new \Exception($e->getMessage());
  2247.         } catch (\Exception $e) {
  2248.             return throw new \Exception($e->getMessage());
  2249.         }
  2250.     }
  2251.     /**
  2252.      * Humidity
  2253.      *
  2254.      * @param array $coordinates An array of coordinates in the format [[lat1, long1], [lat2, long2], ...]
  2255.      * @param string $startDate The start date for the forecast
  2256.      * @param string $endDate The end date for the forecast
  2257.      * @param string $duration The duration for the forecast (default: '24')
  2258.      * @param string $intervalType type of interval (default: 'hour')
  2259.      * @param string $format The format of the forecast data (default: 'json')
  2260.      * @return array The model forecast data
  2261.      * @throws \InvalidArgumentException If any of the parameters are invalid or missing
  2262.      */
  2263.     public function getHumidity(array $coordinatesstring $startDatestring $endDatestring $duration '24'$unit 'ft'$level '700hPa'string $format "json"$translator)
  2264.     {
  2265.         try {
  2266.             // Validate the input parameters
  2267.             if (empty($coordinates)) {
  2268.                 return ["success" => false"message" => $translator->trans("invalid_coordinates")];
  2269.             }
  2270.             if (!is_array($coordinates) || count($coordinates) < 1) {
  2271.                 return ["success" => false"message" => $translator->trans("coordinates_should_be_a_non_empty_array")];
  2272.             }
  2273.             $latitude $coordinates[0][0] ?? null;
  2274.             $longitude $coordinates[0][1] ?? null;
  2275.             if (!is_numeric($latitude) || $latitude < -90 || $latitude 90) {
  2276.                 return ["success" => false"message" => $translator->trans("invalid_latitude")];
  2277.             }
  2278.             if (!is_numeric($longitude) || $longitude < -180 || $longitude 180) {
  2279.                 return ["success" => false"message" => $translator->trans("invalid_longitude")];
  2280.             }
  2281.             if (empty($startDate) || empty($endDate)) {
  2282.                 return ["success" => false"message" => $translator->trans("invalid_dates")];
  2283.             }
  2284.             if (!is_numeric($duration)) {
  2285.                 return ["success" => false"message" => $translator->trans("duration_should_be_numeric")];
  2286.             }
  2287.             if (!in_array($level, ['1000hPa''950hPa''925hPa''900hPa''850hPa''800hPa''700hPa''500hPa''300hPa''250hPa''200hPa''150hPa''100hPa''70hPa''50hPa''10hPa'])) {
  2288.                 return ["success" => false"message" => $translator->trans("invalid_level")];
  2289.             }
  2290.             if (!in_array($unit, ['p'])) {
  2291.                 return ["success" => false"message" => $translator->trans("invalid_unit")];
  2292.             }
  2293.             $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  2294.             $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  2295.             // Create a Redis key for the cache            
  2296.             $cacheKey sprintf('humidity_%s_%s'implode('_'array_map(function ($coordinate) {
  2297.                 return implode('_'$coordinate);
  2298.             }, $coordinates)), (($startDate) . '-' . ($endDate) . $duration $unit $level));
  2299.             // Try to get the data from Redis cache
  2300.             $cachedData $this->redisCache->get($cacheKey);
  2301.             if ($cachedData !== null) {
  2302.                 // Return the cached data if available
  2303.                 return $cachedData;
  2304.             }
  2305.             // If cache is empty, get the data from the API
  2306.             $url sprintf(
  2307.                 '%s/%s--%s:PT%sH/relative_humidity_%s:%s/%s,%s/json?use_decluttered=true',
  2308.                 $this->apiBaseUrl,
  2309.                 $startDate,
  2310.                 $endDate,
  2311.                 $duration,
  2312.                 $level,
  2313.                 $unit,
  2314.                 $latitude,
  2315.                 $longitude
  2316.             );
  2317.             $client = new Client(['verify' => false]);
  2318.             $response $client->request('GET'$url, [
  2319.                 'auth' => [$this->username$this->password],
  2320.             ]);
  2321.             $statusCode $response->getStatusCode();
  2322.             $data json_decode($response->getBody(), true);
  2323.             // Set the data to Redis cache
  2324.             $this->redisCache->set($cacheKey$data);
  2325.             return $data;
  2326.         } catch (GuzzleHttp\Exception\RequestException $e) {
  2327.             return throw new \Exception($e->getMessage());
  2328.         } catch (\Exception $e) {
  2329.             return throw new \Exception($e->getMessage());
  2330.         }
  2331.     }
  2332.     /**
  2333.      * Fog
  2334.      *
  2335.      * @param array $coordinates An array of coordinates in the format [[lat1, long1], [lat2, long2], ...]
  2336.      * @param string $startDate The start date for the forecast
  2337.      * @param string $endDate The start date for the forecast
  2338.      * @param string $interval Hours gap between search results
  2339.      * @param string $intervalType type of interval (default: 'hour')
  2340.      * @param string $format The format of the forecast data (default: 'json')
  2341.      * @return array The model forecast data
  2342.      * @throws \InvalidArgumentException If any of the parameters are invalid or missing
  2343.      */
  2344.     public function getFog(array $coordinatesstring $startDatestring $endDatestring $interval '1'$unit 'km'$translator,  string $format "json")
  2345.     {
  2346.         try {
  2347.             if (count($coordinates) > 1) {
  2348.                 // Validate the input parameters
  2349.                 foreach ($coordinates as $coordinate) {
  2350.                     if (count($coordinate) < || !is_numeric($coordinate[0]) || !is_numeric($coordinate[1])) {
  2351.                         throw new \InvalidArgumentException('Invalid coordinates');
  2352.                     }
  2353.                 }
  2354.                 if (empty($startDate)) {
  2355.                     return ["success" => false"message" => $translator->trans("invalid_dates")];
  2356.                 }
  2357.                 if (empty($endDate)) {
  2358.                     return ["success" => false"message" => $translator->trans("invalid_dates")];
  2359.                 }
  2360.                 if (!is_numeric($interval)) {
  2361.                     return ["success" => false"message" => $translator->trans("interval_should_be_numeric")];
  2362.                 }
  2363.                 $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  2364.                 $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  2365.                 // Create a Redis key for the cache            
  2366.                 $cacheKey sprintf('visibility:%s,is_fog_%sh:idx'implode('_'array_map(function ($coordinate) {
  2367.                     return implode('_'$coordinate);
  2368.                 }, $coordinates)), (($startDate) . '-' $endDate $interval $unit));
  2369.                 // Try to get the data from Redis cache
  2370.                 $cachedData $this->redisCache->get($cacheKey);
  2371.                 if ($cachedData !== null) {
  2372.                     // Return the cached data if available
  2373.                     return $cachedData;
  2374.                 }
  2375.                 // If cache is empty, get the data from the API
  2376.                 $coordinateString implode('+'array_map(fn($coords) => implode(','$coords), $coordinates));
  2377.                 // If cache is empty, get the data from the API           
  2378.                 $url sprintf(
  2379.                     '%s/%s--%s:PT%sH/visibility:%s,is_fog_%sh:idx/%s/json?use_decluttered=true',
  2380.                     $this->apiBaseUrl,
  2381.                     $startDate,
  2382.                     $endDate,
  2383.                     $interval,
  2384.                     $unit,
  2385.                     $interval,
  2386.                     $coordinateString
  2387.                 );
  2388.                 $client = new Client(['verify' => false]);
  2389.                 $response $client->request('GET'$url, [
  2390.                     'auth' => [$this->username$this->password],
  2391.                 ]);
  2392.                 $statusCode $response->getStatusCode();
  2393.                 $data json_decode($response->getBody(), true);
  2394.                 // Set the data to Redis cache
  2395.                 $this->redisCache->set($cacheKey$data);
  2396.                 return $data;
  2397.             } else {
  2398.                 // Validate the input parameters
  2399.                 if (empty($coordinates)) {
  2400.                     return ["success" => false"message" => $translator->trans("invalid_coordinates")];
  2401.                 }
  2402.                 if (!is_array($coordinates) || count($coordinates) < 1) {
  2403.                     return ["success" => false"message" => $translator->trans("coordinates_should_be_a_non_empty_array")];
  2404.                 }
  2405.                 $latitude $coordinates[0][0] ?? null;
  2406.                 $longitude $coordinates[0][1] ?? null;
  2407.                 if (!is_numeric($latitude) || $latitude < -90 || $latitude 90) {
  2408.                     return ["success" => false"message" => $translator->trans("invalid_latitude")];
  2409.                 }
  2410.                 if (!is_numeric($longitude) || $longitude < -180 || $longitude 180) {
  2411.                     return ["success" => false"message" => $translator->trans("invalid_longitude")];
  2412.                 }
  2413.                 if (empty($startDate)) {
  2414.                     return ["success" => false"message" => $translator->trans("invalid_dates")];
  2415.                 }
  2416.                 if (empty($endDate)) {
  2417.                     return ["success" => false"message" => $translator->trans("invalid_dates")];
  2418.                 }
  2419.                 if (!is_numeric($interval)) {
  2420.                     return ["success" => false"message" => $translator->trans("interval_should_be_numeric")];
  2421.                 }
  2422.                 $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  2423.                 $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  2424.                 // Create a Redis key for the cache            
  2425.                 $cacheKey sprintf('visibility:%s,is_fog_%sh:idx'implode('_'array_map(function ($coordinate) {
  2426.                     return implode('_'$coordinate);
  2427.                 }, $coordinates)), (($startDate) . '-' $endDate $interval $unit));
  2428.                 // Try to get the data from Redis cache
  2429.                 $cachedData $this->redisCache->get($cacheKey);
  2430.                 if ($cachedData !== null) {
  2431.                     // Return the cached data if available
  2432.                     return $cachedData;
  2433.                 }
  2434.                 // If cache is empty, get the data from the API           
  2435.                 $url sprintf(
  2436.                     '%s/%s--%s:PT%sH/visibility:%s,is_fog_%sh:idx/%s,%s/json?use_decluttered=true',
  2437.                     $this->apiBaseUrl,
  2438.                     $startDate,
  2439.                     $endDate,
  2440.                     $interval,
  2441.                     $unit,
  2442.                     $interval,
  2443.                     $latitude,
  2444.                     $longitude
  2445.                 );
  2446.                 $client = new Client(['verify' => false]);
  2447.                 $response $client->request('GET'$url, [
  2448.                     'auth' => [$this->username$this->password],
  2449.                 ]);
  2450.                 $statusCode $response->getStatusCode();
  2451.                 $data json_decode($response->getBody(), true);
  2452.                 // Set the data to Redis cache
  2453.                 $this->redisCache->set($cacheKey$data);
  2454.                 return $data;
  2455.             }
  2456.         } catch (GuzzleHttp\Exception\RequestException $e) {
  2457.             return throw new \Exception($e->getMessage());
  2458.         } catch (\Exception $e) {
  2459.             return throw new \Exception($e->getMessage());
  2460.         }
  2461.     }
  2462.     /**
  2463.      * Icing Potential
  2464.      *
  2465.      * @param array $coordinates An array of coordinates in the format [[lat1, long1], [lat2, long2], ...]
  2466.      * @param string $startDate The start date for the forecast
  2467.      * @param string $interval gap between duration
  2468.      * @param string $duration The duration for the forecast (default: '24')
  2469.      * @param string $intervalType type of interval (default: 'hour')
  2470.      * @param string $format The format of the forecast data (default: 'json')
  2471.      * @return array The model forecast data
  2472.      * @throws \InvalidArgumentException If any of the parameters are invalid or missing
  2473.      */
  2474.     public function getIcingPotential(array $coordinatesstring $startDatestring $duration '24'$interval '1'$level '300hPa'$translatorstring $format "json")
  2475.     {
  2476.         try {
  2477.             // Validate the input parameters
  2478.             if (empty($coordinates)) {
  2479.                 return ["success" => false"message" => $translator->trans("invalid_coordinates")];
  2480.             }
  2481.             if (!is_array($coordinates) || count($coordinates) < 1) {
  2482.                 return ["success" => false"message" => $translator->trans("coordinates_should_be_a_non_empty_array")];
  2483.             }
  2484.             $latitude $coordinates[0][0] ?? null;
  2485.             $longitude $coordinates[0][1] ?? null;
  2486.             if (!is_numeric($latitude) || $latitude < -90 || $latitude 90) {
  2487.                 return ["success" => false"message" => $translator->trans("invalid_latitude")];
  2488.             }
  2489.             if (!is_numeric($longitude) || $longitude < -180 || $longitude 180) {
  2490.                 return ["success" => false"message" => $translator->trans("invalid_longitude")];
  2491.             }
  2492.             if (empty($startDate)) {
  2493.                 return ["success" => false"message" => $translator->trans("invalid_dates")];
  2494.             }
  2495.             if (!is_numeric($duration)) {
  2496.                 return ["success" => false"message" => $translator->trans("duration_should_be_numeric")];
  2497.             }
  2498.             if (!is_numeric($interval)) {
  2499.                 return ["success" => false"message" => $translator->trans("interval_should_be_numeric")];
  2500.             }
  2501.             if (!in_array($level, ['1000hPa''975hPa''950hPa''925hPa''900hPa''875hPa''850hPa''825hPa''800hPa''775hPa''750hPa''700hPa''650hPa''600hPa''550hPa''500hPa''450hPa''400hPa''350hPa''300hPa'])) {
  2502.                 return ["success" => false"message" => $translator->trans("invalid_level")];
  2503.             }
  2504.             $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  2505.             // Create a Redis key for the cache            
  2506.             $cacheKey sprintf('icing_potential_%s_%s'implode('_'array_map(function ($coordinate) {
  2507.                 return implode('_'$coordinate);
  2508.             }, $coordinates)), (($startDate) . $duration $interval $level));
  2509.             // Try to get the data from Redis cache
  2510.             $cachedData $this->redisCache->get($cacheKey);
  2511.             if ($cachedData !== null) {
  2512.                 // Return the cached data if available
  2513.                 return $cachedData;
  2514.             }
  2515.             // If cache is empty, get the data from the API
  2516.             //https://api.meteomatics.com/2023-07-22T00:00:00ZP5D:PT1H/icing_potential_300hPa:idx,icing_potential_500hPa:idx,icing_potential_800hPa:idx/47.457625,8.555272/html
  2517.             $url sprintf(
  2518.                 '%s/%sP%sD:PT%sH/icing_potential_%s:idx/%s,%s/json?use_decluttered=true',
  2519.                 $this->apiBaseUrl,
  2520.                 $startDate,
  2521.                 $duration,
  2522.                 $interval,
  2523.                 $level,
  2524.                 $latitude,
  2525.                 $longitude
  2526.             );
  2527.             $client = new Client(['verify' => false]);
  2528.             $response $client->request('GET'$url, [
  2529.                 'auth' => [$this->username$this->password],
  2530.             ]);
  2531.             $statusCode $response->getStatusCode();
  2532.             $data json_decode($response->getBody(), true);
  2533.             // Set the data to Redis cache
  2534.             $this->redisCache->set($cacheKey$data);
  2535.             return $data;
  2536.         } catch (GuzzleHttp\Exception\RequestException $e) {
  2537.             return throw new \Exception($e->getMessage());
  2538.         } catch (\Exception $e) {
  2539.             return throw new \Exception($e->getMessage());
  2540.         }
  2541.     }
  2542.     /**
  2543.      * Wind Gust
  2544.      *
  2545.      * @param array $coordinates An array of coordinates in the format [[lat1, long1], [lat2, long2], ...]
  2546.      * @param string $startDate The start date for the forecast
  2547.      * @param string $interval Gap between duration
  2548.      * @param string $duration The duration for the forecast (default: '24')
  2549.      * @param string $intervalType type of interval (default: 'hour')
  2550.      * @param string $format The format of the forecast data (default: 'json')
  2551.      * @return array The model forecast data
  2552.      * @throws \InvalidArgumentException If any of the parameters are invalid or missing
  2553.      */
  2554.     //https://api.meteomatics.com/2023-07-11T00:00:00ZP2D:PT3H/wind_dir_10m:d,wind_dir_700hPa:d/47.412164,9.340652/csv
  2555.     public function getWindGust(array $coordinatesstring $startDatestring $duration '24'string $interval '1'$height '100'$translatorstring $format "json")
  2556.     {
  2557.         try {
  2558.             // Validate the input parameters
  2559.             if (empty($coordinates)) {
  2560.                 return ["success" => false"message" => $translator->trans("invalid_coordinates")];
  2561.             }
  2562.             if (!is_array($coordinates) || count($coordinates) < 1) {
  2563.                 return ["success" => false"message" => $translator->trans("coordinates_should_be_a_non_empty_array")];
  2564.             }
  2565.             $latitude $coordinates[0][0] ?? null;
  2566.             $longitude $coordinates[0][1] ?? null;
  2567.             if (!is_numeric($latitude) || $latitude < -90 || $latitude 90) {
  2568.                 return ["success" => false"message" => $translator->trans("invalid_latitude")];
  2569.             }
  2570.             if (!is_numeric($longitude) || $longitude < -180 || $longitude 180) {
  2571.                 return ["success" => false"message" => $translator->trans("invalid_longitude")];
  2572.             }
  2573.             if (empty($startDate)) {
  2574.                 return ["success" => false"message" => $translator->trans("invalid_dates")];
  2575.             }
  2576.             if (!is_numeric($duration)) {
  2577.                 return ["success" => false"message" => $translator->trans("duration_should_be_numeric")];
  2578.             }
  2579.             if (!in_array($duration, ['1''3''6''12''24'])) {
  2580.                 return ["success" => false"message" => $translator->trans("invalid_duration")];
  2581.             }
  2582.             if (!is_numeric($interval)) {
  2583.                 return ["success" => false"message" => $translator->trans("interval_should_be_numeric")];
  2584.             }
  2585.             $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  2586.             // Create a Redis key for the cache            
  2587.             $cacheKey sprintf('wind_gusts_%s_%s'implode('_'array_map(function ($coordinate) {
  2588.                 return implode('_'$coordinate);
  2589.             }, $coordinates)), (($startDate) . $duration $interval));
  2590.             // Try to get the data from Redis cache
  2591.             $cachedData $this->redisCache->get($cacheKey);
  2592.             if ($cachedData !== null) {
  2593.                 // Return the cached data if available
  2594.                 return $cachedData;
  2595.             }
  2596.             // If cache is empty, get the data from the API
  2597.             // https://api.meteomatics.com/2023-07-22T00:00:00ZP4D:PT3H/wind_speed_mean_100m_3h:ms,wind_gusts_100m_3h:ms/47.412164,9.340652/html
  2598.             $url sprintf(
  2599.                 '%s/%sP%sD:PT%sH/wind_gusts_%s_%sh:ms/%s,%s/json?use_decluttered=true',
  2600.                 $this->apiBaseUrl,
  2601.                 $startDate,
  2602.                 $duration,
  2603.                 $interval,
  2604.                 $height,
  2605.                 $interval,
  2606.                 $latitude,
  2607.                 $longitude
  2608.             );
  2609.             $client = new Client(['verify' => false]);
  2610.             $response $client->request('GET'$url, [
  2611.                 'auth' => [$this->username$this->password],
  2612.             ]);
  2613.             $statusCode $response->getStatusCode();
  2614.             $data json_decode($response->getBody(), true);
  2615.             // Set the data to Redis cache
  2616.             $this->redisCache->set($cacheKey$data);
  2617.             return $data;
  2618.         } catch (GuzzleHttp\Exception\RequestException $e) {
  2619.             return throw new \Exception($e->getMessage());
  2620.         } catch (\Exception $e) {
  2621.             return throw new \Exception($e->getMessage());
  2622.         }
  2623.     }
  2624.     /**
  2625.      * Lightning Strikes
  2626.      *
  2627.      * @param array $coordinates An array of coordinates in the format [[lat1, long1], [lat2, long2], ...]    
  2628.      * @throws \InvalidArgumentException If any of the parameters are invalid or missing
  2629.      */
  2630.     public function getLightningStrikes(array $coordinates)
  2631.     {
  2632.         try {
  2633.             // Validate the input parameters
  2634.             if (empty($coordinates)) {
  2635.                 throw new \InvalidArgumentException('Invalid coordinates');
  2636.             }
  2637.             // Create a Redis key for the cache            
  2638.             $cacheKey sprintf('lighting_strikes_%s'implode('_'array_map(function ($coordinate) {
  2639.                 return $coordinate;
  2640.             }, $coordinates)));
  2641.             // Try to get the data from Redis cache
  2642.             $cachedData $this->redisCache->get($cacheKey);
  2643.             if ($cachedData !== null) {
  2644.                 // Return the cached data if available
  2645.                 // return $cachedData;
  2646.             }
  2647.             // If cache is empty, get the data from the API
  2648.             // https://api.meteomatics.com/wfs?SERVICE=WFS&VERSION=1.0.0&REQUEST=GetFeature&TYPENAME=lightnings&BBOX=5.77,45.74,10.65,47.89
  2649.             $url sprintf(
  2650.                 '%s/wfs?SERVICE=WFS&VERSION=1.0.0&REQUEST=GetFeature&TYPENAME=lightnings&BBOX=%s&use_decluttered=true',
  2651.                 $this->apiBaseUrl,
  2652.                 implode(","$coordinates)
  2653.             );
  2654.             $client = new Client(['verify' => false]);
  2655.             $response $client->request('GET'$url, [
  2656.                 'auth' => [$this->username$this->password],
  2657.             ]);
  2658.             $statusCode $response->getStatusCode();
  2659.             $data $response->getBody();
  2660.             // Set the data to Redis cache
  2661.             $this->redisCache->set($cacheKey$data);
  2662.             return $data;
  2663.         } catch (GuzzleHttp\Exception\RequestException $e) {
  2664.             return throw new \Exception($e->getMessage());
  2665.         } catch (\Exception $e) {
  2666.             return throw new \Exception($e->getMessage());
  2667.         }
  2668.     }
  2669.     /**
  2670.      * Fetches Turbulence data from the API or cache
  2671.      *
  2672.      * @param string $datetime The date and time in the format "YYYY-MM-DDTHH:MM:SSZ"
  2673.      * @return mixed The Turbulence data if available, or an error response
  2674.      * @throws \InvalidArgumentException If any of the parameters are invalid or missing
  2675.      */
  2676.     public function getTurbulenceData(string $datetime)
  2677.     {
  2678.         try {
  2679.             // Validate the input parameter
  2680.             if (empty($datetime)) {
  2681.                 throw new \InvalidArgumentException('Invalid datetime');
  2682.             }
  2683.             $datetime date('Y-m-d\TH:i:s.v\Z', (strtotime($datetime)));
  2684.             // Create a Redis key for the cache
  2685.             $cacheKey sprintf('turbulence_%s'$datetime);
  2686.             // Try to get the data from Redis cache
  2687.             $cachedData $this->redisCache->get($cacheKey);
  2688.             if ($cachedData !== null) {
  2689.                 // Return the cached data if available
  2690.                 //return $cachedData;
  2691.             }
  2692.             $client = new Client(['verify' => false]);
  2693.             // If cache is empty, get the data from the API           
  2694.             $url sprintf(
  2695.                 '%s/%s/turbulence_cape:m23s/global:1,1/html_map?use_decluttered=true',
  2696.                 $this->apiBaseUrl,
  2697.                 $datetime
  2698.             );
  2699.             // Make an HTTP request to the API
  2700.             $response $client->request('GET'$url, [
  2701.                 'auth' => [$this->username$this->password],
  2702.             ]);
  2703.             $statusCode $response->getStatusCode();
  2704.             $data json_decode($response->getBody(), true);
  2705.             // Set the data to Redis cache
  2706.             $this->redisCache->set($cacheKey$data);
  2707.             return $data;
  2708.         } catch (GuzzleHttp\Exception\RequestException $e) {
  2709.             // Handle Guzzle HTTP request exceptions and create and return an error response            
  2710.             return throw new \Exception($e->getMessage());
  2711.         } catch (\Exception $e) {
  2712.             // Handle other exceptions and create and return an error response
  2713.             return throw new \Exception($e->getMessage());
  2714.         }
  2715.     }
  2716.     /**
  2717.      * Fetches SIGMET data from the API or cache
  2718.      *
  2719.      * @param string $datetime The date and time in the format "YYYY-MM-DDTHH:MM:SSZ"
  2720.      * @return mixed The SIGMET data if available, or an error response
  2721.      * @throws \InvalidArgumentException If any of the parameters are invalid or missing
  2722.      */
  2723.     public function getSigmetData(string $datetime$translator)
  2724.     {
  2725.         try {
  2726.             // Validate the input parameter
  2727.             if (empty($datetime)) {
  2728.                 return ["success" => false"message" => $translator->trans("invalid_datetime")];
  2729.             }
  2730.             $datetime date('Y-m-d\TH:i:s.v\Z', (strtotime($datetime)));
  2731.             // Create a Redis key for the cache
  2732.             $cacheKey sprintf('sigmet_%s'$datetime);
  2733.             // Try to get the data from Redis cache
  2734.             $cachedData $this->redisCache->get($cacheKey);
  2735.             if ($cachedData !== null) {
  2736.                 // Return the cached data if available
  2737.                 return $cachedData;
  2738.             }
  2739.             // If cache is empty, get the data from the API            
  2740.             $url sprintf(
  2741.                 '%s/mvt/aviation_reports/sigmet:0/style.json?datetime=%s&use_decluttered=true',
  2742.                 $this->apiBaseUrl,
  2743.                 $datetime
  2744.             );
  2745.             $client = new Client(['verify' => false]);
  2746.             // Make an HTTP request to the API
  2747.             $response $client->request('GET'$url, [
  2748.                 'auth' => [$this->username$this->password],
  2749.             ]);
  2750.             $statusCode $response->getStatusCode();
  2751.             $data json_decode($response->getBody(), true);
  2752.             // Set the data to Redis cache
  2753.             $this->redisCache->set($cacheKey$data);
  2754.             return $data;
  2755.         } catch (GuzzleHttp\Exception\RequestException $e) {
  2756.             // Handle Guzzle HTTP request exceptions and create and return an error response
  2757.             return throw new \Exception($e->getMessage());
  2758.         } catch (\Exception $e) {
  2759.             // Handle other exceptions and create and return an error response
  2760.             return throw new \Exception($e->getMessage());
  2761.         }
  2762.     }
  2763.     /**
  2764.      * Fetches Geopotential Height data from the API or cache
  2765.      *
  2766.      * @param array $coordinates The coordinates value
  2767.      * @param string $startDate The start date and time in the format "YYYY-MM-DDTHH:MM:SSZ"
  2768.      * @param string $endDate The end date and time in the format "YYYY-MM-DDTHH:MM:SSZ"     
  2769.      * @param float $level The level value
  2770.      * @param float $interval The interval value
  2771.      * @return mixed The Geopotential Height data if available, or an error response
  2772.      * @throws \InvalidArgumentException If any of the parameters are invalid or missing
  2773.      */
  2774.     public function getGeopotentialHeightData(array $coordinatesstring $startDatestring $endDatestring $levelstring $interval$translator)
  2775.     {
  2776.         try {
  2777.             if ($coordinates) {
  2778.                 $latitude $coordinates[0][0];
  2779.                 $longitude $coordinates[0][1];
  2780.             }
  2781.             // Validate the input parameters
  2782.             if (!preg_match('/^[-]?[0-9]{1,2}\.[0-9]+/'$latitude)) {
  2783.                 return ["success" => false"message" => $translator->trans("invalid_latitude")];
  2784.             }
  2785.             if (!preg_match('/^[-]?[0-9]{1,3}\.[0-9]+/'$longitude)) {
  2786.                 return ["success" => false"message" => $translator->trans("invalid_longitude")];
  2787.             }
  2788.             if (empty($startDate) || empty($endDate)) {
  2789.                 return ["success" => false"message" => $translator->trans("invalid_dates")];;
  2790.             }
  2791.             $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  2792.             $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  2793.             if (!is_numeric($interval)) {
  2794.                 return ["success" => false"message" => $translator->trans("interval_should_be_numeric")];
  2795.             }
  2796.             if (!in_array($level, ['1000hPa''950hPa''925hPa''900hPa''850hPa''800hPa''700hPa''500hPa''300hPa''250hPa''200hPa''150hPa''100hPa''70hPa''50hPa''10hPa'])) {
  2797.                 return ["success" => false"message" => $translator->trans("invalid_level")];
  2798.             }
  2799.             // Create a Redis key for the cache
  2800.             $cacheKey sprintf('geopotential_height_%s_%s_%.6f_%.6f'$startDate$endDate$latitude$longitude);
  2801.             // Try to get the data from Redis cache
  2802.             $cachedData $this->redisCache->get($cacheKey);
  2803.             if ($cachedData !== null) {
  2804.                 // Return the cached data if available
  2805.                 return $cachedData;
  2806.             }
  2807.             // If cache is empty, get the data from the API  
  2808.             //https://api.meteomatics.com/2023-07-23T00:00:00Z--2023-07-26T00:00:00Z:PT1H/geopotential_height_500hPa:m/46.5468,7.9826/html          
  2809.             $url sprintf(
  2810.                 '%s/%s--%s:PT%sH/geopotential_height_%s:m/%s,%s/json?use_decluttered=true',
  2811.                 $this->apiBaseUrl,
  2812.                 $startDate,
  2813.                 $endDate,
  2814.                 $interval,
  2815.                 $level,
  2816.                 $latitude,
  2817.                 $longitude
  2818.             );
  2819.             $client = new Client(['verify' => false]);
  2820.             // Make an HTTP request to the API
  2821.             $response $client->request('GET'$url, [
  2822.                 'auth' => [$this->username$this->password],
  2823.             ]);
  2824.             $statusCode $response->getStatusCode();
  2825.             $data json_decode($response->getBody(), true);
  2826.             // Set the data to Redis cache
  2827.             $this->redisCache->set($cacheKey$data);
  2828.             return $data;
  2829.         } catch (GuzzleHttp\Exception\RequestException $e) {
  2830.             // Handle Guzzle HTTP request exceptions and create and return an error response
  2831.             return throw new \Exception($e->getMessage());
  2832.         } catch (\Exception $e) {
  2833.             // Handle other exceptions and create and return an error response
  2834.             return throw new \Exception($e->getMessage());
  2835.         }
  2836.     }
  2837.     /**
  2838.      * Pressure at Higher Altitudes data from the API or cache
  2839.      *
  2840.      * @param array $coordinates The coordinates value
  2841.      * @param string $startDate The start date and time in the format "YYYY-MM-DDTHH:MM:SSZ"
  2842.      * @param string $endDate The end date and time in the format "YYYY-MM-DDTHH:MM:SSZ"     
  2843.      * @param float $level The level value
  2844.      * @param float $interval The interval value
  2845.      * @return mixed The Geopotential Height data if available, or an error response
  2846.      * @throws \InvalidArgumentException If any of the parameters are invalid or missing
  2847.      */
  2848.     public function getPressureData(array $coordinatesstring $startDatestring $endDatestring $levelstring $interval)
  2849.     {
  2850.         try {
  2851.             if ($coordinates) {
  2852.                 $latitude $coordinates[0][0];
  2853.                 $longitude $coordinates[0][1];
  2854.             }
  2855.             // Validate the input parameters
  2856.             if (!preg_match('/^[-]?[0-9]{1,2}\.[0-9]+/'$latitude)) {
  2857.                 throw new \InvalidArgumentException('Invalid latitude');
  2858.             }
  2859.             if (!preg_match('/^[-]?[0-9]{1,3}\.[0-9]+/'$longitude)) {
  2860.                 throw new \InvalidArgumentException('Invalid longitude');
  2861.             }
  2862.             if (empty($startDate) || empty($endDate)) {
  2863.                 throw new \InvalidArgumentException('Invalid dates');
  2864.             }
  2865.             $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  2866.             $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  2867.             if (!is_numeric($interval)) {
  2868.                 throw new \InvalidArgumentException('Interval should be numeric');
  2869.             }
  2870.             if ($level && $level 20000) {
  2871.                 throw new \InvalidArgumentException('Invalid level it should be between 1 to 20000');
  2872.             }
  2873.             // Create a Redis key for the cache
  2874.             $cacheKey sprintf('pressure_%s_%s_%.6f_%.6f'$startDate$endDate$latitude$longitude);
  2875.             // Try to get the data from Redis cache
  2876.             $cachedData $this->redisCache->get($cacheKey);
  2877.             if ($cachedData !== null) {
  2878.                 // Return the cached data if available
  2879.                 return $cachedData;
  2880.             }
  2881.             // If cache is empty, get the data from the API  
  2882.             ///pressure_1000m:hPa/-16.489689,-68.119293/html          
  2883.             $url sprintf(
  2884.                 '%s/%s--%s:PT%sH/pressure_%sm:hPa/%s,%s/json?use_decluttered=true',
  2885.                 $this->apiBaseUrl,
  2886.                 $startDate,
  2887.                 $endDate,
  2888.                 $interval,
  2889.                 $level,
  2890.                 $latitude,
  2891.                 $longitude
  2892.             );
  2893.             $client = new Client(['verify' => false]);
  2894.             // Make an HTTP request to the API
  2895.             $response $client->request('GET'$url, [
  2896.                 'auth' => [$this->username$this->password],
  2897.             ]);
  2898.             $statusCode $response->getStatusCode();
  2899.             $data json_decode($response->getBody(), true);
  2900.             // Set the data to Redis cache
  2901.             $this->redisCache->set($cacheKey$data);
  2902.             return $data;
  2903.         } catch (GuzzleHttp\Exception\RequestException $e) {
  2904.             // Handle Guzzle HTTP request exceptions and create and return an error response           
  2905.             return throw new \Exception($e->getMessage());
  2906.         } catch (\Exception $e) {
  2907.             // Handle other exceptions and create and return an error response
  2908.             return throw new \Exception($e->getMessage());
  2909.         }
  2910.     }
  2911.     /**
  2912.      * Cloud Cover data from the API or cache
  2913.      *
  2914.      * @param array $coordinates The coordinates value
  2915.      * @param string $startDate The start date and time in the format "YYYY-MM-DDTHH:MM:SSZ"
  2916.      * @param string $endDate The end date and time in the format "YYYY-MM-DDTHH:MM:SSZ"     
  2917.      * @param float $level The level value
  2918.      * @param float $interval The interval value
  2919.      * @return mixed The Geopotential Height data if available, or an error response
  2920.      * @throws \InvalidArgumentException If any of the parameters are invalid or missing
  2921.      */
  2922.     public function getCloudCover(array $coordinatesstring $startDatestring $endDate$translator)
  2923.     {
  2924.         try {
  2925.             if ($coordinates) {
  2926.                 $latitude $coordinates[0][0];
  2927.                 $longitude $coordinates[0][1];
  2928.             }
  2929.             // Validate the input parameters
  2930.             if (!preg_match('/^[-]?[0-9]{1,2}\.[0-9]+/'$latitude)) {
  2931.                 return ["success" => false"message" => $translator->trans("invalid_latitude")];
  2932.             }
  2933.             if (!preg_match('/^[-]?[0-9]{1,3}\.[0-9]+/'$longitude)) {
  2934.                 return ["success" => false"message" => $translator->trans("invalid_longitude")];
  2935.             }
  2936.             if (empty($startDate) || empty($endDate)) {
  2937.                 return ["success" => false"message" => $translator->trans("invalid_dates")];
  2938.             }
  2939.             $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  2940.             $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  2941.             // Create a Redis key for the cache
  2942.             $cacheKey sprintf('cloud_cover_%s_%s_%.6f_%.6f'$startDate$endDate$latitude$longitude);
  2943.             // Try to get the data from Redis cache
  2944.             $cachedData $this->redisCache->get($cacheKey);
  2945.             if ($cachedData !== null) {
  2946.                 // Return the cached data if available
  2947.                 return $cachedData;
  2948.             }
  2949.             // If cache is empty, get the data from the API                
  2950.             $url sprintf(
  2951.                 '%s/%s/effective_cloud_cover:octas/%s,%s/json?use_decluttered=true',
  2952.                 $this->apiBaseUrl,
  2953.                 $startDate,
  2954.                 $latitude,
  2955.                 $longitude
  2956.             );
  2957.             $client = new Client(['verify' => false]);
  2958.             // Make an HTTP request to the API
  2959.             $response $client->request('GET'$url, [
  2960.                 'auth' => [$this->username$this->password],
  2961.             ]);
  2962.             $statusCode $response->getStatusCode();
  2963.             $data json_decode($response->getBody(), true);
  2964.             // Set the data to Redis cache
  2965.             $this->redisCache->set($cacheKey$data);
  2966.             return $data;
  2967.         } catch (GuzzleHttp\Exception\RequestException $e) {
  2968.             // Handle Guzzle HTTP request exceptions and create and return an error response           
  2969.             return throw new \Exception($e->getMessage());
  2970.         } catch (\Exception $e) {
  2971.             // Handle other exceptions and create and return an error response
  2972.             return throw new \Exception($e->getMessage());
  2973.         }
  2974.     }
  2975.     /**
  2976.      * Wind Speed vertical components data from the API or cache
  2977.      *
  2978.      * @param array $coordinates The coordinates value
  2979.      * @param string $startDate The start date and time in the format "YYYY-MM-DDTHH:MM:SSZ"
  2980.      * @param string $endDate The end date and time in the format "YYYY-MM-DDTHH:MM:SSZ"     
  2981.      * @param int $level The day value
  2982.      * @param string $level The level value
  2983.      * @param string $unit The level value
  2984.      * @param intervalInMinutes $interval The interval value
  2985.      * @return mixed The Geopotential Height data if available, or an error response
  2986.      * @throws \InvalidArgumentException If any of the parameters are invalid or missing
  2987.      */
  2988.     public function getWindSpeedVerticalComponents(array $coordinatesstring $startDateint $dayint $intervalInMinutesstring $levelstring $unit$translator)
  2989.     {
  2990.         try {
  2991.             if ($coordinates) {
  2992.                 $latitude $coordinates[0][0];
  2993.                 $longitude $coordinates[0][1];
  2994.             }
  2995.             // Validate the input parameters
  2996.             if (!preg_match('/^[-]?[0-9]{1,2}\.[0-9]+/'$latitude)) {
  2997.                 return ["success" => false"message" => $translator->trans("invalid_latitude")];
  2998.             }
  2999.             if (!preg_match('/^[-]?[0-9]{1,3}\.[0-9]+/'$longitude)) {
  3000.                 return ["success" => false"message" => $translator->trans("invalid_longitude")];
  3001.             }
  3002.             if (empty($startDate)) {
  3003.                 return ["success" => false"message" => $translator->trans("invalid_dates")];
  3004.             }
  3005.             $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  3006.             if (!is_numeric($day)) {
  3007.                 return ["success" => false"message" => $translator->trans("day_should_be_numeric")];
  3008.             }
  3009.             if (!is_numeric($intervalInMinutes)) {
  3010.                 return ["success" => false"message" => $translator->trans("interval_in_minute_should_be_numeric")];
  3011.             }
  3012.             if (!in_array($level, ['1000hPa''950hPa''925hPa''900hPa''850hPa''800hPa''700hPa''500hPa''300hPa''250hPa''200hPa''150hPa''100hPa''70hPa'])) {
  3013.                 return ["success" => false"message" => $translator->trans("invalid_level")];
  3014.             }
  3015.             // Create a Redis key for the cache
  3016.             $cacheKey sprintf('wind_speed_vertical_%s_%s_%s_%s_%.6f_%.6f'$startDate$day$level$unit$latitude$longitude);
  3017.             // Try to get the data from Redis cache
  3018.             $cachedData $this->redisCache->get($cacheKey);
  3019.             if ($cachedData !== null) {
  3020.                 // Return the cached data if available
  3021.                 return $cachedData;
  3022.             }
  3023.             // If cache is empty, get the data from the API                
  3024.             $url sprintf(
  3025.                 '%s/%sP%sD:PT%sM/wind_speed_w_%s:%s/%s,%s/json?use_decluttered=true',
  3026.                 $this->apiBaseUrl,
  3027.                 $startDate,
  3028.                 $day,
  3029.                 $intervalInMinutes,
  3030.                 $level,
  3031.                 $unit,
  3032.                 $latitude,
  3033.                 $longitude
  3034.             );
  3035.             $client = new Client(['verify' => false]);
  3036.             // Make an HTTP request to the API
  3037.             $response $client->request('GET'$url, [
  3038.                 'auth' => [$this->username$this->password],
  3039.             ]);
  3040.             $data json_decode($response->getBody(), true);
  3041.             // Set the data to Redis cache
  3042.             $this->redisCache->set($cacheKey$data);
  3043.             return $data;
  3044.         } catch (GuzzleHttp\Exception\RequestException $e) {
  3045.             // Handle Guzzle HTTP request exceptions and create and return an error response           
  3046.             return throw new \Exception($e->getMessage());
  3047.         } catch (\Exception $e) {
  3048.             // Handle other exceptions and create and return an error response
  3049.             return throw new \Exception($e->getMessage());
  3050.         }
  3051.     }
  3052.     /**
  3053.      * Wind Power data from the API or cache
  3054.      * @param array $coordinates The coordinates value    
  3055.      * @param string $startDate The start date and time in the format "YYYY-MM-DDTHH:MM:SSZ"
  3056.      * @param string $endDate The end date and time in the format "YYYY-MM-DDTHH:MM:SSZ"       
  3057.      * @param int $intervalInMinutes The interval value
  3058.      * @param int $height The height value
  3059.      * @param float $unit The interval value
  3060.      * @return mixed The Geopotential Height data if available, or an error response
  3061.      * @throws \InvalidArgumentException If any of the parameters are invalid or missing
  3062.      */
  3063.     public function getWindPower(array $coordinatesstring $startDatestring $endDateint $heightint $intervalInMinutesstring $unitstring $turbine_id$translator)
  3064.     {
  3065.         try {
  3066.             if ($coordinates) {
  3067.                 $latitude $coordinates[0][0];
  3068.                 $longitude $coordinates[0][1];
  3069.             }
  3070.             // Validate the input parameters
  3071.             if (!preg_match('/^[-]?[0-9]{1,2}\.[0-9]+/'$latitude)) {
  3072.                 return ["success" => false"message" => $translator->trans("invalid_latitude")];
  3073.             }
  3074.             if (!preg_match('/^[-]?[0-9]{1,3}\.[0-9]+/'$longitude)) {
  3075.                 return ["success" => false"message" => $translator->trans("invalid_longitude")];
  3076.             }
  3077.             if (empty($startDate)) {
  3078.                 return ["success" => false"message" => $translator->trans("invalid_dates")];
  3079.             }
  3080.             $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  3081.             $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  3082.             if (!is_numeric($intervalInMinutes)) {
  3083.                 return ["success" => false"message" => $translator->trans("interval_should_be_numeric")];
  3084.             }
  3085.             if (!is_numeric($height)) {
  3086.                 return ["success" => false"message" => $translator->trans("height_should_be_numeric")];
  3087.             }
  3088.             // Create a Redis key for the cache
  3089.             $cacheKey sprintf('wind_power_%s_%s_%s_%s_%s'$startDate$endDate$height$unit$intervalInMinutes);
  3090.             // Try to get the data from Redis cache
  3091.             $cachedData $this->redisCache->get($cacheKey);
  3092.             if ($cachedData !== null) {
  3093.                 // Return the cached data if available
  3094.                 return $cachedData;
  3095.             }
  3096.             // If cache is empty, get the data from the API                
  3097.             $url sprintf(
  3098.                 '%s/%s--%s:PT%sM/wind_power_turbine_%s_hub_height_%sm:%s,wind_power_turbine_siemens_swt_2_3_93_2300_hub_height_%sm:%s,wind_power_turbine_enercon_e66_2000_hub_height_%sm:%s/%s,%s/json?use_decluttered=true',
  3099.                 $this->apiBaseUrl,
  3100.                 $startDate,
  3101.                 $endDate,
  3102.                 $intervalInMinutes,
  3103.                 $turbine_id,
  3104.                 $height,
  3105.                 $unit,
  3106.                 $height,
  3107.                 $unit,
  3108.                 $height,
  3109.                 $unit,
  3110.                 $latitude,
  3111.                 $longitude
  3112.             );
  3113.             $client = new Client(['verify' => false]);
  3114.             // Make an HTTP request to the API
  3115.             $response $client->request('GET'$url, [
  3116.                 'auth' => [$this->username$this->password],
  3117.             ]);
  3118.             $data json_decode($response->getBody(), true);
  3119.             // Set the data to Redis cache
  3120.             $this->redisCache->set($cacheKey$data);
  3121.             return $data;
  3122.         } catch (GuzzleHttp\Exception\RequestException $e) {
  3123.             // Handle Guzzle HTTP request exceptions and create and return an error response           
  3124.             return throw new \Exception($e->getMessage());
  3125.         } catch (\Exception $e) {
  3126.             // Handle other exceptions and create and return an error response
  3127.             return throw new \Exception($e->getMessage());
  3128.         }
  3129.     }
  3130.     /**
  3131.      * Radiation data from the API or cache
  3132.      * @param array $coordinates The coordinates value    
  3133.      * @param string $startDate The start date and time in the format "YYYY-MM-DDTHH:MM:SSZ"           
  3134.      * @param int $intervalInHours The interval value
  3135.      * @param int $day The day value
  3136.      * @param string $typeOfRadiation should be one of these "clear_sky_rad","diffuse_rad","direct_rad","global_rad"
  3137.      * @return mixed The Geopotential Height data if available, or an error response
  3138.      * @throws \InvalidArgumentException If any of the parameters are invalid or missing
  3139.      */
  3140.     public function getRadiation(array $coordinatesstring $startDateint $dayint $intervalInHoursstring $typeOfRadiation$translator)
  3141.     {
  3142.         try {
  3143.             if ($coordinates) {
  3144.                 $latitude $coordinates[0][0];
  3145.                 $longitude $coordinates[0][1];
  3146.             }
  3147.             // Validate the input parameters
  3148.             if (!preg_match('/^[-]?[0-9]{1,2}\.[0-9]+/'$latitude)) {
  3149.                 return ["success" => false"message" => $translator->trans("invalid_latitude")];
  3150.             }
  3151.             if (!preg_match('/^[-]?[0-9]{1,3}\.[0-9]+/'$longitude)) {
  3152.                 return ["success" => false"message" => $translator->trans("invalid_longitude")];
  3153.             }
  3154.             if (empty($startDate)) {
  3155.                 return ["success" => false"message" => $translator->trans("invalid_dates")];
  3156.             }
  3157.             $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  3158.             if (!is_numeric($intervalInHours)) {
  3159.                 return ["success" => false"message" => $translator->trans("interval_in_hour_should_be_numeric")];
  3160.             }
  3161.             if (!is_numeric($day)) {
  3162.                 return ["success" => false"message" => $translator->trans("day_should_be_numeric")];
  3163.             }
  3164.             if (!in_array($typeOfRadiation, ["clear_sky_rad""diffuse_rad""direct_rad""global_rad"])) {
  3165.                 // throw new \InvalidArgumentException('Radiation type should be one of these "clear_sky_rad","diffuse_rad","direct_rad","global_rad"');
  3166.                 return ["success" => false"message" => $translator->trans("Radiation_should_be_type:'clear_sky_rad','diffuse_rad','direct_rad','global_rad'")];
  3167.             }
  3168.             // Create a Redis key for the cache
  3169.             $cacheKey sprintf('radiation_%s_%s_%s_%s_%s_%s'$startDate$day$intervalInHours$typeOfRadiation$latitude$longitude);
  3170.             // Try to get the data from Redis cache
  3171.             $cachedData $this->redisCache->get($cacheKey);
  3172.             if ($cachedData !== null) {
  3173.                 // Return the cached data if available
  3174.                 return $cachedData;
  3175.             }
  3176.             // If cache is empty, get the data from the API                
  3177.             $url sprintf(
  3178.                 '%s/%sP%sD:PT%sH/%s:W/%s,%s/json?use_decluttered=true',
  3179.                 $this->apiBaseUrl,
  3180.                 $startDate,
  3181.                 $day,
  3182.                 $intervalInHours,
  3183.                 $typeOfRadiation,
  3184.                 $latitude,
  3185.                 $longitude
  3186.             );
  3187.             $client = new Client(['verify' => false]);
  3188.             // Make an HTTP request to the API
  3189.             $response $client->request('GET'$url, [
  3190.                 'auth' => [$this->username$this->password],
  3191.             ]);
  3192.             $data json_decode($response->getBody(), true);
  3193.             // Set the data to Redis cache
  3194.             $this->redisCache->set($cacheKey$data);
  3195.             return $data;
  3196.         } catch (GuzzleHttp\Exception\RequestException $e) {
  3197.             // Handle Guzzle HTTP request exceptions and create and return an error response           
  3198.             return throw new \Exception($e->getMessage());
  3199.         } catch (\Exception $e) {
  3200.             // Handle other exceptions and create and return an error response
  3201.             return throw new \Exception($e->getMessage());
  3202.         }
  3203.     }
  3204.     /**
  3205.      * Solar Power data from the API or cache
  3206.      * @param array $coordinates The coordinates value    
  3207.      * @param string $startDate The start date and time in the format "YYYY-MM-DDTHH:MM:SSZ" 
  3208.      * @param string $endDate The start date and time in the format "YYYY-MM-DDTHH:MM:SSZ"           
  3209.      * @param int $intervalInHours The interval value
  3210.      * @param int $hour The hour value
  3211.      * @param string $unit The unit value
  3212.      * @param string $specification would be like this "installed_capacity_<capacity>"
  3213.      * @return mixed The Geopotential Height data if available, or an error response
  3214.      * @throws \InvalidArgumentException If any of the parameters are invalid or missing
  3215.      */
  3216.     public function getSolarPower(array $coordinatesstring $startDatestring $endDateint $intervalInHoursstring $hourstring $unitstring $specification$translator)
  3217.     {
  3218.         try {
  3219.             if ($coordinates) {
  3220.                 $latitude $coordinates[0][0];
  3221.                 $longitude $coordinates[0][1];
  3222.             }
  3223.             // Validate the input parameters
  3224.             if (!preg_match('/^[-]?[0-9]{1,2}\.[0-9]+/'$latitude)) {
  3225.                 return ["success" => false"message" => $translator->trans("invalid_latitude")];
  3226.             }
  3227.             if (!preg_match('/^[-]?[0-9]{1,3}\.[0-9]+/'$longitude)) {
  3228.                 return ["success" => false"message" => $translator->trans("invalid_longitude")];
  3229.             }
  3230.             if (empty($startDate)) {
  3231.                 return ["success" => false"message" => $translator->trans("invalid_start_date")];
  3232.             }
  3233.             if (empty($endDate)) {
  3234.                 return ["success" => false"message" => $translator->trans("invalid_end_date")];
  3235.             }
  3236.             $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  3237.             $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  3238.             if (!is_numeric($intervalInHours)) {
  3239.                 return ["success" => false"message" => $translator->trans("interval_should_be_numeric")];;
  3240.             }
  3241.             if (!is_numeric($hour)) {
  3242.                 return ["success" => false"message" => $translator->trans("hours_should_be_numeric")];
  3243.             }
  3244.             // Create a Redis key for the cache
  3245.             $cacheKey sprintf('solar_power_%s_%s_%s_%s_%s_%s_%s'$specification$startDate$endDate$unit$hour$latitude$longitude);
  3246.             // Try to get the data from Redis cache
  3247.             $cachedData $this->redisCache->get($cacheKey);
  3248.             if ($cachedData !== null) {
  3249.                 // Return the cached data if available
  3250.                 return $cachedData;
  3251.             }
  3252.             // If cache is empty, get the data from the API                
  3253.             $url sprintf(
  3254.                 '%s/%s--%s:PT%sH/solar_power_%s:%s/%s,%s/json?use_decluttered=true',
  3255.                 $this->apiBaseUrl,
  3256.                 $startDate,
  3257.                 $endDate,
  3258.                 $hour,
  3259.                 $specification,
  3260.                 $unit,
  3261.                 $latitude,
  3262.                 $longitude
  3263.             );
  3264.             // p_r($url);
  3265.             // exit;
  3266.             $client = new Client(['verify' => false]);
  3267.             // Make an HTTP request to the API
  3268.             $response $client->request('GET'$url, [
  3269.                 'auth' => [$this->username$this->password],
  3270.             ]);
  3271.             $data json_decode($response->getBody(), true);
  3272.             // Set the data to Redis cache
  3273.             $this->redisCache->set($cacheKey$data);
  3274.             return $data;
  3275.         } catch (GuzzleHttp\Exception\RequestException $e) {
  3276.             // Handle Guzzle HTTP request exceptions and create and return an error response           
  3277.             return throw new \Exception($e->getMessage());
  3278.         } catch (\Exception $e) {
  3279.             // Handle other exceptions and create and return an error response
  3280.             return throw new \Exception($e->getMessage());
  3281.         }
  3282.     }
  3283.     /**
  3284.      * Rainfall
  3285.      *
  3286.      * @param array $coordinates An array of coordinates in the format [[lat1, long1], [lat2, long2], ...]
  3287.      * @param string $startDate The start date for the forecast
  3288.      * @param string $endDate The end date for the forecast
  3289.      * @param string $duration The duration for the forecast (default: '24')
  3290.      * @param string $intervalType type of interval (default: 'hour')
  3291.      * @param string $format The format of the forecast data (default: 'json')
  3292.      * @return array The model forecast data
  3293.      * @throws \InvalidArgumentException If any of the parameters are invalid or missing
  3294.      */
  3295.     public function getRainfall(array $coordinatesstring $startDatestring $endDatestring $duration '24'$translatorstring $format "json")
  3296.     {
  3297.         try {
  3298.             // Validate the input parameters
  3299.             if (empty($coordinates)) {
  3300.                 return ["success" => false"message" => $translator->trans("invalid_coordinates")];
  3301.             }
  3302.             if (!is_array($coordinates) || count($coordinates) < 1) {
  3303.                 return ["success" => false"message" => $translator->trans("coordinates_should_be_a_non_empty_array")];
  3304.             }
  3305.             $latitude $coordinates[0][0] ?? null;
  3306.             $longitude $coordinates[0][1] ?? null;
  3307.             if (!is_numeric($latitude) || $latitude < -90 || $latitude 90) {
  3308.                 return ["success" => false"message" => $translator->trans("invalid_latitude")];
  3309.             }
  3310.             if (!is_numeric($longitude) || $longitude < -180 || $longitude 180) {
  3311.                 return ["success" => false"message" => $translator->trans("invalid_longitude")];
  3312.             }
  3313.             if (empty($startDate) || empty($endDate)) {
  3314.                 return ["success" => false"message" => $translator->trans("invalid_dates")];
  3315.             }
  3316.             if (!is_numeric($duration)) {
  3317.                 return ["success" => false"message" => $translator->trans("duration_should_be_numeric")];
  3318.             }
  3319.             $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  3320.             $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  3321.             // Create a Redis key for the cache            
  3322.             $cacheKey sprintf('rainfall_%s_%s'implode('_'array_map(function ($coordinate) {
  3323.                 return implode('_'$coordinate);
  3324.             }, $coordinates)), (($startDate) . '-' . ($endDate) . $duration));
  3325.             // Try to get the data from Redis cache
  3326.             $cachedData $this->redisCache->get($cacheKey);
  3327.             if ($cachedData !== null) {
  3328.                 // Return the cached data if available
  3329.                 return $cachedData;
  3330.             }
  3331.             // If cache is empty, get the data from the API
  3332.             $url sprintf(
  3333.                 '%s/%s--%s:PT%sM/is_rain_%smin:idx/%s,%s/json?use_decluttered=true',
  3334.                 $this->apiBaseUrl,
  3335.                 $startDate,
  3336.                 $endDate,
  3337.                 $duration,
  3338.                 $duration,
  3339.                 $latitude,
  3340.                 $longitude
  3341.             );
  3342.             $client = new Client(['verify' => false]);
  3343.             $response $client->request('GET'$url, [
  3344.                 'auth' => [$this->username$this->password],
  3345.             ]);
  3346.             $statusCode $response->getStatusCode();
  3347.             $data json_decode($response->getBody(), true);
  3348.             // Set the data to Redis cache
  3349.             $this->redisCache->set($cacheKey$data);
  3350.             return $data;
  3351.         } catch (GuzzleHttp\Exception\RequestException $e) {
  3352.             return throw new \Exception($e->getMessage());
  3353.         } catch (\Exception $e) {
  3354.             return throw new \Exception($e->getMessage());
  3355.         }
  3356.     }
  3357.     /**
  3358.      * Dew Point
  3359.      *
  3360.      * @param array $coordinates An array of coordinates in the format [[lat1, long1], [lat2, long2], ...]
  3361.      * @param string $startDate The start date for the forecast
  3362.      * @param string $endDate The end date for the forecast
  3363.      * @param string $duration The duration for the forecast (default: '24')
  3364.      * @param string $intervalType type of interval (default: 'hour')
  3365.      * @param string $format The format of the forecast data (default: 'json')
  3366.      * @return array The model forecast data
  3367.      * @throws \InvalidArgumentException If any of the parameters are invalid or missing
  3368.      */
  3369.     public function getDewPoint(array $coordinatesstring $startDatestring $endDatestring $duration '24'$translatorstring $format "json")
  3370.     {
  3371.         try {
  3372.             // Validate the input parameters
  3373.             if (empty($coordinates)) {
  3374.                 return ["success" => false"message" => $translator->trans("invalid_coordinates")];
  3375.             }
  3376.             if (!is_array($coordinates) || count($coordinates) < 1) {
  3377.                 return ["success" => false"message" => $translator->trans("coordinates_should_be_a_non_empty_array")];
  3378.             }
  3379.             $latitude $coordinates[0][0] ?? null;
  3380.             $longitude $coordinates[0][1] ?? null;
  3381.             if (!is_numeric($latitude) || $latitude < -90 || $latitude 90) {
  3382.                 return ["success" => false"message" => $translator->trans("invalid_latitude")];
  3383.             }
  3384.             if (!is_numeric($longitude) || $longitude < -180 || $longitude 180) {
  3385.                 return ["success" => false"message" => $translator->trans("invalid_longitude")];
  3386.             }
  3387.             if (empty($startDate) || empty($endDate)) {
  3388.                 return ["success" => false"message" => $translator->trans("invalid_dates")];
  3389.             }
  3390.             if (!is_numeric($duration)) {
  3391.                 return ["success" => false"message" => $translator->trans("duration_should_be_numeric")];
  3392.             }
  3393.             $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  3394.             $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  3395.             // Create a Redis key for the cache            
  3396.             $cacheKey sprintf('dew_point_%s_%s'implode('_'array_map(function ($coordinate) {
  3397.                 return implode('_'$coordinate);
  3398.             }, $coordinates)), (($startDate) . '-' . ($endDate) . $duration));
  3399.             // Try to get the data from Redis cache
  3400.             $cachedData $this->redisCache->get($cacheKey);
  3401.             if ($cachedData !== null) {
  3402.                 // Return the cached data if available
  3403.                 return $cachedData;
  3404.             }
  3405.             // If cache is empty, get the data from the API
  3406.             $url sprintf(
  3407.                 '%s/%s--%s:PT%sH/dew_point_2m:C/%s,%s/json?use_decluttered=true',
  3408.                 $this->apiBaseUrl,
  3409.                 $startDate,
  3410.                 $endDate,
  3411.                 $duration,
  3412.                 $latitude,
  3413.                 $longitude
  3414.             );
  3415.             $client = new Client(['verify' => false]);
  3416.             $response $client->request('GET'$url, [
  3417.                 'auth' => [$this->username$this->password],
  3418.             ]);
  3419.             $statusCode $response->getStatusCode();
  3420.             $data json_decode($response->getBody(), true);
  3421.             // Set the data to Redis cache
  3422.             $this->redisCache->set($cacheKey$data);
  3423.             return $data;
  3424.         } catch (GuzzleHttp\Exception\RequestException $e) {
  3425.             return throw new \Exception($e->getMessage());
  3426.         } catch (\Exception $e) {
  3427.             return throw new \Exception($e->getMessage());
  3428.         }
  3429.     }
  3430.     /**
  3431.      * Wms
  3432.      *     
  3433.      * @throws \InvalidArgumentException If any of the parameters are invalid or missing
  3434.      */
  3435.     public function getWms($params)
  3436.     {
  3437.         try {
  3438.             // If cache is empty, get the data from the API
  3439.             $url sprintf(
  3440.                 '%s/wms?%s&use_decluttered=true',
  3441.                 $this->apiBaseUrl,
  3442.                 $params['param']
  3443.             );
  3444.             $client = new Client(['verify' => false]);
  3445.             $response $client->request('GET'$url, [
  3446.                 'auth' => [$this->username$this->password],
  3447.             ]);
  3448.             return $response->getBody();
  3449.         } catch (GuzzleHttp\Exception\RequestException $e) {
  3450.             return throw new \Exception($e->getMessage());
  3451.         } catch (\Exception $e) {
  3452.             return throw new \Exception($e->getMessage());
  3453.         }
  3454.     }
  3455.     /**
  3456.      * Compute Web Mercator bbox for a tile (z/x/y) and fetch WMS tile.
  3457.      */
  3458.     public function getWmsTileByXYZ(
  3459.         string $layers,
  3460.         string $time,
  3461.         int $z,
  3462.         int $x,
  3463.         int $y,
  3464.         string $format 'image/webp',
  3465.         ?string $model null,
  3466.         string $version '1.3.0',
  3467.         string $crs 'EPSG:3857',
  3468.         int $width 256,
  3469.         int $height 256,
  3470.         string $transparent 'true',
  3471.         string $styles ''
  3472.     ) {
  3473.         // Web Mercator constants
  3474.         $earthRadius 6378137;
  3475.         $circumference pi() * $earthRadius;
  3476.         $resolution = ($circumference 256) / pow(2$z);
  3477.         $minX = ($x 256) * $resolution $circumference 2.0;
  3478.         $minY = ($y 256) * $resolution $circumference 2.0;
  3479.         $maxX = (($x 1) * 256) * $resolution $circumference 2.0;
  3480.         $maxY = (($y 1) * 256) * $resolution $circumference 2.0;
  3481.         $bbox $minX ',' $minY ',' $maxX ',' $maxY;
  3482.         $queryParams = [
  3483.             'service' => 'WMS',
  3484.             'request' => 'GetMap',
  3485.             'layers' => $layers,
  3486.             'styles' => $styles,
  3487.             'format' => $format,
  3488.             'transparent' => $transparent,
  3489.             'version' => $version,
  3490.             'time' => $time,
  3491.             'width' => $width,
  3492.             'height' => $height,
  3493.             'crs' => $crs,
  3494.             'bbox' => $bbox,
  3495.         ];
  3496.         if (!empty($model)) {
  3497.             $queryParams['model'] = $model;
  3498.         }
  3499.         $paramString http_build_query($queryParams'''&'PHP_QUERY_RFC3986);
  3500.         return $this->getWms(['param' => $paramString]);
  3501.     }
  3502.     /**
  3503.      * Wind
  3504.      *
  3505.      * @param array $coordinates An array of coordinates in the format [[lat1, long1], [lat2, long2], ...]
  3506.      * @param string $startDate The start date for the forecast
  3507.      * @param string $endDate The end date for the forecast
  3508.      * @param string $duration The duration for the forecast (default: '24')
  3509.      * @param string $intervalType type of interval (default: 'hour')
  3510.      * @param string $format The format of the forecast data (default: 'json')
  3511.      * @return array The model forecast data
  3512.      * @throws \InvalidArgumentException If any of the parameters are invalid or missing
  3513.      */
  3514.     public function getWind(array $coordinatesstring $startDatestring $endDatestring $duration '24'$translatorstring $format "json")
  3515.     {
  3516.         try {
  3517.             // Validate the input parameters
  3518.             if (empty($coordinates)) {
  3519.                 return ["success" => false"message" => $translator->trans("invalid_coordinates")];
  3520.             }
  3521.             if (!is_array($coordinates) || count($coordinates) < 1) {
  3522.                 return ["success" => false"message" => $translator->trans("coordinates_should_be_a_non_empty_array")];
  3523.             }
  3524.             $latitude $coordinates[0][0] ?? null;
  3525.             $longitude $coordinates[0][1] ?? null;
  3526.             if (!is_numeric($latitude) || $latitude < -90 || $latitude 90) {
  3527.                 return ["success" => false"message" => $translator->trans("invalid_latitude")];
  3528.             }
  3529.             if (!is_numeric($longitude) || $longitude < -180 || $longitude 180) {
  3530.                 return ["success" => false"message" => $translator->trans("invalid_longitude")];
  3531.             }
  3532.             if (empty($startDate) || empty($endDate)) {
  3533.                 return ["success" => false"message" => $translator->trans("invalid_dates")];
  3534.             }
  3535.             if (!is_numeric($duration)) {
  3536.                 return ["success" => false"message" => $translator->trans("duration_should_be_numeric")];
  3537.             }
  3538.             $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  3539.             $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  3540.             // Create a Redis key for the cache            
  3541.             $cacheKey sprintf('wind_%s_%s'implode('_'array_map(function ($coordinate) {
  3542.                 return implode('_'$coordinate);
  3543.             }, $coordinates)), (($startDate) . '-' . ($endDate) . $duration));
  3544.             // Try to get the data from Redis cache
  3545.             $cachedData $this->redisCache->get($cacheKey);
  3546.             if ($cachedData !== null) {
  3547.                 // Return the cached data if available
  3548.                 return $cachedData;
  3549.             }
  3550.             // If cache is empty, get the data from the API
  3551.             $url sprintf(
  3552.                 '%s/%s--%s:PT%sH/wind_speed_u_10m:ms,wind_speed_v_10m:ms/%s,%s/json?use_decluttered=true',
  3553.                 $this->apiBaseUrl,
  3554.                 $startDate,
  3555.                 $endDate,
  3556.                 $duration,
  3557.                 $latitude,
  3558.                 $longitude
  3559.             );
  3560.             $client = new Client(['verify' => false]);
  3561.             $response $client->request('GET'$url, [
  3562.                 'auth' => [$this->username$this->password],
  3563.             ]);
  3564.             $statusCode $response->getStatusCode();
  3565.             $data json_decode($response->getBody(), true);
  3566.             // Set the data to Redis cache
  3567.             $this->redisCache->set($cacheKey$data);
  3568.             return $data;
  3569.         } catch (GuzzleHttp\Exception\RequestException $e) {
  3570.             return throw new \Exception($e->getMessage());
  3571.         } catch (\Exception $e) {
  3572.             return throw new \Exception($e->getMessage());
  3573.         }
  3574.     }
  3575.     /**
  3576.      * Wind by location
  3577.      *    
  3578.      * @param string $time The type of data time (e.g., '2023-09-01')
  3579.      * @param string $location The latitude and longitude of location
  3580.      * @param string $format return type of json
  3581.      * @throws \InvalidArgumentException If any parameter has an invalid data type
  3582.      */
  3583.     public function getWindByLocation(
  3584.         string $time,
  3585.         string $location,
  3586.         string $format 'json'
  3587.     ) {
  3588.         if (!is_string($time)) {
  3589.             throw new \InvalidArgumentException('Invalid data type for $time. Expected string.');
  3590.         }
  3591.         if (!is_string($location)) {
  3592.             throw new \InvalidArgumentException('Invalid data type for $location. Expected string.');
  3593.         }
  3594.         if (!is_string($format)) {
  3595.             throw new \InvalidArgumentException('Invalid data type for $format. Expected string.');
  3596.         }
  3597.         $cacheKey sprintf('wind_location_%s_%s'$time$location);
  3598.         // Try to get the data from Redis cache
  3599.         $cachedData $this->redisCache->get($cacheKey);
  3600.         if ($cachedData !== null) {
  3601.             // Return the cached data if available
  3602.             return $cachedData;
  3603.         }
  3604.         try {
  3605.             // If cache is empty, get the data from the API
  3606.             $client = new Client(['verify' => false]);
  3607.             $url sprintf(
  3608.                 '%s/%s/wind_speed_u_10m:ms,wind_speed_v_10m:ms/%s/%s?use_decluttered=true',
  3609.                 $this->apiBaseUrl,
  3610.                 $time,
  3611.                 $location,
  3612.                 $format
  3613.             );
  3614.             $response $client->request('GET'$url, [
  3615.                 'auth' => [$this->username$this->password],
  3616.             ]);
  3617.             $data json_decode($response->getBody(), true);
  3618.             // Set the data to Redis cache
  3619.             $this->redisCache->set($cacheKey$data);
  3620.             return $data;
  3621.         } catch (GuzzleHttp\Exception\RequestException $e) {
  3622.             return throw new \Exception($e->getMessage());
  3623.         } catch (\Exception $e) {
  3624.             return throw new \Exception($e->getMessage());
  3625.         }
  3626.     }
  3627.     /**
  3628.      * Water Temperature
  3629.      *    
  3630.      * @param string $query The type of data request (e.g., 't_sea_sfc:ms')
  3631.      * @param string $format return type of json
  3632.      * @param string $date The type of data date (e.g., '2023-09-01')
  3633.      * @param array $coordinates The latitude and longitude of location in array
  3634.      * @param string $dimensions type of data (e.g., '500x300')
  3635.      * @param string $modal type of data (e.g., 'noaa-hycom', 'ecmwf-cmems', 'noaa-hycom')
  3636.      * @throws \InvalidArgumentException If any parameter has an invalid data type
  3637.      */
  3638.     public function getWaterTempereture(
  3639.         string $query,
  3640.         array $coordinates,
  3641.         string $dimensions,
  3642.         string $format,
  3643.         string $date,
  3644.         string $modal
  3645.     ) {
  3646.         try {
  3647.             if (empty($query)) {
  3648.                 throw new \InvalidArgumentException('Invalid query');
  3649.             }
  3650.             if (empty($date)) {
  3651.                 throw new \InvalidArgumentException('Invalid date');
  3652.             }
  3653.             $date date('Y-m-d\TH:i:s\Z', (strtotime($date)));
  3654.             foreach ($coordinates as $coordinate) {
  3655.                 if (count($coordinate) < || !is_numeric($coordinate[0]) || !is_numeric($coordinate[1])) {
  3656.                     throw new \InvalidArgumentException('Invalid coordinates');
  3657.                 }
  3658.             }
  3659.             $coordinateString implode('+'array_map(fn($coords) => implode(','$coords), $coordinates));
  3660.             $cacheKey \App\Lib\Utility::generateKey($query,  $coordinates,  $dimensions,  $date,  $format$modal);
  3661.             // Try to get the data from Redis cache
  3662.             $cachedData $this->redisCache->get($cacheKey);
  3663.             if ($cachedData !== null) {
  3664.                 // Return the cached data if available
  3665.                 return $cachedData;
  3666.             }
  3667.             // If cache is empty, get the data from the API
  3668.             $client = new Client(['verify' => false]);
  3669.             $url sprintf(
  3670.                 '%s/%s/%s/%s:%s/%s?%s&use_decluttered=true',
  3671.                 $this->apiBaseUrl,
  3672.                 $date,
  3673.                 $query,
  3674.                 $coordinateString,
  3675.                 $dimensions,
  3676.                 $format,
  3677.                 $modal
  3678.             );
  3679.             $response $client->request('GET'$url, [
  3680.                 'auth' => [$this->username$this->password],
  3681.             ]);
  3682.             if ($format == "json") {
  3683.                 $data json_decode($response->getBody(), true);
  3684.             } else {
  3685.                 $data $response->getBody();
  3686.             }
  3687.             // Set the data to Redis cache
  3688.             $this->redisCache->set($cacheKey$data);
  3689.             return $data;
  3690.         } catch (GuzzleHttp\Exception\RequestException $e) {
  3691.             return throw new \Exception($e->getMessage());
  3692.         } catch (\Exception $e) {
  3693.             return throw new \Exception($e->getMessage());
  3694.         }
  3695.     }
  3696.     /**
  3697.      * Ocean Current
  3698.      *    
  3699.      * @param string $query The type of data request (e.g., 'ocean_current_speed_2m:ms')
  3700.      * @param string $format return type of json
  3701.      * @param string $date The type of data date (e.g., '2023-09-01')
  3702.      * @param array $coordinates The latitude and longitude of location in array
  3703.      * @param string $dimensions type of data (e.g., '500x300')
  3704.      * @throws \InvalidArgumentException If any parameter has an invalid data type
  3705.      */
  3706.     public function getOceanCurrent(
  3707.         string $query,
  3708.         array $coordinates,
  3709.         string $dimensions,
  3710.         string $format,
  3711.         string $date
  3712.     ) {
  3713.         try {
  3714.             if (empty($query)) {
  3715.                 throw new \InvalidArgumentException('Invalid query');
  3716.             }
  3717.             if (empty($date)) {
  3718.                 throw new \InvalidArgumentException('Invalid dates');
  3719.             }
  3720.             $date date('Y-m-d\TH:i:s\Z', (strtotime($date)));
  3721.             foreach ($coordinates as $coordinate) {
  3722.                 if (count($coordinate) < || !is_numeric($coordinate[0]) || !is_numeric($coordinate[1])) {
  3723.                     throw new \InvalidArgumentException('Invalid coordinates');
  3724.                 }
  3725.             }
  3726.             $coordinateString implode('+'array_map(fn($coords) => implode(','$coords), $coordinates));
  3727.             $cacheKey \App\Lib\Utility::generateKey($query,  $coordinates,  $dimensions,  $date,  $format);
  3728.             // Try to get the data from Redis cache
  3729.             $cachedData $this->redisCache->get($cacheKey);
  3730.             if ($cachedData !== null) {
  3731.                 // Return the cached data if available
  3732.                 return $cachedData;
  3733.             }
  3734.             // If cache is empty, get the data from the API
  3735.             $client = new Client(['verify' => false]);
  3736.             $url sprintf(
  3737.                 '%s/%s/%s/%s:%s/%s?use_decluttered=true',
  3738.                 $this->apiBaseUrl,
  3739.                 $date,
  3740.                 $query,
  3741.                 $coordinateString,
  3742.                 $dimensions,
  3743.                 $format
  3744.             );
  3745.             $response $client->request('GET'$url, [
  3746.                 'auth' => [$this->username$this->password],
  3747.             ]);
  3748.             if ($format == "json") {
  3749.                 $data json_decode($response->getBody(), true);
  3750.             } else {
  3751.                 $data $response->getBody();
  3752.             }
  3753.             // Set the data to Redis cache
  3754.             $this->redisCache->set($cacheKey$data);
  3755.             return $data;
  3756.         } catch (GuzzleHttp\Exception\RequestException $e) {
  3757.             return throw new \Exception($e->getMessage());
  3758.         } catch (\Exception $e) {
  3759.             return throw new \Exception($e->getMessage());
  3760.         }
  3761.     }
  3762.     /**
  3763.      * Oceanic Tides
  3764.      *    
  3765.      * @param string $query The type of data request (e.g., 'tidal_amplitude:cm')
  3766.      * @param string $format return type of json
  3767.      * @param string $date The type of data date (e.g., '2023-09-01')
  3768.      * @param array $latitude The latitude of location
  3769.      * @param array $longitude The longitude of location
  3770.      * @param string $dimensions type of data (e.g., '500x300')
  3771.      * @throws \InvalidArgumentException If any parameter has an invalid data type
  3772.      */
  3773.     public function getOceanicTides(
  3774.         string $query,
  3775.         string $latitude,
  3776.         string $longitude,
  3777.         string $date,
  3778.         string $format,
  3779.         string $resolution,
  3780.         string $model
  3781.     ) {
  3782.         try {
  3783.             if (empty($query)) {
  3784.                 throw new \InvalidArgumentException('Invalid query');
  3785.             }
  3786.             if (empty($date)) {
  3787.                 throw new \InvalidArgumentException('Invalid dates');
  3788.             }
  3789.             if (!is_numeric($latitude) || $latitude < -90 || $latitude 90) {
  3790.                 throw new \InvalidArgumentException('Invalid latitude');
  3791.             }
  3792.             if (!is_numeric($longitude) || $longitude < -180 || $longitude 180) {
  3793.                 throw new \InvalidArgumentException('Invalid longitude');
  3794.             }
  3795.             $date date('Y-m-d\TH:i:s\Z', (strtotime($date)));
  3796.             $cacheKey \App\Lib\Utility::generateKey($query,  $latitude,  $longitude,  $date,  $format$resolution$model);
  3797.             // Try to get the data from Redis cache
  3798.             $cachedData $this->redisCache->get($cacheKey);
  3799.             if ($cachedData !== null) {
  3800.                 // Return the cached data if available
  3801.                 return $cachedData;
  3802.             }
  3803.             // If cache is empty, get the data from the API
  3804.             $client = new Client(['verify' => false]);
  3805.             $url sprintf(
  3806.                 '%s/%s%s/%s/%s,%s/%s?%s&use_decluttered=true',
  3807.                 $this->apiBaseUrl,
  3808.                 $date,
  3809.                 $resolution,
  3810.                 $query,
  3811.                 $latitude,
  3812.                 $longitude,
  3813.                 $format,
  3814.                 $model
  3815.             );
  3816.             $response $client->request('GET'$url, [
  3817.                 'auth' => [$this->username$this->password],
  3818.             ]);
  3819.             if ($format == "json") {
  3820.                 $data json_decode($response->getBody(), true);
  3821.             } else {
  3822.                 $data $response->getBody();
  3823.             }
  3824.             // Set the data to Redis cache
  3825.             $this->redisCache->set($cacheKey$data);
  3826.             return $data;
  3827.         } catch (GuzzleHttp\Exception\RequestException $e) {
  3828.             return throw new \Exception($e->getMessage());
  3829.         } catch (\Exception $e) {
  3830.             return throw new \Exception($e->getMessage());
  3831.         }
  3832.     }
  3833.     /**
  3834.      * Drought Index
  3835.      *    
  3836.      * @param string $query The type of data request (e.g., 'drought_index:idx/Asia')
  3837.      * @param string $format return type of json or html_map
  3838.      * @param string $startDate The type of data date (e.g., '2023-09-01')
  3839.      * @param string $longitude The longitude of location
  3840.      * @param string $latitude The latitude of location
  3841.      * @throws \InvalidArgumentException If any parameter has an invalid data type
  3842.      */
  3843.     public function getDroughtIndex(
  3844.         string $query,
  3845.         string $latitude,
  3846.         string $longitude,
  3847.         string $date,
  3848.         string $format,
  3849.     ) {
  3850.         try {
  3851.             if (empty($query)) {
  3852.                 throw new \InvalidArgumentException('Invalid query');
  3853.             }
  3854.             if (empty($date)) {
  3855.                 throw new \InvalidArgumentException('Invalid dates');
  3856.             }
  3857.             if (!is_numeric($latitude) || $latitude < -90 || $latitude 90) {
  3858.                 throw new \InvalidArgumentException('Invalid latitude');
  3859.             }
  3860.             if (!is_numeric($longitude) || $longitude < -180 || $longitude 180) {
  3861.                 throw new \InvalidArgumentException('Invalid longitude');
  3862.             }
  3863.             $date date('Y-m-d\TH:i:s\Z', (strtotime($date)));
  3864.             $cacheKey \App\Lib\Utility::generateKey($query,  $latitude,  $longitude,  $date,  $format);
  3865.             // Try to get the data from Redis cache
  3866.             $cachedData $this->redisCache->get($cacheKey);
  3867.             if ($cachedData !== null) {
  3868.                 // Return the cached data if available
  3869.                 return $cachedData;
  3870.             }
  3871.             // If cache is empty, get the data from the API
  3872.             $client = new Client(['verify' => false]);
  3873.             $url sprintf(
  3874.                 '%s/%s/%s:%s,%s/%s?use_decluttered=true',
  3875.                 $this->apiBaseUrl,
  3876.                 $date,
  3877.                 $query,
  3878.                 $latitude,
  3879.                 $longitude,
  3880.                 $format
  3881.             );
  3882.             $response $client->request('GET'$url, [
  3883.                 'auth' => [$this->username$this->password],
  3884.             ]);
  3885.             if ($format == "json") {
  3886.                 $data json_decode($response->getBody(), true);
  3887.             } else {
  3888.                 $data $response->getBody();
  3889.             }
  3890.             // Set the data to Redis cache
  3891.             $this->redisCache->set($cacheKey$data);
  3892.             return $data;
  3893.         } catch (GuzzleHttp\Exception\RequestException $e) {
  3894.             return throw new \Exception($e->getMessage());
  3895.         } catch (\Exception $e) {
  3896.             return throw new \Exception($e->getMessage());
  3897.         }
  3898.     }
  3899.     /**
  3900.      * Fetches the hourly weather forecast data for a given latitude, longitude, and hour
  3901.      *
  3902.      * @param array $coordinates An array of coordinates in the format [[lat1, long1], [lat2, long2], ...]
  3903.      * @param string $hourly The hour for which forecast is required in 24-hour format
  3904.      * @return array The hourly weather forecast data in JSON format
  3905.      */
  3906.     public function getHourlyForecastWithoutSymbols(array $coordinatesstring $startDatestring $endDateint $hourstring $model)
  3907.     {
  3908.         try {
  3909.             if (count($coordinates) > 1) {
  3910.                 // Validate the input parameters
  3911.                 foreach ($coordinates as $coordinate) {
  3912.                     if (count($coordinate) < || !is_numeric($coordinate[0]) || !is_numeric($coordinate[1])) {
  3913.                         throw new \InvalidArgumentException('Invalid coordinates');
  3914.                     }
  3915.                 }
  3916.                 if (empty($startDate) || empty($endDate)) {
  3917.                     throw new \InvalidArgumentException('Invalid dates');
  3918.                 }
  3919.                 $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  3920.                 $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  3921.                 // Create a Redis key for the cache
  3922.                 $cacheKey sprintf('cn_hourly_forecast_%s_%s_%s'implode('_'array_map(function ($coordinate) {
  3923.                     return implode('_'$coordinate);
  3924.                 }, $coordinates)), (($startDate) . '-' . ($endDate)) . $model$hour);
  3925.                 // Try to get the data from Redis cache
  3926.                 $cachedData $this->redisCache->get($cacheKey);
  3927.                 if ($cachedData !== null) {
  3928.                     // Return the cached data if available
  3929.                     return $cachedData;
  3930.                 }
  3931.                 // If cache is empty, get the data from the API
  3932.                 $client = new Client(['verify' => false]);
  3933.                 $coordinateString implode('+'array_map(fn($coords) => implode(','$coords), $coordinates));
  3934.                 $url1 sprintf(
  3935.                     '%s/%s--%s:PT%sH/t_2m:C,wind_speed_10m:kmh,wind_dir_mean_100m_2h:d,prob_precip_2h:p,precip_2h:mm/%+%/json?model=' $model '&use_decluttered=true',
  3936.                     $this->apiBaseUrl,
  3937.                     $startDate,
  3938.                     $endDate,
  3939.                     $hour,
  3940.                     $coordinateString,
  3941.                     $coordinateString
  3942.                 );
  3943.             } else {
  3944.                 if ($coordinates) {
  3945.                     $latitude $coordinates[0][0];
  3946.                     $longitude $coordinates[0][1];
  3947.                 }
  3948.                 // Validate the input parameters
  3949.                 if (!preg_match('/^[-]?[0-9]{1,2}\.[0-9]+/'$latitude)) {
  3950.                     throw new \InvalidArgumentException('Invalid latitude');
  3951.                 }
  3952.                 if (!preg_match('/^[-]?[0-9]{1,3}\.[0-9]+/'$longitude)) {
  3953.                     throw new \InvalidArgumentException('Invalid longitude');
  3954.                 }
  3955.                 if (empty($startDate) || empty($endDate)) {
  3956.                     throw new \InvalidArgumentException('Invalid dates');
  3957.                 }
  3958.                 $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  3959.                 $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  3960.                 // Create a Redis key for the cache            
  3961.                 $cacheKey sprintf('cn_hourly_forecast_%s_%s_%s'implode('_'array_map(function ($coordinate) {
  3962.                     return implode('_'$coordinate);
  3963.                 }, $coordinates)), ($startDate '-' $endDate) . $model$hour);
  3964.                 // Try to get the data from Redis cache
  3965.                 $cachedData $this->redisCache->get($cacheKey);
  3966.                 if ($cachedData !== null) {
  3967.                     // Return the cached data if available
  3968.                     return $cachedData;
  3969.                 }
  3970.                 // If cache is empty, get the data from the API
  3971.                 $client = new Client(['verify' => false]);
  3972.                 $url1 sprintf(
  3973.                     '%s/%s--%s:PT%sH/t_2m:C,wind_speed_10m:kmh,wind_dir_mean_100m_2h:d,prob_precip_2h:p,precip_2h:mm/%s,%s/json?model=' $model '&use_decluttered=true',
  3974.                     $this->apiBaseUrl,
  3975.                     $startDate,
  3976.                     $endDate,
  3977.                     $hour,
  3978.                     $latitude,
  3979.                     $longitude
  3980.                 );
  3981.             }
  3982.             $response $client->request('GET'$url1, [
  3983.                 'auth' => [$this->username$this->password],
  3984.             ]);
  3985.             $statusCode $response->getStatusCode();
  3986.             $data1 json_decode($response->getBody(), true);
  3987.             $this->redisCache->set($cacheKey$data1);
  3988.             return $data1;
  3989.         } catch (GuzzleHttp\Exception\RequestException $e) {
  3990.             return throw new \Exception($e->getMessage());
  3991.         } catch (\Exception $e) {
  3992.             return throw new \Exception($e->getMessage());
  3993.         }
  3994.     }
  3995.     /**
  3996.      * Fetches the weather forecast data for a given latitude, longitude, and for selected data range
  3997.      *
  3998.      * @param array $coordinates An array of coordinates in the format [[lat1, long1], [lat2, long2], ...]
  3999.      * @param int $days The number of days for which forecast is required
  4000.      * @return array|null The weather forecast data in JSON format, or null if there was an error
  4001.      */
  4002.     public function getTempratureByParamsHourly(
  4003.         array $coordinates,
  4004.             // Set the data to Re
  4005.         string $startDate,
  4006.         string $endDate,
  4007.         string $parametersStr,
  4008.         int $hour 2,
  4009.         string $model,
  4010.         $timezone
  4011.     ) {
  4012.         try {
  4013.             // Convert start and end dates to the specified timezone
  4014.             $startDateObj = new \DateTime($startDate$timezone);
  4015.             $endDateObj = new \DateTime($endDate$timezone);
  4016.             // Format dates to ISO 8601 format for the API request
  4017.             $startDate $startDateObj->format('Y-m-d\TH:i:s.v\Z');
  4018.             $endDate $endDateObj->format('Y-m-d\TH:i:s.v\Z');
  4019.             if (count($coordinates) > 1) {
  4020.                 // Validate the input parameters
  4021.                 foreach ($coordinates as $coordinate) {
  4022.                     if (count($coordinate) < || !is_numeric($coordinate[0]) || !is_numeric($coordinate[1])) {
  4023.                         throw new \InvalidArgumentException('Invalid coordinates');
  4024.                     }
  4025.                 }
  4026.                 // Create a Redis key for the cache
  4027.                 $cacheKey sprintf('hourly_custom_noti_%s_%s_%s_%s'md5(implode('_'array_map(function ($coordinate) {
  4028.                     return implode('_'$coordinate);
  4029.                 }, $coordinates))), ($startDate '-' $endDate), $parametersStr$model);
  4030.                 // Try to get the data from Redis cache
  4031.                 $cachedData $this->redisCache->get($cacheKey);
  4032.                 if ($cachedData !== null) {
  4033.                     return $cachedData;
  4034.                 }
  4035.                 // Build the API URL
  4036.                 $coordinateString implode('+'array_map(fn($coords) => implode(','$coords), $coordinates));
  4037.                 $url sprintf(
  4038.                     '%s/%s--%s:PT%sH/%s/%s+%s/json?source=%s&use_decluttered=true',
  4039.                     $this->apiBaseUrl,
  4040.                     $startDate,
  4041.                     $endDate,
  4042.                     $hour,
  4043.                     $parametersStr,
  4044.                     $coordinateString,
  4045.                     $coordinateString,
  4046.                     $model
  4047.                 );
  4048.             } else {
  4049.                 // Handle single coordinate case
  4050.                 if ($coordinates) {
  4051.                     $latitude $coordinates[0][0];
  4052.                     $longitude $coordinates[0][1];
  4053.                 }
  4054.                 if (empty($latitude) || empty($longitude)) {
  4055.                     throw new \InvalidArgumentException('Invalid coordinates');
  4056.                 }
  4057.                 // Create a Redis key for the cache
  4058.                 $cacheKey sprintf('hourly_custom_noti_%s_%s_%s_%s'md5(implode('_'array_map(function ($coordinate) {
  4059.                     return implode('_'$coordinate);
  4060.                 }, $coordinates))), ($startDate '-' $endDate), $parametersStr$model);
  4061.                 // Try to get the data from Redis cache
  4062.                 $cachedData $this->redisCache->get($cacheKey);
  4063.                 if ($cachedData !== null) {
  4064.                     return $cachedData;
  4065.                 }
  4066.                 // Build the API URL for a single coordinate
  4067.                 $url sprintf(
  4068.                     '%s/%s--%s:PT%sH/%s/%s,%s/json?source=%s&use_decluttered=true',
  4069.                     $this->apiBaseUrl,
  4070.                     $startDate,
  4071.                     $endDate,
  4072.                     $hour,
  4073.                     $parametersStr,
  4074.                     $latitude,
  4075.                     $longitude,
  4076.                     $model
  4077.                 );
  4078.             }
  4079.             // Make the API request
  4080.             $client = new Client(['verify' => false]);
  4081.             $response $client->request('GET'$url, [
  4082.                 'auth' => [$this->username$this->password],
  4083.             ]);
  4084.             $data json_decode($response->getBody(), true);
  4085.             // Cache the data in Redis
  4086.             $this->redisCache->set($cacheKey$data);
  4087.             return $data;
  4088.         } catch (GuzzleHttp\Exception\RequestException $e) {
  4089.             throw new \Exception($e->getMessage());
  4090.         } catch (\Exception $e) {
  4091.             throw new \Exception($e->getMessage());
  4092.         }
  4093.     }
  4094.     /**
  4095.      * Tidal Amplitude
  4096.      *  
  4097.      * @param string $hourly The hour for which forecast is required in 24-hour format
  4098.      * @param string $unit The type of data request unit (e.g., 'cm')
  4099.      * @param string $format return type of json
  4100.      * @param string $startDate The type of data date (e.g., '2023-09-01')
  4101.      * @param string $endDate The type of data date (e.g., '2023-09-01')
  4102.      * @param array $coordinates The latitude and longitude of location
  4103.      * @param string $model type of data Api return  (e.g., 'mm-tides')
  4104.      * @throws \InvalidArgumentException If any parameter has an invalid data type
  4105.      */
  4106.     public function getTidalAmplitudes(int $hour$startDate$endDate, array $coordinates$unit$model$format)
  4107.     {
  4108.         try {
  4109.             if (count($coordinates) > 1) {
  4110.                 // Validate the input parameters
  4111.                 foreach ($coordinates as $coordinate) {
  4112.                     if (count($coordinate) < || !is_numeric($coordinate[0]) || !is_numeric($coordinate[1])) {
  4113.                         throw new \InvalidArgumentException('Invalid coordinates');
  4114.                     }
  4115.                 }
  4116.                 if (empty($startDate) || empty($endDate)) {
  4117.                     throw new \InvalidArgumentException('Invalid dates');
  4118.                 }
  4119.                 $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  4120.                 $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  4121.                 $cacheKey \App\Lib\Utility::generateKey($hour$startDate$endDate$coordinates$unit$model$format);
  4122.                 // Try to get the data from Redis cache
  4123.                 $cachedData $this->redisCache->get($cacheKey);
  4124.                 if ($cachedData !== null) {
  4125.                     // Return the cached data if available
  4126.                     return $cachedData;
  4127.                 }
  4128.                 // If cache is empty, get the data from the API
  4129.                 $coordinateString implode('+'array_map(fn($coords) => implode(','$coords), $coordinates));
  4130.                 $client = new Client(['verify' => false]);
  4131.                 $url sprintf(
  4132.                     '%s/%s--%s:PT%sH/tidal_amplitude:%s/%s/%s?model=%s&use_decluttered=true',
  4133.                     $this->apiBaseUrl,
  4134.                     $startDate,
  4135.                     $endDate,
  4136.                     $hour,
  4137.                     $unit,
  4138.                     $coordinateString,
  4139.                     $format,
  4140.                     $model
  4141.                 );
  4142.                 $response $client->request('GET'$url, [
  4143.                     'auth' => [$this->username$this->password],
  4144.                 ]);
  4145.                 $statusCode $response->getStatusCode();
  4146.                 $data json_decode($response->getBody(), true);
  4147.                 if ($statusCode != 200) {
  4148.                     return $this->createErrorResponse($statusCode);
  4149.                 }
  4150.                 // Set the data to Redis cache
  4151.                 $this->redisCache->set($cacheKey$data);
  4152.                 return $data;
  4153.             } else {
  4154.                 if ($coordinates) {
  4155.                     $latitude $coordinates[0][0];
  4156.                     $longitude $coordinates[0][1];
  4157.                 }
  4158.                 // Validate the input parameters
  4159.                 if (!preg_match('/^[-]?[0-9]{1,2}\.[0-9]+/'$latitude)) {
  4160.                     throw new \InvalidArgumentException('Invalid latitude');
  4161.                 }
  4162.                 if (!preg_match('/^[-]?[0-9]{1,3}\.[0-9]+/'$longitude)) {
  4163.                     throw new \InvalidArgumentException('Invalid longitude');
  4164.                 }
  4165.                 if (empty($startDate)) {
  4166.                     throw new \InvalidArgumentException('Invalid startDate');
  4167.                 }
  4168.                 if (empty($endDate)) {
  4169.                     throw new \InvalidArgumentException('Invalid endDate');
  4170.                 }
  4171.                 $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  4172.                 $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  4173.                 // Create a Redis key for the cache            
  4174.                 $cacheKey \App\Lib\Utility::generateKey($hour$startDate$endDate$unit$latitude$longitude,  $model$format);
  4175.                 // Try to get the data from Redis cache
  4176.                 $cachedData $this->redisCache->get($cacheKey);
  4177.                 if ($cachedData !== null) {
  4178.                     // Return the cached data if available
  4179.                     return $cachedData;
  4180.                 }
  4181.                 // If cache is empty, get the data from the API
  4182.                 $client = new Client(['verify' => false]);
  4183.                 $url sprintf(
  4184.                     '%s/%s--%s:PT%sH/tidal_amplitude:%s/%s,%s/%s?model=%s&use_decluttered=true',
  4185.                     $this->apiBaseUrl,
  4186.                     $startDate,
  4187.                     $endDate,
  4188.                     $hour,
  4189.                     $unit,
  4190.                     $latitude,
  4191.                     $longitude,
  4192.                     $format,
  4193.                     $model
  4194.                 );
  4195.                 $response $client->request('GET'$url, [
  4196.                     'auth' => [$this->username$this->password],
  4197.                 ]);
  4198.                 $statusCode $response->getStatusCode();
  4199.                 $data json_decode($response->getBody(), true);
  4200.                 if ($statusCode != 200) {
  4201.                     return $this->createErrorResponse($statusCode);
  4202.                 }
  4203.                 // Set the data to Redis cache
  4204.                 $this->redisCache->set($cacheKey$data);
  4205.                 return $data;
  4206.             }
  4207.         } catch (GuzzleHttp\Exception\RequestException $e) {
  4208.             return throw new \Exception($e->getMessage());
  4209.         } catch (\Exception $e) {
  4210.             return throw new \Exception($e->getMessage());
  4211.         }
  4212.     }
  4213.     /**
  4214.      * High and Low Tide Times
  4215.      *  
  4216.      * @param string $hourly The hour for which forecast is required in 24-hour format
  4217.      * @param string $format return type of json
  4218.      * @param string $startDate The type of data date (e.g., '2023-09-01')
  4219.      * @param string $endDate The type of data date (e.g., '2023-09-01')
  4220.      * @param array $coordinates The latitude and longitude of location
  4221.      * @param string $model type of data Api return  (e.g., 'mm-tides')
  4222.      * @throws \InvalidArgumentException If any parameter has an invalid data type
  4223.      */
  4224.     public function getHighLowTideTimes(int $hour,  $startDate$endDate, array $coordinates$model$format)
  4225.     {
  4226.         try {
  4227.             if (count($coordinates) > 1) {
  4228.                 // Validate the input parameters
  4229.                 foreach ($coordinates as $coordinate) {
  4230.                     if (count($coordinate) < || !is_numeric($coordinate[0]) || !is_numeric($coordinate[1])) {
  4231.                         throw new \InvalidArgumentException('Invalid coordinates');
  4232.                     }
  4233.                 }
  4234.                 if (empty($startDate) || empty($endDate)) {
  4235.                     throw new \InvalidArgumentException('Invalid dates');
  4236.                 }
  4237.                 $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  4238.                 $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  4239.                 $cacheKey \App\Lib\Utility::generateKey($hour$startDate$endDate$coordinates$model$format);
  4240.                 // Try to get the data from Redis cache
  4241.                 $cachedData $this->redisCache->get($cacheKey);
  4242.                 if ($cachedData !== null) {
  4243.                     // Return the cached data if available
  4244.                     return $cachedData;
  4245.                 }
  4246.                 // If cache is empty, get the data from the API
  4247.                 $coordinateString implode('+'array_map(fn($coords) => implode(','$coords), $coordinates));
  4248.                 $client = new Client(['verify' => false]);
  4249.                 $url sprintf(
  4250.                     '%s/%s--%s:PT%sH/first_high_tide:sql,second_high_tide:sql,first_low_tide:sql,second_low_tide:sql/%s/%s?model=%s&use_decluttered=true',
  4251.                     $this->apiBaseUrl,
  4252.                     $startDate,
  4253.                     $endDate,
  4254.                     $hour,
  4255.                     $coordinateString,
  4256.                     $format,
  4257.                     $model
  4258.                 );
  4259.                 $response $client->request('GET'$url, [
  4260.                     'auth' => [$this->username$this->password],
  4261.                 ]);
  4262.                 $statusCode $response->getStatusCode();
  4263.                 $data json_decode($response->getBody(), true);
  4264.                 if ($statusCode != 200) {
  4265.                     return $this->createErrorResponse($statusCode);
  4266.                 }
  4267.                 // Set the data to Redis cache
  4268.                 $this->redisCache->set($cacheKey$data);
  4269.                 return $data;
  4270.             } else {
  4271.                 if ($coordinates) {
  4272.                     $latitude $coordinates[0][0];
  4273.                     $longitude $coordinates[0][1];
  4274.                 }
  4275.                 // Validate the input parameters
  4276.                 if (!preg_match('/^[-]?[0-9]{1,2}\.[0-9]+/'$latitude)) {
  4277.                     throw new \InvalidArgumentException('Invalid latitude');
  4278.                 }
  4279.                 if (!preg_match('/^[-]?[0-9]{1,3}\.[0-9]+/'$longitude)) {
  4280.                     throw new \InvalidArgumentException('Invalid longitude');
  4281.                 }
  4282.                 if (empty($startDate)) {
  4283.                     throw new \InvalidArgumentException('Invalid startDate');
  4284.                 }
  4285.                 if (empty($endDate)) {
  4286.                     throw new \InvalidArgumentException('Invalid endDate');
  4287.                 }
  4288.                 $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  4289.                 $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  4290.                 // Create a Redis key for the cache            
  4291.                 $cacheKey \App\Lib\Utility::generateKey($hour$startDate$endDate$latitude$longitude,  $model$format);
  4292.                 // Try to get the data from Redis cache
  4293.                 $cachedData $this->redisCache->get($cacheKey);
  4294.                 if ($cachedData !== null) {
  4295.                     // Return the cached data if available
  4296.                     return $cachedData;
  4297.                 }
  4298.                 // If cache is empty, get the data from the API
  4299.                 $client = new Client(['verify' => false]);
  4300.                 $url sprintf(
  4301.                     '%s/%s--%s:PT%sH/first_high_tide:sql,second_high_tide:sql,first_low_tide:sql,second_low_tide:sql/%s,%s/%s?model=%s&use_decluttered=true',
  4302.                     $this->apiBaseUrl,
  4303.                     $startDate,
  4304.                     $endDate,
  4305.                     $hour,
  4306.                     $latitude,
  4307.                     $longitude,
  4308.                     $format,
  4309.                     $model
  4310.                 );
  4311.                 $response $client->request('GET'$url, [
  4312.                     'auth' => [$this->username$this->password],
  4313.                 ]);
  4314.                 $statusCode $response->getStatusCode();
  4315.                 $data json_decode($response->getBody(), true);
  4316.                 if ($statusCode != 200) {
  4317.                     return $this->createErrorResponse($statusCode);
  4318.                 }
  4319.                 // Set the data to Redis cache
  4320.                 $this->redisCache->set($cacheKey$data);
  4321.                 return $data;
  4322.             }
  4323.         } catch (GuzzleHttp\Exception\RequestException $e) {
  4324.             return throw new \Exception($e->getMessage());
  4325.         } catch (\Exception $e) {
  4326.             return throw new \Exception($e->getMessage());
  4327.         }
  4328.     }
  4329.     /**
  4330.      * Significant Wave Height
  4331.      *  
  4332.      * @param string $hourly The hour for which forecast is required in 24-hour format
  4333.      * @param string $format return type of json
  4334.      * @param string $startDate The type of data date (e.g., '2023-09-01')
  4335.      * @param string $endDate The type of data date (e.g., '2023-09-01')
  4336.      * @param array $coordinates The latitude and longitude of location
  4337.      * @throws \InvalidArgumentException If any parameter has an invalid data type
  4338.      */
  4339.     public function getSignificantWaveHeight(int $hour,  $startDate,  $endDate, array $coordinates$format)
  4340.     {
  4341.         try {
  4342.             if (count($coordinates) > 1) {
  4343.                 // Validate the input parameters
  4344.                 foreach ($coordinates as $coordinate) {
  4345.                     if (count($coordinate) < || !is_numeric($coordinate[0]) || !is_numeric($coordinate[1])) {
  4346.                         throw new \InvalidArgumentException('Invalid coordinates');
  4347.                     }
  4348.                 }
  4349.                 if (empty($startDate) || empty($endDate)) {
  4350.                     throw new \InvalidArgumentException('Invalid dates');
  4351.                 }
  4352.                 $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  4353.                 $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  4354.                 $cacheKey \App\Lib\Utility::generateKey($hour$startDate$endDate$coordinate$format);
  4355.                 // Try to get the data from Redis cache
  4356.                 $cachedData $this->redisCache->get($cacheKey);
  4357.                 if ($cachedData !== null) {
  4358.                     // Return the cached data if available
  4359.                     return $cachedData;
  4360.                 }
  4361.                 // If cache is empty, get the data from the API
  4362.                 $coordinateString implode('+'array_map(fn($coords) => implode(','$coords), $coordinates));
  4363.                 $client = new Client(['verify' => false]);
  4364.                 $url sprintf(
  4365.                     '%s/%s--%s:PT%sH/significant_height_wind_waves:m/%s/%s?use_decluttered=true',
  4366.                     $this->apiBaseUrl,
  4367.                     $startDate,
  4368.                     $endDate,
  4369.                     $hour,
  4370.                     $coordinateString,
  4371.                     $format
  4372.                 );
  4373.                 $response $client->request('GET'$url, [
  4374.                     'auth' => [$this->username$this->password],
  4375.                 ]);
  4376.                 $statusCode $response->getStatusCode();
  4377.                 $data json_decode($response->getBody(), true);
  4378.                 if ($statusCode != 200) {
  4379.                     return $this->createErrorResponse($statusCode);
  4380.                 }
  4381.                 // Set the data to Redis cache
  4382.                 $this->redisCache->set($cacheKey$data);
  4383.                 return $data;
  4384.             } else {
  4385.                 if ($coordinates) {
  4386.                     $latitude $coordinates[0][0];
  4387.                     $longitude $coordinates[0][1];
  4388.                 }
  4389.                 // Validate the input parameters
  4390.                 if (!preg_match('/^[-]?[0-9]{1,2}\.[0-9]+/'$latitude)) {
  4391.                     throw new \InvalidArgumentException('Invalid latitude');
  4392.                 }
  4393.                 if (!preg_match('/^[-]?[0-9]{1,3}\.[0-9]+/'$longitude)) {
  4394.                     throw new \InvalidArgumentException('Invalid longitude');
  4395.                 }
  4396.                 if (empty($startDate)) {
  4397.                     throw new \InvalidArgumentException('Invalid startDate');
  4398.                 }
  4399.                 if (empty($endDate)) {
  4400.                     throw new \InvalidArgumentException('Invalid endDate');
  4401.                 }
  4402.                 $startDate date('Y-m-d\TH\Z', (strtotime($startDate)));
  4403.                 $endDate date('Y-m-d\TH\Z', (strtotime($endDate)));
  4404.                 // Create a Redis key for the cache            
  4405.                 $cacheKey \App\Lib\Utility::generateKey($hour$startDate$endDate$latitude$longitude$format);
  4406.                 // Try to get the data from Redis cache
  4407.                 $cachedData $this->redisCache->get($cacheKey);
  4408.                 if ($cachedData !== null) {
  4409.                     // Return the cached data if available
  4410.                     return $cachedData;
  4411.                 }
  4412.                 // If cache is empty, get the data from the API
  4413.                 $client = new Client(['verify' => false]);
  4414.                 $url sprintf(
  4415.                     '%s/%s--%s:PT%sH/significant_height_wind_waves:m/%s,%s/%s?use_decluttered=true',
  4416.                     $this->apiBaseUrl,
  4417.                     $startDate,
  4418.                     $endDate,
  4419.                     $hour,
  4420.                     $latitude,
  4421.                     $longitude,
  4422.                     $format
  4423.                 );
  4424.                 $response $client->request('GET'$url, [
  4425.                     'auth' => [$this->username$this->password],
  4426.                 ]);
  4427.                 $statusCode $response->getStatusCode();
  4428.                 $data json_decode($response->getBody(), true);
  4429.                 if ($statusCode != 200) {
  4430.                     return $this->createErrorResponse($statusCode);
  4431.                 }
  4432.                 // Set the data to Redis cache
  4433.                 $this->redisCache->set($cacheKey$data);
  4434.                 return $data;
  4435.             }
  4436.         } catch (GuzzleHttp\Exception\RequestException $e) {
  4437.             return throw new \Exception($e->getMessage());
  4438.         } catch (\Exception $e) {
  4439.             return throw new \Exception($e->getMessage());
  4440.         }
  4441.     }
  4442.     /**
  4443.      * Surge Amplitude
  4444.      *  
  4445.      * @param string $hourly The hour for which forecast is required in 24-hour format
  4446.      * @param string $format return type of json
  4447.      * @param string $startDate The type of data date (e.g., '2023-09-01')
  4448.      * @param string $endDate The type of data date (e.g., '2023-09-01')
  4449.      * @param string $unit The type of data unit (e.g., 'cm')
  4450.      * @param array $coordinates The latitude and longitude of location
  4451.      * @param string $model type of data Api return  (e.g., 'mix')
  4452.      * @throws \InvalidArgumentException If any parameter has an invalid data type
  4453.      */
  4454.     public function getSurgeAmplitude(int $hour,  $startDate,  $endDate, array $coordinates$unit$model$format)
  4455.     {
  4456.         try {
  4457.             if (count($coordinates) > 1) {
  4458.                 // Validate the input parameters
  4459.                 foreach ($coordinates as $coordinate) {
  4460.                     if (count($coordinate) < || !is_numeric($coordinate[0]) || !is_numeric($coordinate[1])) {
  4461.                         throw new \InvalidArgumentException('Invalid coordinates');
  4462.                     }
  4463.                 }
  4464.                 if (empty($startDate) || empty($endDate)) {
  4465.                     throw new \InvalidArgumentException('Invalid dates');
  4466.                 }
  4467.                 $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  4468.                 $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  4469.                 $cacheKey \App\Lib\Utility::generateKey($hour$startDate$endDate$coordinate$unit,  $model$format);
  4470.                 // Try to get the data from Redis cache
  4471.                 $cachedData $this->redisCache->get($cacheKey);
  4472.                 if ($cachedData !== null) {
  4473.                     // Return the cached data if available
  4474.                     return $cachedData;
  4475.                 }
  4476.                 // If cache is empty, get the data from the API
  4477.                 $coordinateString implode('+'array_map(fn($coords) => implode(','$coords), $coordinates));
  4478.                 $client = new Client(['verify' => false]);
  4479.                 $url sprintf(
  4480.                     '%s/%s--%s:PT%sH/surge_amplitude:%s/%s/%s?model=%s&use_decluttered=true',
  4481.                     $this->apiBaseUrl,
  4482.                     $startDate,
  4483.                     $endDate,
  4484.                     $hour,
  4485.                     $unit,
  4486.                     $coordinateString,
  4487.                     $format,
  4488.                     $model
  4489.                 );
  4490.                 $response $client->request('GET'$url, [
  4491.                     'auth' => [$this->username$this->password],
  4492.                 ]);
  4493.                 $statusCode $response->getStatusCode();
  4494.                 $data json_decode($response->getBody(), true);
  4495.                 if ($statusCode != 200) {
  4496.                     return $this->createErrorResponse($statusCode);
  4497.                 }
  4498.                 // Set the data to Redis cache
  4499.                 $this->redisCache->set($cacheKey$data);
  4500.                 return $data;
  4501.             } else {
  4502.                 if ($coordinates) {
  4503.                     $latitude $coordinates[0][0];
  4504.                     $longitude $coordinates[0][1];
  4505.                 }
  4506.                 // Validate the input parameters
  4507.                 if (!preg_match('/^[-]?[0-9]{1,2}\.[0-9]+/'$latitude)) {
  4508.                     throw new \InvalidArgumentException('Invalid latitude');
  4509.                 }
  4510.                 if (!preg_match('/^[-]?[0-9]{1,3}\.[0-9]+/'$longitude)) {
  4511.                     throw new \InvalidArgumentException('Invalid longitude');
  4512.                 }
  4513.                 if (empty($startDate)) {
  4514.                     throw new \InvalidArgumentException('Invalid startDate');
  4515.                 }
  4516.                 if (empty($endDate)) {
  4517.                     throw new \InvalidArgumentException('Invalid endDate');
  4518.                 }
  4519.                 $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  4520.                 $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  4521.                 // Create a Redis key for the cache            
  4522.                 $cacheKey \App\Lib\Utility::generateKey($hour$startDate$endDate$latitude$longitude$unit,  $model$format);
  4523.                 // Try to get the data from Redis cache
  4524.                 $cachedData $this->redisCache->get($cacheKey);
  4525.                 if ($cachedData !== null) {
  4526.                     // Return the cached data if available
  4527.                     return $cachedData;
  4528.                 }
  4529.                 // If cache is empty, get the data from the API
  4530.                 $client = new Client(['verify' => false]);
  4531.                 $url sprintf(
  4532.                     '%s/%s--%s:PT%sH/surge_amplitude:%s/%s,%s/%s?model=%s&use_decluttered=true',
  4533.                     $this->apiBaseUrl,
  4534.                     $startDate,
  4535.                     $endDate,
  4536.                     $hour,
  4537.                     $unit,
  4538.                     $latitude,
  4539.                     $longitude,
  4540.                     $format,
  4541.                     $model
  4542.                 );
  4543.                 $response $client->request('GET'$url, [
  4544.                     'auth' => [$this->username$this->password],
  4545.                 ]);
  4546.                 $statusCode $response->getStatusCode();
  4547.                 $data json_decode($response->getBody(), true);
  4548.                 if ($statusCode != 200) {
  4549.                     return $this->createErrorResponse($statusCode);
  4550.                 }
  4551.                 // Set the data to Redis cache
  4552.                 $this->redisCache->set($cacheKey$data);
  4553.                 return $data;
  4554.             }
  4555.         } catch (GuzzleHttp\Exception\RequestException $e) {
  4556.             return throw new \Exception($e->getMessage());
  4557.         } catch (\Exception $e) {
  4558.             return throw new \Exception($e->getMessage());
  4559.         }
  4560.     }
  4561.     /**
  4562.      * Heat index
  4563.      *  
  4564.      * @param string $hourly The hour for which forecast is required in 24-hour format
  4565.      * @param string $format return type of json
  4566.      * @param string $unit request unit type (e.g, 'C')
  4567.      * @param string $startDate The type of data date (e.g., '2023-09-01')
  4568.      * @param string $endDate The type of data date (e.g., '2023-09-01')
  4569.      * @param array $coordinates The latitude and longitude of location
  4570.      * @param string $model type of data Api return  (e.g., 'mix')
  4571.      * @throws \InvalidArgumentException If any parameter has an invalid data type
  4572.      */
  4573.     public function getHeatIndex(int $hour,  $startDate,  $endDate, array $coordinates,  $unit,  $format$model)
  4574.     {
  4575.         try {
  4576.             if (count($coordinates) > 1) {
  4577.                 // Validate the input parameters
  4578.                 foreach ($coordinates as $coordinate) {
  4579.                     if (count($coordinate) < || !is_numeric($coordinate[0]) || !is_numeric($coordinate[1])) {
  4580.                         throw new \InvalidArgumentException('Invalid coordinates');
  4581.                     }
  4582.                 }
  4583.                 if (empty($startDate) || empty($endDate)) {
  4584.                     throw new \InvalidArgumentException('Invalid dates');
  4585.                 }
  4586.                 $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  4587.                 $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  4588.                 $cacheKey \App\Lib\Utility::generateKey($hour$startDate$endDate$coordinate,  $model$unit$format);
  4589.                 // Try to get the data from Redis cache
  4590.                 $cachedData $this->redisCache->get($cacheKey);
  4591.                 if ($cachedData !== null) {
  4592.                     // Return the cached data if available
  4593.                     return $cachedData;
  4594.                 }
  4595.                 // If cache is empty, get the data from the API
  4596.                 $coordinateString implode('+'array_map(fn($coords) => implode(','$coords), $coordinates));
  4597.                 $client = new Client(['verify' => false]);
  4598.                 $url sprintf(
  4599.                     '%s/%s--%s:PT%sH/heat_index:%s,t_2m:%s/%s/%s?model=%s&use_decluttered=true',
  4600.                     $this->apiBaseUrl,
  4601.                     $startDate,
  4602.                     $endDate,
  4603.                     $hour,
  4604.                     $unit,
  4605.                     $unit,
  4606.                     $coordinateString,
  4607.                     $format,
  4608.                     $model
  4609.                 );
  4610.                 $response $client->request('GET'$url, [
  4611.                     'auth' => [$this->username$this->password],
  4612.                 ]);
  4613.                 $statusCode $response->getStatusCode();
  4614.                 $data json_decode($response->getBody(), true);
  4615.                 if ($statusCode != 200) {
  4616.                     return $this->createErrorResponse($statusCode);
  4617.                 }
  4618.                 // Set the data to Redis cache
  4619.                 $this->redisCache->set($cacheKey$data);
  4620.                 return $data;
  4621.             } else {
  4622.                 if ($coordinates) {
  4623.                     $latitude $coordinates[0][0];
  4624.                     $longitude $coordinates[0][1];
  4625.                 }
  4626.                 // Validate the input parameters
  4627.                 if (!preg_match('/^[-]?[0-9]{1,2}\.[0-9]+/'$latitude)) {
  4628.                     throw new \InvalidArgumentException('Invalid latitude');
  4629.                 }
  4630.                 if (!preg_match('/^[-]?[0-9]{1,3}\.[0-9]+/'$longitude)) {
  4631.                     throw new \InvalidArgumentException('Invalid longitude');
  4632.                 }
  4633.                 if (empty($startDate)) {
  4634.                     throw new \InvalidArgumentException('Invalid startDate');
  4635.                 }
  4636.                 if (empty($endDate)) {
  4637.                     throw new \InvalidArgumentException('Invalid endDate');
  4638.                 }
  4639.                 $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  4640.                 $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  4641.                 // Create a Redis key for the cache            
  4642.                 $cacheKey \App\Lib\Utility::generateKey($hour$startDate$endDate$latitude$longitude,  $unit,  $model$format);
  4643.                 // Try to get the data from Redis cache
  4644.                 $cachedData $this->redisCache->get($cacheKey);
  4645.                 if ($cachedData !== null) {
  4646.                     // Return the cached data if available
  4647.                     return $cachedData;
  4648.                 }
  4649.                 // If cache is empty, get the data from the API
  4650.                 $client = new Client(['verify' => false]);
  4651.                 $url sprintf(
  4652.                     '%s/%s--%s:PT%sH/heat_index:%s,t_2m:%s/%s,%s/%s?model=%s&use_decluttered=true',
  4653.                     $this->apiBaseUrl,
  4654.                     $startDate,
  4655.                     $endDate,
  4656.                     $hour,
  4657.                     $unit,
  4658.                     $unit,
  4659.                     $latitude,
  4660.                     $longitude,
  4661.                     $format,
  4662.                     $model
  4663.                 );
  4664.                 $response $client->request('GET'$url, [
  4665.                     'auth' => [$this->username$this->password],
  4666.                 ]);
  4667.                 $statusCode $response->getStatusCode();
  4668.                 $data json_decode($response->getBody(), true);
  4669.                 if ($statusCode != 200) {
  4670.                     return $this->createErrorResponse($statusCode);
  4671.                 }
  4672.                 // Set the data to Redis cache
  4673.                 $this->redisCache->set($cacheKey$data);
  4674.                 return $data;
  4675.             }
  4676.         } catch (GuzzleHttp\Exception\RequestException $e) {
  4677.             return throw new \Exception($e->getMessage());
  4678.         } catch (\Exception $e) {
  4679.             return throw new \Exception($e->getMessage());
  4680.         }
  4681.     }
  4682.     /**
  4683.      * Atmospheric Density
  4684.      *  
  4685.      * @param string $hourly The hour for which forecast is required in 24-hour format
  4686.      * @param string $format return type of json
  4687.      * @param string $level The type of level request (e.g., '2m')
  4688.      * @param string $unit The type of unit request (e.g., 'kgm3')
  4689.      * @param string $startDate The type of data date (e.g., '2023-09-01')
  4690.      * @param string $endDate The type of data date (e.g., '2023-09-01')
  4691.      * @param array $coordinates The latitude and longitude of location
  4692.      * @throws \InvalidArgumentException If any parameter has an invalid data type
  4693.      */
  4694.     public function getAirdensity(int $hour,  $startDate,  $endDate, array $coordinates$level$unit$format)
  4695.     {
  4696.         try {
  4697.             if (count($coordinates) > 1) {
  4698.                 // Validate the input parameters
  4699.                 foreach ($coordinates as $coordinate) {
  4700.                     if (count($coordinate) < || !is_numeric($coordinate[0]) || !is_numeric($coordinate[1])) {
  4701.                         throw new \InvalidArgumentException('Invalid coordinates');
  4702.                     }
  4703.                 }
  4704.                 if (empty($startDate) || empty($endDate)) {
  4705.                     throw new \InvalidArgumentException('Invalid dates');
  4706.                 }
  4707.                 $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  4708.                 $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  4709.                 $cacheKey \App\Lib\Utility::generateKey($hour$startDate$endDate$coordinates,  $level$unit$format);
  4710.                 // Try to get the data from Redis cache
  4711.                 $cachedData $this->redisCache->get($cacheKey);
  4712.                 if ($cachedData !== null) {
  4713.                     // Return the cached data if available
  4714.                     return $cachedData;
  4715.                 }
  4716.                 // If cache is empty, get the data from the API
  4717.                 $coordinateString implode('+'array_map(fn($coords) => implode(','$coords), $coordinates));
  4718.                 $client = new Client(['verify' => false]);
  4719.                 $url sprintf(
  4720.                     '%s/%s--%s:PT%sH/air_density_%s:%s/%s/%s?use_decluttered=true',
  4721.                     $this->apiBaseUrl,
  4722.                     $startDate,
  4723.                     $endDate,
  4724.                     $hour,
  4725.                     $level,
  4726.                     $unit,
  4727.                     $coordinateString,
  4728.                     $format
  4729.                 );
  4730.                 $response $client->request('GET'$url, [
  4731.                     'auth' => [$this->username$this->password],
  4732.                 ]);
  4733.                 $statusCode $response->getStatusCode();
  4734.                 $data json_decode($response->getBody(), true);
  4735.                 if ($statusCode != 200) {
  4736.                     return $this->createErrorResponse($statusCode);
  4737.                 }
  4738.                 // Set the data to Redis cache
  4739.                 $this->redisCache->set($cacheKey$data);
  4740.                 return $data;
  4741.             } else {
  4742.                 if ($coordinates) {
  4743.                     $latitude $coordinates[0][0];
  4744.                     $longitude $coordinates[0][1];
  4745.                 }
  4746.                 // Validate the input parameters
  4747.                 if (!preg_match('/^[-]?[0-9]{1,2}\.[0-9]+/'$latitude)) {
  4748.                     throw new \InvalidArgumentException('Invalid latitude');
  4749.                 }
  4750.                 if (!preg_match('/^[-]?[0-9]{1,3}\.[0-9]+/'$longitude)) {
  4751.                     throw new \InvalidArgumentException('Invalid longitude');
  4752.                 }
  4753.                 if (empty($startDate)) {
  4754.                     throw new \InvalidArgumentException('Invalid startDate');
  4755.                 }
  4756.                 if (empty($endDate)) {
  4757.                     throw new \InvalidArgumentException('Invalid endDate');
  4758.                 }
  4759.                 $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  4760.                 $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  4761.                 $cacheKey \App\Lib\Utility::generateKey($hour$startDate$endDate$latitude$longitude,  $level$unit$format);
  4762.                 // Try to get the data from Redis cache
  4763.                 $cachedData $this->redisCache->get($cacheKey);
  4764.                 if ($cachedData !== null) {
  4765.                     // Return the cached data if available
  4766.                     return $cachedData;
  4767.                 }
  4768.                 // If cache is empty, get the data from the API
  4769.                 $client = new Client(['verify' => false]);
  4770.                 $url sprintf(
  4771.                     '%s/%s--%s:PT%sH/air_density_%s:%s/%s,%s/%s?use_decluttered=true',
  4772.                     $this->apiBaseUrl,
  4773.                     $startDate,
  4774.                     $endDate,
  4775.                     $hour,
  4776.                     $level,
  4777.                     $unit,
  4778.                     $latitude,
  4779.                     $longitude,
  4780.                     $format
  4781.                 );
  4782.                 $response $client->request('GET'$url, [
  4783.                     'auth' => [$this->username$this->password],
  4784.                 ]);
  4785.                 $statusCode $response->getStatusCode();
  4786.                 $data json_decode($response->getBody(), true);
  4787.                 if ($statusCode != 200) {
  4788.                     return $this->createErrorResponse($statusCode);
  4789.                 }
  4790.                 // Set the data to Redis cache
  4791.                 $this->redisCache->set($cacheKey$data);
  4792.                 return $data;
  4793.             }
  4794.         } catch (GuzzleHttp\Exception\RequestException $e) {
  4795.             return throw new \Exception($e->getMessage());
  4796.         } catch (\Exception $e) {
  4797.             return throw new \Exception($e->getMessage());
  4798.         }
  4799.     }
  4800.     /**
  4801.      * Soil Moisture Index
  4802.      *  
  4803.      * @param string $hourly The hour for which forecast is required in 24-hour format
  4804.      * @param string $format return type of json
  4805.      * @param string $level The type of level request (e.g., '2m')
  4806.      * @param string $unit The type of unit request (e.g., 'kgm3')
  4807.      * @param string $startDate The type of data date (e.g., '2023-09-01')
  4808.      * @param string $endDate The type of data date (e.g., '2023-09-01')
  4809.      * @param array $coordinates The latitude and longitude of location
  4810.      * @throws \InvalidArgumentException If any parameter has an invalid data type
  4811.      */
  4812.     public function getSoilMoistureIndex(int $hour,  $startDate,  $endDate, array $coordinates$level$unit$format)
  4813.     {
  4814.         try {
  4815.             if (count($coordinates) > 1) {
  4816.                 // Validate the input parameters
  4817.                 foreach ($coordinates as $coordinate) {
  4818.                     if (count($coordinate) < || !is_numeric($coordinate[0]) || !is_numeric($coordinate[1])) {
  4819.                         throw new \InvalidArgumentException('Invalid coordinates');
  4820.                     }
  4821.                 }
  4822.                 if (empty($startDate) || empty($endDate)) {
  4823.                     throw new \InvalidArgumentException('Invalid dates');
  4824.                 }
  4825.                 $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  4826.                 $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  4827.                 $cacheKey \App\Lib\Utility::generateKey($hour$startDate$endDate$coordinates,  $level$unit$format);
  4828.                 // Try to get the data from Redis cache
  4829.                 $cachedData $this->redisCache->get($cacheKey);
  4830.                 if ($cachedData !== null) {
  4831.                     // Return the cached data if available
  4832.                     return $cachedData;
  4833.                 }
  4834.                 // If cache is empty, get the data from the API
  4835.                 $coordinateString implode('+'array_map(fn($coords) => implode(','$coords), $coordinates));
  4836.                 $client = new Client(['verify' => false]);
  4837.                 $url sprintf(
  4838.                     '%s/%s--%s:PT%sH/soil_moisture_index_%s:%s/%s/%s?use_decluttered=true',
  4839.                     $this->apiBaseUrl,
  4840.                     $startDate,
  4841.                     $endDate,
  4842.                     $hour,
  4843.                     $level,
  4844.                     $unit,
  4845.                     $coordinateString,
  4846.                     $format
  4847.                 );
  4848.                 $response $client->request('GET'$url, [
  4849.                     'auth' => [$this->username$this->password],
  4850.                 ]);
  4851.                 $statusCode $response->getStatusCode();
  4852.                 $data json_decode($response->getBody(), true);
  4853.                 if ($statusCode != 200) {
  4854.                     return $this->createErrorResponse($statusCode);
  4855.                 }
  4856.                 // Set the data to Redis cache
  4857.                 $this->redisCache->set($cacheKey$data);
  4858.                 return $data;
  4859.             } else {
  4860.                 if ($coordinates) {
  4861.                     $latitude $coordinates[0][0];
  4862.                     $longitude $coordinates[0][1];
  4863.                 }
  4864.                 // Validate the input parameters
  4865.                 if (!preg_match('/^[-]?[0-9]{1,2}\.[0-9]+/'$latitude)) {
  4866.                     throw new \InvalidArgumentException('Invalid latitude');
  4867.                 }
  4868.                 if (!preg_match('/^[-]?[0-9]{1,3}\.[0-9]+/'$longitude)) {
  4869.                     throw new \InvalidArgumentException('Invalid longitude');
  4870.                 }
  4871.                 if (empty($startDate)) {
  4872.                     throw new \InvalidArgumentException('Invalid startDate');
  4873.                 }
  4874.                 if (empty($endDate)) {
  4875.                     throw new \InvalidArgumentException('Invalid endDate');
  4876.                 }
  4877.                 $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  4878.                 $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  4879.                 $cacheKey \App\Lib\Utility::generateKey($hour$startDate$endDate$latitude$longitude,  $level$unit$format);
  4880.                 // Try to get the data from Redis cache
  4881.                 $cachedData $this->redisCache->get($cacheKey);
  4882.                 if ($cachedData !== null) {
  4883.                     // Return the cached data if available
  4884.                     return $cachedData;
  4885.                 }
  4886.                 // If cache is empty, get the data from the API
  4887.                 $client = new Client(['verify' => false]);
  4888.                 $url sprintf(
  4889.                     '%s/%s--%s:PT%sH/soil_moisture_index_%s:%s/%s,%s/%s?use_decluttered=true',
  4890.                     $this->apiBaseUrl,
  4891.                     $startDate,
  4892.                     $endDate,
  4893.                     $hour,
  4894.                     $level,
  4895.                     $unit,
  4896.                     $latitude,
  4897.                     $longitude,
  4898.                     $format
  4899.                 );
  4900.                 $response $client->request('GET'$url, [
  4901.                     'auth' => [$this->username$this->password],
  4902.                 ]);
  4903.                 $statusCode $response->getStatusCode();
  4904.                 $data json_decode($response->getBody(), true);
  4905.                 if ($statusCode != 200) {
  4906.                     return $this->createErrorResponse($statusCode);
  4907.                 }
  4908.                 // Set the data to Redis cache
  4909.                 $this->redisCache->set($cacheKey$data);
  4910.                 return $data;
  4911.             }
  4912.         } catch (GuzzleHttp\Exception\RequestException $e) {
  4913.             return throw new \Exception($e->getMessage());
  4914.         } catch (\Exception $e) {
  4915.             return throw new \Exception($e->getMessage());
  4916.         }
  4917.     }
  4918.     /**
  4919.      * Frost & Thaw Depth
  4920.      *  
  4921.      * @param string $hourly The hour for which forecast is required in 24-hour format
  4922.      * @param string $format return type of json
  4923.      * @param string $startDate The type of data date (e.g., '2023-09-01')
  4924.      * @param string $endDate The type of data date (e.g., '2023-09-01')
  4925.      * @param string $unit The type of data request (e.g., 'cm')
  4926.      * @param array $coordinates The latitude and longitude of location
  4927.      * @throws \InvalidArgumentException If any parameter has an invalid data type
  4928.      */
  4929.     public function getFrostThawAndDepth(int $hour,  $startDate,  $endDate$unit, array $coordinates$format)
  4930.     {
  4931.         try {
  4932.             if (count($coordinates) > 1) {
  4933.                 // Validate the input parameters
  4934.                 foreach ($coordinates as $coordinate) {
  4935.                     if (count($coordinate) < || !is_numeric($coordinate[0]) || !is_numeric($coordinate[1])) {
  4936.                         throw new \InvalidArgumentException('Invalid coordinates');
  4937.                     }
  4938.                 }
  4939.                 if (empty($startDate) || empty($endDate)) {
  4940.                     throw new \InvalidArgumentException('Invalid dates');
  4941.                 }
  4942.                 $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  4943.                 $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  4944.                 $cacheKey \App\Lib\Utility::generateKey($hour$startDate$endDate$unit$coordinates$format);
  4945.                 // Try to get the data from Redis cache
  4946.                 $cachedData $this->redisCache->get($cacheKey);
  4947.                 if ($cachedData !== null) {
  4948.                     // Return the cached data if available
  4949.                     return $cachedData;
  4950.                 }
  4951.                 // If cache is empty, get the data from the API
  4952.                 $coordinateString implode('+'array_map(fn($coords) => implode(','$coords), $coordinates));
  4953.                 $client = new Client(['verify' => false]);
  4954.                 $url sprintf(
  4955.                     '%s/%s--%s:PT%sH/frost_depth:%s,thaw_depth:%s/%s/%s?use_decluttered=true',
  4956.                     $this->apiBaseUrl,
  4957.                     $startDate,
  4958.                     $endDate,
  4959.                     $hour,
  4960.                     $unit,
  4961.                     $unit,
  4962.                     $coordinateString,
  4963.                     $format
  4964.                 );
  4965.                 $response $client->request('GET'$url, [
  4966.                     'auth' => [$this->username$this->password],
  4967.                 ]);
  4968.                 $statusCode $response->getStatusCode();
  4969.                 $data json_decode($response->getBody(), true);
  4970.                 if ($statusCode != 200) {
  4971.                     return $this->createErrorResponse($statusCode);
  4972.                 }
  4973.                 // Set the data to Redis cache
  4974.                 $this->redisCache->set($cacheKey$data);
  4975.                 return $data;
  4976.             } else {
  4977.                 if ($coordinates) {
  4978.                     $latitude $coordinates[0][0];
  4979.                     $longitude $coordinates[0][1];
  4980.                 }
  4981.                 // Validate the input parameters
  4982.                 if (!preg_match('/^[-]?[0-9]{1,2}\.[0-9]+/'$latitude)) {
  4983.                     throw new \InvalidArgumentException('Invalid latitude');
  4984.                 }
  4985.                 if (!preg_match('/^[-]?[0-9]{1,3}\.[0-9]+/'$longitude)) {
  4986.                     throw new \InvalidArgumentException('Invalid longitude');
  4987.                 }
  4988.                 if (empty($startDate)) {
  4989.                     throw new \InvalidArgumentException('Invalid startDate');
  4990.                 }
  4991.                 if (empty($endDate)) {
  4992.                     throw new \InvalidArgumentException('Invalid endDate');
  4993.                 }
  4994.                 $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  4995.                 $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  4996.                 $cacheKey \App\Lib\Utility::generateKey($hour$startDate$endDate$unit$latitude$longitude$format);
  4997.                 // Try to get the data from Redis cache
  4998.                 $cachedData $this->redisCache->get($cacheKey);
  4999.                 if ($cachedData !== null) {
  5000.                     // Return the cached data if available
  5001.                     return $cachedData;
  5002.                 }
  5003.                 // If cache is empty, get the data from the API
  5004.                 $client = new Client(['verify' => false]);
  5005.                 $url sprintf(
  5006.                     '%s/%s--%s:PT%sH/frost_depth:%s,thaw_depth:%s/%s,%s/%s?use_decluttered=true',
  5007.                     $this->apiBaseUrl,
  5008.                     $startDate,
  5009.                     $endDate,
  5010.                     $hour,
  5011.                     $unit,
  5012.                     $unit,
  5013.                     $latitude,
  5014.                     $longitude,
  5015.                     $format
  5016.                 );
  5017.                 $response $client->request('GET'$url, [
  5018.                     'auth' => [$this->username$this->password],
  5019.                 ]);
  5020.                 $statusCode $response->getStatusCode();
  5021.                 $data json_decode($response->getBody(), true);
  5022.                 if ($statusCode != 200) {
  5023.                     return $this->createErrorResponse($statusCode);
  5024.                 }
  5025.                 // Set the data to Redis cache
  5026.                 $this->redisCache->set($cacheKey$data);
  5027.                 return $data;
  5028.             }
  5029.         } catch (GuzzleHttp\Exception\RequestException $e) {
  5030.             return throw new \Exception($e->getMessage());
  5031.         } catch (\Exception $e) {
  5032.             return throw new \Exception($e->getMessage());
  5033.         }
  5034.     }
  5035.     public function getReportForecastData(array $coordinatesstring $startDatestring $endDateint $hoursstring $model "ksancm-wrf-48", array $parameters = [], $translator, array $cities$params)
  5036.     {
  5037.         try {
  5038.                 if (count($coordinates) > 1) {
  5039.                     // Validate the input parameters
  5040.                     foreach ($coordinates as $coordinate) {
  5041.                         if (count($coordinate) < || !is_numeric($coordinate[0]) || !is_numeric($coordinate[1])) {
  5042.                             // throw new \InvalidArgumentException('Invalid coordinates');
  5043.                             return ["success" => false"message" => $translator->trans("invalid_coordinates")];
  5044.                         }
  5045.                     }
  5046.                 }else{
  5047.                     if ($coordinates) {
  5048.                         $latitude $coordinates[0][0];
  5049.                         $longitude $coordinates[0][1];
  5050.                     }
  5051.                     // Validate the input parameters
  5052.                     if (!preg_match('/^[-]?[0-9]{1,2}\.[0-9]+/'$latitude)) {
  5053.                         // throw new \InvalidArgumentException('Invalid latitude');
  5054.                         return ["success" => false"message" => $translator->trans("invalid_latitude")];
  5055.                     }
  5056.                     if (!preg_match('/^[-]?[0-9]{1,3}\.[0-9]+/'$longitude)) {
  5057.                         // throw new \InvalidArgumentException('Invalid longitude');
  5058.                         return ["success" => false"message" => $translator->trans("invalid_longitude")];
  5059.                     }
  5060.                 }
  5061.     
  5062.                 if (empty($startDate) || empty($endDate)) {
  5063.                     return ["success" => false"message" => $translator->trans("invalid_dates")];
  5064.                     // throw new \InvalidArgumentException('Invalid dates');
  5065.                 }
  5066.                 if ($hours and $hours 24) {
  5067.                     throw new \InvalidArgumentException('Invalid hour');
  5068.                 }
  5069.                 $startDate date('Y-m-d\TH:i:s.v\Z', (strtotime($startDate)));
  5070.                 $endDate date('Y-m-d\TH:i:s.v\Z', (strtotime($endDate)));
  5071.                 if (empty($parameters)) {
  5072.                     $parameters = [
  5073.                         't_2m:C',
  5074.                         't_max_2m_%sh:C',
  5075.                         't_min_2m_%sh:C',
  5076.                         't_apparent_min_%sh:C',
  5077.                         't_apparent_max_%sh:C',
  5078.                         'wind_speed_mean_10m_%sh:kmh',
  5079.                         'wind_dir_mean_10m_%sh:d',
  5080.                         'prob_precip_%sh:p',
  5081.                         'precip_%sh:mm',
  5082.                         'relative_humidity_mean_2m_%sh:p',
  5083.                         'effective_cloud_cover_mean_%sh:octas',
  5084.                         'dew_point_mean_2m_%sh:C',
  5085.                         'wind_gusts_10m_%sh:kmh',
  5086.                         'visibility:km',
  5087.                     ];
  5088.                 }
  5089.                 // Create a Redis key for the cache
  5090.                 $cacheKey sprintf('daily_forecast_%s_%s_%s'implode('_'array_map(function ($coordinate) {
  5091.                     return implode('_'$coordinate);
  5092.                 }, $coordinates)), (($startDate) . '-' . ($endDate)) . $modelimplode('_'$parameters));
  5093.                 // Try to get the data from Redis cache
  5094.                 $cachedData $this->redisCache->get($cacheKey);
  5095.                 if ($cachedData !== null) {
  5096.                     // Return the cached data if available
  5097.                     return $cachedData;
  5098.                 }
  5099.     
  5100.                 $timeResolutions = [
  5101.                     '_24h' => '+1 day',
  5102.                     '_12h' => '+12 hours',
  5103.                     '_6h' => '+6 hours',
  5104.                     '_3h' => '+3 hours',
  5105.                     '_1h' => '+1 hour'
  5106.                 ];
  5107.     
  5108.                 $adjustedParameters = [];
  5109.                 $nonAdjustedParameters = [];
  5110.     
  5111.                 foreach ($parameters as $parameter) {
  5112.                     $matched false;
  5113.                     foreach ($timeResolutions as $key => $adjustment) {
  5114.                         if (strpos($parameter$key) !== false) {
  5115.                             $matched true;
  5116.                             $adjustedParameters[$adjustment][] = $parameter;
  5117.                             break;
  5118.                         }
  5119.                     }
  5120.                     if (!$matched) {
  5121.                         $nonAdjustedParameters[] = $parameter;
  5122.                     }
  5123.                 }
  5124.                 // Construct the URL
  5125.                 // $url = "{$this->apiBaseUrl}/{$startDate}--{$endDate}:{$duration}/t_2m:C,wind_speed_10m:kmh,wind_dir_10m:d,sfs_pressure:hPa,precip_10min:mm,relative_humidity_2m:p,visibility:km,dew_point_2m:c,wind_gusts_10m_1h:kmh,ceiling_height_agl:m,geopotential_height:m,t_min_2m_24h:C,t_max_2m_24h:C,precip_24h:mm,total_cloud_cover:octas/metar_{$metar}/json?source=mix-obs&on_invalid=fill_with_invalid";
  5126.                 $dataFinal = [
  5127.                     'data' => []
  5128.                 ];
  5129.     
  5130.                 foreach ($adjustedParameters as $adjustment => $adjParams) {
  5131.                     $adjustedStartDate = (new \DateTime($startDate))->modify($adjustment)->format('Y-m-d\TH:i:s.v\Z');
  5132.                     $adjustedEndDate = (new \DateTime($endDate))->modify($adjustment)->format('Y-m-d\TH:i:s.v\Z');
  5133.                     $data $this->fetchReportForecastData($coordinates,$adjustedStartDate,$adjustedEndDate,$hours$model,$adjParams,$translator,$cities$params);
  5134.                     // Revert dates to original range
  5135.                     if (isset($data['data']) && is_array($data['data'])) {
  5136.     
  5137.                         foreach ($data['data'] as &$datum) {
  5138.                             if (isset($datum['coordinates']) && is_array($datum['coordinates'])) {
  5139.                                 foreach ($datum['coordinates'] as &$coordinate) {
  5140.                                     if (isset($coordinate['dates']) && is_array($coordinate['dates'])) {
  5141.                                         foreach ($coordinate['dates'] as &$date) {
  5142.                                             if (isset($date['date'])) {
  5143.                                                 // Convert the date back by subtracting the adjustment
  5144.                                                 $adjustmentValue str_replace(['+'' '], ''$adjustment); // Remove '+' and extra spaces
  5145.                                                 $date['date'] = (new \DateTime($date['date']))
  5146.                                                     ->modify('-' $adjustmentValue)
  5147.                                                     ->format('Y-m-d\TH:i:s\Z');
  5148.                                             }
  5149.                                         }
  5150.                                     }
  5151.                                 }
  5152.                             }
  5153.                         }
  5154.                     } else {
  5155.                         $data['data'] = []; // Ensure 'data' exists
  5156.                     }
  5157.     
  5158.                     // Merge into $dataFinal
  5159.                     if (empty($dataFinal['version'])) {
  5160.                         $dataFinal['version'] = $data['version'] ?? null;
  5161.                         $dataFinal['user'] = $data['user'] ?? null;
  5162.                         $dataFinal['dateGenerated'] = $data['dateGenerated'] ?? null;
  5163.                         $dataFinal['status'] = $data['status'] ?? null;
  5164.                     }
  5165.     
  5166.                     if (isset($data['data']) && is_array($data['data'])) {
  5167.                         $dataFinal['data'] = array_merge($dataFinal['data'], $data['data']);
  5168.                     }
  5169.                 }
  5170.                 // Process non-adjusted parameters
  5171.                 if (!empty($nonAdjustedParameters)) {
  5172.                     $data$this->fetchReportForecastData($coordinates,$startDate,$endDate,$hours$model ,$nonAdjustedParameters,$translator,$cities$params);
  5173.                     if (isset($data['data']) && is_array($data['data'])) {
  5174.                         $dataFinal['data'] = array_merge($dataFinal['data'], $data['data']);
  5175.                     }
  5176.                 }
  5177.                 // Reorder data based on parameters
  5178.                     $dataFinal['data'] = isset($dataFinal['data']) && is_array($dataFinal['data'])
  5179.                     ? $this->orderResults($dataFinal['data'], $parameters)
  5180.                     : [];
  5181.     
  5182.                 $result = [];
  5183.                 foreach ($dataFinal['data'] as $param) {
  5184.                     foreach ($param['coordinates'] as $paramCoordinates) {
  5185.                         // exit;
  5186.                         $paramCoordinates['lat'] = number_format($paramCoordinates['lat'], 6'.''');
  5187.                         $paramCoordinates['lon'] = number_format($paramCoordinates['lon'], 6'.''');
  5188.                         $cityKey $paramCoordinates['lat'] . '|' $paramCoordinates['lon'];
  5189.                         if (!isset($result[$cityKey])) {
  5190.                             $result[$cityKey] = [];
  5191.                             if (isset($cities[$cityKey]['en'])) {
  5192.                                 $result[$cityKey] = [
  5193.                                     'cityEn' => $cities[$cityKey]['en'],
  5194.                                     'cityAr' => $cities[$cityKey]['ar'],
  5195.                                     'lat' => $paramCoordinates['lat'],
  5196.                                     'lon' => $paramCoordinates['lon'],
  5197.                                     'parameters' => [],
  5198.                                 ];
  5199.                             } else {
  5200.                                 $result[$cityKey] = [
  5201.                                     'city' => $cities[$cityKey],
  5202.                                     'lat' => $paramCoordinates['lat'],
  5203.                                     'lon' => $paramCoordinates['lon'],
  5204.                                     'parameters' => [],
  5205.                                 ];
  5206.                             }
  5207.                         }
  5208.                         $parameterData = [
  5209.                             'parameter' => $param['parameter'],
  5210.                             'dates' => [],
  5211.                         ]; 
  5212.                         foreach ($paramCoordinates['dates'] as $item) {
  5213.                             $parameterData['dates'][] = [
  5214.                                 'date' => $item['date'], // You can modify this as needed
  5215.                                 'value' => $item['value'], // You can modify this as needed
  5216.                             ];
  5217.                         }
  5218.     
  5219.                         // p_r($parameterData);
  5220.                         $result[$cityKey]['parameters'][] = $parameterData;
  5221.                     }
  5222.                 }
  5223.     
  5224.                 $result array_values($result);
  5225.                 if (isset($params['report_type_id']) && !empty($params['report_type_id'])) {
  5226.                     $latestReport = new Report\Listing();
  5227.                     $latestReport->setCondition('reportType__id = ?', [$params['report_type_id']]);
  5228.                     $latestReport->setOrderKey("o_creationDate");
  5229.                     $latestReport->setOrder("desc");
  5230.                     $latestReport $latestReport->current();
  5231.                     if ($latestReport) {
  5232.                         $jsonData json_decode($latestReport->getJsonData(), true);
  5233.                         foreach ($result as &$value) {
  5234.                             // Compare latitude and longitude
  5235.                             foreach ($jsonData as $jsonEntry) {
  5236.                                 if ($value['lat'] == $jsonEntry['lat'] && $value['lon'] == $jsonEntry['lon']) {
  5237.                                     // Latitude and longitude match, proceed with parameter comparison
  5238.                                     foreach ($value['parameters'] as &$paramValue) {
  5239.                                         foreach ($jsonEntry['parameters'] as $jsonParam) {
  5240.                                             if ($jsonParam['parameter'] == $paramValue['parameter']) {
  5241.                                                 // Parameter matches, check dates now
  5242.                                                 foreach ($paramValue['dates'] as &$dateValue) {
  5243.                                                     foreach ($jsonParam['dates'] as $jsonDate) {
  5244.                                                         if ($dateValue['date'] == $jsonDate['date']) {
  5245.                                                             // Exact match found, override the value
  5246.                                                             $dateValue['value'] = $jsonDate['value'];
  5247.                                                             // Continue checking all dates, no break here
  5248.                                                         }
  5249.                                                     }
  5250.                                                 }
  5251.                                                 unset($dateValue); // Ensure reference is not carried over
  5252.                                             }
  5253.                                         }
  5254.                                     }
  5255.                                     unset($paramValue); // Ensure reference is not carried over
  5256.                                 }
  5257.                             }
  5258.                         }
  5259.                         unset($value); // Ensure reference is not carried over
  5260.                     }
  5261.                 }
  5262.     
  5263.             // Set the data to Redis cache
  5264.             $this->redisCache->set($cacheKey$dataFinal);
  5265.             return $result;
  5266.         } catch (GuzzleHttp\Exception\RequestException $e) {
  5267.             return throw new \Exception($e->getMessage());
  5268.         } catch (\Exception $e) {
  5269.             return throw new \Exception($e->getMessage());
  5270.         }
  5271.     }
  5272.     
  5273.     public function fetchReportForecastData($coordinates,$startDate,$endDate,$hours$model,$parameters,$translator,$cities$params){
  5274.         $dataFinal = [];
  5275.          $client = new Client(['verify' => false]);
  5276.          if (count($coordinates) > 1) {
  5277.          $coordinateString implode('+'array_map(fn($coords) => implode(','$coords), $coordinates));
  5278.          }else{
  5279.             if ($coordinates) {
  5280.                 $latitude $coordinates[0][0];
  5281.                 $longitude $coordinates[0][1];
  5282.             }
  5283.          }
  5284.                 while (!empty($parameters)) {
  5285.     
  5286.                     $batchParameters array_splice($parameters010); // Take up to 10 parameters
  5287.                     $batchQueryString implode(','$batchParameters);
  5288.                     $batchQueryString str_replace('%s'$hours$batchQueryString);
  5289.     
  5290.                     if (count($coordinates) > 1) {
  5291.                         $url sprintf(
  5292.                         '%s/%s--%s:PT%sH/%s/%s/json?model=%s&use_decluttered=true',
  5293.                         $this->apiBaseUrl,
  5294.                         $startDate,
  5295.                         $endDate,
  5296.                         $hours,
  5297.                         $batchQueryString,
  5298.                         $coordinateString,
  5299.                         $model
  5300.                     );
  5301.                     }else{
  5302.                         $url sprintf(
  5303.                             '%s/%s--%s:PT%sH/%s/%s,%s/json?model=%s&use_decluttered=true',
  5304.                             $this->apiBaseUrl,
  5305.                             $startDate,
  5306.                             $endDate,
  5307.                             $hours,
  5308.                             $batchQueryString,
  5309.                             $latitude,
  5310.                             $longitude,
  5311.                             $model
  5312.                         );
  5313.                     }
  5314.                     $response $client->request('GET'$url, [
  5315.                         'auth' => [$this->username$this->password],
  5316.                     ]);
  5317.                     $statusCode $response->getStatusCode();
  5318.                     $data json_decode($response->getBody(), true);
  5319.                     // Merge data from the current API call into the final data
  5320.                     $dataFinal['data'] = array_merge($dataFinal['data'] ?? [], $data['data']);
  5321.                 }
  5322.                 foreach ($coordinates as $coOrd) {
  5323.                     $weatherSymbols $this->getWeatherSymbols([$coOrd], $startDate$endDate'PT' $hours 'H'$hours 'h'true);
  5324.                     if (isset($weatherSymbols['data'][0]['parameter'])) {
  5325.                         $dataFinal['symbols'][$coOrd[0] . '|' $coOrd[1]] = $weatherSymbols['data'][0];
  5326.                     }
  5327.                 }
  5328.                 // p_r($dataFinal);
  5329.                 // exit;
  5330.     
  5331.                 // Convert the associative array to indexed array
  5332.     
  5333.                 return $dataFinal;
  5334.     
  5335.     }
  5336.     public function getAdminReportForecastData(array $coordinatesstring $startDatestring $endDatestring $model "ksancm-wrf-48", array $parameters12h = [], array $parameters24h = [], $translator, array $cities$params)
  5337.     {
  5338.         try {
  5339.             if (isset($params['report_type_id']) && !empty($params['report_type_id'])) {
  5340.                 $latestReport = new Report\Listing();
  5341.                 $reportType \Pimcore\Model\DataObject::getById($params['report_type_id']);
  5342.                 $latestReport->filterByReportType($reportType);
  5343.                 $latestReport->setOrderKey("createdOn");
  5344.                 $latestReport->setLimit(1);
  5345.                 $latestReport->setOrder("desc");
  5346.                 $latestReport $latestReport->current();
  5347.             }
  5348.             if (count($coordinates) > 1) {
  5349.                 // Validate the input parameters
  5350.                 foreach ($coordinates as $coordinate) {
  5351.                     if (count($coordinate) < || !is_numeric($coordinate[0]) || !is_numeric($coordinate[1])) {
  5352.                         // throw new \InvalidArgumentException('Invalid coordinates');
  5353.                         return ["success" => false"message" => $translator->trans("invalid_coordinates")];
  5354.                     }
  5355.                 }
  5356.                 if (empty($startDate) || empty($endDate)) {
  5357.                     return ["success" => false"message" => $translator->trans("invalid_dates")];
  5358.                     // throw new \InvalidArgumentException('Invalid dates');
  5359.                 }
  5360.                 // Create a Redis key for the cache
  5361.                 $cacheKey sprintf('daily_forecast_%s_%s_%s_%s'implode('_'array_map(function ($coordinate) {
  5362.                     return implode('_'$coordinate);
  5363.                 }, $coordinates)), (($startDate) . '-' . ($endDate)) . $modelimplode('_'$parameters12h), implode('_'$parameters24h));
  5364.                 // Try to get the data from Redis cache
  5365.                 $cachedData $this->redisCache->get($cacheKey);
  5366.                 if ($cachedData !== null) {
  5367.                     // Return the cached data if available
  5368.                     // return $cachedData;
  5369.                 }
  5370.                 // If cache is empty, get the data from the API
  5371.                 $client = new Client(['verify' => false]);
  5372.                 $coordinateString implode('+'array_map(fn($coords) => implode(','$coords), $coordinates));
  5373.                 $dataFinal = [];
  5374.                 // Function to perform the API requests for given parameters and hours
  5375.                 $performApiRequests = function ($parameters$hours$startDate$endDate) use ($client$coordinateString$model) {
  5376.                     $data = [];
  5377.                     while (!empty($parameters)) {
  5378.                         $batchParameters array_splice($parameters010); // Take up to 10 parameters
  5379.                         $batchQueryString implode(','$batchParameters);
  5380.                         $url sprintf(
  5381.                             '%s/%s--%s:PT%sH/%s/%s/json?model=%s&use_decluttered=true',
  5382.                             $this->apiBaseUrl,
  5383.                             $startDate,
  5384.                             $endDate,
  5385.                             $hours,
  5386.                             $batchQueryString,
  5387.                             $coordinateString,
  5388.                             $model
  5389.                         );
  5390.                         $response $client->request('GET'$url, [
  5391.                             'auth' => [$this->username$this->password],
  5392.                         ]);
  5393.                         $statusCode $response->getStatusCode();
  5394.                         $dataBatch json_decode($response->getBody(), true);
  5395.                         // Merge data from the current API call into the data array
  5396.                         $data['data'] = array_merge($data['data'] ?? [], $dataBatch['data']);
  5397.                     }
  5398.                     return $data;
  5399.                 };
  5400.                 // Process 24h parameters
  5401.                 if (!empty($parameters24h)) {
  5402.                     $startDateUpdated = new DateTime($startDate);
  5403.                     $endDateUpdated = new DateTime($endDate);
  5404.                     $startDateUpdated $startDateUpdated->modify('+1 day')->format('Y-m-d\TH:i:s\Z');
  5405.                     $endDateUpdated $endDateUpdated->modify('+1 day')->format('Y-m-d\TH:i:s\Z');
  5406.                     $data24h $performApiRequests($parameters24h24$startDateUpdated$endDateUpdated);
  5407.                     if (!empty($data24h)) {
  5408.                         foreach ($data24h['data'] as &$datum) {
  5409.                             if (isset($datum['coordinates'])) {
  5410.                                 foreach ($datum['coordinates'] as &$coordinate) {
  5411.                                     foreach ($coordinate['dates'] as &$date) {
  5412.                                         $dateObj = new DateTime($date['date']);
  5413.                                         $date['date'] = $dateObj->modify('-1 day')->format('Y-m-d\TH:i:s\Z');
  5414.                                     }
  5415.                                 }
  5416.                             }
  5417.                         }
  5418.                         // Correctly append 24h data to $datainal
  5419.                         if (!empty($data24h['data'])) {
  5420.                             // Existing code to adjust dates in $data24h['data']
  5421.                             if (empty($dataFinal['data'])) {
  5422.                                 $dataFinal['data'] = $data24h['data'];
  5423.                             } else {
  5424.                                 $dataFinal['data'] = array_merge($dataFinal['data'], $data24h['data']);
  5425.                             }
  5426.                         }
  5427.                     }
  5428.                 }
  5429.                 // Process 12h parameters
  5430.                 if (!empty($parameters12h)) {
  5431.                     $startDateUpdated = new DateTime($startDate);
  5432.                     $endDateUpdated = new DateTime($endDate);
  5433.                     $startDateUpdated $startDateUpdated->modify('+12 hours')->format('Y-m-d\TH:i:s\Z');
  5434.                     $endDateUpdated $endDateUpdated->modify('+12 hours')->format('Y-m-d\TH:i:s\Z');
  5435.                     $data12h $performApiRequests($parameters12h12$startDateUpdated$endDateUpdated);
  5436.                     if (!empty($data12h)) {
  5437.                         foreach ($data12h['data'] as &$datum) {
  5438.                             if (isset($datum['coordinates'])) {
  5439.                                 foreach ($datum['coordinates'] as &$coordinate) {
  5440.                                     foreach ($coordinate['dates'] as &$date) {
  5441.                                         $dateObj = new DateTime($date['date']);
  5442.                                         $date['date'] = $dateObj->modify('-12 hours')->format('Y-m-d\TH:i:s\Z');
  5443.                                     }
  5444.                                 }
  5445.                             }
  5446.                         }
  5447.                         // Correctly append 12h data to $dataFinal
  5448.                         if (!empty($data12h['data'])) {
  5449.                             // Existing code to adjust dates in $data12h['data']
  5450.                             if (empty($dataFinal['data'])) {
  5451.                                 $dataFinal['data'] = $data12h['data'];
  5452.                             } else {
  5453.                                 $dataFinal['data'] = array_merge($dataFinal['data'], $data12h['data']);
  5454.                             }
  5455.                         }
  5456.                     }
  5457.                 }
  5458.                 foreach ($coordinates as $coOrd) {
  5459.                     $weatherSymbols $this->getWeatherSymbols([$coOrd], $startDate$endDate'PT12H''12h');
  5460.                     if (isset($weatherSymbols['data'][0]['parameter'])) {
  5461.                         $dataFinal['symbols'][$coOrd[0] . '|' $coOrd[1]] = $weatherSymbols['data'][0];
  5462.                     }
  5463.                 }
  5464.                 $result = [];
  5465.                 foreach ($dataFinal['data'] as $param) {
  5466.                     foreach ($param['coordinates'] as $paramCoordinates) {
  5467.                         $paramCoordinates['lat'] = number_format($paramCoordinates['lat'], 6'.''');
  5468.                         $paramCoordinates['lon'] = number_format($paramCoordinates['lon'], 6'.''');
  5469.                         $cityKey $paramCoordinates['lat'] . '|' $paramCoordinates['lon'];
  5470.                         if (!isset($result[$cityKey])) {
  5471.                             $result[$cityKey] = [];
  5472.                             if (isset($cities[$cityKey]['en'])) {
  5473.                                 if (isset($paramCoordinates['lat']) && isset($paramCoordinates['lon'])) {
  5474.                                     $result[$cityKey] = [
  5475.                                         'cityEn' => $cities[$cityKey]['en'],
  5476.                                         'cityAr' => $cities[$cityKey]['ar'],
  5477.                                         'lat' => $paramCoordinates['lat'],
  5478.                                         'lon' => $paramCoordinates['lon'],
  5479.                                         'parameters' => [],
  5480.                                     ];
  5481.                                 }
  5482.                             } else {
  5483.                                 if (isset($paramCoordinates['lat']) && isset($paramCoordinates['lon'])) {
  5484.                                     $result[$cityKey] = [
  5485.                                         'city' => $cities[$cityKey],
  5486.                                         'lat' => $paramCoordinates['lat'],
  5487.                                         'lon' => $paramCoordinates['lon'],
  5488.                                         'parameters' => [],
  5489.                                     ];
  5490.                                 }
  5491.                             }
  5492.                         }
  5493.                         $parameterData = [
  5494.                             'parameter' => $param['parameter'],
  5495.                             'dates' => [],
  5496.                         ];
  5497.                         foreach ($paramCoordinates['dates'] as $date) {
  5498.                             $parameterData['dates'][] = [
  5499.                                 'date' => $date['date'], // You can modify this as needed
  5500.                                 'value' => $date['value'], // You can modify this as needed
  5501.                             ];
  5502.                         }
  5503.                         $result[$cityKey]['parameters'][] = $parameterData;
  5504.                     }
  5505.                 }
  5506.                 // Convert the associative array to indexed array
  5507.                 $result array_values($result);
  5508.                 if (isset($params['report_type_id']) && !empty($params['report_type_id'])) {
  5509.                     // $latestReport = new Report\Listing();
  5510.                     // $reportType = \Pimcore\Model\DataObject::getById($params['report_type_id']);
  5511.                     // $latestReport->filterByReportType($reportType);
  5512.                     // $latestReport->setOrderKey("createdOn");
  5513.                     // $latestReport->setOrder("desc");
  5514.                     // $latestReport = $latestReport->current();
  5515.                     if ($latestReport) {
  5516.                         $jsonData json_decode($latestReport->getJsonData(), true);
  5517.                         foreach ($result as &$value) {
  5518.                             // Compare latitude and longitude
  5519.                             foreach ($jsonData as $jsonEntry) {
  5520.                                 if (isset($value['lat']) && isset($jsonEntry['lat']) && ($value['lat'] == $jsonEntry['lat']) && isset($value['lon']) && isset($jsonEntry['lon']) && ($value['lon'] == $jsonEntry['lon'])) {
  5521.                                     // Latitude and longitude match, proceed with parameter comparison
  5522.                                     foreach ($value['parameters'] as &$paramValue) {
  5523.                                         foreach ($jsonEntry['parameters'] as $jsonParam) {
  5524.                                             if ($jsonParam['parameter'] == $paramValue['parameter']) {
  5525.                                                 // Parameter matches, check dates now
  5526.                                                 foreach ($paramValue['dates'] as &$dateValue) {
  5527.                                                     foreach ($jsonParam['dates'] as $jsonDate) {
  5528.                                                         if ($dateValue['date'] == $jsonDate['date']) {
  5529.                                                             // Exact match found, override the value
  5530.                                                             $dateValue['value'] = $jsonDate['value'];
  5531.                                                             // Continue checking all dates, no break here
  5532.                                                         }
  5533.                                                     }
  5534.                                                 }
  5535.                                                 unset($dateValue); // Ensure reference is not carried over
  5536.                                             }
  5537.                                         }
  5538.                                     }
  5539.                                     unset($paramValue); // Ensure reference is not carried over
  5540.                                 }
  5541.                             }
  5542.                         }
  5543.                         unset($value); // Ensure reference is not carried over
  5544.                     }
  5545.                 }
  5546.             } else {
  5547.                 if ($coordinates) {
  5548.                     $latitude $coordinates[0][0];
  5549.                     $longitude $coordinates[0][1];
  5550.                 }
  5551.                 // Validate the input parameters
  5552.                 if (!preg_match('/^[-]?[0-9]{1,2}\.[0-9]+/'$latitude)) {
  5553.                     // throw new \InvalidArgumentException('Invalid latitude');
  5554.                     return ["success" => false"message" => $translator->trans("invalid_latitude")];
  5555.                 }
  5556.                 if (!preg_match('/^[-]?[0-9]{1,3}\.[0-9]+/'$longitude)) {
  5557.                     // throw new \InvalidArgumentException('Invalid longitude');
  5558.                     return ["success" => false"message" => $translator->trans("invalid_longitude")];
  5559.                 }
  5560.                 if (empty($startDate) || empty($endDate)) {
  5561.                     return ["success" => false"message" => $translator->trans("invalid_dates")];
  5562.                 }
  5563.                 // Create a Redis key for the cache
  5564.                 $cacheKey sprintf('daily_forecast_%s_%s_%s_%s'implode('_'array_map(function ($coordinate) {
  5565.                     return implode('_'$coordinate);
  5566.                 }, $coordinates)), (($startDate) . '-' . ($endDate)) . $modelimplode('_'$parameters12h), implode('_'$parameters24h));
  5567.                 // Try to get the data from Redis cache
  5568.                 $cachedData $this->redisCache->get($cacheKey);
  5569.                 if ($cachedData !== null) {
  5570.                     // Return the cached data if available
  5571.                     return $cachedData;
  5572.                 }
  5573.                 // If cache is empty, get the data from the API
  5574.                 $client = new Client(['verify' => false]);
  5575.                 $coordinateString implode('+'array_map(fn($coords) => implode(','$coords), $coordinates));
  5576.                 $dataFinal = [];
  5577.                 // Function to perform the API requests for given parameters and hours
  5578.                 $performApiRequests = function ($parameters$hours$startDate$endDate) use ($client$latitude$longitude$model) {
  5579.                     $data = [];
  5580.                     while (!empty($parameters)) {
  5581.                         $batchParameters array_splice($parameters010); // Take up to 10 parameters
  5582.                         $batchQueryString implode(','$batchParameters);
  5583.                         $url sprintf(
  5584.                             '%s/%s--%s:PT%sH/%s/%s,%s/json?model=%s&use_decluttered=true',
  5585.                             $this->apiBaseUrl,
  5586.                             $startDate,
  5587.                             $endDate,
  5588.                             $hours,
  5589.                             $batchQueryString,
  5590.                             $latitude,
  5591.                             $longitude,
  5592.                             $model
  5593.                         );
  5594.                         $response $client->request('GET'$url, [
  5595.                             'auth' => [$this->username$this->password],
  5596.                             'timeout' => 600
  5597.                         ]);
  5598.                         $statusCode $response->getStatusCode();
  5599.                         $dataBatch json_decode($response->getBody(), true);
  5600.                         // Merge data from the current API call into the data array
  5601.                         $data['data'] = array_merge($data['data'] ?? [], $dataBatch['data']);
  5602.                     }
  5603.                     return $data;
  5604.                 };
  5605.                 // Process 24h parameters
  5606.                 if (!empty($parameters24h)) {
  5607.                     $startDateUpdated = new DateTime($startDate);
  5608.                     $endDateUpdated = new DateTime($endDate);
  5609.                     $startDateUpdated $startDateUpdated->modify('+1 day')->format('Y-m-d\TH:i:s\Z');
  5610.                     $endDateUpdated $endDateUpdated->modify('+1 day')->format('Y-m-d\TH:i:s\Z');
  5611.                     $data24h $performApiRequests($parameters24h24$startDateUpdated$endDateUpdated);
  5612.                     if (!empty($data24h)) {
  5613.                         foreach ($data24h['data'] as &$datum) {
  5614.                             if (isset($datum['coordinates'])) {
  5615.                                 foreach ($datum['coordinates'] as &$coordinate) {
  5616.                                     foreach ($coordinate['dates'] as &$date) {
  5617.                                         $dateObj = new DateTime($date['date']);
  5618.                                         $date['date'] = $dateObj->modify('-1 day')->format('Y-m-d\TH:i:s\Z');
  5619.                                     }
  5620.                                 }
  5621.                             }
  5622.                         }
  5623.                         // Correctly append 24h data to $datainal
  5624.                         if (!empty($data24h['data'])) {
  5625.                             // Existing code to adjust dates in $data24h['data']
  5626.                             if (empty($dataFinal['data'])) {
  5627.                                 $dataFinal['data'] = $data24h['data'];
  5628.                             } else {
  5629.                                 $dataFinal['data'] = array_merge($dataFinal['data'], $data24h['data']);
  5630.                             }
  5631.                         }
  5632.                     }
  5633.                 }
  5634.                 // Process 12h parameters
  5635.                 if (!empty($parameters12h)) {
  5636.                     $startDateUpdated = new DateTime($startDate);
  5637.                     $endDateUpdated = new DateTime($endDate);
  5638.                     $startDateUpdated $startDateUpdated->modify('+12 hours')->format('Y-m-d\TH:i:s\Z');
  5639.                     $endDateUpdated $endDateUpdated->modify('+12 hours')->format('Y-m-d\TH:i:s\Z');
  5640.                     $data12h $performApiRequests($parameters12h12$startDateUpdated$endDateUpdated);
  5641.                     if (!empty($data12h)) {
  5642.                         foreach ($data12h['data'] as &$datum) {
  5643.                             if (isset($datum['coordinates'])) {
  5644.                                 foreach ($datum['coordinates'] as &$coordinate) {
  5645.                                     foreach ($coordinate['dates'] as &$date) {
  5646.                                         $dateObj = new DateTime($date['date']);
  5647.                                         $date['date'] = $dateObj->modify('-12 hours')->format('Y-m-d\TH:i:s\Z');
  5648.                                     }
  5649.                                 }
  5650.                             }
  5651.                         }
  5652.                         // Correctly append 12h data to $dataFinal
  5653.                         if (!empty($data12h['data'])) {
  5654.                             // Existing code to adjust dates in $data12h['data']
  5655.                             if (empty($dataFinal['data'])) {
  5656.                                 $dataFinal['data'] = $data12h['data'];
  5657.                             } else {
  5658.                                 $dataFinal['data'] = array_merge($dataFinal['data'], $data12h['data']);
  5659.                             }
  5660.                         }
  5661.                     }
  5662.                 }
  5663.                 foreach ($coordinates as $coOrd) {
  5664.                     $weatherSymbols $this->getWeatherSymbols([$coOrd], $startDate$endDate'PT12H''12h');
  5665.                     if (isset($weatherSymbols['data'][0]['parameter'])) {
  5666.                         $dataFinal['symbols'][$coOrd[0] . '|' $coOrd[1]] = $weatherSymbols['data'][0];
  5667.                     }
  5668.                 }
  5669.                 $result = [];
  5670.                 foreach ($dataFinal['data'] as $param) {
  5671.                     foreach ($param['coordinates'] as $paramCoordinates) {
  5672.                         $paramCoordinates['lat'] = number_format($paramCoordinates['lat'], 6'.''');
  5673.                         $paramCoordinates['lon'] = number_format($paramCoordinates['lon'], 6'.''');
  5674.                         $cityKey $paramCoordinates['lat'] . '|' $paramCoordinates['lon'];
  5675.                         if (!isset($result[$cityKey])) {
  5676.                             $result[$cityKey] = [];
  5677.                             if (isset($cities[$cityKey]['en'])) {
  5678.                                 $result[$cityKey] = [
  5679.                                     'cityEn' => $cities[$cityKey]['en'],
  5680.                                     'cityAr' => $cities[$cityKey]['ar'],
  5681.                                     'lat' => $paramCoordinates['lat'],
  5682.                                     'lon' => $paramCoordinates['lon'],
  5683.                                     'parameters' => [],
  5684.                                 ];
  5685.                             } else {
  5686.                                 $result[$cityKey] = [
  5687.                                     'city' => $cities[$cityKey],
  5688.                                     'lat' => $paramCoordinates['lat'],
  5689.                                     'lon' => $paramCoordinates['lon'],
  5690.                                     'parameters' => [],
  5691.                                 ];
  5692.                             }
  5693.                         }
  5694.                         $parameterData = [
  5695.                             'parameter' => $param['parameter'],
  5696.                             'dates' => [],
  5697.                         ];
  5698.                         foreach ($paramCoordinates['dates'] as $date) {
  5699.                             $parameterData['dates'][] = [
  5700.                                 'date' => $date['date'], // You can modify this as needed
  5701.                                 'value' => $date['value'], // You can modify this as needed
  5702.                             ];
  5703.                         }
  5704.                         $result[$cityKey]['parameters'][] = $parameterData;
  5705.                     }
  5706.                 }
  5707.                 // Convert the associative array to indexed array
  5708.                 $result array_values($result);
  5709.                 if (isset($params['report_type_id']) && !empty($params['report_type_id'])) {
  5710.                     // $latestReport = new Report\Listing();
  5711.                     // $reportType = \Pimcore\Model\DataObject::getById($params['report_type_id']);
  5712.                     // $latestReport->filterByReportType($reportType);
  5713.                     // $latestReport->setOrderKey("createdOn");
  5714.                     // $latestReport->setOrder("desc");
  5715.                     // $latestReport = $latestReport->current();
  5716.                     if ($latestReport) {
  5717.                         $jsonData json_decode($latestReport->getJsonData(), true);
  5718.                         foreach ($result as &$value) {
  5719.                             // Compare latitude and longitude
  5720.                             foreach ($jsonData as $jsonEntry) {
  5721.                                 if ($value['lat'] == $jsonEntry['lat'] && $value['lon'] == $jsonEntry['lon']) {
  5722.                                     // Latitude and longitude match, proceed with parameter comparison
  5723.                                     foreach ($value['parameters'] as &$paramValue) {
  5724.                                         foreach ($jsonEntry['parameters'] as $jsonParam) {
  5725.                                             if ($jsonParam['parameter'] == $paramValue['parameter']) {
  5726.                                                 // Parameter matches, check dates now
  5727.                                                 foreach ($paramValue['dates'] as &$dateValue) {
  5728.                                                     foreach ($jsonParam['dates'] as $jsonDate) {
  5729.                                                         if ($dateValue['date'] == $jsonDate['date']) {
  5730.                                                             // Exact match found, override the value
  5731.                                                             $dateValue['value'] = $jsonDate['value'];
  5732.                                                             // Continue checking all dates, no break here
  5733.                                                         }
  5734.                                                     }
  5735.                                                 }
  5736.                                                 unset($dateValue); // Ensure reference is not carried over
  5737.                                             }
  5738.                                         }
  5739.                                     }
  5740.                                     unset($paramValue); // Ensure reference is not carried over
  5741.                                 }
  5742.                             }
  5743.                         }
  5744.                         unset($value); // Ensure reference is not carried over
  5745.                     }
  5746.                 }
  5747.             }
  5748.             // Set the data to Redis cache
  5749.             // $this->redisCache->set($cacheKey, $dataFinal);
  5750.             return $result;
  5751.         } catch (GuzzleHttp\Exception\RequestException $e) {
  5752.             // p_r($e->getMessage());
  5753.             return throw new \Exception($e->getMessage());
  5754.         } catch (\Exception $e) {
  5755.             return throw new \Exception($e->getMessage());
  5756.         }
  5757.     }
  5758.     public function getBarbs(string $windSpeedstring $dateTimestring $resolutionstring $accessToken): array
  5759.     {
  5760.         try {
  5761.             $client = new Client(['verify' => false]);
  5762.             $url sprintf(
  5763.                 "%s/mvt/barbs/%s/style.json?datetime=%s&resolution=%s&access_token=%s&use_decluttered=true",
  5764.                 $this->apiBaseUrl,
  5765.                 $windSpeed,
  5766.                 $dateTime,
  5767.                 $resolution,
  5768.                 $accessToken
  5769.             );
  5770.             $response $client->request('GET'$url, ['auth' => [$this->username$this->password]]);
  5771.             $data json_decode($response->getBody(), true);
  5772.             return $data;
  5773.         } catch (RequestException $e) {
  5774.             throw new \Exception($e->getMessage());
  5775.         } catch (\Exception $e) {
  5776.             throw new \Exception($e->getMessage());
  5777.         }
  5778.     }
  5779.     public function getMaskLayerData(string $query): array
  5780.     {
  5781.         try {
  5782.             $client = new Client(['verify' => false]);
  5783.             $url sprintf(
  5784.                 "%s/%s",
  5785.                 $this->apiBaseUrl,
  5786.                 $query
  5787.             );
  5788.             $response $client->request('GET'$url, ['auth' => [$this->username$this->password]]);
  5789.             $data json_decode($response->getBody(), true);
  5790.             return $data;
  5791.         } catch (RequestException $e) {
  5792.             throw new \Exception($e->getMessage());
  5793.         } catch (\Exception $e) {
  5794.             throw new \Exception($e->getMessage());
  5795.         }
  5796.     }
  5797.     public function getIsolines(string $measurestring $dateTimestring $accessToken): array
  5798.     {
  5799.         try {
  5800.             $client = new Client(['verify' => false]);
  5801.             $url sprintf(
  5802.                 "%s/mvt/isolines/%s/style.json?datetime=%s&access_token=%s&use_decluttered=true",
  5803.                 $this->apiBaseUrl,
  5804.                 $measure,
  5805.                 $dateTime,
  5806.                 $accessToken
  5807.             );
  5808.             $response $client->request('GET'$url, ['auth' => [$this->username$this->password]]);
  5809.             $data json_decode($response->getBody(), true);
  5810.             return $data;
  5811.         } catch (RequestException $e) {
  5812.             throw new \Exception($e->getMessage());
  5813.         } catch (\Exception $e) {
  5814.             throw new \Exception($e->getMessage());
  5815.         }
  5816.     }
  5817.     public function getAviationReports(string $meterstring $dateTimestring $accessToken): array
  5818.     {
  5819.         try {
  5820.             $client = new Client(['verify' => false]);
  5821.             $url sprintf(
  5822.                 "%s/mvt/aviation_reports/%s/style.json?datetime=%s&access_token=%s&use_decluttered=true",
  5823.                 $this->apiBaseUrl,
  5824.                 $meter,
  5825.                 $dateTime,
  5826.                 $accessToken
  5827.             );
  5828.             $response $client->request('GET'$url, ['auth' => [$this->username$this->password]]);
  5829.             $data json_decode($response->getBody(), true);
  5830.             return $data;
  5831.         } catch (RequestException $e) {
  5832.             throw new \Exception($e->getMessage());
  5833.         } catch (\Exception $e) {
  5834.             throw new \Exception($e->getMessage());
  5835.         }
  5836.     }
  5837.     public function getOceanCurrentSpeed(string $dateTimestring $bBox): array
  5838.     {
  5839.         try {
  5840.             $client = new Client(['verify' => false]);
  5841.             $url sprintf(
  5842.                 "%s/%s/ocean_current_u:ms,ocean_current_v:ms/%s/json?use_decluttered=true",
  5843.                 $this->apiBaseUrl,
  5844.                 $dateTime,
  5845.                 $bBox
  5846.             );
  5847.             $response $client->request('GET'$url, ['auth' => [$this->username$this->password]]);
  5848.             $data json_decode($response->getBody(), true);
  5849.             return $data;
  5850.         } catch (RequestException $e) {
  5851.             throw new \Exception($e->getMessage());
  5852.         } catch (\Exception $e) {
  5853.             throw new \Exception($e->getMessage());
  5854.         }
  5855.     }
  5856.     public function getWMSMAP(string $legendGraphicsstring $formatstring $layerstring $style '')
  5857.     {
  5858.         try {
  5859.             $client = new Client(['verify' => false]);
  5860.             // Conditionally add the STYLE parameter to the URL if it's provided
  5861.             $stylePart $style "&STYLE=" urlencode($style) : '';
  5862.             $url sprintf(
  5863.                 "%s/wms?VERSION=1.3.0&REQUEST=%s&FORMAT=%s&LAYER=%s%s&use_decluttered=true",
  5864.                 $this->apiBaseUrl,
  5865.                 $legendGraphics,
  5866.                 $format,
  5867.                 $layer,
  5868.                 $stylePart
  5869.             );
  5870.             $response $client->request('GET'$url, ['auth' => [$this->username$this->password]]);
  5871.             // Get the raw body content
  5872.             $content $response->getBody()->getContents();
  5873.             // Set headers and create a response object for the PNG image
  5874.             $httpResponse = new Response($contentResponse::HTTP_OK, [
  5875.                 'Content-Type' => $format
  5876.             ]);
  5877.             return $httpResponse;
  5878.         } catch (RequestException $e) {
  5879.             throw new \Exception($e->getMessage());
  5880.         } catch (\Exception $e) {
  5881.             throw new \Exception($e->getMessage());
  5882.         }
  5883.     }
  5884.     public function getWeatherStationData(string $typeNamestring $parametersstring $dateTimestring $bBox)
  5885.     {
  5886.         try {
  5887.             $dateTimeObj = new \DateTime($dateTime);
  5888.             $dateTimeObj->sub(new \DateInterval('PT10M'));
  5889.             $adjustedDateTime $dateTimeObj->format('Y-m-d\TH:i:s.v\Z');
  5890.             
  5891.             $client = new Client(['verify' => false]);
  5892.             $url sprintf(
  5893.                 "%s/wfs?SERVICE=WFS&VERSION=1.0.0&REQUEST=GetFeature&TYPENAME=%s&PARAMETERS=%s&TIME=%s&BBOX=%s&use_decluttered=true",
  5894.                 $this->apiBaseUrl,
  5895.                 $typeName,
  5896.                 $parameters,
  5897.                 $adjustedDateTime,
  5898.                 $bBox
  5899.             );
  5900.             $response $client->request('GET'$url, ['auth' => [$this->username$this->password]]);
  5901.             // Get the raw body content
  5902.             $content $response->getBody()->getContents();
  5903.             // Set headers and create a response object for the XML File
  5904.             $httpResponse = new Response($contentResponse::HTTP_OK, [
  5905.                 'Content-Type' => 'application/xml'
  5906.             ]);
  5907.             return $httpResponse;
  5908.             // return $response->getBody();
  5909.         } catch (RequestException $e) {
  5910.             throw new \Exception($e->getMessage());
  5911.         } catch (\Exception $e) {
  5912.             throw new \Exception($e->getMessage());
  5913.         }
  5914.     }
  5915.     function getGridLayer($dateTime$parameter$coordinates$resolution$format$source$bbox$calibrated$translator)
  5916.     {
  5917.         try {
  5918.             if (!preg_match('/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/'$dateTime)) {
  5919.                 throw new \InvalidArgumentException($translator->trans('invalid_date_format'));
  5920.             }
  5921.             if (!$parameter) {
  5922.                 throw new \InvalidArgumentException($translator->trans('invalid_unit'));
  5923.             }
  5924.             if (count($coordinates) < || !is_array($coordinates[0]) || count($coordinates[0]) < 2) {
  5925.                 throw new \InvalidArgumentException($translator->trans('invalid_co_ordinates'));
  5926.             }
  5927.             if (!preg_match('/^\d+x\d+$/'$resolution)) {
  5928.                 throw new \InvalidArgumentException($translator->trans('invalid_resolution'));
  5929.             }
  5930.             if (!in_array($format, ['json''xml''csv'])) {
  5931.                 throw new \InvalidArgumentException($translator->trans('invalid_format'));
  5932.             }
  5933.             if (!is_string($source)) {
  5934.                 throw new \InvalidArgumentException($translator->trans('invalid_source'));
  5935.             }
  5936.             if (count($bbox) !== 4) {
  5937.                 throw new \InvalidArgumentException($translator->trans('invalid_bbox_elements'));
  5938.             }
  5939.             if (!in_array($calibrated, ['true''false'])) {
  5940.                 throw new \InvalidArgumentException($translator->trans('invalid_calibrated_value'));
  5941.             }
  5942.             $dateTime urlencode($dateTime);
  5943.             $coordinateString implode('_'array_map(fn($coords) => implode(','$coords), $coordinates));
  5944.             $bboxString implode(','$bbox);
  5945.             $cacheKey \App\Lib\Utility::generateKey($dateTime$coordinateString$bboxString$parameter);
  5946.             $cachedData $this->redisCache->get($cacheKey);
  5947.             if ($cachedData !== null) {
  5948.                 // Return the cached data if available
  5949.                 return $cachedData;
  5950.             }
  5951.             // Construct the URL
  5952.             $url "{$this->apiBaseUrl}/{$dateTime}/{$parameter}/{$coordinateString}:{$resolution}/{$format}?source={$source}&bbox={$bboxString}&calibrated={$calibrated}&use_decluttered=true";
  5953.             $client = new Client(['verify' => false]);
  5954.             $response $client->request('GET'$url, [
  5955.                 'auth' => [$this->username$this->password],
  5956.             ]);
  5957.             $statusCode $response->getStatusCode();
  5958.             $data json_decode($response->getBody(), true);
  5959.             if ($statusCode != 200) {
  5960.                 return $this->createErrorResponse($statusCode);
  5961.             }
  5962.             // Set the data to Redis cache
  5963.             $this->redisCache->set($cacheKey$data);
  5964.             return $data;
  5965.         } catch (RequestException $e) {
  5966.             throw new \Exception($e->getMessage());
  5967.         } catch (\Exception $e) {
  5968.             throw new \Exception($e->getMessage());
  5969.         }
  5970.     }
  5971.     public function getMetarData($startDate$endDate$metar$duration$translator$parameter$genExcel)
  5972.     {
  5973.         try {
  5974.             // if (!preg_match('/^20\d{2}-\d{2}-\d{2}T\d{2}Z$/', $startDate)) {
  5975.             //     throw new \InvalidArgumentException($translator->trans('invalid_date_format'));
  5976.             // }
  5977.             // if (!preg_match('/^20\d{2}-\d{2}-\d{2}T\d{2}Z$/', $endDate)) {
  5978.             //     throw new \InvalidArgumentException($translator->trans('invalid_date_format'));
  5979.             // }
  5980.             if (!preg_match('/^20\d{2}-\d{2}-\d{2}T\d{2}(:\d{2}(:\d{2}(\.\d{3})?)?)?Z$/'$startDate)) {
  5981.                 throw new \InvalidArgumentException($translator->trans('invalid_date_format'));
  5982.             }
  5983.             if (!preg_match('/^20\d{2}-\d{2}-\d{2}T\d{2}(:\d{2}(:\d{2}(\.\d{3})?)?)?Z$/'$endDate)) {
  5984.                 throw new \InvalidArgumentException($translator->trans('invalid_date_format'));
  5985.             }
  5986.             $cacheKey \App\Lib\Utility::generateKey($startDate$endDate$metar$duration$parameter);
  5987.             $cachedData $this->redisCache->get($cacheKey);
  5988.             if ($cachedData !== null && !$genExcel) {
  5989.                 // Return the cached data if available
  5990.                return $cachedData;
  5991.             }
  5992.             if ($genExcel) {
  5993.                 $parameters[] = $parameter;
  5994.             } elseif (!empty($parameter) && !$genExcel) {
  5995.                 $parameters $parameter;
  5996.             } else {
  5997.                 $parameters = [
  5998.                     't_2m:C',
  5999.                     'wind_speed_10m:kmh',
  6000.                     'wind_dir_10m:d',
  6001.                     'msl_pressure:hPa',
  6002.                     'precip_1h:mm',
  6003.                     'relative_humidity_2m:p',
  6004.                     'visibility:km',
  6005.                     'dew_point_2m:c',
  6006.                     'wind_gusts_10m_1h:kmh',
  6007.                     'ceiling_height_agl:m',
  6008.                     'geopotential_height:m',
  6009.                     't_min_2m_24h:C',
  6010.                     't_max_2m_24h:C',
  6011.                     'precip_24h:mm',
  6012.                     'total_cloud_cover:octas',
  6013.                     'wind_speed_10m:kn',
  6014.                     'wind_gusts_10m_1h:kn',
  6015.                 ];
  6016.             }
  6017.     
  6018.             $timeResolutions = [
  6019.                 '_24h' => '+1 day',
  6020.                 '_12h' => '+12 hours',
  6021.                 '_6h' => '+6 hours',
  6022.                 '_3h' => '+3 hours',
  6023.                 '_1h' => '+1 hour'
  6024.             ];
  6025.     
  6026.             $adjustedParameters = [];
  6027.             $nonAdjustedParameters = [];
  6028.     
  6029.             foreach ($parameters as $parameter) {
  6030.                 $matched false;
  6031.                 foreach ($timeResolutions as $key => $adjustment) {
  6032.                     if (strpos($parameter$key) !== false) {
  6033.                         $matched true;
  6034.                         $adjustedParameters[$adjustment][] = $parameter;
  6035.                         break;
  6036.                     }
  6037.                 }
  6038.                 if (!$matched) {
  6039.                     $nonAdjustedParameters[] = $parameter;
  6040.                 }
  6041.             }
  6042.             // Construct the URL
  6043.             // $url = "{$this->apiBaseUrl}/{$startDate}--{$endDate}:{$duration}/t_2m:C,wind_speed_10m:kmh,wind_dir_10m:d,sfs_pressure:hPa,precip_10min:mm,relative_humidity_2m:p,visibility:km,dew_point_2m:c,wind_gusts_10m_1h:kmh,ceiling_height_agl:m,geopotential_height:m,t_min_2m_24h:C,t_max_2m_24h:C,precip_24h:mm,total_cloud_cover:octas/metar_{$metar}/json?source=mix-obs&on_invalid=fill_with_invalid";
  6044.             $dataFinal = [
  6045.                 'version' => null,
  6046.                 'user' => null,
  6047.                 'dateGenerated' => null,
  6048.                 'status' => null,
  6049.                 'data' => []
  6050.             ];
  6051.     
  6052.             foreach ($adjustedParameters as $adjustment => $params) {
  6053.                 $adjustedStartDate = (new \DateTime($startDate))->format('Y-m-d\TH:i:s.v\Z');
  6054.                 $adjustedEndDate = (new \DateTime($endDate))->format('Y-m-d\TH:i:s.v\Z');
  6055.                 $data $this->fetchMetarData($adjustedStartDate$adjustedEndDate$metar$duration$params);
  6056.                 // Revert dates to original range
  6057.                 if (isset($data['data']) && is_array($data['data'])) {
  6058.     
  6059.                     foreach ($data['data'] as &$datum) {
  6060.     
  6061.                         if (isset($datum['coordinates'][0]['dates']) && is_array($datum['coordinates'][0]['dates'])) {
  6062.                             foreach ($datum['coordinates'][0]['dates'] as &$date) {
  6063.                                 if (isset($date['date'])) {
  6064.                                     $date['date'] = (new \DateTime($date['date']))
  6065.                                         ->modify('-' ltrim($adjustment'+'))
  6066.                                         ->format('Y-m-d\TH:i:s.v\Z');
  6067.                                 }
  6068.                             }
  6069.                         }
  6070.                     }
  6071.                 } else {
  6072.                     $data['data'] = []; // Ensure 'data' exists
  6073.                 }
  6074.     
  6075.                 // Merge into $dataFinal
  6076.                 if (empty($dataFinal['version'])) {
  6077.                     $dataFinal['version'] = $data['version'] ?? null;
  6078.                     $dataFinal['user'] = $data['user'] ?? null;
  6079.                     $dataFinal['dateGenerated'] = $data['dateGenerated'] ?? null;
  6080.                     $dataFinal['status'] = $data['status'] ?? null;
  6081.                 }
  6082.     
  6083.                 if (isset($data['data']) && is_array($data['data'])) {
  6084.                     $dataFinal['data'] = array_merge($dataFinal['data'], $data['data']);
  6085.                 }
  6086.             }
  6087.             if (!empty($nonAdjustedParameters)) {
  6088.                 $data $this->fetchMetarData($startDate$endDate$metar$duration$nonAdjustedParameters);
  6089.                 if (isset($data['data']) && is_array($data['data'])) {
  6090.                     $dataFinal['version'] = $data['version'] ?? null;
  6091.                     $dataFinal['user'] = $data['user'] ?? null;
  6092.                     $dataFinal['dateGenerated'] = $data['dateGenerated'] ?? null;
  6093.                     $dataFinal['status'] = $data['status'] ?? null;
  6094.                     $dataFinal['data'] = array_merge($dataFinal['data'], $data['data']);
  6095.                 }
  6096.             }
  6097.     
  6098.             // Reorder data based on parameters
  6099.             $dataFinal['data'] = isset($dataFinal['data']) && is_array($dataFinal['data'])
  6100.                 ? $this->orderResults($dataFinal['data'], $parameters)
  6101.                 : [];
  6102.     
  6103.     
  6104.             if (strpos($metar',') === false) {
  6105.     
  6106.                 //get station name and alternative id
  6107.                 if (count($dataFinal['data']) > 0) {
  6108.                     $metarData $this->getWeatherStationDataByHashId($dataFinal['data']);
  6109.                     $dataFinal['data'] = $metarData;
  6110.                 }
  6111.             }
  6112.             if ($genExcel) {
  6113.                 $csvData[] = ['Station Name''Parameter(c)''Last Update'];
  6114.                 if (count($dataFinal['data']) > 0) {
  6115.                     foreach ($dataFinal['data'] as $key => $value) {
  6116.                         foreach ($value as $key1 => $value1) {
  6117.                             if (!is_array($value1)) {
  6118.                                 $parameter $value1;
  6119.                             }
  6120.                             if (is_array($value1)) {
  6121.                                 foreach ($value1 as $key2 => $value2) {
  6122.                                     $station_hash $value2['station_id'] ?? null;
  6123.                                     $station_name $value2['station_name'] ?? null;
  6124.                                     $station_id $value2['station_alternativeIds'] ?? null;
  6125.                                     if (is_array($value2['dates'])) {
  6126.                                         foreach ($value2['dates'] as $key3 => $value3) {
  6127.                                             $date  $value3['date'];
  6128.                                             $value $value3['value'];
  6129.                                             $csvData[] = [$station_name$value == '-999' 'N/A' $value$date];
  6130.                                         }
  6131.                                     }
  6132.                                 }
  6133.                             }
  6134.                         }
  6135.                     }
  6136.                 }
  6137.                 $xlsxReport \Pimcore\Model\Asset::getByPath("/report/StationCsv/metar_station_weather_data.xlsx");
  6138.                 if ($xlsxReport !== null) {
  6139.                     $xlsxReport->delete();
  6140.                 }
  6141.                 $excelData ExcelGenerator::createAndSaveXlsx($csvData"metar_station_weather_data"true'StationCsv');
  6142.                 return $excelData;
  6143.             }
  6144.             // Set the data to Redis cache
  6145.             $this->redisCache->set($cacheKey$dataFinal);
  6146.             return $dataFinal;
  6147.         } catch (RequestException $e) {
  6148.             throw new \Exception($e->getMessage());
  6149.         } catch (\Exception $e) {
  6150.             throw new \Exception($e->getMessage());
  6151.         }
  6152.     }
  6153.     private function fetchMetarData($startDate$endDate$metar$duration$parameters)
  6154.     {
  6155.         $dataFinal = [];
  6156.             while (!empty($parameters)) {
  6157.                 $batchParameters array_splice($parameters010); // Take up to 10 parameters
  6158.                 $batchQueryString implode(','$batchParameters);
  6159.                 $url sprintf(
  6160.                     '%s/%s--%s:%s/%s/%s/json?source=mix-obs&on_invalid=fill_with_invalid&use_decluttered=true',
  6161.                     $this->apiBaseUrl,
  6162.                     $startDate,
  6163.                     $endDate,
  6164.                     $duration,
  6165.                     $batchQueryString,
  6166.                     $metar
  6167.     
  6168.                 );
  6169.                 $client = new Client(['verify' => false]);
  6170.                 $response $client->request('GET'$url, [
  6171.                     'auth' => [$this->username$this->password],
  6172.                 ]);
  6173.                 $statusCode $response->getStatusCode();
  6174.                 $data json_decode($response->getBody(), true);
  6175.                 if ($statusCode != 200) {
  6176.                     return $this->createErrorResponse($statusCode);
  6177.                 }
  6178.                 // Merge data from the current API call into the final data
  6179.                 $dataFinal['version'] = $data['version'];
  6180.                 $dataFinal['user'] = $data['user'];
  6181.                 $dataFinal['dateGenerated'] = $data['dateGenerated'];
  6182.                 $dataFinal['status'] = $data['status'];
  6183.                 $dataFinal['data'] = array_merge($dataFinal['data'] ?? [], $data['data']);
  6184.             }
  6185.     
  6186.             // Check if there is no comma in $metar
  6187.     
  6188.         return $dataFinal;
  6189.     }
  6190.     public function getWeatherStationDataByHashId($metarData)
  6191.     {
  6192.         $response = [];
  6193.         foreach ($metarData as &$metarParam) {
  6194.             if (count($metarParam['coordinates']) > 0) {
  6195.                 foreach ($metarParam['coordinates'] as &$coordinates) {
  6196.                     $stationData \Pimcore\Model\DataObject\WeatherStations::getByHash($coordinates['station_id'], true);
  6197.                     if ($stationData) {
  6198.                         $newData = [
  6199.                             'station_name' => $stationData->getName(),
  6200.                             'station_name_ar' => $stationData->getName('ar'),
  6201.                             'station_wmo_id' => $stationData->getWmoId(),
  6202.                             'station_alternativeIds' => $stationData->getAlternativeIds(),
  6203.                         ];
  6204.                         // Append the new data to the existing coordinates without reindexing
  6205.                         $coordinates += $newData;
  6206.                     }
  6207.                 }
  6208.                 // Reset array keys to be sequential
  6209.                 $metarParam['coordinates'] = array_values($metarParam['coordinates']);
  6210.             }
  6211.         }
  6212.         return $metarData;
  6213.     }
  6214.     // public function getWeatherStatioData($startDate, $endDate, $parameters, $hash, $genExcel, $translator, $interval = 'PT24H')
  6215.     // {
  6216.     //     try {
  6217.     //         if (!preg_match('/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/', $startDate)) {
  6218.     //             throw new \InvalidArgumentException($translator->trans('invalid_start_date_format'));
  6219.     //         }
  6220.     //         if (!preg_match('/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/', $startDate)) {
  6221.     //             throw new \InvalidArgumentException($translator->trans('invalid_end_date_format'));
  6222.     //         }
  6223.     //         $cacheKey = \App\Lib\Utility::generateKey($startDate, $endDate, $parameters, $hash);
  6224.     //         $cachedData = $this->redisCache->get($cacheKey);
  6225.     //         if ($cachedData !== null && !$genExcel) {
  6226.     //             // Return the cached data if available
  6227.     //             return $cachedData;
  6228.     //         }
  6229.     //         $dataFinal = [];
  6230.     //         while (!empty($parameters)) {
  6231.     //             $batchParameters = array_splice($parameters, 0, 10); // Take up to 10 parameters
  6232.     //             $batchQueryString = implode(',', $batchParameters);
  6233.     //             $url = sprintf(
  6234.     //                 "%s/%s--%s:%s/%s/%s/json?source=mix-obs&on_invalid=fill_with_invalid",
  6235.     //                 $this->apiBaseUrl,
  6236.     //                 $startDate,
  6237.     //                 $endDate,
  6238.     //                 $interval,
  6239.     //                 $batchQueryString,
  6240.     //                 $hash
  6241.     //             );
  6242.     //             $client = new Client(['verify' => false]);
  6243.     //             $response = $client->request('GET', $url, [
  6244.     //                 'auth' => [$this->username, $this->password],
  6245.     //             ]);
  6246.     //             $statusCode = $response->getStatusCode();
  6247.     //             $data = json_decode($response->getBody(), true);
  6248.     //             if ($statusCode != 200) {
  6249.     //                 return $this->createErrorResponse($statusCode);
  6250.     //             }
  6251.     //             // Merge data from the current API call into the final data
  6252.     //             $dataFinal['version'] = $data['version'];
  6253.     //             $dataFinal['user'] = $data['user'];
  6254.     //             $dataFinal['dateGenerated'] = $data['dateGenerated'];
  6255.     //             $dataFinal['status'] = $data['status'];
  6256.     //             $dataFinal['data'] = array_merge($dataFinal['data'] ?? [], $data['data']);
  6257.     //         }
  6258.     //         if (count($dataFinal['data']) > 0) {
  6259.     //             $metarData = $this->getWeatherStationDataByHashId($dataFinal['data']);
  6260.     //             $dataFinal['data'] = $metarData;
  6261.     //         }
  6262.     //         if ($genExcel) {
  6263.     //             $csvData[] = ['parameter', 'station id', 'station name', 'date', 'value'];
  6264.     //             if (count($dataFinal['data']) > 0) {
  6265.     //                 foreach ($dataFinal['data'] as $key => $value) {
  6266.     //                     foreach ($value as $key1 => $value1) {
  6267.     //                         if (!is_array($value1)) {
  6268.     //                             $parameter = $value1;
  6269.     //                         }
  6270.     //                         if (is_array($value1)) {
  6271.     //                             foreach ($value1 as $key2 => $value2) {
  6272.     //                                 $station_hash = $value2['station_id'];
  6273.     //                                 $station_name = $value2['station_name'];
  6274.     //                                 $station_id = $value2['station_alternativeIds'];
  6275.     //                                 if (is_array($value2['dates'])) {
  6276.     //                                     foreach ($value2['dates'] as $key3 => $value3) {
  6277.     //                                         $date  = $value3['date'];
  6278.     //                                         $value = $value3['value'];
  6279.     //                                         $csvData[] = [$parameter, $station_id, $station_name, $date, $value];
  6280.     //                                     }
  6281.     //                                 }
  6282.     //                             }
  6283.     //                         }
  6284.     //                     }
  6285.     //                 }
  6286.     //             }
  6287.     //             $xlsxReport = \Pimcore\Model\Asset::getByPath("/report/StationCsv/historical_weather_data.xlsx");
  6288.     //             if ($xlsxReport !== null) {
  6289.     //                 $xlsxReport->delete();
  6290.     //             }
  6291.     //             $excelData = ExcelGenerator::createAndSaveXlsx($csvData, "historical_weather_data", true, 'StationCsv');
  6292.     //             return $excelData;
  6293.     //         }
  6294.     //         // Set the data to Redis cache
  6295.     //         $this->redisCache->set($cacheKey, $dataFinal);
  6296.     //         return $dataFinal;
  6297.     //     } catch (RequestException $e) {
  6298.     //         throw new \Exception($e->getMessage());
  6299.     //     } catch (\Exception $e) {
  6300.     //         throw new \Exception($e->getMessage());
  6301.     //     }
  6302.     // }
  6303.     public function getWeatherStatioData($startDate$endDate$parameters$hash$genExcel$translator$interval 'PT24H')
  6304.     {
  6305.         try {
  6306.             if (!preg_match('/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/'$startDate)) {
  6307.                 throw new \InvalidArgumentException($translator->trans('invalid_start_date_format'));
  6308.             }
  6309.             if (!preg_match('/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/'$endDate)) {
  6310.                 throw new \InvalidArgumentException($translator->trans('invalid_end_date_format'));
  6311.             }
  6312.             $timeResolutions = [
  6313.                 '_24h' => '+1 day',
  6314.                 '_12h' => '+12 hours',
  6315.                 '_6h' => '+6 hours',
  6316.                 '_3h' => '+3 hours',
  6317.                 '_1h' => '+1 hour'
  6318.             ];
  6319.             $adjustedParameters = [];
  6320.             $nonAdjustedParameters = [];
  6321.             foreach ($parameters as $parameter) {
  6322.                 $matched false;
  6323.                 foreach ($timeResolutions as $key => $adjustment) {
  6324.                     if (strpos($parameter$key) !== false) {
  6325.                         $matched true;
  6326.                         $adjustedParameters[$adjustment][] = $parameter;
  6327.                         break;
  6328.                     }
  6329.                 }
  6330.                 if (!$matched) {
  6331.                     $nonAdjustedParameters[] = $parameter;
  6332.                 }
  6333.             }
  6334.             // Initialize $dataFinal
  6335.             $dataFinal = [
  6336.                 'version' => null,
  6337.                 'user' => null,
  6338.                 'dateGenerated' => null,
  6339.                 'status' => null,
  6340.                 'data' => []
  6341.             ];
  6342.             // Process adjusted parameters
  6343.             foreach ($adjustedParameters as $adjustment => $params) {
  6344.                 $adjustedStartDate = (new \DateTime($startDate))->modify($adjustment)->format('Y-m-d\TH:i:s.v\Z');
  6345.                 $adjustedEndDate = (new \DateTime($endDate))->modify($adjustment)->format('Y-m-d\TH:i:s.v\Z');
  6346.                 $data $this->fetchWeatherData($adjustedStartDate$adjustedEndDate$params$hash$interval);
  6347.                 // Revert dates to original range
  6348.                 if (isset($data['data']) && is_array($data['data'])) {
  6349.                     foreach ($data['data'] as &$datum) {
  6350.                         if (isset($datum['coordinates'][0]['dates']) && is_array($datum['coordinates'][0]['dates'])) {
  6351.                             foreach ($datum['coordinates'][0]['dates'] as &$date) {
  6352.                                 if (isset($date['date'])) {
  6353.                                     $date['date'] = (new \DateTime($date['date']))
  6354.                                         ->modify('-' ltrim($adjustment'+'))
  6355.                                         ->format('Y-m-d\TH:i:s.v\Z');
  6356.                                 }
  6357.                             }
  6358.                         }
  6359.                     }
  6360.                 } else {
  6361.                     $data['data'] = []; // Ensure 'data' exists
  6362.                 }
  6363.                 // Merge into $dataFinal
  6364.                 if (empty($dataFinal['version'])) {
  6365.                     $dataFinal['version'] = $data['version'] ?? null;
  6366.                     $dataFinal['user'] = $data['user'] ?? null;
  6367.                     $dataFinal['dateGenerated'] = $data['dateGenerated'] ?? null;
  6368.                     $dataFinal['status'] = $data['status'] ?? null;
  6369.                 }
  6370.                 if (isset($data['data']) && is_array($data['data'])) {
  6371.                     $dataFinal['data'] = array_merge($dataFinal['data'], $data['data']);
  6372.                 }
  6373.             }
  6374.             // Process non-adjusted parameters
  6375.             if (!empty($nonAdjustedParameters)) {
  6376.                 $data $this->fetchWeatherData($startDate$endDate$nonAdjustedParameters$hash$interval);
  6377.                 if (isset($data['data']) && is_array($data['data'])) {
  6378.                     $dataFinal['data'] = array_merge($dataFinal['data'], $data['data']);
  6379.                 }
  6380.             }
  6381.             // Reorder data based on parameters
  6382.             $dataFinal['data'] = isset($dataFinal['data']) && is_array($dataFinal['data'])
  6383.                 ? $this->orderResults($dataFinal['data'], $parameters)
  6384.                 : [];
  6385.             if (count($dataFinal['data']) > 0) {
  6386.                 $metarData $this->getWeatherStationDataByHashId($dataFinal['data']);
  6387.                 $dataFinal['data'] = $metarData;
  6388.             }
  6389.             if ($genExcel) {
  6390.                 return $this->generateExcel($dataFinal);
  6391.             }
  6392.             return $dataFinal;
  6393.         } catch (RequestException $e) {
  6394.             throw new \Exception($e->getMessage());
  6395.         } catch (\Exception $e) {
  6396.             throw new \Exception($e->getMessage());
  6397.         }
  6398.     }
  6399.     private function fetchWeatherData($startDate$endDate$parameters$hash$interval)
  6400.     {
  6401.         $dataFinal = [];
  6402.         while (!empty($parameters)) {
  6403.             $batchParameters array_splice($parameters010);
  6404.             $batchQueryString implode(','$batchParameters);
  6405.             $url sprintf(
  6406.                 "%s/%s--%s:%s/%s/%s/json?source=mix-obs&on_invalid=fill_with_invalid&use_decluttered=true",
  6407.                 $this->apiBaseUrl,
  6408.                 $startDate,
  6409.                 $endDate,
  6410.                 $interval,
  6411.                 $batchQueryString,
  6412.                 $hash
  6413.             );
  6414.             $client = new Client(['verify' => false]);
  6415.             $response $client->request('GET'$url, [
  6416.                 'auth' => [$this->username$this->password],
  6417.             ]);
  6418.             if ($response->getStatusCode() != 200) {
  6419.                 throw new \Exception("Failed to fetch data from API");
  6420.             }
  6421.             $data json_decode($response->getBody(), true);
  6422.             $dataFinal array_merge_recursive($dataFinal$data);
  6423.         }
  6424.         return $dataFinal;
  6425.     }
  6426.     private function generateExcel($data)
  6427.     {
  6428.         $csvData[] = ['parameter''station id''station name''date''value'];
  6429.         if (count($data['data']) > 0) {
  6430.             foreach ($data['data'] as $key => $value) {
  6431.                 foreach ($value as $key1 => $value1) {
  6432.                     if (!is_array($value1)) {
  6433.                         $parameter $value1;
  6434.                     }
  6435.                     if (is_array($value1)) {
  6436.                         foreach ($value1 as $key2 => $value2) {
  6437.                             $station_hash $value2['station_id'];
  6438.                             $station_name $value2['station_name'];
  6439.                             $station_id $value2['station_alternativeIds'];
  6440.                             if (is_array($value2['dates'])) {
  6441.                                 foreach ($value2['dates'] as $key3 => $value3) {
  6442.                                     $date  $value3['date'];
  6443.                                     $value $value3['value'];
  6444.                                     $csvData[] = [$parameter$station_id$station_name$date$value];
  6445.                                 }
  6446.                             }
  6447.                         }
  6448.                     }
  6449.                 }
  6450.             }
  6451.         }
  6452.         $xlsxReport \Pimcore\Model\Asset::getByPath("/report/StationCsv/historical_weather_data.xlsx");
  6453.         if ($xlsxReport !== null) {
  6454.             $xlsxReport->delete();
  6455.         }
  6456.         $excelData ExcelGenerator::createAndSaveXlsx($csvData"historical_weather_data"true'StationCsv');
  6457.         return $excelData;
  6458.     }
  6459.     private function orderResults($data$parameters)
  6460.     {
  6461.         $orderedResults = [];
  6462.         foreach ($parameters as $parameter) {
  6463.             foreach ($data as $key => $item) {
  6464.                 if ($item['parameter'] === $parameter) {
  6465.                     $orderedResults[] = $item;
  6466.                     break;
  6467.                 }
  6468.             }
  6469.         }
  6470.         return $orderedResults;
  6471.     }
  6472.     // Assuming $data1 and $data2 contain the response data from URL1 and URL2 respectively
  6473.     // and that the relevant date fields in these responses are in a format that can be converted to a timestamp
  6474.     function adjustResponseDates(&$data$hours)
  6475. {
  6476.     error_log(print_r($datatrue)); // Log the initial data
  6477.     if ($hours == 24) {
  6478.         $timeAdjustment '-1 day';
  6479.     } elseif ($hours == 1) {
  6480.         $timeAdjustment '-1 hour';
  6481.     } elseif ($hours == 12) {
  6482.         $timeAdjustment '-12 hour';
  6483.     } else {
  6484.         $timeAdjustment '';
  6485.     }
  6486.     foreach ($data as &$parameter) { // Iterate through each parameter
  6487.         if ($timeAdjustment && isset($parameter['coordinates'])) {
  6488.             foreach ($parameter['coordinates'] as $coordKey => $coordinate) {
  6489.                 if (isset($coordinate['dates']) && is_array($coordinate['dates'])) {
  6490.                     foreach ($coordinate['dates'] as $dateKey => $dateInfo) {
  6491.                         if (isset($dateInfo['date'])) {
  6492.                             // Create DateTime object with UTC timezone
  6493.                             $date = new DateTime($dateInfo['date'], new DateTimeZone('UTC'));
  6494.                             // Apply time adjustment
  6495.                             $date->modify($timeAdjustment);
  6496.                             // Format adjusted date in UTC
  6497.                             $adjustedDate $date->format('Y-m-d\TH:i:s.v\Z');
  6498.                             // Update the date in the data
  6499.                             $parameter['coordinates'][$coordKey]['dates'][$dateKey]['date'] = $adjustedDate;
  6500.                             // Log the adjusted date
  6501.                             error_log("Adjusted Date: " $adjustedDate);
  6502.                         }
  6503.                     }
  6504.                 }
  6505.             }
  6506.         }
  6507.     }
  6508.   
  6509.     unset($parameter); // Break the reference with the last element
  6510.     error_log("Final Data: " print_r($datatrue)); // Log the final modified data
  6511. }
  6512. }