src/Service/MeteomaticsWeatherService.php line 2259

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