src/Service/MeteomaticsWeatherService.php line 1865

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