src/Service/MeteomaticsWeatherService.php line 398

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