From 1f582b18f446eab671019eaaf636e7b0c3e7f5f3 Mon Sep 17 00:00:00 2001 From: Martin Eyben Date: Tue, 19 Nov 2024 11:47:16 +0100 Subject: [PATCH 01/29] fix: declare scope --- src/main/java/eirb/pg203/WeatherData.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/eirb/pg203/WeatherData.java b/src/main/java/eirb/pg203/WeatherData.java index 01f02f9..c069d85 100644 --- a/src/main/java/eirb/pg203/WeatherData.java +++ b/src/main/java/eirb/pg203/WeatherData.java @@ -3,7 +3,7 @@ package eirb.pg203; import java.time.Instant; import java.util.concurrent.locks.Condition; -class WeatherData { +public class WeatherData { enum Condition { SUNNY("☀️"), PARTIAL("🌤"), From 1548da7b4bd91850697286eff04967072565b716 Mon Sep 17 00:00:00 2001 From: Martin Eyben Date: Tue, 19 Nov 2024 11:51:15 +0100 Subject: [PATCH 02/29] fix: redefine scope --- src/main/java/eirb/pg203/City.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/eirb/pg203/City.java b/src/main/java/eirb/pg203/City.java index af20548..9c65bcf 100644 --- a/src/main/java/eirb/pg203/City.java +++ b/src/main/java/eirb/pg203/City.java @@ -12,7 +12,7 @@ import org.json.JSONObject; import eirb.pg203.utils.Coords; -class City { +public class City { private String cityName; private Coords cityCoords; From a7ec614471e6b36ac6b773419bb4a552a0ca2a12 Mon Sep 17 00:00:00 2001 From: Martin Eyben Date: Tue, 19 Nov 2024 17:43:20 +0100 Subject: [PATCH 03/29] feat: documentation --- src/main/java/eirb/pg203/WeatherData.java | 71 +++++++++++++++++-- src/main/java/eirb/pg203/WeatherDataAPI.java | 10 +++ .../java/eirb/pg203/WeatherDisplayBasic.java | 53 ++++++++++++-- 3 files changed, 124 insertions(+), 10 deletions(-) diff --git a/src/main/java/eirb/pg203/WeatherData.java b/src/main/java/eirb/pg203/WeatherData.java index c069d85..0b558ed 100644 --- a/src/main/java/eirb/pg203/WeatherData.java +++ b/src/main/java/eirb/pg203/WeatherData.java @@ -4,14 +4,18 @@ import java.time.Instant; import java.util.concurrent.locks.Condition; public class WeatherData { - enum Condition { + + /** + * Representation of a weather condition (with a smiley for String representation) + */ + public enum Condition { SUNNY("☀️"), PARTIAL("🌤"), CLOUDY("☁️"), RAINY("🌧"), ERROR("E"); - private String desc; + private final String desc; Condition(String desc) { this.desc = desc; } @Override @@ -20,7 +24,10 @@ public class WeatherData { } } - enum WindDirection { + /** + * Representation of the wind direction with an arrow + */ + public enum WindDirection { N("🡩"), NE("🡭"), E("🡪"), @@ -31,7 +38,7 @@ public class WeatherData { NW("🡬"), ERROR("E"); - private String desc; + private final String desc; WindDirection(String desc) {this.desc = desc;} @Override @@ -45,9 +52,14 @@ public class WeatherData { private Condition condition; // cloudly, sunny ... private float windSpeed; private float windDirectionAngle; - private WindDirection windDirection; + private final WindDirection windDirection; + /** + * Get wind direction representation based on the angle + * @param windDirectionAngle float representation of the wind direction + * @return wind direction representation + */ private WindDirection getWindDirection(float windDirectionAngle) { if (windDirectionAngle >= 337.5 || windDirectionAngle <= 22.5) return WindDirection.N; @@ -69,6 +81,7 @@ public class WeatherData { return WindDirection.ERROR; } + WeatherData(City city, Instant date, float temp, float windSpeed, float windDirectionAngle, Condition condition) { this.city = city; this.date = date; @@ -79,56 +92,104 @@ public class WeatherData { this.windDirection = this.getWindDirection(windDirectionAngle); } + /** + * @return city + */ public City getCity() { return city; } + /** + * + * @return date of the Weather data + */ public Instant getDate() { return date; } + /** + * @return Weather Condition representation + */ public Condition getCondition() { return condition; } + /** + * @return float temperature + */ public float getTemp() { return temp; } + /** + * @return wind speed + */ public float getWindSpeed() { return windSpeed; } + /** + * @return Wind direction angle + */ public float getWindDirectionAngle() {return this.windDirectionAngle;} + /** + * @return wind direction representation + */ public WindDirection getWindDirection() { return windDirection; } + /** + * Set city + * @param city city of the weather representation + */ public void setCity(City city) { this.city = city; } + /** + * Set date of the weather data + * @param date date + */ public void setDate(Instant date) { this.date = date; } + /** + * Set weather data representation + * @param condition weather data representation + */ public void setCondition(Condition condition) { this.condition = condition; } + /** + * @param temp Weather data temperature + */ public void setTemp(float temp) { this.temp = temp; } + /** + * @param windSpeed Wind speed + */ public void setWindSpeed(float windSpeed) { this.windSpeed = windSpeed; } + /** + * @param windDirectionAngle wind direction angle + */ public void setWindDirectionAngle(float windDirectionAngle) { this.windDirectionAngle = windDirectionAngle; } + /** + * WeatherData representation + * 10,70° 🌧 25,80km/h 243,00° 🡯 + * @return String representation of the WeatherData + */ @Override public String toString() { return String.format("%05.2f° %s %05.2fkm/h %06.2f° %s", diff --git a/src/main/java/eirb/pg203/WeatherDataAPI.java b/src/main/java/eirb/pg203/WeatherDataAPI.java index ccea000..ab269f7 100644 --- a/src/main/java/eirb/pg203/WeatherDataAPI.java +++ b/src/main/java/eirb/pg203/WeatherDataAPI.java @@ -3,6 +3,9 @@ package eirb.pg203; import java.io.IOException; import java.util.ArrayList; +/** + * Interface to discuss with a weather API + */ public interface WeatherDataAPI { /** @@ -24,6 +27,13 @@ public interface WeatherDataAPI { */ WeatherData getTemperature(int day, int hour, String city) throws IOException; + /** + * Fetch the temperature for multiple day since today + * @param days number of days to fetch + * @param city name of te city + * @return List of WeatherData + * @throws IOException when request failed + */ ArrayList getTemperatures(int days, String city) throws IOException; /*** diff --git a/src/main/java/eirb/pg203/WeatherDisplayBasic.java b/src/main/java/eirb/pg203/WeatherDisplayBasic.java index d224da1..dddc38c 100644 --- a/src/main/java/eirb/pg203/WeatherDisplayBasic.java +++ b/src/main/java/eirb/pg203/WeatherDisplayBasic.java @@ -4,12 +4,18 @@ import java.util.ArrayList; import java.util.HashMap; class WeatherDisplayBasic implements WeatherDisplay { - private ArrayList apis; - - WeatherDisplayBasic() { - this.apis = new ArrayList(); - } + /** + * List of apis + */ + private final ArrayList apis = new ArrayList<>(); + /** + * Display header + * Source J + 0 J + 1 J + 2 + * @param days number of columns + * @param sourceColumnSize size of the first column + * @param dayColumnSize day column size + */ private void displayHeader(int days, double sourceColumnSize, double dayColumnSize) { StringBuilder line = new StringBuilder(); line.append("Source\t"); @@ -26,6 +32,13 @@ class WeatherDisplayBasic implements WeatherDisplay { System.out.println(line); } + /** + * Calculate column size based on the WeatherData + * Check the size of the string of the weather data and return the max for the column + * WARNING : Special chars in the WeatherData string will introduce an offset + * @param weatherDataAPIArrayListHashMap list of Weather data for every WeatherDataApi + * @return day column size + */ private double getColumnSize(HashMap> weatherDataAPIArrayListHashMap) { double max = 0; for (WeatherDataAPI api: weatherDataAPIArrayListHashMap.keySet()) { @@ -39,6 +52,15 @@ class WeatherDisplayBasic implements WeatherDisplay { return max; } + /** + * Display a line of data with a column separator + * OpenWeatherMap | 14,49° ☁️ 07,71km/h 254,25° 🡨 | 11,29° ☁️ 04,33km/h 296,00° 🡬 | 12,06° ☁️ 07,53km/h 188,88° 🡫 | + * @param apiName Name of the api if the first column + * @param weatherDatas List of Weather data to display + * @param startColumnString Separator between column + * @param sourceColumnSize Size of the first column + * @param dayColumnSize Size for day columns + */ private void displayWeatherDatas(String apiName, ArrayList weatherDatas, String startColumnString, double sourceColumnSize, double dayColumnSize) { StringBuilder line = new StringBuilder(); String weatherDataString; @@ -61,6 +83,13 @@ class WeatherDisplayBasic implements WeatherDisplay { } + /** + * Display in stdout the line between apis + * -------------------------------+------------------------------+------------------------------+------------------------------+ + * @param days number of days to display + * @param sourceColumnSize size for the first column (where the name of the api is display) + * @param dayColumnSize column size for the days (where the temperature is displayed) + */ private void displaySeparatorLine(int days, double sourceColumnSize, double dayColumnSize) { String mainSeparator = "-"; String secondSeparator = "+"; @@ -77,6 +106,20 @@ class WeatherDisplayBasic implements WeatherDisplay { System.out.println(line); } + /** + * Display an array like this in stdout + * Source J + 0 J + 1 J + 2 + * -------------------------------+------------------------------+------------------------------+------------------------------+ + * OpenWeatherMap | 14,49° ☁️ 07,71km/h 254,25° 🡨 | 11,29° ☁️ 04,33km/h 296,00° 🡬 | 12,06° ☁️ 07,53km/h 188,88° 🡫 | + * -------------------------------+------------------------------+------------------------------+------------------------------+ + * WeatherAPI | 12,50° 🌧 20,31km/h 238,67° 🡯 | 11,20° 🌧 17,23km/h 291,92° 🡨 | 11,00° 🌧 25,59km/h 256,88° 🡨 | + * -------------------------------+------------------------------+------------------------------+------------------------------+ + * OpenMeteo | 10,70° 🌧 25,80km/h 243,00° 🡯 | 11,35° 🌧 24,30km/h 276,00° 🡨 | 11,00° 🌧 31,50km/h 238,00° 🡯 | + * -------------------------------+------------------------------+------------------------------+------------------------------+ + * + * @param weatherDataAPIArrayListHashMap Hashmap with WeatherData array for each api + * @param days number of days to display + */ private void displayAllWeatherDatas(HashMap> weatherDataAPIArrayListHashMap, int days) { double dayColumnSize = this.getColumnSize(weatherDataAPIArrayListHashMap); From ebf840a73dd8c8e2d56c6b403ef3c8f8b0097cca Mon Sep 17 00:00:00 2001 From: Martin Eyben Date: Tue, 19 Nov 2024 17:45:52 +0100 Subject: [PATCH 04/29] feat: remove Temperature --- src/main/java/eirb/pg203/Temperature.java | 42 ----------------------- 1 file changed, 42 deletions(-) delete mode 100644 src/main/java/eirb/pg203/Temperature.java diff --git a/src/main/java/eirb/pg203/Temperature.java b/src/main/java/eirb/pg203/Temperature.java deleted file mode 100644 index e37efe1..0000000 --- a/src/main/java/eirb/pg203/Temperature.java +++ /dev/null @@ -1,42 +0,0 @@ -package eirb.pg203; - -import java.time.Instant; - -/** - * Representation of a temperature in a city at a specific date - */ -public class Temperature { - private City city; - private Instant date; - private float temp; - - Temperature(float temp, City city, Instant date) { - this.temp = temp; - this.city = city; - this.date = date; - } - - /** - * Get the name of the city from where the temperature come from - * @return city - */ - public String getCity() { - return this.city.getCityName(); - } - - /** - * Get the date - * @return date - */ - public Instant getDate() { - return date; - } - - /** - * Get the temperature - * @return temperature - */ - public float getTemp() { - return temp; - } -} From d263a3b68fb2c45316788cbc7b8f9af7e00d5eea Mon Sep 17 00:00:00 2001 From: Martin Eyben Date: Tue, 19 Nov 2024 18:10:22 +0100 Subject: [PATCH 05/29] feat: refactor city fecth --- src/main/java/eirb/pg203/City.java | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/main/java/eirb/pg203/City.java b/src/main/java/eirb/pg203/City.java index 9c65bcf..6a9dbb9 100644 --- a/src/main/java/eirb/pg203/City.java +++ b/src/main/java/eirb/pg203/City.java @@ -7,6 +7,7 @@ import java.net.HttpURLConnection; import java.net.URI; import java.net.URL; +import eirb.pg203.utils.JSONFetcher; import org.json.JSONArray; import org.json.JSONObject; @@ -28,17 +29,7 @@ public class City { ) ).toURL(); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setRequestMethod("GET"); - - try (BufferedReader reader = new BufferedReader( - new InputStreamReader(conn.getInputStream()))) { - for (String line; (line = reader.readLine()) != null; ) { - result.append(line); - } - } - - return new JSONObject(result.toString()); + return JSONFetcher.fetch(url); } private static Coords getCoordsFromName(String cityName) throws IOException { From decbd83316b316c15e36adfda9b104d86f9bd0ac Mon Sep 17 00:00:00 2001 From: Martin Eyben Date: Tue, 19 Nov 2024 17:59:45 +0100 Subject: [PATCH 06/29] feat: documentation --- src/main/java/eirb/pg203/City.java | 8 +++ src/main/java/eirb/pg203/Main.java | 19 +++++- src/main/java/eirb/pg203/OpenMeteo.java | 9 +++ src/main/java/eirb/pg203/OpenWeatherMap.java | 5 +- src/main/java/eirb/pg203/WeatherData.java | 59 ++++++++++++++++++- src/main/java/eirb/pg203/WeatherDataAPI.java | 2 +- .../exceptions/WeatherFetchingException.java | 7 +++ src/main/java/eirb/pg203/utils/Coords.java | 24 ++++++++ .../java/eirb/pg203/utils/JSONFetcher.java | 27 ++++++++- 9 files changed, 152 insertions(+), 8 deletions(-) diff --git a/src/main/java/eirb/pg203/City.java b/src/main/java/eirb/pg203/City.java index 6a9dbb9..9051b0d 100644 --- a/src/main/java/eirb/pg203/City.java +++ b/src/main/java/eirb/pg203/City.java @@ -13,6 +13,10 @@ import org.json.JSONObject; import eirb.pg203.utils.Coords; +/** + * Representation of a city + * Possibility to get city coordinates based on the name + */ public class City { private String cityName; private Coords cityCoords; @@ -49,6 +53,10 @@ public class City { this.cityName = cityName; } + /** + * Get city name + * @return city name string + */ public String getCityName() { return cityName; } diff --git a/src/main/java/eirb/pg203/Main.java b/src/main/java/eirb/pg203/Main.java index 1ed2048..25b7d95 100644 --- a/src/main/java/eirb/pg203/Main.java +++ b/src/main/java/eirb/pg203/Main.java @@ -1,12 +1,19 @@ package eirb.pg203; -import java.io.IOException; -import java.time.Instant; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +/** + * Main class + */ public class Main { + + /** + * Default constructor (private) + */ + private Main() {}; + private static class Option { String flag, value; public Option(String flag, String value){ @@ -27,7 +34,13 @@ public class Main { return value; } } - public static void main(String[] args) throws IOException, IllegalArgumentException{ + + /** + * Main loop + * @param args list of arguments + * @throws IllegalArgumentException if arguments are not provided or in a wrong way + */ + public static void main(String[] args) throws IllegalArgumentException{ HashMap options = new HashMap<>(); List argsList = new ArrayList<>(); diff --git a/src/main/java/eirb/pg203/OpenMeteo.java b/src/main/java/eirb/pg203/OpenMeteo.java index c33e999..d0ee73b 100644 --- a/src/main/java/eirb/pg203/OpenMeteo.java +++ b/src/main/java/eirb/pg203/OpenMeteo.java @@ -14,10 +14,19 @@ import eirb.pg203.WeatherData.Condition; // https://api.open-meteo.com/v1/forecast?latitude=52.52&longitude=13.41&hourly=temperature_2m +/** + * OpenMeteo implementation + */ public class OpenMeteo implements WeatherDataAPI { private static final String forecastBaseURL = "https://api.open-meteo.com/v1/forecast"; private static final String dailyQuery = "weather_code,temperature_2m_max,temperature_2m_min,wind_speed_10m_max,wind_direction_10m_dominant"; + /** + * Default constructor + */ + public OpenMeteo() {} + + // https://www.nodc.noaa.gov/archive/arc0021/0002199/1.1/data/0-data/HTML/WMO-CODE/WMO4677.HTM private JSONObject fetchWeather(int days, City city) throws IOException { URL url = URI.create( diff --git a/src/main/java/eirb/pg203/OpenWeatherMap.java b/src/main/java/eirb/pg203/OpenWeatherMap.java index 6ea2d91..e811d15 100644 --- a/src/main/java/eirb/pg203/OpenWeatherMap.java +++ b/src/main/java/eirb/pg203/OpenWeatherMap.java @@ -14,8 +14,9 @@ import java.time.ZoneId; import java.util.ArrayList; import eirb.pg203.WeatherData.Condition; -// https://api.open-meteo.com/v1/forecast?latitude=52.52&longitude=13.41&hourly=temperature_2m - +/** + * OpenWeatherMap api implementation + */ public class OpenWeatherMap implements WeatherDataAPI { private static final String forecastBaseURL = "https://api.openweathermap.org/data/2.5/forecast"; private String APIKey; diff --git a/src/main/java/eirb/pg203/WeatherData.java b/src/main/java/eirb/pg203/WeatherData.java index 0b558ed..1006651 100644 --- a/src/main/java/eirb/pg203/WeatherData.java +++ b/src/main/java/eirb/pg203/WeatherData.java @@ -3,16 +3,36 @@ package eirb.pg203; import java.time.Instant; import java.util.concurrent.locks.Condition; +/** + * Weather Data representation + * A weather data is a Temperature, on a date, in a city, with a weather condition and wind speed + direction + * The representation of the data is managed in this class. + */ public class WeatherData { /** * Representation of a weather condition (with a smiley for String representation) */ public enum Condition { + /** + * Sunny condition + */ SUNNY("☀️"), + /** + * A little a bit of sun and a little bit of clouds + */ PARTIAL("🌤"), + /** + * Cloudy weather + */ CLOUDY("☁️"), + /** + * Rainy weather -> like most of the time in Bordeaux + */ RAINY("🌧"), + /** + * Impossible to determine a Weather Condition + */ ERROR("E"); private final String desc; @@ -28,14 +48,42 @@ public class WeatherData { * Representation of the wind direction with an arrow */ public enum WindDirection { + /** + * North direction + */ N("🡩"), + /** + * North East direction + */ NE("🡭"), + /** + * East direction + */ E("🡪"), + /** + * South East direction + */ SE("🡮"), + /** + * South direction + */ S("🡫"), + /** + * South West direction + */ SW("🡯"), + /** + * West direction + */ W("🡨"), + /** + * North West direction + */ NW("🡬"), + + /** + * Error wind direction + */ ERROR("E"); private final String desc; @@ -93,6 +141,7 @@ public class WeatherData { } /** + * Get city from where the weather data come from * @return city */ public City getCity() { @@ -100,7 +149,7 @@ public class WeatherData { } /** - * + * Get date of the weather data * @return date of the Weather data */ public Instant getDate() { @@ -108,6 +157,7 @@ public class WeatherData { } /** + * Get weather condition representation * @return Weather Condition representation */ public Condition getCondition() { @@ -115,6 +165,7 @@ public class WeatherData { } /** + * Get temperature value * @return float temperature */ public float getTemp() { @@ -122,6 +173,7 @@ public class WeatherData { } /** + * Get wind speed value * @return wind speed */ public float getWindSpeed() { @@ -129,11 +181,13 @@ public class WeatherData { } /** + * Get wind direction angle value * @return Wind direction angle */ public float getWindDirectionAngle() {return this.windDirectionAngle;} /** + * Get wind direction representation * @return wind direction representation */ public WindDirection getWindDirection() { @@ -165,6 +219,7 @@ public class WeatherData { } /** + * Set temperature value * @param temp Weather data temperature */ public void setTemp(float temp) { @@ -172,6 +227,7 @@ public class WeatherData { } /** + * Set wind speed value * @param windSpeed Wind speed */ public void setWindSpeed(float windSpeed) { @@ -179,6 +235,7 @@ public class WeatherData { } /** + * Set wind direction angle value * @param windDirectionAngle wind direction angle */ public void setWindDirectionAngle(float windDirectionAngle) { diff --git a/src/main/java/eirb/pg203/WeatherDataAPI.java b/src/main/java/eirb/pg203/WeatherDataAPI.java index ab269f7..c91c416 100644 --- a/src/main/java/eirb/pg203/WeatherDataAPI.java +++ b/src/main/java/eirb/pg203/WeatherDataAPI.java @@ -18,7 +18,7 @@ public interface WeatherDataAPI { WeatherData getTemperature(int day, String city) throws IOException; /** - * + * Get WeatherData for a specific day * @param day Since D+0 * @param hour Since H+0 * @param city Localisation diff --git a/src/main/java/eirb/pg203/exceptions/WeatherFetchingException.java b/src/main/java/eirb/pg203/exceptions/WeatherFetchingException.java index 4e21982..bf286f4 100644 --- a/src/main/java/eirb/pg203/exceptions/WeatherFetchingException.java +++ b/src/main/java/eirb/pg203/exceptions/WeatherFetchingException.java @@ -1,6 +1,13 @@ package eirb.pg203.exceptions; +/** + * Exception when an error during the api call + */ public class WeatherFetchingException extends Exception { + /** + * Weather Fetching exception + * @param message message of the exception + */ public WeatherFetchingException(String message) { super(message); } diff --git a/src/main/java/eirb/pg203/utils/Coords.java b/src/main/java/eirb/pg203/utils/Coords.java index 9f58cb7..4308549 100644 --- a/src/main/java/eirb/pg203/utils/Coords.java +++ b/src/main/java/eirb/pg203/utils/Coords.java @@ -1,26 +1,50 @@ package eirb.pg203.utils; +/** + * Coordinates representation + */ public class Coords { private float lat; private float lon; + /** + * Coordinates representation + * @param lat latitude + * @param lon longitude + */ public Coords(float lat, float lon) { this.lat = lat; this.lon = lon; } + /** + * Get latitude + * @return latitude + */ public float getLat() { return lat; } + /** + * Get longitude + * @return longitude + */ public float getLon() { return lon; } + /** + * Set latitude + * @param lat latitude + */ public void setLat(float lat) { this.lat = lat; } + /** + * Set longitude + * @param lon longitude + */ public void setLon(float lon) { this.lon = lon; } diff --git a/src/main/java/eirb/pg203/utils/JSONFetcher.java b/src/main/java/eirb/pg203/utils/JSONFetcher.java index bf3d928..079a0b8 100644 --- a/src/main/java/eirb/pg203/utils/JSONFetcher.java +++ b/src/main/java/eirb/pg203/utils/JSONFetcher.java @@ -9,8 +9,21 @@ import java.net.URL; import org.json.JSONArray; import org.json.JSONObject; +/** + * Util for http calls + */ public class JSONFetcher { + /** + * No need for constructor + */ + private JSONFetcher() {}; + /** + * Make the request + * @param url url to fetch + * @return String of the response + * @throws IOException if the request failed + */ private static String fetchString(URL url) throws IOException{ StringBuilder result = new StringBuilder(); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); @@ -24,13 +37,25 @@ public class JSONFetcher { return result.toString(); } - + + /** + * Fetch an url + * @param url url + * @return Json object of the response + * @throws IOException if the request failed + */ public static JSONObject fetch(URL url) throws IOException { String result = fetchString(url); return new JSONObject(result); } + /** + * Fetch a Json array from an url + * @param url url + * @return JSON array + * @throws IOException when request failed + */ public static JSONArray fetchArray(URL url) throws IOException { String result = fetchString(url); From 70c573eb814c6290ae8b4fae1169c81fcc447ef6 Mon Sep 17 00:00:00 2001 From: Martin Eyben Date: Tue, 19 Nov 2024 18:08:44 +0100 Subject: [PATCH 07/29] fix: Locale format --- src/main/java/eirb/pg203/City.java | 7 ++++--- src/main/java/eirb/pg203/OpenMeteo.java | 7 ++++--- src/main/java/eirb/pg203/OpenWeatherMap.java | 4 +++- src/main/java/eirb/pg203/WeatherAPI.java | 3 ++- src/main/java/eirb/pg203/WeatherData.java | 3 ++- 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/main/java/eirb/pg203/City.java b/src/main/java/eirb/pg203/City.java index 9051b0d..40f3158 100644 --- a/src/main/java/eirb/pg203/City.java +++ b/src/main/java/eirb/pg203/City.java @@ -6,6 +6,7 @@ import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URI; import java.net.URL; +import java.util.Locale; import eirb.pg203.utils.JSONFetcher; import org.json.JSONArray; @@ -28,7 +29,7 @@ public class City { private static JSONObject getDataFromName(String cityName) throws IOException { StringBuilder result = new StringBuilder(); URL url = URI.create( - String.format("https://api-adresse.data.gouv.fr/search/?q=%s&autocomplete=0&limit=1", + String.format(Locale.ENGLISH, "https://api-adresse.data.gouv.fr/search/?q=%s&autocomplete=0&limit=1", cityName ) ).toURL(); @@ -79,7 +80,7 @@ public class City { public String toString() { try { Coords coords = this.getCityCoords(); - return String.format( + return String.format(Locale.ENGLISH, "City(%s, lat: %f, lon: %f)", this.cityName, coords.getLat(), @@ -87,7 +88,7 @@ public class City { ); } catch (IOException e) { - return String.format( + return String.format(Locale.ENGLISH, "City(%s, lat: Request failed, lon: Request Failed)", this.cityName ); diff --git a/src/main/java/eirb/pg203/OpenMeteo.java b/src/main/java/eirb/pg203/OpenMeteo.java index d0ee73b..bbe5b00 100644 --- a/src/main/java/eirb/pg203/OpenMeteo.java +++ b/src/main/java/eirb/pg203/OpenMeteo.java @@ -10,6 +10,8 @@ import java.net.URI; import java.net.URL; import java.time.Instant; import java.util.ArrayList; +import java.util.Locale; + import eirb.pg203.WeatherData.Condition; // https://api.open-meteo.com/v1/forecast?latitude=52.52&longitude=13.41&hourly=temperature_2m @@ -30,15 +32,14 @@ public class OpenMeteo implements WeatherDataAPI { // https://www.nodc.noaa.gov/archive/arc0021/0002199/1.1/data/0-data/HTML/WMO-CODE/WMO4677.HTM private JSONObject fetchWeather(int days, City city) throws IOException { URL url = URI.create( - String.format(forecastBaseURL + "?latitude=%.2f&longitude=%.2f&forecast_days=%d&daily=" + dailyQuery, + String.format(Locale.ENGLISH, forecastBaseURL + "?latitude=%.2f&longitude=%.2f&forecast_days=%d&daily=" + dailyQuery, city.getCityCoords().getLat(), city.getCityCoords().getLon(), days ) ).toURL(); - JSONArray jsonArray = JSONFetcher.fetchArray(url); - return jsonArray.getJSONObject(0); + return JSONFetcher.fetch(url); } private static Condition getConditionFromCode(int WMOCode) { diff --git a/src/main/java/eirb/pg203/OpenWeatherMap.java b/src/main/java/eirb/pg203/OpenWeatherMap.java index e811d15..c79ffa4 100644 --- a/src/main/java/eirb/pg203/OpenWeatherMap.java +++ b/src/main/java/eirb/pg203/OpenWeatherMap.java @@ -12,6 +12,8 @@ import java.time.DayOfWeek; import java.time.Instant; import java.time.ZoneId; import java.util.ArrayList; +import java.util.Locale; + import eirb.pg203.WeatherData.Condition; /** @@ -27,7 +29,7 @@ public class OpenWeatherMap implements WeatherDataAPI { private JSONObject fetchWeather(int days, City city) throws IOException { URL url = URI.create( - String.format(forecastBaseURL + "?appid=%s&lat=%.2f&lon=%.2f&units=metric", + String.format(Locale.ENGLISH, forecastBaseURL + "?appid=%s&lat=%.2f&lon=%.2f&units=metric", APIKey, city.getCityCoords().getLat(), city.getCityCoords().getLon(), diff --git a/src/main/java/eirb/pg203/WeatherAPI.java b/src/main/java/eirb/pg203/WeatherAPI.java index 5f76aae..383a55c 100644 --- a/src/main/java/eirb/pg203/WeatherAPI.java +++ b/src/main/java/eirb/pg203/WeatherAPI.java @@ -11,6 +11,7 @@ import java.net.URI; import java.net.URL; import java.time.Instant; import java.util.ArrayList; +import java.util.Locale; /** * WeatherAPI implementation @@ -25,7 +26,7 @@ public class WeatherAPI implements WeatherDataAPI{ private JSONObject fetchWeather(int days, String city) throws IOException { URL url = URI.create( - String.format(forecastBaseURL + "?key=%s&q=%s&days=%d", + String.format(Locale.ENGLISH, forecastBaseURL + "?key=%s&q=%s&days=%d", this.weatherAPIKey, city, days diff --git a/src/main/java/eirb/pg203/WeatherData.java b/src/main/java/eirb/pg203/WeatherData.java index 1006651..602bb55 100644 --- a/src/main/java/eirb/pg203/WeatherData.java +++ b/src/main/java/eirb/pg203/WeatherData.java @@ -1,6 +1,7 @@ package eirb.pg203; import java.time.Instant; +import java.util.Locale; import java.util.concurrent.locks.Condition; /** @@ -249,7 +250,7 @@ public class WeatherData { */ @Override public String toString() { - return String.format("%05.2f° %s %05.2fkm/h %06.2f° %s", + return String.format(Locale.ENGLISH, "%05.2f° %s %05.2fkm/h %06.2f° %s", this.getTemp(), this.getCondition().toString(), this.getWindSpeed(), From 52e63be79c19882c931d8308b473d95e0a7add0b Mon Sep 17 00:00:00 2001 From: Martin Eyben Date: Sat, 23 Nov 2024 10:42:04 +0100 Subject: [PATCH 08/29] feat: fake jsonfetcher --- src/main/java/eirb/pg203/City.java | 2 ++ src/main/java/eirb/pg203/OpenMeteo.java | 2 ++ src/main/java/eirb/pg203/OpenWeatherMap.java | 2 ++ src/main/java/eirb/pg203/WeatherAPI.java | 4 +++- src/main/java/eirb/pg203/utils/JSONFetcher.java | 10 ++++++---- .../eirb/pg203/utils/JSONFetcherInterface.java | 16 ++++++++++++++++ 6 files changed, 31 insertions(+), 5 deletions(-) create mode 100644 src/main/java/eirb/pg203/utils/JSONFetcherInterface.java diff --git a/src/main/java/eirb/pg203/City.java b/src/main/java/eirb/pg203/City.java index 40f3158..e89e682 100644 --- a/src/main/java/eirb/pg203/City.java +++ b/src/main/java/eirb/pg203/City.java @@ -9,6 +9,7 @@ import java.net.URL; import java.util.Locale; import eirb.pg203.utils.JSONFetcher; +import eirb.pg203.utils.JSONFetcherInterface; import org.json.JSONArray; import org.json.JSONObject; @@ -21,6 +22,7 @@ import eirb.pg203.utils.Coords; public class City { private String cityName; private Coords cityCoords; + private static final JSONFetcherInterface JSONFetcher = new JSONFetcher(); /** * Fetch data from adresse.data.gouv.fr diff --git a/src/main/java/eirb/pg203/OpenMeteo.java b/src/main/java/eirb/pg203/OpenMeteo.java index bbe5b00..dab51de 100644 --- a/src/main/java/eirb/pg203/OpenMeteo.java +++ b/src/main/java/eirb/pg203/OpenMeteo.java @@ -1,5 +1,6 @@ package eirb.pg203; +import eirb.pg203.utils.JSONFetcherInterface; import org.json.JSONArray; import org.json.JSONObject; @@ -22,6 +23,7 @@ import eirb.pg203.WeatherData.Condition; public class OpenMeteo implements WeatherDataAPI { private static final String forecastBaseURL = "https://api.open-meteo.com/v1/forecast"; private static final String dailyQuery = "weather_code,temperature_2m_max,temperature_2m_min,wind_speed_10m_max,wind_direction_10m_dominant"; + private static final JSONFetcherInterface JSONFetcher = new JSONFetcher(); /** * Default constructor diff --git a/src/main/java/eirb/pg203/OpenWeatherMap.java b/src/main/java/eirb/pg203/OpenWeatherMap.java index c79ffa4..9a54da3 100644 --- a/src/main/java/eirb/pg203/OpenWeatherMap.java +++ b/src/main/java/eirb/pg203/OpenWeatherMap.java @@ -1,5 +1,6 @@ package eirb.pg203; +import eirb.pg203.utils.JSONFetcherInterface; import org.json.JSONObject; import org.json.JSONArray; @@ -22,6 +23,7 @@ import eirb.pg203.WeatherData.Condition; public class OpenWeatherMap implements WeatherDataAPI { private static final String forecastBaseURL = "https://api.openweathermap.org/data/2.5/forecast"; private String APIKey; + private static final JSONFetcherInterface JSONFetcher = new JSONFetcher(); OpenWeatherMap(String APIKey) { this.APIKey = APIKey; diff --git a/src/main/java/eirb/pg203/WeatherAPI.java b/src/main/java/eirb/pg203/WeatherAPI.java index 383a55c..9abd91e 100644 --- a/src/main/java/eirb/pg203/WeatherAPI.java +++ b/src/main/java/eirb/pg203/WeatherAPI.java @@ -1,5 +1,6 @@ package eirb.pg203; +import eirb.pg203.utils.JSONFetcherInterface; import org.json.JSONArray; import org.json.JSONObject; @@ -18,7 +19,8 @@ import java.util.Locale; */ public class WeatherAPI implements WeatherDataAPI{ private final String weatherAPIKey; - private static final String forecastBaseURL = "https://api.weatherapi.com/v1/forecast.json"; + private static final JSONFetcherInterface JSONFetcher = new JSONFetcher(); + private static final String forecastBaseURL = "https://api.weatherapi.com/v1/forecast.json"; WeatherAPI(String weatherAPIKey) { this.weatherAPIKey = weatherAPIKey; diff --git a/src/main/java/eirb/pg203/utils/JSONFetcher.java b/src/main/java/eirb/pg203/utils/JSONFetcher.java index 079a0b8..16484a9 100644 --- a/src/main/java/eirb/pg203/utils/JSONFetcher.java +++ b/src/main/java/eirb/pg203/utils/JSONFetcher.java @@ -12,12 +12,12 @@ import org.json.JSONObject; /** * Util for http calls */ -public class JSONFetcher { +public class JSONFetcher implements JSONFetcherInterface{ /** * No need for constructor */ - private JSONFetcher() {}; + public JSONFetcher() {}; /** * Make the request * @param url url to fetch @@ -44,7 +44,8 @@ public class JSONFetcher { * @return Json object of the response * @throws IOException if the request failed */ - public static JSONObject fetch(URL url) throws IOException { + @Override + public JSONObject fetch(URL url) throws IOException { String result = fetchString(url); return new JSONObject(result); @@ -56,7 +57,8 @@ public class JSONFetcher { * @return JSON array * @throws IOException when request failed */ - public static JSONArray fetchArray(URL url) throws IOException { + @Override + public JSONArray fetchArray(URL url) throws IOException { String result = fetchString(url); return new JSONArray(result); diff --git a/src/main/java/eirb/pg203/utils/JSONFetcherInterface.java b/src/main/java/eirb/pg203/utils/JSONFetcherInterface.java new file mode 100644 index 0000000..0adbd33 --- /dev/null +++ b/src/main/java/eirb/pg203/utils/JSONFetcherInterface.java @@ -0,0 +1,16 @@ +package eirb.pg203.utils; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.IOException; +import java.net.URL; + +/** + * Interface use to be overrided by a fake jsonFetcher during tests + */ +public interface JSONFetcherInterface { + JSONObject fetch(URL url) throws IOException; + + JSONArray fetchArray(URL url) throws IOException; +} From e62635c057c3a3a73da23d786e701ddf581ffcbf Mon Sep 17 00:00:00 2001 From: Martin Eyben Date: Sat, 23 Nov 2024 18:41:40 +0100 Subject: [PATCH 09/29] feat: WeatherAPI tests --- src/main/java/eirb/pg203/WeatherAPI.java | 2 +- .../eirb/pg203/FakeJSONFetcherWeatherAPI.java | 43 + src/test/java/eirb/pg203/WeatherAPITest.java | 114 +- .../eirb/pg203/utils/FileResourcesUtils.java | 53 + .../java/eirb/pg203/utils/SplitQueryUrl.java | 20 + .../WeatherAPI/Bordeaux-1-partial.json | 3057 +++++++++++++ .../WeatherAPI/Bordeaux-2-partial-sunny.json | 3057 +++++++++++++ .../Bordeaux-3-partial-sunny-rain.json | 3057 +++++++++++++ .../Bordeaux-4-partial-sunny-rain-cloudy.json | 4059 +++++++++++++++++ .../resources/WeatherAPI/wrong-apikey.json | 6 + 10 files changed, 13425 insertions(+), 43 deletions(-) create mode 100644 src/test/java/eirb/pg203/FakeJSONFetcherWeatherAPI.java create mode 100644 src/test/java/eirb/pg203/utils/FileResourcesUtils.java create mode 100644 src/test/java/eirb/pg203/utils/SplitQueryUrl.java create mode 100644 src/test/resources/WeatherAPI/Bordeaux-1-partial.json create mode 100644 src/test/resources/WeatherAPI/Bordeaux-2-partial-sunny.json create mode 100644 src/test/resources/WeatherAPI/Bordeaux-3-partial-sunny-rain.json create mode 100644 src/test/resources/WeatherAPI/Bordeaux-4-partial-sunny-rain-cloudy.json create mode 100644 src/test/resources/WeatherAPI/wrong-apikey.json diff --git a/src/main/java/eirb/pg203/WeatherAPI.java b/src/main/java/eirb/pg203/WeatherAPI.java index 9abd91e..6e30274 100644 --- a/src/main/java/eirb/pg203/WeatherAPI.java +++ b/src/main/java/eirb/pg203/WeatherAPI.java @@ -19,7 +19,7 @@ import java.util.Locale; */ public class WeatherAPI implements WeatherDataAPI{ private final String weatherAPIKey; - private static final JSONFetcherInterface JSONFetcher = new JSONFetcher(); + JSONFetcherInterface JSONFetcher = new JSONFetcher(); private static final String forecastBaseURL = "https://api.weatherapi.com/v1/forecast.json"; WeatherAPI(String weatherAPIKey) { diff --git a/src/test/java/eirb/pg203/FakeJSONFetcherWeatherAPI.java b/src/test/java/eirb/pg203/FakeJSONFetcherWeatherAPI.java new file mode 100644 index 0000000..045381a --- /dev/null +++ b/src/test/java/eirb/pg203/FakeJSONFetcherWeatherAPI.java @@ -0,0 +1,43 @@ +package eirb.pg203; + +import eirb.pg203.utils.FileResourcesUtils; +import eirb.pg203.utils.JSONFetcherInterface; +import eirb.pg203.utils.SplitQueryUrl; +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Map; + +public class FakeJSONFetcherWeatherAPI implements JSONFetcherInterface { + private final static String baseUrlFormat = "https://api.weatherapi.com/v1/forecast.json?key=%s&q=%s&days=%d"; + private final String apiKey = "realKey"; + private static final JSONObject wrongKeyRequest = FileResourcesUtils.getFileFromResourceAsJson("WeatherAPI/wrong-apikey.json"); + private static ArrayList bordeauxRequests() { + ArrayList bordeauxRequest = new ArrayList<>(); + bordeauxRequest.add(FileResourcesUtils.getFileFromResourceAsJson("WeatherAPI/Bordeaux-1-partial.json")); + bordeauxRequest.add(FileResourcesUtils.getFileFromResourceAsJson("WeatherAPI/Bordeaux-2-partial-sunny.json")); + bordeauxRequest.add(FileResourcesUtils.getFileFromResourceAsJson("WeatherAPI/Bordeaux-3-partial-sunny-rain.json")); + bordeauxRequest.add(FileResourcesUtils.getFileFromResourceAsJson("WeatherAPI/Bordeaux-4-partial-sunny-rain-cloudy.json")); + return bordeauxRequest; + } + + @Override + public JSONObject fetch(URL url) throws IOException { + Map params = SplitQueryUrl.splitQuery(url); + int days = Integer.parseInt(params.get("days")); + + if (!params.get("key").contentEquals(apiKey)) + return wrongKeyRequest; + + return bordeauxRequests().get(days - 1); + } + + @Override + public JSONArray fetchArray(URL url) throws IOException { + return null; + } +} diff --git a/src/test/java/eirb/pg203/WeatherAPITest.java b/src/test/java/eirb/pg203/WeatherAPITest.java index af23ba3..c8b800c 100644 --- a/src/test/java/eirb/pg203/WeatherAPITest.java +++ b/src/test/java/eirb/pg203/WeatherAPITest.java @@ -2,70 +2,100 @@ package eirb.pg203; import org.json.JSONObject; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.*; import java.io.IOException; +import java.util.ArrayList; +import java.util.stream.Stream; public class WeatherAPITest { - private static String APIKey = "cef8e1b6ea364994b5072423240111"; + private static final String APIKey = "realKey"; + private static final float epsilon = 0.01F; + private WeatherAPI weatherAPI; - @Test - public void testRightAPIKey() { - WeatherAPI weatherAPI = new WeatherAPI(WeatherAPITest.APIKey); - int day = 0; - // int hour = 10; - int days = 7; - String city = "Bordeaux"; + @BeforeEach + public void setupWeatherApi() { + this.weatherAPI = new WeatherAPI(WeatherAPITest.APIKey); + this.weatherAPI.JSONFetcher = new FakeJSONFetcherWeatherAPI(); + } - Assertions.assertAll( - () -> weatherAPI.getTemperature(day, city), - // () -> weatherAPI.getTemperature(day, hour, city), - () -> weatherAPI.getTemperatures(days, city) + /** + * List of args for Temperature testing + * @return Args for testing + */ + private static Stream testGetTemperature(){ + return Stream.of( + Arguments.arguments(0, 8.1F,WeatherData.Condition.PARTIAL, 17.45F, 142.08F), + Arguments.arguments(1, 13F, WeatherData.Condition.SUNNY, 23.03F, 142.58F), + Arguments.arguments(2, 12.7F, WeatherData.Condition.RAINY, 13.19F, 222.92F), + Arguments.arguments(3, 8.1F,WeatherData.Condition.CLOUDY, 17.45F, 142.08F) ); } - @Test - public void testWrongAPIKey() { - WeatherAPI weatherAPI = new WeatherAPI(""); - int day = 0; - // int hour = 10; - int days = 7; + @ParameterizedTest(name="Temperature fetch at Bordeaux D+{0}") + @MethodSource + public void testGetTemperature(int day, float expectedTemp, WeatherData.Condition expectedCond, float expectedWindSpeed, float expectedWindAngle) throws IOException { String city = "Bordeaux"; + WeatherData weatherData; + weatherData = weatherAPI.getTemperature(day, city); - Assertions.assertThrows(IOException.class, () -> weatherAPI.getTemperature(day, city)); - Assertions.assertThrows(IOException.class, () -> weatherAPI.getTemperatures(days, city)); + /* Temperatures */ + Assertions.assertEquals(expectedTemp, weatherData.getTemp()); + /* Condition */ + Assertions.assertEquals(expectedCond, weatherData.getCondition()); + /* Wind */ + Assertions.assertTrue(expectedWindSpeed - weatherData.getWindSpeed() < epsilon); + Assertions.assertTrue(expectedWindAngle - weatherData.getWindDirectionAngle() < epsilon); } + /** + * For coverage (hour not yet implemented) + * @throws IOException never + */ @Test - public void testWrongDay() { - WeatherAPI weatherAPI = new WeatherAPI(WeatherAPITest.APIKey); + public void testGetTemperatureByHour() throws IOException { String city = "Bordeaux"; - - Assertions.assertThrows(IOException.class, () -> weatherAPI.getTemperature(-1, city)); - Assertions.assertThrows(IOException.class, () -> weatherAPI.getTemperature(15, city)); - Assertions.assertThrows(IOException.class, () -> weatherAPI.getTemperatures(15, city)); - Assertions.assertThrows(IOException.class, () -> weatherAPI.getTemperatures(-1, city)); - } - - @Test - public void testRightDay() { - WeatherAPI weatherAPI = new WeatherAPI(WeatherAPITest.APIKey); - String city = "Bordeaux"; - Assertions.assertAll( - () -> weatherAPI.getTemperature(0, city), - () -> weatherAPI.getTemperature(5, city), - () -> weatherAPI.getTemperature(14, city), - () -> weatherAPI.getTemperatures(0, city), - () -> weatherAPI.getTemperatures(8, city), - () -> weatherAPI.getTemperatures(14, city) + () -> weatherAPI.getTemperature(0,1, city) ); + + } + + @Test + @DisplayName("Multiple day temperature fetch") + public void testGetTemperatures() throws IOException { + String city = "Bordeaux"; + int days = 3; + float[] expectedTemperatures = {8.1F, 13F, 12.7F, 8.1F}; + WeatherData.Condition[] expectedConditions = {WeatherData.Condition.PARTIAL, WeatherData.Condition.SUNNY, WeatherData.Condition.RAINY, WeatherData.Condition.CLOUDY}; + float[] expectedWindSpeed = {17.45F, 23.03F, 13.19F, 17.45F}; + float[] expectedWindDirection = {142.08F, 142.58F, 222.92F, 142.08F}; + + ArrayList weatherDatas; + WeatherData weatherData; + weatherDatas = weatherAPI.getTemperatures(days, city); + + for (int index = 0; index < days; index++) { + weatherData = weatherDatas.get(index); + + + /* Temperatures */ + Assertions.assertEquals(expectedTemperatures[index], weatherData.getTemp()); + /* Weather condition */ + Assertions.assertEquals(expectedConditions[index], weatherData.getCondition()); + /* Wind */ + Assertions.assertTrue(expectedWindSpeed[index] - weatherData.getWindSpeed() < epsilon); + Assertions.assertTrue(expectedWindDirection[index] - weatherData.getWindDirectionAngle() < epsilon); + } } @Test public void testGetAPIName() { - WeatherAPI weatherAPI = new WeatherAPI(WeatherAPITest.APIKey); - Assertions.assertTrue(weatherAPI.getAPIName().equals("WeatherAPI")); + Assertions.assertEquals("WeatherAPI", weatherAPI.getAPIName()); } } diff --git a/src/test/java/eirb/pg203/utils/FileResourcesUtils.java b/src/test/java/eirb/pg203/utils/FileResourcesUtils.java new file mode 100644 index 0000000..8398e38 --- /dev/null +++ b/src/test/java/eirb/pg203/utils/FileResourcesUtils.java @@ -0,0 +1,53 @@ +package eirb.pg203.utils; + +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +public class FileResourcesUtils { + /** + * Fetch ressource file + * Code from https://mkyong.com + * @param fileName + * @return + */ + public static InputStream getFileFromResourceAsStream(String fileName) { + + // The class loader that loaded the class + ClassLoader classLoader = FileResourcesUtils.class.getClassLoader(); + InputStream inputStream = classLoader.getResourceAsStream(fileName); + + // the stream holding the file content + if (inputStream == null) { + throw new IllegalArgumentException("file not found! " + fileName); + } else { + return inputStream; + } + + } + + public static JSONObject getFileFromResourceAsJson(String fileName) { + InputStream inputStream = getFileFromResourceAsStream(fileName); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + StringBuilder stringBuilder = new StringBuilder(); + String line; + while(true){ + try { + if (!bufferedReader.ready()) break; + } catch (IOException e) { + throw new RuntimeException(e); + } + try { + line = bufferedReader.readLine(); + } catch (IOException e) { + throw new RuntimeException(e); + } + stringBuilder.append(line); + } + + return new JSONObject(stringBuilder.toString()); + } +} diff --git a/src/test/java/eirb/pg203/utils/SplitQueryUrl.java b/src/test/java/eirb/pg203/utils/SplitQueryUrl.java new file mode 100644 index 0000000..00819c6 --- /dev/null +++ b/src/test/java/eirb/pg203/utils/SplitQueryUrl.java @@ -0,0 +1,20 @@ +package eirb.pg203.utils; + +import java.io.UnsupportedEncodingException; +import java.net.URL; +import java.net.URLDecoder; +import java.util.LinkedHashMap; +import java.util.Map; + +public class SplitQueryUrl { + public static Map splitQuery(URL url) throws UnsupportedEncodingException { + Map query_pairs = new LinkedHashMap(); + String query = url.getQuery(); + String[] pairs = query.split("&"); + for (String pair : pairs) { + int idx = pair.indexOf("="); + query_pairs.put(URLDecoder.decode(pair.substring(0, idx), "UTF-8"), URLDecoder.decode(pair.substring(idx + 1), "UTF-8")); + } + return query_pairs; + } +} diff --git a/src/test/resources/WeatherAPI/Bordeaux-1-partial.json b/src/test/resources/WeatherAPI/Bordeaux-1-partial.json new file mode 100644 index 0000000..6e10f39 --- /dev/null +++ b/src/test/resources/WeatherAPI/Bordeaux-1-partial.json @@ -0,0 +1,3057 @@ +{ + "location": { + "name": "Bordeaux", + "region": "Aquitaine", + "country": "France", + "lat": 44.8333, + "lon": -0.5667, + "tz_id": "Europe/Paris", + "localtime_epoch": 1732356248, + "localtime": "2024-11-23 11:04" + }, + "current": { + "last_updated_epoch": 1732356000, + "last_updated": "2024-11-23 11:00", + "temp_c": 7.4, + "temp_f": 45.3, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 11.4, + "wind_kph": 18.4, + "wind_degree": 148, + "wind_dir": "SSE", + "pressure_mb": 1018, + "pressure_in": 30.06, + "precip_mm": 0, + "precip_in": 0, + "humidity": 76, + "cloud": 0, + "feelslike_c": 4.3, + "feelslike_f": 39.7, + "windchill_c": 6, + "windchill_f": 42.7, + "heatindex_c": 8.7, + "heatindex_f": 47.7, + "dewpoint_c": 1.3, + "dewpoint_f": 34.3, + "vis_km": 10, + "vis_miles": 6, + "uv": 0.9, + "gust_mph": 15.9, + "gust_kph": 25.5 + }, + "forecast": { + "forecastday": [ + { + "date": "2024-11-23", + "date_epoch": 1732320000, + "day": { + "maxtemp_c": 13.1, + "maxtemp_f": 55.7, + "mintemp_c": 4, + "mintemp_f": 39.3, + "avgtemp_c": 8.1, + "avgtemp_f": 46.6, + "maxwind_mph": 13, + "maxwind_kph": 20.9, + "totalprecip_mm": 0, + "totalprecip_in": 0, + "totalsnow_cm": 0, + "avgvis_km": 10, + "avgvis_miles": 6, + "avghumidity": 69, + "daily_will_it_rain": 0, + "daily_chance_of_rain": 0, + "daily_will_it_snow": 0, + "daily_chance_of_snow": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/day/116.png", + "code": 1003 + }, + "uv": 0.3 + }, + "astro": { + "sunrise": "08:10 AM", + "sunset": "05:27 PM", + "moonrise": "12:08 AM", + "moonset": "02:13 PM", + "moon_phase": "Last Quarter", + "moon_illumination": 51, + "is_moon_up": 0, + "is_sun_up": 0 + }, + "hour": [ + { + "time_epoch": 1732316400, + "time": "2024-11-23 00:00", + "temp_c": 5.6, + "temp_f": 42, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 6.7, + "wind_kph": 10.8, + "wind_degree": 150, + "wind_dir": "SSE", + "pressure_mb": 1022, + "pressure_in": 30.19, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 81, + "cloud": 17, + "feelslike_c": 3.2, + "feelslike_f": 37.7, + "windchill_c": 3.2, + "windchill_f": 37.7, + "heatindex_c": 5.6, + "heatindex_f": 42, + "dewpoint_c": 2.6, + "dewpoint_f": 36.6, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 13.7, + "gust_kph": 22, + "uv": 0 + }, + { + "time_epoch": 1732320000, + "time": "2024-11-23 01:00", + "temp_c": 5.8, + "temp_f": 42.4, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 6.9, + "wind_kph": 11.2, + "wind_degree": 141, + "wind_dir": "SE", + "pressure_mb": 1022, + "pressure_in": 30.17, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 80, + "cloud": 47, + "feelslike_c": 3.3, + "feelslike_f": 38, + "windchill_c": 3.3, + "windchill_f": 38, + "heatindex_c": 5.8, + "heatindex_f": 42.4, + "dewpoint_c": 2.5, + "dewpoint_f": 36.6, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 13.4, + "gust_kph": 21.6, + "uv": 0 + }, + { + "time_epoch": 1732323600, + "time": "2024-11-23 02:00", + "temp_c": 5.6, + "temp_f": 42.1, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 8.3, + "wind_kph": 13.3, + "wind_degree": 137, + "wind_dir": "SE", + "pressure_mb": 1021, + "pressure_in": 30.14, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 79, + "cloud": 56, + "feelslike_c": 2.8, + "feelslike_f": 37, + "windchill_c": 2.8, + "windchill_f": 37, + "heatindex_c": 5.6, + "heatindex_f": 42.1, + "dewpoint_c": 2.3, + "dewpoint_f": 36.1, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 15.3, + "gust_kph": 24.7, + "uv": 0 + }, + { + "time_epoch": 1732327200, + "time": "2024-11-23 03:00", + "temp_c": 5.1, + "temp_f": 41.3, + "is_day": 0, + "condition": { + "text": "Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/119.png", + "code": 1006 + }, + "wind_mph": 9.8, + "wind_kph": 15.8, + "wind_degree": 133, + "wind_dir": "SE", + "pressure_mb": 1020, + "pressure_in": 30.13, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 79, + "cloud": 86, + "feelslike_c": 1.8, + "feelslike_f": 35.3, + "windchill_c": 1.8, + "windchill_f": 35.3, + "heatindex_c": 5.2, + "heatindex_f": 41.3, + "dewpoint_c": 1.9, + "dewpoint_f": 35.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 17.6, + "gust_kph": 28.4, + "uv": 0 + }, + { + "time_epoch": 1732330800, + "time": "2024-11-23 04:00", + "temp_c": 4.8, + "temp_f": 40.7, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 10.5, + "wind_kph": 16.9, + "wind_degree": 130, + "wind_dir": "SE", + "pressure_mb": 1020, + "pressure_in": 30.12, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 79, + "cloud": 44, + "feelslike_c": 1.3, + "feelslike_f": 34.3, + "windchill_c": 1.3, + "windchill_f": 34.3, + "heatindex_c": 4.9, + "heatindex_f": 40.7, + "dewpoint_c": 1.5, + "dewpoint_f": 34.7, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 18.1, + "gust_kph": 29.1, + "uv": 0 + }, + { + "time_epoch": 1732334400, + "time": "2024-11-23 05:00", + "temp_c": 4.3, + "temp_f": 39.7, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 11, + "wind_kph": 17.6, + "wind_degree": 130, + "wind_dir": "SE", + "pressure_mb": 1019, + "pressure_in": 30.1, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 80, + "cloud": 46, + "feelslike_c": 0.5, + "feelslike_f": 32.8, + "windchill_c": 0.5, + "windchill_f": 32.8, + "heatindex_c": 4.3, + "heatindex_f": 39.7, + "dewpoint_c": 1.2, + "dewpoint_f": 34.1, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 19.1, + "gust_kph": 30.8, + "uv": 0 + }, + { + "time_epoch": 1732338000, + "time": "2024-11-23 06:00", + "temp_c": 4, + "temp_f": 39.3, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 11.2, + "wind_kph": 18, + "wind_degree": 138, + "wind_dir": "SE", + "pressure_mb": 1019, + "pressure_in": 30.08, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 80, + "cloud": 39, + "feelslike_c": 0.1, + "feelslike_f": 32.2, + "windchill_c": 0.1, + "windchill_f": 32.2, + "heatindex_c": 4, + "heatindex_f": 39.3, + "dewpoint_c": 0.9, + "dewpoint_f": 33.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 19.8, + "gust_kph": 31.8, + "uv": 0 + }, + { + "time_epoch": 1732341600, + "time": "2024-11-23 07:00", + "temp_c": 4, + "temp_f": 39.3, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 11.2, + "wind_kph": 18, + "wind_degree": 138, + "wind_dir": "SE", + "pressure_mb": 1019, + "pressure_in": 30.08, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 78, + "cloud": 26, + "feelslike_c": 0.1, + "feelslike_f": 32.2, + "windchill_c": 0.1, + "windchill_f": 32.2, + "heatindex_c": 4, + "heatindex_f": 39.3, + "dewpoint_c": 0.6, + "dewpoint_f": 33, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 19.9, + "gust_kph": 32, + "uv": 0 + }, + { + "time_epoch": 1732345200, + "time": "2024-11-23 08:00", + "temp_c": 4.1, + "temp_f": 39.4, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 10.5, + "wind_kph": 16.9, + "wind_degree": 140, + "wind_dir": "SE", + "pressure_mb": 1019, + "pressure_in": 30.09, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 75, + "cloud": 7, + "feelslike_c": 0.4, + "feelslike_f": 32.6, + "windchill_c": 0.4, + "windchill_f": 32.6, + "heatindex_c": 4.1, + "heatindex_f": 39.4, + "dewpoint_c": 0.1, + "dewpoint_f": 32.1, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 18.8, + "gust_kph": 30.2, + "uv": 0 + }, + { + "time_epoch": 1732348800, + "time": "2024-11-23 09:00", + "temp_c": 4.6, + "temp_f": 40.3, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 10.7, + "wind_kph": 17.3, + "wind_degree": 143, + "wind_dir": "SE", + "pressure_mb": 1019, + "pressure_in": 30.09, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 71, + "cloud": 13, + "feelslike_c": 1, + "feelslike_f": 33.7, + "windchill_c": 1, + "windchill_f": 33.7, + "heatindex_c": 4.6, + "heatindex_f": 40.4, + "dewpoint_c": -0.2, + "dewpoint_f": 31.7, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 19, + "gust_kph": 30.5, + "uv": 0 + }, + { + "time_epoch": 1732352400, + "time": "2024-11-23 10:00", + "temp_c": 6.6, + "temp_f": 43.9, + "is_day": 1, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/day/116.png", + "code": 1003 + }, + "wind_mph": 11.2, + "wind_kph": 18, + "wind_degree": 146, + "wind_dir": "SSE", + "pressure_mb": 1019, + "pressure_in": 30.08, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 63, + "cloud": 49, + "feelslike_c": 3.4, + "feelslike_f": 38, + "windchill_c": 3.4, + "windchill_f": 38, + "heatindex_c": 6.6, + "heatindex_f": 43.9, + "dewpoint_c": 0.2, + "dewpoint_f": 32.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 17.5, + "gust_kph": 28.2, + "uv": 0.4 + }, + { + "time_epoch": 1732356000, + "time": "2024-11-23 11:00", + "temp_c": 7.4, + "temp_f": 45.3, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 11.4, + "wind_kph": 18.4, + "wind_degree": 148, + "wind_dir": "SSE", + "pressure_mb": 1018, + "pressure_in": 30.06, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 76, + "cloud": 0, + "feelslike_c": 6, + "feelslike_f": 42.7, + "windchill_c": 6, + "windchill_f": 42.7, + "heatindex_c": 8.7, + "heatindex_f": 47.7, + "dewpoint_c": 1.3, + "dewpoint_f": 34.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 15.9, + "gust_kph": 25.5, + "uv": 0.9 + }, + { + "time_epoch": 1732359600, + "time": "2024-11-23 12:00", + "temp_c": 10.4, + "temp_f": 50.8, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 11, + "wind_kph": 17.6, + "wind_degree": 149, + "wind_dir": "SSE", + "pressure_mb": 1018, + "pressure_in": 30.07, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 56, + "cloud": 21, + "feelslike_c": 8.2, + "feelslike_f": 46.7, + "windchill_c": 8.2, + "windchill_f": 46.7, + "heatindex_c": 10.5, + "heatindex_f": 50.8, + "dewpoint_c": 2.2, + "dewpoint_f": 35.9, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 14.7, + "gust_kph": 23.6, + "uv": 1.3 + }, + { + "time_epoch": 1732363200, + "time": "2024-11-23 13:00", + "temp_c": 12.3, + "temp_f": 54.2, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 11.4, + "wind_kph": 18.4, + "wind_degree": 150, + "wind_dir": "SSE", + "pressure_mb": 1017, + "pressure_in": 30.04, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 52, + "cloud": 22, + "feelslike_c": 10.5, + "feelslike_f": 50.8, + "windchill_c": 10.5, + "windchill_f": 50.8, + "heatindex_c": 12.3, + "heatindex_f": 54.2, + "dewpoint_c": 2.8, + "dewpoint_f": 37, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 14.9, + "gust_kph": 23.9, + "uv": 1.6 + }, + { + "time_epoch": 1732366800, + "time": "2024-11-23 14:00", + "temp_c": 13, + "temp_f": 55.4, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 10.3, + "wind_kph": 16.6, + "wind_degree": 149, + "wind_dir": "SSE", + "pressure_mb": 1017, + "pressure_in": 30.03, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 54, + "cloud": 12, + "feelslike_c": 11.5, + "feelslike_f": 52.7, + "windchill_c": 11.5, + "windchill_f": 52.7, + "heatindex_c": 13, + "heatindex_f": 55.4, + "dewpoint_c": 3.8, + "dewpoint_f": 38.9, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 14, + "gust_kph": 22.6, + "uv": 1.4 + }, + { + "time_epoch": 1732370400, + "time": "2024-11-23 15:00", + "temp_c": 13.1, + "temp_f": 55.7, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 11.4, + "wind_kph": 18.4, + "wind_degree": 150, + "wind_dir": "SSE", + "pressure_mb": 1016, + "pressure_in": 30.01, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 54, + "cloud": 22, + "feelslike_c": 11.5, + "feelslike_f": 52.7, + "windchill_c": 11.5, + "windchill_f": 52.7, + "heatindex_c": 13.2, + "heatindex_f": 55.7, + "dewpoint_c": 4.2, + "dewpoint_f": 39.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 16.8, + "gust_kph": 27.1, + "uv": 0.9 + }, + { + "time_epoch": 1732374000, + "time": "2024-11-23 16:00", + "temp_c": 12.5, + "temp_f": 54.4, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 10.7, + "wind_kph": 17.3, + "wind_degree": 148, + "wind_dir": "SSE", + "pressure_mb": 1017, + "pressure_in": 30.02, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 57, + "cloud": 14, + "feelslike_c": 10.7, + "feelslike_f": 51.3, + "windchill_c": 10.7, + "windchill_f": 51.3, + "heatindex_c": 12.5, + "heatindex_f": 54.4, + "dewpoint_c": 4.1, + "dewpoint_f": 39.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 17.5, + "gust_kph": 28.1, + "uv": 0.4 + }, + { + "time_epoch": 1732377600, + "time": "2024-11-23 17:00", + "temp_c": 11.2, + "temp_f": 52.1, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 11.2, + "wind_kph": 18, + "wind_degree": 141, + "wind_dir": "SE", + "pressure_mb": 1017, + "pressure_in": 30.02, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 61, + "cloud": 22, + "feelslike_c": 9, + "feelslike_f": 48.2, + "windchill_c": 9, + "windchill_f": 48.2, + "heatindex_c": 11.2, + "heatindex_f": 52.1, + "dewpoint_c": 4, + "dewpoint_f": 39.2, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 20.2, + "gust_kph": 32.5, + "uv": 0 + }, + { + "time_epoch": 1732381200, + "time": "2024-11-23 18:00", + "temp_c": 10.2, + "temp_f": 50.4, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 11.6, + "wind_kph": 18.7, + "wind_degree": 141, + "wind_dir": "SE", + "pressure_mb": 1016, + "pressure_in": 30.02, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 64, + "cloud": 47, + "feelslike_c": 7.8, + "feelslike_f": 46, + "windchill_c": 7.8, + "windchill_f": 46, + "heatindex_c": 10.2, + "heatindex_f": 50.4, + "dewpoint_c": 3.8, + "dewpoint_f": 38.9, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 22.2, + "gust_kph": 35.7, + "uv": 0 + }, + { + "time_epoch": 1732384800, + "time": "2024-11-23 19:00", + "temp_c": 10, + "temp_f": 50.1, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 12.3, + "wind_kph": 19.8, + "wind_degree": 144, + "wind_dir": "SE", + "pressure_mb": 1017, + "pressure_in": 30.02, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 66, + "cloud": 62, + "feelslike_c": 7.5, + "feelslike_f": 45.4, + "windchill_c": 7.5, + "windchill_f": 45.4, + "heatindex_c": 10, + "heatindex_f": 50.1, + "dewpoint_c": 4, + "dewpoint_f": 39.2, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 22.8, + "gust_kph": 36.8, + "uv": 0 + }, + { + "time_epoch": 1732388400, + "time": "2024-11-23 20:00", + "temp_c": 10, + "temp_f": 50, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 12.5, + "wind_kph": 20.2, + "wind_degree": 143, + "wind_dir": "SE", + "pressure_mb": 1017, + "pressure_in": 30.03, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 68, + "cloud": 57, + "feelslike_c": 7.3, + "feelslike_f": 45.2, + "windchill_c": 7.3, + "windchill_f": 45.2, + "heatindex_c": 10, + "heatindex_f": 50, + "dewpoint_c": 4.3, + "dewpoint_f": 39.8, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 22.6, + "gust_kph": 36.3, + "uv": 0 + }, + { + "time_epoch": 1732392000, + "time": "2024-11-23 21:00", + "temp_c": 9.7, + "temp_f": 49.5, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 12.5, + "wind_kph": 20.2, + "wind_degree": 142, + "wind_dir": "SE", + "pressure_mb": 1017, + "pressure_in": 30.03, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 71, + "cloud": 46, + "feelslike_c": 7, + "feelslike_f": 44.6, + "windchill_c": 7, + "windchill_f": 44.6, + "heatindex_c": 9.7, + "heatindex_f": 49.5, + "dewpoint_c": 4.6, + "dewpoint_f": 40.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 22.3, + "gust_kph": 35.9, + "uv": 0 + }, + { + "time_epoch": 1732395600, + "time": "2024-11-23 22:00", + "temp_c": 9.5, + "temp_f": 49.1, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 13, + "wind_kph": 20.9, + "wind_degree": 139, + "wind_dir": "SE", + "pressure_mb": 1017, + "pressure_in": 30.03, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 73, + "cloud": 24, + "feelslike_c": 6.6, + "feelslike_f": 44, + "windchill_c": 6.6, + "windchill_f": 44, + "heatindex_c": 9.5, + "heatindex_f": 49.1, + "dewpoint_c": 5, + "dewpoint_f": 41, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 23, + "gust_kph": 37, + "uv": 0 + }, + { + "time_epoch": 1732399200, + "time": "2024-11-23 23:00", + "temp_c": 9.3, + "temp_f": 48.8, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 12.8, + "wind_kph": 20.5, + "wind_degree": 140, + "wind_dir": "SE", + "pressure_mb": 1017, + "pressure_in": 30.03, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 76, + "cloud": 21, + "feelslike_c": 6.5, + "feelslike_f": 43.7, + "windchill_c": 6.5, + "windchill_f": 43.7, + "heatindex_c": 9.3, + "heatindex_f": 48.8, + "dewpoint_c": 5.3, + "dewpoint_f": 41.6, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 23, + "gust_kph": 37, + "uv": 0 + } + ] + }, + { + "date": "2024-11-24", + "date_epoch": 1732406400, + "day": { + "maxtemp_c": 17.2, + "maxtemp_f": 63, + "mintemp_c": 9.3, + "mintemp_f": 48.8, + "avgtemp_c": 13, + "avgtemp_f": 55.3, + "maxwind_mph": 16.3, + "maxwind_kph": 26.3, + "totalprecip_mm": 0, + "totalprecip_in": 0, + "totalsnow_cm": 0, + "avgvis_km": 10, + "avgvis_miles": 6, + "avghumidity": 70, + "daily_will_it_rain": 0, + "daily_chance_of_rain": 0, + "daily_will_it_snow": 0, + "daily_chance_of_snow": 0, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "uv": 0.3 + }, + "astro": { + "sunrise": "08:12 AM", + "sunset": "05:26 PM", + "moonrise": "01:14 AM", + "moonset": "02:30 PM", + "moon_phase": "Waning Crescent", + "moon_illumination": 41, + "is_moon_up": 0, + "is_sun_up": 0 + }, + "hour": [ + { + "time_epoch": 1732402800, + "time": "2024-11-24 00:00", + "temp_c": 9.3, + "temp_f": 48.8, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 12.5, + "wind_kph": 20.2, + "wind_degree": 140, + "wind_dir": "SE", + "pressure_mb": 1017, + "pressure_in": 30.03, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 77, + "cloud": 41, + "feelslike_c": 6.5, + "feelslike_f": 43.8, + "windchill_c": 6.5, + "windchill_f": 43.8, + "heatindex_c": 9.3, + "heatindex_f": 48.8, + "dewpoint_c": 5.6, + "dewpoint_f": 42, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 23.1, + "gust_kph": 37.2, + "uv": 0 + }, + { + "time_epoch": 1732406400, + "time": "2024-11-24 01:00", + "temp_c": 9.5, + "temp_f": 49.1, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 12.5, + "wind_kph": 20.2, + "wind_degree": 139, + "wind_dir": "SE", + "pressure_mb": 1017, + "pressure_in": 30.02, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 77, + "cloud": 33, + "feelslike_c": 6.7, + "feelslike_f": 44.1, + "windchill_c": 6.7, + "windchill_f": 44.1, + "heatindex_c": 9.5, + "heatindex_f": 49.1, + "dewpoint_c": 5.7, + "dewpoint_f": 42.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 23.4, + "gust_kph": 37.7, + "uv": 0 + }, + { + "time_epoch": 1732410000, + "time": "2024-11-24 02:00", + "temp_c": 9.7, + "temp_f": 49.4, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 12.3, + "wind_kph": 19.8, + "wind_degree": 139, + "wind_dir": "SE", + "pressure_mb": 1016, + "pressure_in": 30, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 77, + "cloud": 22, + "feelslike_c": 7, + "feelslike_f": 44.5, + "windchill_c": 7, + "windchill_f": 44.5, + "heatindex_c": 9.7, + "heatindex_f": 49.4, + "dewpoint_c": 5.8, + "dewpoint_f": 42.4, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 23.2, + "gust_kph": 37.3, + "uv": 0 + }, + { + "time_epoch": 1732413600, + "time": "2024-11-24 03:00", + "temp_c": 9.7, + "temp_f": 49.5, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 12.8, + "wind_kph": 20.5, + "wind_degree": 138, + "wind_dir": "SE", + "pressure_mb": 1016, + "pressure_in": 30, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 76, + "cloud": 14, + "feelslike_c": 7, + "feelslike_f": 44.6, + "windchill_c": 7, + "windchill_f": 44.6, + "heatindex_c": 9.7, + "heatindex_f": 49.5, + "dewpoint_c": 5.7, + "dewpoint_f": 42.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 24.3, + "gust_kph": 39.2, + "uv": 0 + }, + { + "time_epoch": 1732417200, + "time": "2024-11-24 04:00", + "temp_c": 9.8, + "temp_f": 49.6, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 13, + "wind_kph": 20.9, + "wind_degree": 137, + "wind_dir": "SE", + "pressure_mb": 1015, + "pressure_in": 29.98, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 75, + "cloud": 10, + "feelslike_c": 7, + "feelslike_f": 44.6, + "windchill_c": 7, + "windchill_f": 44.6, + "heatindex_c": 9.8, + "heatindex_f": 49.6, + "dewpoint_c": 5.7, + "dewpoint_f": 42.2, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 24.6, + "gust_kph": 39.6, + "uv": 0 + }, + { + "time_epoch": 1732420800, + "time": "2024-11-24 05:00", + "temp_c": 9.7, + "temp_f": 49.4, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 13.4, + "wind_kph": 21.6, + "wind_degree": 136, + "wind_dir": "SE", + "pressure_mb": 1015, + "pressure_in": 29.98, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 76, + "cloud": 10, + "feelslike_c": 6.8, + "feelslike_f": 44.3, + "windchill_c": 6.8, + "windchill_f": 44.3, + "heatindex_c": 9.7, + "heatindex_f": 49.4, + "dewpoint_c": 5.7, + "dewpoint_f": 42.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 25.3, + "gust_kph": 40.7, + "uv": 0 + }, + { + "time_epoch": 1732424400, + "time": "2024-11-24 06:00", + "temp_c": 9.7, + "temp_f": 49.5, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 13.6, + "wind_kph": 22, + "wind_degree": 137, + "wind_dir": "SE", + "pressure_mb": 1015, + "pressure_in": 29.98, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 77, + "cloud": 11, + "feelslike_c": 6.8, + "feelslike_f": 44.3, + "windchill_c": 6.8, + "windchill_f": 44.3, + "heatindex_c": 9.7, + "heatindex_f": 49.5, + "dewpoint_c": 5.8, + "dewpoint_f": 42.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 25.7, + "gust_kph": 41.4, + "uv": 0 + }, + { + "time_epoch": 1732428000, + "time": "2024-11-24 07:00", + "temp_c": 9.7, + "temp_f": 49.4, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 13.6, + "wind_kph": 22, + "wind_degree": 138, + "wind_dir": "SE", + "pressure_mb": 1015, + "pressure_in": 29.97, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 78, + "cloud": 8, + "feelslike_c": 6.8, + "feelslike_f": 44.2, + "windchill_c": 6.8, + "windchill_f": 44.2, + "heatindex_c": 9.7, + "heatindex_f": 49.4, + "dewpoint_c": 6, + "dewpoint_f": 42.7, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 25.5, + "gust_kph": 41, + "uv": 0 + }, + { + "time_epoch": 1732431600, + "time": "2024-11-24 08:00", + "temp_c": 9.7, + "temp_f": 49.5, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 13.2, + "wind_kph": 21.2, + "wind_degree": 138, + "wind_dir": "SE", + "pressure_mb": 1015, + "pressure_in": 29.98, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 78, + "cloud": 7, + "feelslike_c": 6.9, + "feelslike_f": 44.4, + "windchill_c": 6.9, + "windchill_f": 44.4, + "heatindex_c": 9.7, + "heatindex_f": 49.5, + "dewpoint_c": 6.1, + "dewpoint_f": 43, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 24.8, + "gust_kph": 40, + "uv": 0 + }, + { + "time_epoch": 1732435200, + "time": "2024-11-24 09:00", + "temp_c": 10.1, + "temp_f": 50.1, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 13.6, + "wind_kph": 22, + "wind_degree": 137, + "wind_dir": "SE", + "pressure_mb": 1015, + "pressure_in": 29.98, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 78, + "cloud": 7, + "feelslike_c": 7.3, + "feelslike_f": 45.1, + "windchill_c": 7.3, + "windchill_f": 45.1, + "heatindex_c": 10.1, + "heatindex_f": 50.1, + "dewpoint_c": 6.4, + "dewpoint_f": 43.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 24.5, + "gust_kph": 39.5, + "uv": 0 + }, + { + "time_epoch": 1732438800, + "time": "2024-11-24 10:00", + "temp_c": 11.4, + "temp_f": 52.5, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 14.5, + "wind_kph": 23.4, + "wind_degree": 137, + "wind_dir": "SE", + "pressure_mb": 1015, + "pressure_in": 29.97, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 74, + "cloud": 5, + "feelslike_c": 8.9, + "feelslike_f": 47.9, + "windchill_c": 8.9, + "windchill_f": 47.9, + "heatindex_c": 11.4, + "heatindex_f": 52.5, + "dewpoint_c": 7, + "dewpoint_f": 44.6, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 23.2, + "gust_kph": 37.3, + "uv": 0.4 + }, + { + "time_epoch": 1732442400, + "time": "2024-11-24 11:00", + "temp_c": 13.2, + "temp_f": 55.7, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 15.7, + "wind_kph": 25.2, + "wind_degree": 137, + "wind_dir": "SE", + "pressure_mb": 1015, + "pressure_in": 29.96, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 70, + "cloud": 4, + "feelslike_c": 11, + "feelslike_f": 51.8, + "windchill_c": 11, + "windchill_f": 51.8, + "heatindex_c": 13.2, + "heatindex_f": 55.7, + "dewpoint_c": 7.8, + "dewpoint_f": 46, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 22.5, + "gust_kph": 36.3, + "uv": 0.8 + }, + { + "time_epoch": 1732446000, + "time": "2024-11-24 12:00", + "temp_c": 15, + "temp_f": 58.9, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 16.1, + "wind_kph": 25.9, + "wind_degree": 140, + "wind_dir": "SE", + "pressure_mb": 1014, + "pressure_in": 29.95, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 65, + "cloud": 4, + "feelslike_c": 13.3, + "feelslike_f": 55.9, + "windchill_c": 13.3, + "windchill_f": 55.9, + "heatindex_c": 15, + "heatindex_f": 58.9, + "dewpoint_c": 8.4, + "dewpoint_f": 47.1, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 21.9, + "gust_kph": 35.2, + "uv": 1.3 + }, + { + "time_epoch": 1732449600, + "time": "2024-11-24 13:00", + "temp_c": 16.4, + "temp_f": 61.6, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 16.1, + "wind_kph": 25.9, + "wind_degree": 142, + "wind_dir": "SE", + "pressure_mb": 1013, + "pressure_in": 29.91, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 60, + "cloud": 5, + "feelslike_c": 16.4, + "feelslike_f": 61.6, + "windchill_c": 16.4, + "windchill_f": 61.6, + "heatindex_c": 16.4, + "heatindex_f": 61.6, + "dewpoint_c": 8.8, + "dewpoint_f": 47.8, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 21.6, + "gust_kph": 34.7, + "uv": 1.4 + }, + { + "time_epoch": 1732453200, + "time": "2024-11-24 14:00", + "temp_c": 17.2, + "temp_f": 62.9, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 16.3, + "wind_kph": 26.3, + "wind_degree": 143, + "wind_dir": "SE", + "pressure_mb": 1012, + "pressure_in": 29.89, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 59, + "cloud": 4, + "feelslike_c": 17.2, + "feelslike_f": 62.9, + "windchill_c": 17.2, + "windchill_f": 62.9, + "heatindex_c": 17.2, + "heatindex_f": 62.9, + "dewpoint_c": 9, + "dewpoint_f": 48.2, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 22.9, + "gust_kph": 36.8, + "uv": 1.3 + }, + { + "time_epoch": 1732456800, + "time": "2024-11-24 15:00", + "temp_c": 17.2, + "temp_f": 63, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 16.3, + "wind_kph": 26.3, + "wind_degree": 145, + "wind_dir": "SE", + "pressure_mb": 1012, + "pressure_in": 29.87, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 59, + "cloud": 1, + "feelslike_c": 17.2, + "feelslike_f": 63, + "windchill_c": 17.2, + "windchill_f": 63, + "heatindex_c": 17.2, + "heatindex_f": 63, + "dewpoint_c": 9.2, + "dewpoint_f": 48.6, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 25, + "gust_kph": 40.2, + "uv": 0.8 + }, + { + "time_epoch": 1732460400, + "time": "2024-11-24 16:00", + "temp_c": 16.6, + "temp_f": 61.9, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 15.7, + "wind_kph": 25.2, + "wind_degree": 147, + "wind_dir": "SSE", + "pressure_mb": 1011, + "pressure_in": 29.86, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 61, + "cloud": 3, + "feelslike_c": 16.6, + "feelslike_f": 61.9, + "windchill_c": 16.6, + "windchill_f": 61.9, + "heatindex_c": 16.6, + "heatindex_f": 61.9, + "dewpoint_c": 9.2, + "dewpoint_f": 48.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 25.9, + "gust_kph": 41.7, + "uv": 0.4 + }, + { + "time_epoch": 1732464000, + "time": "2024-11-24 17:00", + "temp_c": 15.7, + "temp_f": 60.3, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 15.4, + "wind_kph": 24.8, + "wind_degree": 147, + "wind_dir": "SSE", + "pressure_mb": 1011, + "pressure_in": 29.86, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 65, + "cloud": 22, + "feelslike_c": 15.7, + "feelslike_f": 60.3, + "windchill_c": 15.7, + "windchill_f": 60.3, + "heatindex_c": 15.7, + "heatindex_f": 60.3, + "dewpoint_c": 9.1, + "dewpoint_f": 48.4, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 27, + "gust_kph": 43.5, + "uv": 0 + }, + { + "time_epoch": 1732467600, + "time": "2024-11-24 18:00", + "temp_c": 15.3, + "temp_f": 59.5, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 15.4, + "wind_kph": 24.8, + "wind_degree": 149, + "wind_dir": "SSE", + "pressure_mb": 1011, + "pressure_in": 29.86, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 66, + "cloud": 15, + "feelslike_c": 15.3, + "feelslike_f": 59.5, + "windchill_c": 15.3, + "windchill_f": 59.5, + "heatindex_c": 15.3, + "heatindex_f": 59.5, + "dewpoint_c": 9.1, + "dewpoint_f": 48.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 27.8, + "gust_kph": 44.8, + "uv": 0 + }, + { + "time_epoch": 1732471200, + "time": "2024-11-24 19:00", + "temp_c": 15.2, + "temp_f": 59.4, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 15.7, + "wind_kph": 25.2, + "wind_degree": 150, + "wind_dir": "SSE", + "pressure_mb": 1011, + "pressure_in": 29.86, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 67, + "cloud": 47, + "feelslike_c": 15.2, + "feelslike_f": 59.4, + "windchill_c": 15.2, + "windchill_f": 59.4, + "heatindex_c": 15.2, + "heatindex_f": 59.4, + "dewpoint_c": 9.1, + "dewpoint_f": 48.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 28.4, + "gust_kph": 45.7, + "uv": 0 + }, + { + "time_epoch": 1732474800, + "time": "2024-11-24 20:00", + "temp_c": 15.2, + "temp_f": 59.4, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 14.8, + "wind_kph": 23.8, + "wind_degree": 149, + "wind_dir": "SSE", + "pressure_mb": 1012, + "pressure_in": 29.87, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 67, + "cloud": 56, + "feelslike_c": 15.2, + "feelslike_f": 59.4, + "windchill_c": 15.2, + "windchill_f": 59.4, + "heatindex_c": 15.2, + "heatindex_f": 59.4, + "dewpoint_c": 9.2, + "dewpoint_f": 48.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 26.5, + "gust_kph": 42.6, + "uv": 0 + }, + { + "time_epoch": 1732478400, + "time": "2024-11-24 21:00", + "temp_c": 15.4, + "temp_f": 59.7, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 14.3, + "wind_kph": 23, + "wind_degree": 151, + "wind_dir": "SSE", + "pressure_mb": 1011, + "pressure_in": 29.87, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 67, + "cloud": 49, + "feelslike_c": 15.4, + "feelslike_f": 59.7, + "windchill_c": 15.4, + "windchill_f": 59.7, + "heatindex_c": 15.4, + "heatindex_f": 59.7, + "dewpoint_c": 9.3, + "dewpoint_f": 48.8, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 24.7, + "gust_kph": 39.8, + "uv": 0 + }, + { + "time_epoch": 1732482000, + "time": "2024-11-24 22:00", + "temp_c": 15.3, + "temp_f": 59.5, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 13, + "wind_kph": 20.9, + "wind_degree": 155, + "wind_dir": "SSE", + "pressure_mb": 1011, + "pressure_in": 29.87, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 67, + "cloud": 47, + "feelslike_c": 15.3, + "feelslike_f": 59.5, + "windchill_c": 15.3, + "windchill_f": 59.5, + "heatindex_c": 15.3, + "heatindex_f": 59.5, + "dewpoint_c": 9.2, + "dewpoint_f": 48.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 22.7, + "gust_kph": 36.5, + "uv": 0 + }, + { + "time_epoch": 1732485600, + "time": "2024-11-24 23:00", + "temp_c": 15.3, + "temp_f": 59.5, + "is_day": 0, + "condition": { + "text": "Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/119.png", + "code": 1006 + }, + "wind_mph": 13.4, + "wind_kph": 21.6, + "wind_degree": 151, + "wind_dir": "SSE", + "pressure_mb": 1011, + "pressure_in": 29.87, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 65, + "cloud": 66, + "feelslike_c": 15.3, + "feelslike_f": 59.5, + "windchill_c": 15.3, + "windchill_f": 59.5, + "heatindex_c": 15.3, + "heatindex_f": 59.5, + "dewpoint_c": 8.8, + "dewpoint_f": 47.9, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 23.8, + "gust_kph": 38.3, + "uv": 0 + } + ] + }, + { + "date": "2024-11-25", + "date_epoch": 1732492800, + "day": { + "maxtemp_c": 15.5, + "maxtemp_f": 59.8, + "mintemp_c": 9.5, + "mintemp_f": 49.1, + "avgtemp_c": 12.7, + "avgtemp_f": 54.8, + "maxwind_mph": 15, + "maxwind_kph": 24.1, + "totalprecip_mm": 7.56, + "totalprecip_in": 0.3, + "totalsnow_cm": 0, + "avgvis_km": 8.6, + "avgvis_miles": 5, + "avghumidity": 80, + "daily_will_it_rain": 1, + "daily_chance_of_rain": 89, + "daily_will_it_snow": 0, + "daily_chance_of_snow": 0, + "condition": { + "text": "Moderate rain", + "icon": "//cdn.weatherapi.com/weather/64x64/day/302.png", + "code": 1189 + }, + "uv": 0 + }, + "astro": { + "sunrise": "08:13 AM", + "sunset": "05:26 PM", + "moonrise": "02:17 AM", + "moonset": "02:46 PM", + "moon_phase": "Waning Crescent", + "moon_illumination": 32, + "is_moon_up": 0, + "is_sun_up": 0 + }, + "hour": [ + { + "time_epoch": 1732489200, + "time": "2024-11-25 00:00", + "temp_c": 15.5, + "temp_f": 59.8, + "is_day": 0, + "condition": { + "text": "Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/119.png", + "code": 1006 + }, + "wind_mph": 13.4, + "wind_kph": 21.6, + "wind_degree": 153, + "wind_dir": "SSE", + "pressure_mb": 1011, + "pressure_in": 29.85, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 63, + "cloud": 66, + "feelslike_c": 15.5, + "feelslike_f": 59.8, + "windchill_c": 15.5, + "windchill_f": 59.8, + "heatindex_c": 15.5, + "heatindex_f": 59.8, + "dewpoint_c": 8.5, + "dewpoint_f": 47.2, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 24.7, + "gust_kph": 39.7, + "uv": 0 + }, + { + "time_epoch": 1732492800, + "time": "2024-11-25 01:00", + "temp_c": 15.3, + "temp_f": 59.5, + "is_day": 0, + "condition": { + "text": "Patchy rain nearby", + "icon": "//cdn.weatherapi.com/weather/64x64/night/176.png", + "code": 1063 + }, + "wind_mph": 15, + "wind_kph": 24.1, + "wind_degree": 150, + "wind_dir": "SSE", + "pressure_mb": 1011, + "pressure_in": 29.86, + "precip_mm": 0.04, + "precip_in": 0, + "snow_cm": 0, + "humidity": 64, + "cloud": 56, + "feelslike_c": 15.3, + "feelslike_f": 59.5, + "windchill_c": 15.3, + "windchill_f": 59.5, + "heatindex_c": 15.3, + "heatindex_f": 59.5, + "dewpoint_c": 8.5, + "dewpoint_f": 47.3, + "will_it_rain": 0, + "chance_of_rain": 69, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 26.1, + "gust_kph": 42.1, + "uv": 0 + }, + { + "time_epoch": 1732496400, + "time": "2024-11-25 02:00", + "temp_c": 15.3, + "temp_f": 59.5, + "is_day": 0, + "condition": { + "text": "Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/119.png", + "code": 1006 + }, + "wind_mph": 14.5, + "wind_kph": 23.4, + "wind_degree": 148, + "wind_dir": "SSE", + "pressure_mb": 1010, + "pressure_in": 29.82, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 62, + "cloud": 67, + "feelslike_c": 15.3, + "feelslike_f": 59.5, + "windchill_c": 15.3, + "windchill_f": 59.5, + "heatindex_c": 15.3, + "heatindex_f": 59.5, + "dewpoint_c": 8.1, + "dewpoint_f": 46.6, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 26.6, + "gust_kph": 42.7, + "uv": 0 + }, + { + "time_epoch": 1732500000, + "time": "2024-11-25 03:00", + "temp_c": 15.5, + "temp_f": 60, + "is_day": 0, + "condition": { + "text": "Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/119.png", + "code": 1006 + }, + "wind_mph": 15, + "wind_kph": 24.1, + "wind_degree": 154, + "wind_dir": "SSE", + "pressure_mb": 1010, + "pressure_in": 29.81, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 59, + "cloud": 82, + "feelslike_c": 15.5, + "feelslike_f": 60, + "windchill_c": 15.5, + "windchill_f": 60, + "heatindex_c": 15.5, + "heatindex_f": 60, + "dewpoint_c": 7.5, + "dewpoint_f": 45.4, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 26.2, + "gust_kph": 42.1, + "uv": 0 + }, + { + "time_epoch": 1732503600, + "time": "2024-11-25 04:00", + "temp_c": 16.4, + "temp_f": 61.6, + "is_day": 0, + "condition": { + "text": "Patchy rain nearby", + "icon": "//cdn.weatherapi.com/weather/64x64/night/176.png", + "code": 1063 + }, + "wind_mph": 15, + "wind_kph": 24.1, + "wind_degree": 160, + "wind_dir": "SSE", + "pressure_mb": 1009, + "pressure_in": 29.79, + "precip_mm": 0.01, + "precip_in": 0, + "snow_cm": 0, + "humidity": 54, + "cloud": 85, + "feelslike_c": 16.4, + "feelslike_f": 61.6, + "windchill_c": 16.4, + "windchill_f": 61.6, + "heatindex_c": 16.4, + "heatindex_f": 61.6, + "dewpoint_c": 7, + "dewpoint_f": 44.7, + "will_it_rain": 0, + "chance_of_rain": 70, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 26.5, + "gust_kph": 42.7, + "uv": 0 + }, + { + "time_epoch": 1732507200, + "time": "2024-11-25 05:00", + "temp_c": 15.5, + "temp_f": 59.9, + "is_day": 0, + "condition": { + "text": "Patchy rain nearby", + "icon": "//cdn.weatherapi.com/weather/64x64/night/176.png", + "code": 1063 + }, + "wind_mph": 12.5, + "wind_kph": 20.2, + "wind_degree": 154, + "wind_dir": "SSE", + "pressure_mb": 1009, + "pressure_in": 29.8, + "precip_mm": 0.01, + "precip_in": 0, + "snow_cm": 0, + "humidity": 59, + "cloud": 86, + "feelslike_c": 15.5, + "feelslike_f": 59.9, + "windchill_c": 15.5, + "windchill_f": 59.9, + "heatindex_c": 15.5, + "heatindex_f": 59.9, + "dewpoint_c": 7.5, + "dewpoint_f": 45.5, + "will_it_rain": 0, + "chance_of_rain": 67, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 21.5, + "gust_kph": 34.5, + "uv": 0 + }, + { + "time_epoch": 1732510800, + "time": "2024-11-25 06:00", + "temp_c": 15.5, + "temp_f": 59.8, + "is_day": 0, + "condition": { + "text": "Patchy rain nearby", + "icon": "//cdn.weatherapi.com/weather/64x64/night/176.png", + "code": 1063 + }, + "wind_mph": 8.5, + "wind_kph": 13.7, + "wind_degree": 181, + "wind_dir": "S", + "pressure_mb": 1010, + "pressure_in": 29.83, + "precip_mm": 0.04, + "precip_in": 0, + "snow_cm": 0, + "humidity": 60, + "cloud": 85, + "feelslike_c": 15.5, + "feelslike_f": 59.8, + "windchill_c": 15.5, + "windchill_f": 59.8, + "heatindex_c": 15.5, + "heatindex_f": 59.8, + "dewpoint_c": 7.8, + "dewpoint_f": 46.1, + "will_it_rain": 0, + "chance_of_rain": 64, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 15.4, + "gust_kph": 24.8, + "uv": 0 + }, + { + "time_epoch": 1732514400, + "time": "2024-11-25 07:00", + "temp_c": 15.1, + "temp_f": 59.1, + "is_day": 0, + "condition": { + "text": "Patchy light drizzle", + "icon": "//cdn.weatherapi.com/weather/64x64/night/263.png", + "code": 1150 + }, + "wind_mph": 7.2, + "wind_kph": 11.5, + "wind_degree": 238, + "wind_dir": "WSW", + "pressure_mb": 1011, + "pressure_in": 29.84, + "precip_mm": 0.32, + "precip_in": 0.01, + "snow_cm": 0, + "humidity": 73, + "cloud": 71, + "feelslike_c": 15.1, + "feelslike_f": 59.1, + "windchill_c": 15.1, + "windchill_f": 59.1, + "heatindex_c": 15.1, + "heatindex_f": 59.1, + "dewpoint_c": 10.2, + "dewpoint_f": 50.3, + "will_it_rain": 1, + "chance_of_rain": 100, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 5, + "vis_miles": 3, + "gust_mph": 12.5, + "gust_kph": 20.1, + "uv": 0 + }, + { + "time_epoch": 1732518000, + "time": "2024-11-25 08:00", + "temp_c": 13.6, + "temp_f": 56.5, + "is_day": 0, + "condition": { + "text": "Light rain", + "icon": "//cdn.weatherapi.com/weather/64x64/night/296.png", + "code": 1183 + }, + "wind_mph": 8.5, + "wind_kph": 13.7, + "wind_degree": 301, + "wind_dir": "WNW", + "pressure_mb": 1012, + "pressure_in": 29.88, + "precip_mm": 0.84, + "precip_in": 0.03, + "snow_cm": 0, + "humidity": 91, + "cloud": 100, + "feelslike_c": 12.5, + "feelslike_f": 54.5, + "windchill_c": 12.5, + "windchill_f": 54.5, + "heatindex_c": 13.6, + "heatindex_f": 56.5, + "dewpoint_c": 12.2, + "dewpoint_f": 53.9, + "will_it_rain": 1, + "chance_of_rain": 100, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 9, + "vis_miles": 5, + "gust_mph": 12.6, + "gust_kph": 20.3, + "uv": 0 + }, + { + "time_epoch": 1732521600, + "time": "2024-11-25 09:00", + "temp_c": 12.4, + "temp_f": 54.3, + "is_day": 1, + "condition": { + "text": "Light drizzle", + "icon": "//cdn.weatherapi.com/weather/64x64/day/266.png", + "code": 1153 + }, + "wind_mph": 6.7, + "wind_kph": 10.8, + "wind_degree": 316, + "wind_dir": "NW", + "pressure_mb": 1013, + "pressure_in": 29.92, + "precip_mm": 0.73, + "precip_in": 0.03, + "snow_cm": 0, + "humidity": 92, + "cloud": 100, + "feelslike_c": 11.4, + "feelslike_f": 52.5, + "windchill_c": 11.4, + "windchill_f": 52.5, + "heatindex_c": 12.4, + "heatindex_f": 54.3, + "dewpoint_c": 11.1, + "dewpoint_f": 52.1, + "will_it_rain": 1, + "chance_of_rain": 100, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 2, + "vis_miles": 1, + "gust_mph": 9.8, + "gust_kph": 15.7, + "uv": 0 + }, + { + "time_epoch": 1732525200, + "time": "2024-11-25 10:00", + "temp_c": 12.4, + "temp_f": 54.4, + "is_day": 1, + "condition": { + "text": "Light rain", + "icon": "//cdn.weatherapi.com/weather/64x64/day/296.png", + "code": 1183 + }, + "wind_mph": 5.1, + "wind_kph": 8.3, + "wind_degree": 290, + "wind_dir": "WNW", + "pressure_mb": 1014, + "pressure_in": 29.95, + "precip_mm": 0.89, + "precip_in": 0.04, + "snow_cm": 0, + "humidity": 92, + "cloud": 100, + "feelslike_c": 11.8, + "feelslike_f": 53.3, + "windchill_c": 11.8, + "windchill_f": 53.3, + "heatindex_c": 12.5, + "heatindex_f": 54.4, + "dewpoint_c": 11.2, + "dewpoint_f": 52.1, + "will_it_rain": 1, + "chance_of_rain": 100, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 9, + "vis_miles": 5, + "gust_mph": 7.5, + "gust_kph": 12.1, + "uv": 0 + }, + { + "time_epoch": 1732528800, + "time": "2024-11-25 11:00", + "temp_c": 12.5, + "temp_f": 54.4, + "is_day": 1, + "condition": { + "text": "Light rain", + "icon": "//cdn.weatherapi.com/weather/64x64/day/296.png", + "code": 1183 + }, + "wind_mph": 6, + "wind_kph": 9.7, + "wind_degree": 311, + "wind_dir": "NW", + "pressure_mb": 1015, + "pressure_in": 29.96, + "precip_mm": 1.36, + "precip_in": 0.05, + "snow_cm": 0, + "humidity": 93, + "cloud": 100, + "feelslike_c": 11.6, + "feelslike_f": 52.9, + "windchill_c": 11.6, + "windchill_f": 52.9, + "heatindex_c": 12.5, + "heatindex_f": 54.4, + "dewpoint_c": 11.3, + "dewpoint_f": 52.3, + "will_it_rain": 1, + "chance_of_rain": 100, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 9, + "vis_miles": 5, + "gust_mph": 8.6, + "gust_kph": 13.8, + "uv": 0.1 + }, + { + "time_epoch": 1732532400, + "time": "2024-11-25 12:00", + "temp_c": 11.9, + "temp_f": 53.4, + "is_day": 1, + "condition": { + "text": "Light rain", + "icon": "//cdn.weatherapi.com/weather/64x64/day/296.png", + "code": 1183 + }, + "wind_mph": 8.3, + "wind_kph": 13.3, + "wind_degree": 308, + "wind_dir": "NW", + "pressure_mb": 1016, + "pressure_in": 29.99, + "precip_mm": 2.14, + "precip_in": 0.08, + "snow_cm": 0, + "humidity": 93, + "cloud": 100, + "feelslike_c": 10.4, + "feelslike_f": 50.8, + "windchill_c": 10.4, + "windchill_f": 50.8, + "heatindex_c": 11.9, + "heatindex_f": 53.4, + "dewpoint_c": 10.8, + "dewpoint_f": 51.4, + "will_it_rain": 1, + "chance_of_rain": 100, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 9, + "vis_miles": 5, + "gust_mph": 11.6, + "gust_kph": 18.6, + "uv": 0.1 + }, + { + "time_epoch": 1732536000, + "time": "2024-11-25 13:00", + "temp_c": 11.3, + "temp_f": 52.4, + "is_day": 1, + "condition": { + "text": "Light rain shower", + "icon": "//cdn.weatherapi.com/weather/64x64/day/353.png", + "code": 1240 + }, + "wind_mph": 6.7, + "wind_kph": 10.8, + "wind_degree": 302, + "wind_dir": "WNW", + "pressure_mb": 1016, + "pressure_in": 30.01, + "precip_mm": 0.24, + "precip_in": 0.01, + "snow_cm": 0, + "humidity": 89, + "cloud": 100, + "feelslike_c": 10.1, + "feelslike_f": 50.2, + "windchill_c": 10.1, + "windchill_f": 50.2, + "heatindex_c": 11.3, + "heatindex_f": 52.4, + "dewpoint_c": 9.6, + "dewpoint_f": 49.3, + "will_it_rain": 1, + "chance_of_rain": 100, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 9.5, + "gust_kph": 15.2, + "uv": 0.1 + }, + { + "time_epoch": 1732539600, + "time": "2024-11-25 14:00", + "temp_c": 11.3, + "temp_f": 52.4, + "is_day": 1, + "condition": { + "text": "Light drizzle", + "icon": "//cdn.weatherapi.com/weather/64x64/day/266.png", + "code": 1153 + }, + "wind_mph": 5.6, + "wind_kph": 9, + "wind_degree": 283, + "wind_dir": "WNW", + "pressure_mb": 1017, + "pressure_in": 30.03, + "precip_mm": 0.37, + "precip_in": 0.01, + "snow_cm": 0, + "humidity": 89, + "cloud": 100, + "feelslike_c": 10.4, + "feelslike_f": 50.7, + "windchill_c": 10.4, + "windchill_f": 50.7, + "heatindex_c": 11.3, + "heatindex_f": 52.4, + "dewpoint_c": 9.5, + "dewpoint_f": 49.2, + "will_it_rain": 1, + "chance_of_rain": 100, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 2, + "vis_miles": 1, + "gust_mph": 8, + "gust_kph": 12.9, + "uv": 0.1 + }, + { + "time_epoch": 1732543200, + "time": "2024-11-25 15:00", + "temp_c": 11.3, + "temp_f": 52.3, + "is_day": 1, + "condition": { + "text": "Light drizzle", + "icon": "//cdn.weatherapi.com/weather/64x64/day/266.png", + "code": 1153 + }, + "wind_mph": 7.4, + "wind_kph": 11.9, + "wind_degree": 269, + "wind_dir": "W", + "pressure_mb": 1018, + "pressure_in": 30.06, + "precip_mm": 0.45, + "precip_in": 0.02, + "snow_cm": 0, + "humidity": 88, + "cloud": 100, + "feelslike_c": 9.9, + "feelslike_f": 49.7, + "windchill_c": 9.9, + "windchill_f": 49.7, + "heatindex_c": 11.3, + "heatindex_f": 52.3, + "dewpoint_c": 9.4, + "dewpoint_f": 49, + "will_it_rain": 1, + "chance_of_rain": 100, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 2, + "vis_miles": 1, + "gust_mph": 11, + "gust_kph": 17.6, + "uv": 0.1 + }, + { + "time_epoch": 1732546800, + "time": "2024-11-25 16:00", + "temp_c": 11.4, + "temp_f": 52.5, + "is_day": 1, + "condition": { + "text": "Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/day/119.png", + "code": 1006 + }, + "wind_mph": 3.6, + "wind_kph": 5.8, + "wind_degree": 244, + "wind_dir": "WSW", + "pressure_mb": 1018, + "pressure_in": 30.07, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 82, + "cloud": 65, + "feelslike_c": 11.2, + "feelslike_f": 52.1, + "windchill_c": 11.2, + "windchill_f": 52.1, + "heatindex_c": 11.4, + "heatindex_f": 52.5, + "dewpoint_c": 8.4, + "dewpoint_f": 47.1, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 5.6, + "gust_kph": 9, + "uv": 0.1 + }, + { + "time_epoch": 1732550400, + "time": "2024-11-25 17:00", + "temp_c": 11.4, + "temp_f": 52.4, + "is_day": 1, + "condition": { + "text": "Patchy rain nearby", + "icon": "//cdn.weatherapi.com/weather/64x64/day/176.png", + "code": 1063 + }, + "wind_mph": 5.1, + "wind_kph": 8.3, + "wind_degree": 191, + "wind_dir": "SSW", + "pressure_mb": 1019, + "pressure_in": 30.09, + "precip_mm": 0.02, + "precip_in": 0, + "snow_cm": 0, + "humidity": 82, + "cloud": 58, + "feelslike_c": 10.5, + "feelslike_f": 51, + "windchill_c": 10.5, + "windchill_f": 51, + "heatindex_c": 11.4, + "heatindex_f": 52.4, + "dewpoint_c": 8.4, + "dewpoint_f": 47, + "will_it_rain": 1, + "chance_of_rain": 89, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 8.4, + "gust_kph": 13.5, + "uv": 0 + }, + { + "time_epoch": 1732554000, + "time": "2024-11-25 18:00", + "temp_c": 11.1, + "temp_f": 51.9, + "is_day": 0, + "condition": { + "text": "Patchy rain nearby", + "icon": "//cdn.weatherapi.com/weather/64x64/night/176.png", + "code": 1063 + }, + "wind_mph": 5.6, + "wind_kph": 9, + "wind_degree": 207, + "wind_dir": "SSW", + "pressure_mb": 1020, + "pressure_in": 30.11, + "precip_mm": 0.02, + "precip_in": 0, + "snow_cm": 0, + "humidity": 85, + "cloud": 89, + "feelslike_c": 10.1, + "feelslike_f": 50.1, + "windchill_c": 10.1, + "windchill_f": 50.1, + "heatindex_c": 11.1, + "heatindex_f": 51.9, + "dewpoint_c": 8.6, + "dewpoint_f": 47.4, + "will_it_rain": 1, + "chance_of_rain": 76, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 9.4, + "gust_kph": 15.2, + "uv": 0 + }, + { + "time_epoch": 1732557600, + "time": "2024-11-25 19:00", + "temp_c": 10.6, + "temp_f": 51.1, + "is_day": 0, + "condition": { + "text": "Patchy rain nearby", + "icon": "//cdn.weatherapi.com/weather/64x64/night/176.png", + "code": 1063 + }, + "wind_mph": 6, + "wind_kph": 9.7, + "wind_degree": 198, + "wind_dir": "SSW", + "pressure_mb": 1020, + "pressure_in": 30.13, + "precip_mm": 0.04, + "precip_in": 0, + "snow_cm": 0, + "humidity": 88, + "cloud": 88, + "feelslike_c": 9.4, + "feelslike_f": 48.9, + "windchill_c": 9.4, + "windchill_f": 48.9, + "heatindex_c": 10.6, + "heatindex_f": 51.1, + "dewpoint_c": 8.8, + "dewpoint_f": 47.8, + "will_it_rain": 1, + "chance_of_rain": 76, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 10.6, + "gust_kph": 17.1, + "uv": 0 + }, + { + "time_epoch": 1732561200, + "time": "2024-11-25 20:00", + "temp_c": 10.1, + "temp_f": 50.2, + "is_day": 0, + "condition": { + "text": "Patchy rain nearby", + "icon": "//cdn.weatherapi.com/weather/64x64/night/176.png", + "code": 1063 + }, + "wind_mph": 5.6, + "wind_kph": 9, + "wind_degree": 198, + "wind_dir": "SSW", + "pressure_mb": 1021, + "pressure_in": 30.16, + "precip_mm": 0.04, + "precip_in": 0, + "snow_cm": 0, + "humidity": 92, + "cloud": 87, + "feelslike_c": 8.9, + "feelslike_f": 48.1, + "windchill_c": 8.9, + "windchill_f": 48.1, + "heatindex_c": 10.1, + "heatindex_f": 50.2, + "dewpoint_c": 8.8, + "dewpoint_f": 47.9, + "will_it_rain": 1, + "chance_of_rain": 75, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 10.5, + "gust_kph": 16.9, + "uv": 0 + }, + { + "time_epoch": 1732564800, + "time": "2024-11-25 21:00", + "temp_c": 9.8, + "temp_f": 49.6, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 5.1, + "wind_kph": 8.3, + "wind_degree": 198, + "wind_dir": "SSW", + "pressure_mb": 1022, + "pressure_in": 30.17, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 93, + "cloud": 18, + "feelslike_c": 8.7, + "feelslike_f": 47.7, + "windchill_c": 8.7, + "windchill_f": 47.7, + "heatindex_c": 9.8, + "heatindex_f": 49.6, + "dewpoint_c": 8.7, + "dewpoint_f": 47.7, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 10.2, + "gust_kph": 16.3, + "uv": 0 + }, + { + "time_epoch": 1732568400, + "time": "2024-11-25 22:00", + "temp_c": 9.6, + "temp_f": 49.4, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 5.1, + "wind_kph": 8.3, + "wind_degree": 198, + "wind_dir": "SSW", + "pressure_mb": 1022, + "pressure_in": 30.19, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 93, + "cloud": 24, + "feelslike_c": 8.5, + "feelslike_f": 47.4, + "windchill_c": 8.5, + "windchill_f": 47.4, + "heatindex_c": 9.6, + "heatindex_f": 49.4, + "dewpoint_c": 8.6, + "dewpoint_f": 47.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 10.2, + "gust_kph": 16.5, + "uv": 0 + }, + { + "time_epoch": 1732572000, + "time": "2024-11-25 23:00", + "temp_c": 9.5, + "temp_f": 49.1, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 4.9, + "wind_kph": 7.9, + "wind_degree": 198, + "wind_dir": "SSW", + "pressure_mb": 1023, + "pressure_in": 30.2, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 93, + "cloud": 26, + "feelslike_c": 8.4, + "feelslike_f": 47.2, + "windchill_c": 8.4, + "windchill_f": 47.2, + "heatindex_c": 9.5, + "heatindex_f": 49.1, + "dewpoint_c": 8.5, + "dewpoint_f": 47.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 9.8, + "gust_kph": 15.8, + "uv": 0 + } + ] + } + ] + } +} \ No newline at end of file diff --git a/src/test/resources/WeatherAPI/Bordeaux-2-partial-sunny.json b/src/test/resources/WeatherAPI/Bordeaux-2-partial-sunny.json new file mode 100644 index 0000000..6e10f39 --- /dev/null +++ b/src/test/resources/WeatherAPI/Bordeaux-2-partial-sunny.json @@ -0,0 +1,3057 @@ +{ + "location": { + "name": "Bordeaux", + "region": "Aquitaine", + "country": "France", + "lat": 44.8333, + "lon": -0.5667, + "tz_id": "Europe/Paris", + "localtime_epoch": 1732356248, + "localtime": "2024-11-23 11:04" + }, + "current": { + "last_updated_epoch": 1732356000, + "last_updated": "2024-11-23 11:00", + "temp_c": 7.4, + "temp_f": 45.3, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 11.4, + "wind_kph": 18.4, + "wind_degree": 148, + "wind_dir": "SSE", + "pressure_mb": 1018, + "pressure_in": 30.06, + "precip_mm": 0, + "precip_in": 0, + "humidity": 76, + "cloud": 0, + "feelslike_c": 4.3, + "feelslike_f": 39.7, + "windchill_c": 6, + "windchill_f": 42.7, + "heatindex_c": 8.7, + "heatindex_f": 47.7, + "dewpoint_c": 1.3, + "dewpoint_f": 34.3, + "vis_km": 10, + "vis_miles": 6, + "uv": 0.9, + "gust_mph": 15.9, + "gust_kph": 25.5 + }, + "forecast": { + "forecastday": [ + { + "date": "2024-11-23", + "date_epoch": 1732320000, + "day": { + "maxtemp_c": 13.1, + "maxtemp_f": 55.7, + "mintemp_c": 4, + "mintemp_f": 39.3, + "avgtemp_c": 8.1, + "avgtemp_f": 46.6, + "maxwind_mph": 13, + "maxwind_kph": 20.9, + "totalprecip_mm": 0, + "totalprecip_in": 0, + "totalsnow_cm": 0, + "avgvis_km": 10, + "avgvis_miles": 6, + "avghumidity": 69, + "daily_will_it_rain": 0, + "daily_chance_of_rain": 0, + "daily_will_it_snow": 0, + "daily_chance_of_snow": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/day/116.png", + "code": 1003 + }, + "uv": 0.3 + }, + "astro": { + "sunrise": "08:10 AM", + "sunset": "05:27 PM", + "moonrise": "12:08 AM", + "moonset": "02:13 PM", + "moon_phase": "Last Quarter", + "moon_illumination": 51, + "is_moon_up": 0, + "is_sun_up": 0 + }, + "hour": [ + { + "time_epoch": 1732316400, + "time": "2024-11-23 00:00", + "temp_c": 5.6, + "temp_f": 42, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 6.7, + "wind_kph": 10.8, + "wind_degree": 150, + "wind_dir": "SSE", + "pressure_mb": 1022, + "pressure_in": 30.19, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 81, + "cloud": 17, + "feelslike_c": 3.2, + "feelslike_f": 37.7, + "windchill_c": 3.2, + "windchill_f": 37.7, + "heatindex_c": 5.6, + "heatindex_f": 42, + "dewpoint_c": 2.6, + "dewpoint_f": 36.6, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 13.7, + "gust_kph": 22, + "uv": 0 + }, + { + "time_epoch": 1732320000, + "time": "2024-11-23 01:00", + "temp_c": 5.8, + "temp_f": 42.4, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 6.9, + "wind_kph": 11.2, + "wind_degree": 141, + "wind_dir": "SE", + "pressure_mb": 1022, + "pressure_in": 30.17, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 80, + "cloud": 47, + "feelslike_c": 3.3, + "feelslike_f": 38, + "windchill_c": 3.3, + "windchill_f": 38, + "heatindex_c": 5.8, + "heatindex_f": 42.4, + "dewpoint_c": 2.5, + "dewpoint_f": 36.6, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 13.4, + "gust_kph": 21.6, + "uv": 0 + }, + { + "time_epoch": 1732323600, + "time": "2024-11-23 02:00", + "temp_c": 5.6, + "temp_f": 42.1, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 8.3, + "wind_kph": 13.3, + "wind_degree": 137, + "wind_dir": "SE", + "pressure_mb": 1021, + "pressure_in": 30.14, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 79, + "cloud": 56, + "feelslike_c": 2.8, + "feelslike_f": 37, + "windchill_c": 2.8, + "windchill_f": 37, + "heatindex_c": 5.6, + "heatindex_f": 42.1, + "dewpoint_c": 2.3, + "dewpoint_f": 36.1, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 15.3, + "gust_kph": 24.7, + "uv": 0 + }, + { + "time_epoch": 1732327200, + "time": "2024-11-23 03:00", + "temp_c": 5.1, + "temp_f": 41.3, + "is_day": 0, + "condition": { + "text": "Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/119.png", + "code": 1006 + }, + "wind_mph": 9.8, + "wind_kph": 15.8, + "wind_degree": 133, + "wind_dir": "SE", + "pressure_mb": 1020, + "pressure_in": 30.13, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 79, + "cloud": 86, + "feelslike_c": 1.8, + "feelslike_f": 35.3, + "windchill_c": 1.8, + "windchill_f": 35.3, + "heatindex_c": 5.2, + "heatindex_f": 41.3, + "dewpoint_c": 1.9, + "dewpoint_f": 35.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 17.6, + "gust_kph": 28.4, + "uv": 0 + }, + { + "time_epoch": 1732330800, + "time": "2024-11-23 04:00", + "temp_c": 4.8, + "temp_f": 40.7, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 10.5, + "wind_kph": 16.9, + "wind_degree": 130, + "wind_dir": "SE", + "pressure_mb": 1020, + "pressure_in": 30.12, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 79, + "cloud": 44, + "feelslike_c": 1.3, + "feelslike_f": 34.3, + "windchill_c": 1.3, + "windchill_f": 34.3, + "heatindex_c": 4.9, + "heatindex_f": 40.7, + "dewpoint_c": 1.5, + "dewpoint_f": 34.7, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 18.1, + "gust_kph": 29.1, + "uv": 0 + }, + { + "time_epoch": 1732334400, + "time": "2024-11-23 05:00", + "temp_c": 4.3, + "temp_f": 39.7, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 11, + "wind_kph": 17.6, + "wind_degree": 130, + "wind_dir": "SE", + "pressure_mb": 1019, + "pressure_in": 30.1, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 80, + "cloud": 46, + "feelslike_c": 0.5, + "feelslike_f": 32.8, + "windchill_c": 0.5, + "windchill_f": 32.8, + "heatindex_c": 4.3, + "heatindex_f": 39.7, + "dewpoint_c": 1.2, + "dewpoint_f": 34.1, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 19.1, + "gust_kph": 30.8, + "uv": 0 + }, + { + "time_epoch": 1732338000, + "time": "2024-11-23 06:00", + "temp_c": 4, + "temp_f": 39.3, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 11.2, + "wind_kph": 18, + "wind_degree": 138, + "wind_dir": "SE", + "pressure_mb": 1019, + "pressure_in": 30.08, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 80, + "cloud": 39, + "feelslike_c": 0.1, + "feelslike_f": 32.2, + "windchill_c": 0.1, + "windchill_f": 32.2, + "heatindex_c": 4, + "heatindex_f": 39.3, + "dewpoint_c": 0.9, + "dewpoint_f": 33.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 19.8, + "gust_kph": 31.8, + "uv": 0 + }, + { + "time_epoch": 1732341600, + "time": "2024-11-23 07:00", + "temp_c": 4, + "temp_f": 39.3, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 11.2, + "wind_kph": 18, + "wind_degree": 138, + "wind_dir": "SE", + "pressure_mb": 1019, + "pressure_in": 30.08, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 78, + "cloud": 26, + "feelslike_c": 0.1, + "feelslike_f": 32.2, + "windchill_c": 0.1, + "windchill_f": 32.2, + "heatindex_c": 4, + "heatindex_f": 39.3, + "dewpoint_c": 0.6, + "dewpoint_f": 33, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 19.9, + "gust_kph": 32, + "uv": 0 + }, + { + "time_epoch": 1732345200, + "time": "2024-11-23 08:00", + "temp_c": 4.1, + "temp_f": 39.4, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 10.5, + "wind_kph": 16.9, + "wind_degree": 140, + "wind_dir": "SE", + "pressure_mb": 1019, + "pressure_in": 30.09, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 75, + "cloud": 7, + "feelslike_c": 0.4, + "feelslike_f": 32.6, + "windchill_c": 0.4, + "windchill_f": 32.6, + "heatindex_c": 4.1, + "heatindex_f": 39.4, + "dewpoint_c": 0.1, + "dewpoint_f": 32.1, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 18.8, + "gust_kph": 30.2, + "uv": 0 + }, + { + "time_epoch": 1732348800, + "time": "2024-11-23 09:00", + "temp_c": 4.6, + "temp_f": 40.3, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 10.7, + "wind_kph": 17.3, + "wind_degree": 143, + "wind_dir": "SE", + "pressure_mb": 1019, + "pressure_in": 30.09, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 71, + "cloud": 13, + "feelslike_c": 1, + "feelslike_f": 33.7, + "windchill_c": 1, + "windchill_f": 33.7, + "heatindex_c": 4.6, + "heatindex_f": 40.4, + "dewpoint_c": -0.2, + "dewpoint_f": 31.7, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 19, + "gust_kph": 30.5, + "uv": 0 + }, + { + "time_epoch": 1732352400, + "time": "2024-11-23 10:00", + "temp_c": 6.6, + "temp_f": 43.9, + "is_day": 1, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/day/116.png", + "code": 1003 + }, + "wind_mph": 11.2, + "wind_kph": 18, + "wind_degree": 146, + "wind_dir": "SSE", + "pressure_mb": 1019, + "pressure_in": 30.08, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 63, + "cloud": 49, + "feelslike_c": 3.4, + "feelslike_f": 38, + "windchill_c": 3.4, + "windchill_f": 38, + "heatindex_c": 6.6, + "heatindex_f": 43.9, + "dewpoint_c": 0.2, + "dewpoint_f": 32.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 17.5, + "gust_kph": 28.2, + "uv": 0.4 + }, + { + "time_epoch": 1732356000, + "time": "2024-11-23 11:00", + "temp_c": 7.4, + "temp_f": 45.3, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 11.4, + "wind_kph": 18.4, + "wind_degree": 148, + "wind_dir": "SSE", + "pressure_mb": 1018, + "pressure_in": 30.06, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 76, + "cloud": 0, + "feelslike_c": 6, + "feelslike_f": 42.7, + "windchill_c": 6, + "windchill_f": 42.7, + "heatindex_c": 8.7, + "heatindex_f": 47.7, + "dewpoint_c": 1.3, + "dewpoint_f": 34.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 15.9, + "gust_kph": 25.5, + "uv": 0.9 + }, + { + "time_epoch": 1732359600, + "time": "2024-11-23 12:00", + "temp_c": 10.4, + "temp_f": 50.8, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 11, + "wind_kph": 17.6, + "wind_degree": 149, + "wind_dir": "SSE", + "pressure_mb": 1018, + "pressure_in": 30.07, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 56, + "cloud": 21, + "feelslike_c": 8.2, + "feelslike_f": 46.7, + "windchill_c": 8.2, + "windchill_f": 46.7, + "heatindex_c": 10.5, + "heatindex_f": 50.8, + "dewpoint_c": 2.2, + "dewpoint_f": 35.9, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 14.7, + "gust_kph": 23.6, + "uv": 1.3 + }, + { + "time_epoch": 1732363200, + "time": "2024-11-23 13:00", + "temp_c": 12.3, + "temp_f": 54.2, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 11.4, + "wind_kph": 18.4, + "wind_degree": 150, + "wind_dir": "SSE", + "pressure_mb": 1017, + "pressure_in": 30.04, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 52, + "cloud": 22, + "feelslike_c": 10.5, + "feelslike_f": 50.8, + "windchill_c": 10.5, + "windchill_f": 50.8, + "heatindex_c": 12.3, + "heatindex_f": 54.2, + "dewpoint_c": 2.8, + "dewpoint_f": 37, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 14.9, + "gust_kph": 23.9, + "uv": 1.6 + }, + { + "time_epoch": 1732366800, + "time": "2024-11-23 14:00", + "temp_c": 13, + "temp_f": 55.4, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 10.3, + "wind_kph": 16.6, + "wind_degree": 149, + "wind_dir": "SSE", + "pressure_mb": 1017, + "pressure_in": 30.03, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 54, + "cloud": 12, + "feelslike_c": 11.5, + "feelslike_f": 52.7, + "windchill_c": 11.5, + "windchill_f": 52.7, + "heatindex_c": 13, + "heatindex_f": 55.4, + "dewpoint_c": 3.8, + "dewpoint_f": 38.9, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 14, + "gust_kph": 22.6, + "uv": 1.4 + }, + { + "time_epoch": 1732370400, + "time": "2024-11-23 15:00", + "temp_c": 13.1, + "temp_f": 55.7, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 11.4, + "wind_kph": 18.4, + "wind_degree": 150, + "wind_dir": "SSE", + "pressure_mb": 1016, + "pressure_in": 30.01, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 54, + "cloud": 22, + "feelslike_c": 11.5, + "feelslike_f": 52.7, + "windchill_c": 11.5, + "windchill_f": 52.7, + "heatindex_c": 13.2, + "heatindex_f": 55.7, + "dewpoint_c": 4.2, + "dewpoint_f": 39.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 16.8, + "gust_kph": 27.1, + "uv": 0.9 + }, + { + "time_epoch": 1732374000, + "time": "2024-11-23 16:00", + "temp_c": 12.5, + "temp_f": 54.4, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 10.7, + "wind_kph": 17.3, + "wind_degree": 148, + "wind_dir": "SSE", + "pressure_mb": 1017, + "pressure_in": 30.02, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 57, + "cloud": 14, + "feelslike_c": 10.7, + "feelslike_f": 51.3, + "windchill_c": 10.7, + "windchill_f": 51.3, + "heatindex_c": 12.5, + "heatindex_f": 54.4, + "dewpoint_c": 4.1, + "dewpoint_f": 39.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 17.5, + "gust_kph": 28.1, + "uv": 0.4 + }, + { + "time_epoch": 1732377600, + "time": "2024-11-23 17:00", + "temp_c": 11.2, + "temp_f": 52.1, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 11.2, + "wind_kph": 18, + "wind_degree": 141, + "wind_dir": "SE", + "pressure_mb": 1017, + "pressure_in": 30.02, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 61, + "cloud": 22, + "feelslike_c": 9, + "feelslike_f": 48.2, + "windchill_c": 9, + "windchill_f": 48.2, + "heatindex_c": 11.2, + "heatindex_f": 52.1, + "dewpoint_c": 4, + "dewpoint_f": 39.2, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 20.2, + "gust_kph": 32.5, + "uv": 0 + }, + { + "time_epoch": 1732381200, + "time": "2024-11-23 18:00", + "temp_c": 10.2, + "temp_f": 50.4, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 11.6, + "wind_kph": 18.7, + "wind_degree": 141, + "wind_dir": "SE", + "pressure_mb": 1016, + "pressure_in": 30.02, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 64, + "cloud": 47, + "feelslike_c": 7.8, + "feelslike_f": 46, + "windchill_c": 7.8, + "windchill_f": 46, + "heatindex_c": 10.2, + "heatindex_f": 50.4, + "dewpoint_c": 3.8, + "dewpoint_f": 38.9, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 22.2, + "gust_kph": 35.7, + "uv": 0 + }, + { + "time_epoch": 1732384800, + "time": "2024-11-23 19:00", + "temp_c": 10, + "temp_f": 50.1, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 12.3, + "wind_kph": 19.8, + "wind_degree": 144, + "wind_dir": "SE", + "pressure_mb": 1017, + "pressure_in": 30.02, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 66, + "cloud": 62, + "feelslike_c": 7.5, + "feelslike_f": 45.4, + "windchill_c": 7.5, + "windchill_f": 45.4, + "heatindex_c": 10, + "heatindex_f": 50.1, + "dewpoint_c": 4, + "dewpoint_f": 39.2, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 22.8, + "gust_kph": 36.8, + "uv": 0 + }, + { + "time_epoch": 1732388400, + "time": "2024-11-23 20:00", + "temp_c": 10, + "temp_f": 50, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 12.5, + "wind_kph": 20.2, + "wind_degree": 143, + "wind_dir": "SE", + "pressure_mb": 1017, + "pressure_in": 30.03, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 68, + "cloud": 57, + "feelslike_c": 7.3, + "feelslike_f": 45.2, + "windchill_c": 7.3, + "windchill_f": 45.2, + "heatindex_c": 10, + "heatindex_f": 50, + "dewpoint_c": 4.3, + "dewpoint_f": 39.8, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 22.6, + "gust_kph": 36.3, + "uv": 0 + }, + { + "time_epoch": 1732392000, + "time": "2024-11-23 21:00", + "temp_c": 9.7, + "temp_f": 49.5, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 12.5, + "wind_kph": 20.2, + "wind_degree": 142, + "wind_dir": "SE", + "pressure_mb": 1017, + "pressure_in": 30.03, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 71, + "cloud": 46, + "feelslike_c": 7, + "feelslike_f": 44.6, + "windchill_c": 7, + "windchill_f": 44.6, + "heatindex_c": 9.7, + "heatindex_f": 49.5, + "dewpoint_c": 4.6, + "dewpoint_f": 40.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 22.3, + "gust_kph": 35.9, + "uv": 0 + }, + { + "time_epoch": 1732395600, + "time": "2024-11-23 22:00", + "temp_c": 9.5, + "temp_f": 49.1, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 13, + "wind_kph": 20.9, + "wind_degree": 139, + "wind_dir": "SE", + "pressure_mb": 1017, + "pressure_in": 30.03, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 73, + "cloud": 24, + "feelslike_c": 6.6, + "feelslike_f": 44, + "windchill_c": 6.6, + "windchill_f": 44, + "heatindex_c": 9.5, + "heatindex_f": 49.1, + "dewpoint_c": 5, + "dewpoint_f": 41, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 23, + "gust_kph": 37, + "uv": 0 + }, + { + "time_epoch": 1732399200, + "time": "2024-11-23 23:00", + "temp_c": 9.3, + "temp_f": 48.8, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 12.8, + "wind_kph": 20.5, + "wind_degree": 140, + "wind_dir": "SE", + "pressure_mb": 1017, + "pressure_in": 30.03, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 76, + "cloud": 21, + "feelslike_c": 6.5, + "feelslike_f": 43.7, + "windchill_c": 6.5, + "windchill_f": 43.7, + "heatindex_c": 9.3, + "heatindex_f": 48.8, + "dewpoint_c": 5.3, + "dewpoint_f": 41.6, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 23, + "gust_kph": 37, + "uv": 0 + } + ] + }, + { + "date": "2024-11-24", + "date_epoch": 1732406400, + "day": { + "maxtemp_c": 17.2, + "maxtemp_f": 63, + "mintemp_c": 9.3, + "mintemp_f": 48.8, + "avgtemp_c": 13, + "avgtemp_f": 55.3, + "maxwind_mph": 16.3, + "maxwind_kph": 26.3, + "totalprecip_mm": 0, + "totalprecip_in": 0, + "totalsnow_cm": 0, + "avgvis_km": 10, + "avgvis_miles": 6, + "avghumidity": 70, + "daily_will_it_rain": 0, + "daily_chance_of_rain": 0, + "daily_will_it_snow": 0, + "daily_chance_of_snow": 0, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "uv": 0.3 + }, + "astro": { + "sunrise": "08:12 AM", + "sunset": "05:26 PM", + "moonrise": "01:14 AM", + "moonset": "02:30 PM", + "moon_phase": "Waning Crescent", + "moon_illumination": 41, + "is_moon_up": 0, + "is_sun_up": 0 + }, + "hour": [ + { + "time_epoch": 1732402800, + "time": "2024-11-24 00:00", + "temp_c": 9.3, + "temp_f": 48.8, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 12.5, + "wind_kph": 20.2, + "wind_degree": 140, + "wind_dir": "SE", + "pressure_mb": 1017, + "pressure_in": 30.03, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 77, + "cloud": 41, + "feelslike_c": 6.5, + "feelslike_f": 43.8, + "windchill_c": 6.5, + "windchill_f": 43.8, + "heatindex_c": 9.3, + "heatindex_f": 48.8, + "dewpoint_c": 5.6, + "dewpoint_f": 42, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 23.1, + "gust_kph": 37.2, + "uv": 0 + }, + { + "time_epoch": 1732406400, + "time": "2024-11-24 01:00", + "temp_c": 9.5, + "temp_f": 49.1, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 12.5, + "wind_kph": 20.2, + "wind_degree": 139, + "wind_dir": "SE", + "pressure_mb": 1017, + "pressure_in": 30.02, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 77, + "cloud": 33, + "feelslike_c": 6.7, + "feelslike_f": 44.1, + "windchill_c": 6.7, + "windchill_f": 44.1, + "heatindex_c": 9.5, + "heatindex_f": 49.1, + "dewpoint_c": 5.7, + "dewpoint_f": 42.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 23.4, + "gust_kph": 37.7, + "uv": 0 + }, + { + "time_epoch": 1732410000, + "time": "2024-11-24 02:00", + "temp_c": 9.7, + "temp_f": 49.4, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 12.3, + "wind_kph": 19.8, + "wind_degree": 139, + "wind_dir": "SE", + "pressure_mb": 1016, + "pressure_in": 30, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 77, + "cloud": 22, + "feelslike_c": 7, + "feelslike_f": 44.5, + "windchill_c": 7, + "windchill_f": 44.5, + "heatindex_c": 9.7, + "heatindex_f": 49.4, + "dewpoint_c": 5.8, + "dewpoint_f": 42.4, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 23.2, + "gust_kph": 37.3, + "uv": 0 + }, + { + "time_epoch": 1732413600, + "time": "2024-11-24 03:00", + "temp_c": 9.7, + "temp_f": 49.5, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 12.8, + "wind_kph": 20.5, + "wind_degree": 138, + "wind_dir": "SE", + "pressure_mb": 1016, + "pressure_in": 30, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 76, + "cloud": 14, + "feelslike_c": 7, + "feelslike_f": 44.6, + "windchill_c": 7, + "windchill_f": 44.6, + "heatindex_c": 9.7, + "heatindex_f": 49.5, + "dewpoint_c": 5.7, + "dewpoint_f": 42.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 24.3, + "gust_kph": 39.2, + "uv": 0 + }, + { + "time_epoch": 1732417200, + "time": "2024-11-24 04:00", + "temp_c": 9.8, + "temp_f": 49.6, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 13, + "wind_kph": 20.9, + "wind_degree": 137, + "wind_dir": "SE", + "pressure_mb": 1015, + "pressure_in": 29.98, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 75, + "cloud": 10, + "feelslike_c": 7, + "feelslike_f": 44.6, + "windchill_c": 7, + "windchill_f": 44.6, + "heatindex_c": 9.8, + "heatindex_f": 49.6, + "dewpoint_c": 5.7, + "dewpoint_f": 42.2, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 24.6, + "gust_kph": 39.6, + "uv": 0 + }, + { + "time_epoch": 1732420800, + "time": "2024-11-24 05:00", + "temp_c": 9.7, + "temp_f": 49.4, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 13.4, + "wind_kph": 21.6, + "wind_degree": 136, + "wind_dir": "SE", + "pressure_mb": 1015, + "pressure_in": 29.98, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 76, + "cloud": 10, + "feelslike_c": 6.8, + "feelslike_f": 44.3, + "windchill_c": 6.8, + "windchill_f": 44.3, + "heatindex_c": 9.7, + "heatindex_f": 49.4, + "dewpoint_c": 5.7, + "dewpoint_f": 42.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 25.3, + "gust_kph": 40.7, + "uv": 0 + }, + { + "time_epoch": 1732424400, + "time": "2024-11-24 06:00", + "temp_c": 9.7, + "temp_f": 49.5, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 13.6, + "wind_kph": 22, + "wind_degree": 137, + "wind_dir": "SE", + "pressure_mb": 1015, + "pressure_in": 29.98, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 77, + "cloud": 11, + "feelslike_c": 6.8, + "feelslike_f": 44.3, + "windchill_c": 6.8, + "windchill_f": 44.3, + "heatindex_c": 9.7, + "heatindex_f": 49.5, + "dewpoint_c": 5.8, + "dewpoint_f": 42.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 25.7, + "gust_kph": 41.4, + "uv": 0 + }, + { + "time_epoch": 1732428000, + "time": "2024-11-24 07:00", + "temp_c": 9.7, + "temp_f": 49.4, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 13.6, + "wind_kph": 22, + "wind_degree": 138, + "wind_dir": "SE", + "pressure_mb": 1015, + "pressure_in": 29.97, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 78, + "cloud": 8, + "feelslike_c": 6.8, + "feelslike_f": 44.2, + "windchill_c": 6.8, + "windchill_f": 44.2, + "heatindex_c": 9.7, + "heatindex_f": 49.4, + "dewpoint_c": 6, + "dewpoint_f": 42.7, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 25.5, + "gust_kph": 41, + "uv": 0 + }, + { + "time_epoch": 1732431600, + "time": "2024-11-24 08:00", + "temp_c": 9.7, + "temp_f": 49.5, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 13.2, + "wind_kph": 21.2, + "wind_degree": 138, + "wind_dir": "SE", + "pressure_mb": 1015, + "pressure_in": 29.98, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 78, + "cloud": 7, + "feelslike_c": 6.9, + "feelslike_f": 44.4, + "windchill_c": 6.9, + "windchill_f": 44.4, + "heatindex_c": 9.7, + "heatindex_f": 49.5, + "dewpoint_c": 6.1, + "dewpoint_f": 43, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 24.8, + "gust_kph": 40, + "uv": 0 + }, + { + "time_epoch": 1732435200, + "time": "2024-11-24 09:00", + "temp_c": 10.1, + "temp_f": 50.1, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 13.6, + "wind_kph": 22, + "wind_degree": 137, + "wind_dir": "SE", + "pressure_mb": 1015, + "pressure_in": 29.98, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 78, + "cloud": 7, + "feelslike_c": 7.3, + "feelslike_f": 45.1, + "windchill_c": 7.3, + "windchill_f": 45.1, + "heatindex_c": 10.1, + "heatindex_f": 50.1, + "dewpoint_c": 6.4, + "dewpoint_f": 43.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 24.5, + "gust_kph": 39.5, + "uv": 0 + }, + { + "time_epoch": 1732438800, + "time": "2024-11-24 10:00", + "temp_c": 11.4, + "temp_f": 52.5, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 14.5, + "wind_kph": 23.4, + "wind_degree": 137, + "wind_dir": "SE", + "pressure_mb": 1015, + "pressure_in": 29.97, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 74, + "cloud": 5, + "feelslike_c": 8.9, + "feelslike_f": 47.9, + "windchill_c": 8.9, + "windchill_f": 47.9, + "heatindex_c": 11.4, + "heatindex_f": 52.5, + "dewpoint_c": 7, + "dewpoint_f": 44.6, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 23.2, + "gust_kph": 37.3, + "uv": 0.4 + }, + { + "time_epoch": 1732442400, + "time": "2024-11-24 11:00", + "temp_c": 13.2, + "temp_f": 55.7, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 15.7, + "wind_kph": 25.2, + "wind_degree": 137, + "wind_dir": "SE", + "pressure_mb": 1015, + "pressure_in": 29.96, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 70, + "cloud": 4, + "feelslike_c": 11, + "feelslike_f": 51.8, + "windchill_c": 11, + "windchill_f": 51.8, + "heatindex_c": 13.2, + "heatindex_f": 55.7, + "dewpoint_c": 7.8, + "dewpoint_f": 46, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 22.5, + "gust_kph": 36.3, + "uv": 0.8 + }, + { + "time_epoch": 1732446000, + "time": "2024-11-24 12:00", + "temp_c": 15, + "temp_f": 58.9, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 16.1, + "wind_kph": 25.9, + "wind_degree": 140, + "wind_dir": "SE", + "pressure_mb": 1014, + "pressure_in": 29.95, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 65, + "cloud": 4, + "feelslike_c": 13.3, + "feelslike_f": 55.9, + "windchill_c": 13.3, + "windchill_f": 55.9, + "heatindex_c": 15, + "heatindex_f": 58.9, + "dewpoint_c": 8.4, + "dewpoint_f": 47.1, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 21.9, + "gust_kph": 35.2, + "uv": 1.3 + }, + { + "time_epoch": 1732449600, + "time": "2024-11-24 13:00", + "temp_c": 16.4, + "temp_f": 61.6, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 16.1, + "wind_kph": 25.9, + "wind_degree": 142, + "wind_dir": "SE", + "pressure_mb": 1013, + "pressure_in": 29.91, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 60, + "cloud": 5, + "feelslike_c": 16.4, + "feelslike_f": 61.6, + "windchill_c": 16.4, + "windchill_f": 61.6, + "heatindex_c": 16.4, + "heatindex_f": 61.6, + "dewpoint_c": 8.8, + "dewpoint_f": 47.8, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 21.6, + "gust_kph": 34.7, + "uv": 1.4 + }, + { + "time_epoch": 1732453200, + "time": "2024-11-24 14:00", + "temp_c": 17.2, + "temp_f": 62.9, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 16.3, + "wind_kph": 26.3, + "wind_degree": 143, + "wind_dir": "SE", + "pressure_mb": 1012, + "pressure_in": 29.89, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 59, + "cloud": 4, + "feelslike_c": 17.2, + "feelslike_f": 62.9, + "windchill_c": 17.2, + "windchill_f": 62.9, + "heatindex_c": 17.2, + "heatindex_f": 62.9, + "dewpoint_c": 9, + "dewpoint_f": 48.2, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 22.9, + "gust_kph": 36.8, + "uv": 1.3 + }, + { + "time_epoch": 1732456800, + "time": "2024-11-24 15:00", + "temp_c": 17.2, + "temp_f": 63, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 16.3, + "wind_kph": 26.3, + "wind_degree": 145, + "wind_dir": "SE", + "pressure_mb": 1012, + "pressure_in": 29.87, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 59, + "cloud": 1, + "feelslike_c": 17.2, + "feelslike_f": 63, + "windchill_c": 17.2, + "windchill_f": 63, + "heatindex_c": 17.2, + "heatindex_f": 63, + "dewpoint_c": 9.2, + "dewpoint_f": 48.6, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 25, + "gust_kph": 40.2, + "uv": 0.8 + }, + { + "time_epoch": 1732460400, + "time": "2024-11-24 16:00", + "temp_c": 16.6, + "temp_f": 61.9, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 15.7, + "wind_kph": 25.2, + "wind_degree": 147, + "wind_dir": "SSE", + "pressure_mb": 1011, + "pressure_in": 29.86, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 61, + "cloud": 3, + "feelslike_c": 16.6, + "feelslike_f": 61.9, + "windchill_c": 16.6, + "windchill_f": 61.9, + "heatindex_c": 16.6, + "heatindex_f": 61.9, + "dewpoint_c": 9.2, + "dewpoint_f": 48.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 25.9, + "gust_kph": 41.7, + "uv": 0.4 + }, + { + "time_epoch": 1732464000, + "time": "2024-11-24 17:00", + "temp_c": 15.7, + "temp_f": 60.3, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 15.4, + "wind_kph": 24.8, + "wind_degree": 147, + "wind_dir": "SSE", + "pressure_mb": 1011, + "pressure_in": 29.86, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 65, + "cloud": 22, + "feelslike_c": 15.7, + "feelslike_f": 60.3, + "windchill_c": 15.7, + "windchill_f": 60.3, + "heatindex_c": 15.7, + "heatindex_f": 60.3, + "dewpoint_c": 9.1, + "dewpoint_f": 48.4, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 27, + "gust_kph": 43.5, + "uv": 0 + }, + { + "time_epoch": 1732467600, + "time": "2024-11-24 18:00", + "temp_c": 15.3, + "temp_f": 59.5, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 15.4, + "wind_kph": 24.8, + "wind_degree": 149, + "wind_dir": "SSE", + "pressure_mb": 1011, + "pressure_in": 29.86, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 66, + "cloud": 15, + "feelslike_c": 15.3, + "feelslike_f": 59.5, + "windchill_c": 15.3, + "windchill_f": 59.5, + "heatindex_c": 15.3, + "heatindex_f": 59.5, + "dewpoint_c": 9.1, + "dewpoint_f": 48.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 27.8, + "gust_kph": 44.8, + "uv": 0 + }, + { + "time_epoch": 1732471200, + "time": "2024-11-24 19:00", + "temp_c": 15.2, + "temp_f": 59.4, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 15.7, + "wind_kph": 25.2, + "wind_degree": 150, + "wind_dir": "SSE", + "pressure_mb": 1011, + "pressure_in": 29.86, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 67, + "cloud": 47, + "feelslike_c": 15.2, + "feelslike_f": 59.4, + "windchill_c": 15.2, + "windchill_f": 59.4, + "heatindex_c": 15.2, + "heatindex_f": 59.4, + "dewpoint_c": 9.1, + "dewpoint_f": 48.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 28.4, + "gust_kph": 45.7, + "uv": 0 + }, + { + "time_epoch": 1732474800, + "time": "2024-11-24 20:00", + "temp_c": 15.2, + "temp_f": 59.4, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 14.8, + "wind_kph": 23.8, + "wind_degree": 149, + "wind_dir": "SSE", + "pressure_mb": 1012, + "pressure_in": 29.87, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 67, + "cloud": 56, + "feelslike_c": 15.2, + "feelslike_f": 59.4, + "windchill_c": 15.2, + "windchill_f": 59.4, + "heatindex_c": 15.2, + "heatindex_f": 59.4, + "dewpoint_c": 9.2, + "dewpoint_f": 48.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 26.5, + "gust_kph": 42.6, + "uv": 0 + }, + { + "time_epoch": 1732478400, + "time": "2024-11-24 21:00", + "temp_c": 15.4, + "temp_f": 59.7, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 14.3, + "wind_kph": 23, + "wind_degree": 151, + "wind_dir": "SSE", + "pressure_mb": 1011, + "pressure_in": 29.87, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 67, + "cloud": 49, + "feelslike_c": 15.4, + "feelslike_f": 59.7, + "windchill_c": 15.4, + "windchill_f": 59.7, + "heatindex_c": 15.4, + "heatindex_f": 59.7, + "dewpoint_c": 9.3, + "dewpoint_f": 48.8, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 24.7, + "gust_kph": 39.8, + "uv": 0 + }, + { + "time_epoch": 1732482000, + "time": "2024-11-24 22:00", + "temp_c": 15.3, + "temp_f": 59.5, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 13, + "wind_kph": 20.9, + "wind_degree": 155, + "wind_dir": "SSE", + "pressure_mb": 1011, + "pressure_in": 29.87, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 67, + "cloud": 47, + "feelslike_c": 15.3, + "feelslike_f": 59.5, + "windchill_c": 15.3, + "windchill_f": 59.5, + "heatindex_c": 15.3, + "heatindex_f": 59.5, + "dewpoint_c": 9.2, + "dewpoint_f": 48.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 22.7, + "gust_kph": 36.5, + "uv": 0 + }, + { + "time_epoch": 1732485600, + "time": "2024-11-24 23:00", + "temp_c": 15.3, + "temp_f": 59.5, + "is_day": 0, + "condition": { + "text": "Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/119.png", + "code": 1006 + }, + "wind_mph": 13.4, + "wind_kph": 21.6, + "wind_degree": 151, + "wind_dir": "SSE", + "pressure_mb": 1011, + "pressure_in": 29.87, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 65, + "cloud": 66, + "feelslike_c": 15.3, + "feelslike_f": 59.5, + "windchill_c": 15.3, + "windchill_f": 59.5, + "heatindex_c": 15.3, + "heatindex_f": 59.5, + "dewpoint_c": 8.8, + "dewpoint_f": 47.9, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 23.8, + "gust_kph": 38.3, + "uv": 0 + } + ] + }, + { + "date": "2024-11-25", + "date_epoch": 1732492800, + "day": { + "maxtemp_c": 15.5, + "maxtemp_f": 59.8, + "mintemp_c": 9.5, + "mintemp_f": 49.1, + "avgtemp_c": 12.7, + "avgtemp_f": 54.8, + "maxwind_mph": 15, + "maxwind_kph": 24.1, + "totalprecip_mm": 7.56, + "totalprecip_in": 0.3, + "totalsnow_cm": 0, + "avgvis_km": 8.6, + "avgvis_miles": 5, + "avghumidity": 80, + "daily_will_it_rain": 1, + "daily_chance_of_rain": 89, + "daily_will_it_snow": 0, + "daily_chance_of_snow": 0, + "condition": { + "text": "Moderate rain", + "icon": "//cdn.weatherapi.com/weather/64x64/day/302.png", + "code": 1189 + }, + "uv": 0 + }, + "astro": { + "sunrise": "08:13 AM", + "sunset": "05:26 PM", + "moonrise": "02:17 AM", + "moonset": "02:46 PM", + "moon_phase": "Waning Crescent", + "moon_illumination": 32, + "is_moon_up": 0, + "is_sun_up": 0 + }, + "hour": [ + { + "time_epoch": 1732489200, + "time": "2024-11-25 00:00", + "temp_c": 15.5, + "temp_f": 59.8, + "is_day": 0, + "condition": { + "text": "Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/119.png", + "code": 1006 + }, + "wind_mph": 13.4, + "wind_kph": 21.6, + "wind_degree": 153, + "wind_dir": "SSE", + "pressure_mb": 1011, + "pressure_in": 29.85, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 63, + "cloud": 66, + "feelslike_c": 15.5, + "feelslike_f": 59.8, + "windchill_c": 15.5, + "windchill_f": 59.8, + "heatindex_c": 15.5, + "heatindex_f": 59.8, + "dewpoint_c": 8.5, + "dewpoint_f": 47.2, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 24.7, + "gust_kph": 39.7, + "uv": 0 + }, + { + "time_epoch": 1732492800, + "time": "2024-11-25 01:00", + "temp_c": 15.3, + "temp_f": 59.5, + "is_day": 0, + "condition": { + "text": "Patchy rain nearby", + "icon": "//cdn.weatherapi.com/weather/64x64/night/176.png", + "code": 1063 + }, + "wind_mph": 15, + "wind_kph": 24.1, + "wind_degree": 150, + "wind_dir": "SSE", + "pressure_mb": 1011, + "pressure_in": 29.86, + "precip_mm": 0.04, + "precip_in": 0, + "snow_cm": 0, + "humidity": 64, + "cloud": 56, + "feelslike_c": 15.3, + "feelslike_f": 59.5, + "windchill_c": 15.3, + "windchill_f": 59.5, + "heatindex_c": 15.3, + "heatindex_f": 59.5, + "dewpoint_c": 8.5, + "dewpoint_f": 47.3, + "will_it_rain": 0, + "chance_of_rain": 69, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 26.1, + "gust_kph": 42.1, + "uv": 0 + }, + { + "time_epoch": 1732496400, + "time": "2024-11-25 02:00", + "temp_c": 15.3, + "temp_f": 59.5, + "is_day": 0, + "condition": { + "text": "Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/119.png", + "code": 1006 + }, + "wind_mph": 14.5, + "wind_kph": 23.4, + "wind_degree": 148, + "wind_dir": "SSE", + "pressure_mb": 1010, + "pressure_in": 29.82, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 62, + "cloud": 67, + "feelslike_c": 15.3, + "feelslike_f": 59.5, + "windchill_c": 15.3, + "windchill_f": 59.5, + "heatindex_c": 15.3, + "heatindex_f": 59.5, + "dewpoint_c": 8.1, + "dewpoint_f": 46.6, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 26.6, + "gust_kph": 42.7, + "uv": 0 + }, + { + "time_epoch": 1732500000, + "time": "2024-11-25 03:00", + "temp_c": 15.5, + "temp_f": 60, + "is_day": 0, + "condition": { + "text": "Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/119.png", + "code": 1006 + }, + "wind_mph": 15, + "wind_kph": 24.1, + "wind_degree": 154, + "wind_dir": "SSE", + "pressure_mb": 1010, + "pressure_in": 29.81, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 59, + "cloud": 82, + "feelslike_c": 15.5, + "feelslike_f": 60, + "windchill_c": 15.5, + "windchill_f": 60, + "heatindex_c": 15.5, + "heatindex_f": 60, + "dewpoint_c": 7.5, + "dewpoint_f": 45.4, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 26.2, + "gust_kph": 42.1, + "uv": 0 + }, + { + "time_epoch": 1732503600, + "time": "2024-11-25 04:00", + "temp_c": 16.4, + "temp_f": 61.6, + "is_day": 0, + "condition": { + "text": "Patchy rain nearby", + "icon": "//cdn.weatherapi.com/weather/64x64/night/176.png", + "code": 1063 + }, + "wind_mph": 15, + "wind_kph": 24.1, + "wind_degree": 160, + "wind_dir": "SSE", + "pressure_mb": 1009, + "pressure_in": 29.79, + "precip_mm": 0.01, + "precip_in": 0, + "snow_cm": 0, + "humidity": 54, + "cloud": 85, + "feelslike_c": 16.4, + "feelslike_f": 61.6, + "windchill_c": 16.4, + "windchill_f": 61.6, + "heatindex_c": 16.4, + "heatindex_f": 61.6, + "dewpoint_c": 7, + "dewpoint_f": 44.7, + "will_it_rain": 0, + "chance_of_rain": 70, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 26.5, + "gust_kph": 42.7, + "uv": 0 + }, + { + "time_epoch": 1732507200, + "time": "2024-11-25 05:00", + "temp_c": 15.5, + "temp_f": 59.9, + "is_day": 0, + "condition": { + "text": "Patchy rain nearby", + "icon": "//cdn.weatherapi.com/weather/64x64/night/176.png", + "code": 1063 + }, + "wind_mph": 12.5, + "wind_kph": 20.2, + "wind_degree": 154, + "wind_dir": "SSE", + "pressure_mb": 1009, + "pressure_in": 29.8, + "precip_mm": 0.01, + "precip_in": 0, + "snow_cm": 0, + "humidity": 59, + "cloud": 86, + "feelslike_c": 15.5, + "feelslike_f": 59.9, + "windchill_c": 15.5, + "windchill_f": 59.9, + "heatindex_c": 15.5, + "heatindex_f": 59.9, + "dewpoint_c": 7.5, + "dewpoint_f": 45.5, + "will_it_rain": 0, + "chance_of_rain": 67, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 21.5, + "gust_kph": 34.5, + "uv": 0 + }, + { + "time_epoch": 1732510800, + "time": "2024-11-25 06:00", + "temp_c": 15.5, + "temp_f": 59.8, + "is_day": 0, + "condition": { + "text": "Patchy rain nearby", + "icon": "//cdn.weatherapi.com/weather/64x64/night/176.png", + "code": 1063 + }, + "wind_mph": 8.5, + "wind_kph": 13.7, + "wind_degree": 181, + "wind_dir": "S", + "pressure_mb": 1010, + "pressure_in": 29.83, + "precip_mm": 0.04, + "precip_in": 0, + "snow_cm": 0, + "humidity": 60, + "cloud": 85, + "feelslike_c": 15.5, + "feelslike_f": 59.8, + "windchill_c": 15.5, + "windchill_f": 59.8, + "heatindex_c": 15.5, + "heatindex_f": 59.8, + "dewpoint_c": 7.8, + "dewpoint_f": 46.1, + "will_it_rain": 0, + "chance_of_rain": 64, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 15.4, + "gust_kph": 24.8, + "uv": 0 + }, + { + "time_epoch": 1732514400, + "time": "2024-11-25 07:00", + "temp_c": 15.1, + "temp_f": 59.1, + "is_day": 0, + "condition": { + "text": "Patchy light drizzle", + "icon": "//cdn.weatherapi.com/weather/64x64/night/263.png", + "code": 1150 + }, + "wind_mph": 7.2, + "wind_kph": 11.5, + "wind_degree": 238, + "wind_dir": "WSW", + "pressure_mb": 1011, + "pressure_in": 29.84, + "precip_mm": 0.32, + "precip_in": 0.01, + "snow_cm": 0, + "humidity": 73, + "cloud": 71, + "feelslike_c": 15.1, + "feelslike_f": 59.1, + "windchill_c": 15.1, + "windchill_f": 59.1, + "heatindex_c": 15.1, + "heatindex_f": 59.1, + "dewpoint_c": 10.2, + "dewpoint_f": 50.3, + "will_it_rain": 1, + "chance_of_rain": 100, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 5, + "vis_miles": 3, + "gust_mph": 12.5, + "gust_kph": 20.1, + "uv": 0 + }, + { + "time_epoch": 1732518000, + "time": "2024-11-25 08:00", + "temp_c": 13.6, + "temp_f": 56.5, + "is_day": 0, + "condition": { + "text": "Light rain", + "icon": "//cdn.weatherapi.com/weather/64x64/night/296.png", + "code": 1183 + }, + "wind_mph": 8.5, + "wind_kph": 13.7, + "wind_degree": 301, + "wind_dir": "WNW", + "pressure_mb": 1012, + "pressure_in": 29.88, + "precip_mm": 0.84, + "precip_in": 0.03, + "snow_cm": 0, + "humidity": 91, + "cloud": 100, + "feelslike_c": 12.5, + "feelslike_f": 54.5, + "windchill_c": 12.5, + "windchill_f": 54.5, + "heatindex_c": 13.6, + "heatindex_f": 56.5, + "dewpoint_c": 12.2, + "dewpoint_f": 53.9, + "will_it_rain": 1, + "chance_of_rain": 100, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 9, + "vis_miles": 5, + "gust_mph": 12.6, + "gust_kph": 20.3, + "uv": 0 + }, + { + "time_epoch": 1732521600, + "time": "2024-11-25 09:00", + "temp_c": 12.4, + "temp_f": 54.3, + "is_day": 1, + "condition": { + "text": "Light drizzle", + "icon": "//cdn.weatherapi.com/weather/64x64/day/266.png", + "code": 1153 + }, + "wind_mph": 6.7, + "wind_kph": 10.8, + "wind_degree": 316, + "wind_dir": "NW", + "pressure_mb": 1013, + "pressure_in": 29.92, + "precip_mm": 0.73, + "precip_in": 0.03, + "snow_cm": 0, + "humidity": 92, + "cloud": 100, + "feelslike_c": 11.4, + "feelslike_f": 52.5, + "windchill_c": 11.4, + "windchill_f": 52.5, + "heatindex_c": 12.4, + "heatindex_f": 54.3, + "dewpoint_c": 11.1, + "dewpoint_f": 52.1, + "will_it_rain": 1, + "chance_of_rain": 100, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 2, + "vis_miles": 1, + "gust_mph": 9.8, + "gust_kph": 15.7, + "uv": 0 + }, + { + "time_epoch": 1732525200, + "time": "2024-11-25 10:00", + "temp_c": 12.4, + "temp_f": 54.4, + "is_day": 1, + "condition": { + "text": "Light rain", + "icon": "//cdn.weatherapi.com/weather/64x64/day/296.png", + "code": 1183 + }, + "wind_mph": 5.1, + "wind_kph": 8.3, + "wind_degree": 290, + "wind_dir": "WNW", + "pressure_mb": 1014, + "pressure_in": 29.95, + "precip_mm": 0.89, + "precip_in": 0.04, + "snow_cm": 0, + "humidity": 92, + "cloud": 100, + "feelslike_c": 11.8, + "feelslike_f": 53.3, + "windchill_c": 11.8, + "windchill_f": 53.3, + "heatindex_c": 12.5, + "heatindex_f": 54.4, + "dewpoint_c": 11.2, + "dewpoint_f": 52.1, + "will_it_rain": 1, + "chance_of_rain": 100, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 9, + "vis_miles": 5, + "gust_mph": 7.5, + "gust_kph": 12.1, + "uv": 0 + }, + { + "time_epoch": 1732528800, + "time": "2024-11-25 11:00", + "temp_c": 12.5, + "temp_f": 54.4, + "is_day": 1, + "condition": { + "text": "Light rain", + "icon": "//cdn.weatherapi.com/weather/64x64/day/296.png", + "code": 1183 + }, + "wind_mph": 6, + "wind_kph": 9.7, + "wind_degree": 311, + "wind_dir": "NW", + "pressure_mb": 1015, + "pressure_in": 29.96, + "precip_mm": 1.36, + "precip_in": 0.05, + "snow_cm": 0, + "humidity": 93, + "cloud": 100, + "feelslike_c": 11.6, + "feelslike_f": 52.9, + "windchill_c": 11.6, + "windchill_f": 52.9, + "heatindex_c": 12.5, + "heatindex_f": 54.4, + "dewpoint_c": 11.3, + "dewpoint_f": 52.3, + "will_it_rain": 1, + "chance_of_rain": 100, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 9, + "vis_miles": 5, + "gust_mph": 8.6, + "gust_kph": 13.8, + "uv": 0.1 + }, + { + "time_epoch": 1732532400, + "time": "2024-11-25 12:00", + "temp_c": 11.9, + "temp_f": 53.4, + "is_day": 1, + "condition": { + "text": "Light rain", + "icon": "//cdn.weatherapi.com/weather/64x64/day/296.png", + "code": 1183 + }, + "wind_mph": 8.3, + "wind_kph": 13.3, + "wind_degree": 308, + "wind_dir": "NW", + "pressure_mb": 1016, + "pressure_in": 29.99, + "precip_mm": 2.14, + "precip_in": 0.08, + "snow_cm": 0, + "humidity": 93, + "cloud": 100, + "feelslike_c": 10.4, + "feelslike_f": 50.8, + "windchill_c": 10.4, + "windchill_f": 50.8, + "heatindex_c": 11.9, + "heatindex_f": 53.4, + "dewpoint_c": 10.8, + "dewpoint_f": 51.4, + "will_it_rain": 1, + "chance_of_rain": 100, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 9, + "vis_miles": 5, + "gust_mph": 11.6, + "gust_kph": 18.6, + "uv": 0.1 + }, + { + "time_epoch": 1732536000, + "time": "2024-11-25 13:00", + "temp_c": 11.3, + "temp_f": 52.4, + "is_day": 1, + "condition": { + "text": "Light rain shower", + "icon": "//cdn.weatherapi.com/weather/64x64/day/353.png", + "code": 1240 + }, + "wind_mph": 6.7, + "wind_kph": 10.8, + "wind_degree": 302, + "wind_dir": "WNW", + "pressure_mb": 1016, + "pressure_in": 30.01, + "precip_mm": 0.24, + "precip_in": 0.01, + "snow_cm": 0, + "humidity": 89, + "cloud": 100, + "feelslike_c": 10.1, + "feelslike_f": 50.2, + "windchill_c": 10.1, + "windchill_f": 50.2, + "heatindex_c": 11.3, + "heatindex_f": 52.4, + "dewpoint_c": 9.6, + "dewpoint_f": 49.3, + "will_it_rain": 1, + "chance_of_rain": 100, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 9.5, + "gust_kph": 15.2, + "uv": 0.1 + }, + { + "time_epoch": 1732539600, + "time": "2024-11-25 14:00", + "temp_c": 11.3, + "temp_f": 52.4, + "is_day": 1, + "condition": { + "text": "Light drizzle", + "icon": "//cdn.weatherapi.com/weather/64x64/day/266.png", + "code": 1153 + }, + "wind_mph": 5.6, + "wind_kph": 9, + "wind_degree": 283, + "wind_dir": "WNW", + "pressure_mb": 1017, + "pressure_in": 30.03, + "precip_mm": 0.37, + "precip_in": 0.01, + "snow_cm": 0, + "humidity": 89, + "cloud": 100, + "feelslike_c": 10.4, + "feelslike_f": 50.7, + "windchill_c": 10.4, + "windchill_f": 50.7, + "heatindex_c": 11.3, + "heatindex_f": 52.4, + "dewpoint_c": 9.5, + "dewpoint_f": 49.2, + "will_it_rain": 1, + "chance_of_rain": 100, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 2, + "vis_miles": 1, + "gust_mph": 8, + "gust_kph": 12.9, + "uv": 0.1 + }, + { + "time_epoch": 1732543200, + "time": "2024-11-25 15:00", + "temp_c": 11.3, + "temp_f": 52.3, + "is_day": 1, + "condition": { + "text": "Light drizzle", + "icon": "//cdn.weatherapi.com/weather/64x64/day/266.png", + "code": 1153 + }, + "wind_mph": 7.4, + "wind_kph": 11.9, + "wind_degree": 269, + "wind_dir": "W", + "pressure_mb": 1018, + "pressure_in": 30.06, + "precip_mm": 0.45, + "precip_in": 0.02, + "snow_cm": 0, + "humidity": 88, + "cloud": 100, + "feelslike_c": 9.9, + "feelslike_f": 49.7, + "windchill_c": 9.9, + "windchill_f": 49.7, + "heatindex_c": 11.3, + "heatindex_f": 52.3, + "dewpoint_c": 9.4, + "dewpoint_f": 49, + "will_it_rain": 1, + "chance_of_rain": 100, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 2, + "vis_miles": 1, + "gust_mph": 11, + "gust_kph": 17.6, + "uv": 0.1 + }, + { + "time_epoch": 1732546800, + "time": "2024-11-25 16:00", + "temp_c": 11.4, + "temp_f": 52.5, + "is_day": 1, + "condition": { + "text": "Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/day/119.png", + "code": 1006 + }, + "wind_mph": 3.6, + "wind_kph": 5.8, + "wind_degree": 244, + "wind_dir": "WSW", + "pressure_mb": 1018, + "pressure_in": 30.07, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 82, + "cloud": 65, + "feelslike_c": 11.2, + "feelslike_f": 52.1, + "windchill_c": 11.2, + "windchill_f": 52.1, + "heatindex_c": 11.4, + "heatindex_f": 52.5, + "dewpoint_c": 8.4, + "dewpoint_f": 47.1, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 5.6, + "gust_kph": 9, + "uv": 0.1 + }, + { + "time_epoch": 1732550400, + "time": "2024-11-25 17:00", + "temp_c": 11.4, + "temp_f": 52.4, + "is_day": 1, + "condition": { + "text": "Patchy rain nearby", + "icon": "//cdn.weatherapi.com/weather/64x64/day/176.png", + "code": 1063 + }, + "wind_mph": 5.1, + "wind_kph": 8.3, + "wind_degree": 191, + "wind_dir": "SSW", + "pressure_mb": 1019, + "pressure_in": 30.09, + "precip_mm": 0.02, + "precip_in": 0, + "snow_cm": 0, + "humidity": 82, + "cloud": 58, + "feelslike_c": 10.5, + "feelslike_f": 51, + "windchill_c": 10.5, + "windchill_f": 51, + "heatindex_c": 11.4, + "heatindex_f": 52.4, + "dewpoint_c": 8.4, + "dewpoint_f": 47, + "will_it_rain": 1, + "chance_of_rain": 89, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 8.4, + "gust_kph": 13.5, + "uv": 0 + }, + { + "time_epoch": 1732554000, + "time": "2024-11-25 18:00", + "temp_c": 11.1, + "temp_f": 51.9, + "is_day": 0, + "condition": { + "text": "Patchy rain nearby", + "icon": "//cdn.weatherapi.com/weather/64x64/night/176.png", + "code": 1063 + }, + "wind_mph": 5.6, + "wind_kph": 9, + "wind_degree": 207, + "wind_dir": "SSW", + "pressure_mb": 1020, + "pressure_in": 30.11, + "precip_mm": 0.02, + "precip_in": 0, + "snow_cm": 0, + "humidity": 85, + "cloud": 89, + "feelslike_c": 10.1, + "feelslike_f": 50.1, + "windchill_c": 10.1, + "windchill_f": 50.1, + "heatindex_c": 11.1, + "heatindex_f": 51.9, + "dewpoint_c": 8.6, + "dewpoint_f": 47.4, + "will_it_rain": 1, + "chance_of_rain": 76, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 9.4, + "gust_kph": 15.2, + "uv": 0 + }, + { + "time_epoch": 1732557600, + "time": "2024-11-25 19:00", + "temp_c": 10.6, + "temp_f": 51.1, + "is_day": 0, + "condition": { + "text": "Patchy rain nearby", + "icon": "//cdn.weatherapi.com/weather/64x64/night/176.png", + "code": 1063 + }, + "wind_mph": 6, + "wind_kph": 9.7, + "wind_degree": 198, + "wind_dir": "SSW", + "pressure_mb": 1020, + "pressure_in": 30.13, + "precip_mm": 0.04, + "precip_in": 0, + "snow_cm": 0, + "humidity": 88, + "cloud": 88, + "feelslike_c": 9.4, + "feelslike_f": 48.9, + "windchill_c": 9.4, + "windchill_f": 48.9, + "heatindex_c": 10.6, + "heatindex_f": 51.1, + "dewpoint_c": 8.8, + "dewpoint_f": 47.8, + "will_it_rain": 1, + "chance_of_rain": 76, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 10.6, + "gust_kph": 17.1, + "uv": 0 + }, + { + "time_epoch": 1732561200, + "time": "2024-11-25 20:00", + "temp_c": 10.1, + "temp_f": 50.2, + "is_day": 0, + "condition": { + "text": "Patchy rain nearby", + "icon": "//cdn.weatherapi.com/weather/64x64/night/176.png", + "code": 1063 + }, + "wind_mph": 5.6, + "wind_kph": 9, + "wind_degree": 198, + "wind_dir": "SSW", + "pressure_mb": 1021, + "pressure_in": 30.16, + "precip_mm": 0.04, + "precip_in": 0, + "snow_cm": 0, + "humidity": 92, + "cloud": 87, + "feelslike_c": 8.9, + "feelslike_f": 48.1, + "windchill_c": 8.9, + "windchill_f": 48.1, + "heatindex_c": 10.1, + "heatindex_f": 50.2, + "dewpoint_c": 8.8, + "dewpoint_f": 47.9, + "will_it_rain": 1, + "chance_of_rain": 75, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 10.5, + "gust_kph": 16.9, + "uv": 0 + }, + { + "time_epoch": 1732564800, + "time": "2024-11-25 21:00", + "temp_c": 9.8, + "temp_f": 49.6, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 5.1, + "wind_kph": 8.3, + "wind_degree": 198, + "wind_dir": "SSW", + "pressure_mb": 1022, + "pressure_in": 30.17, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 93, + "cloud": 18, + "feelslike_c": 8.7, + "feelslike_f": 47.7, + "windchill_c": 8.7, + "windchill_f": 47.7, + "heatindex_c": 9.8, + "heatindex_f": 49.6, + "dewpoint_c": 8.7, + "dewpoint_f": 47.7, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 10.2, + "gust_kph": 16.3, + "uv": 0 + }, + { + "time_epoch": 1732568400, + "time": "2024-11-25 22:00", + "temp_c": 9.6, + "temp_f": 49.4, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 5.1, + "wind_kph": 8.3, + "wind_degree": 198, + "wind_dir": "SSW", + "pressure_mb": 1022, + "pressure_in": 30.19, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 93, + "cloud": 24, + "feelslike_c": 8.5, + "feelslike_f": 47.4, + "windchill_c": 8.5, + "windchill_f": 47.4, + "heatindex_c": 9.6, + "heatindex_f": 49.4, + "dewpoint_c": 8.6, + "dewpoint_f": 47.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 10.2, + "gust_kph": 16.5, + "uv": 0 + }, + { + "time_epoch": 1732572000, + "time": "2024-11-25 23:00", + "temp_c": 9.5, + "temp_f": 49.1, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 4.9, + "wind_kph": 7.9, + "wind_degree": 198, + "wind_dir": "SSW", + "pressure_mb": 1023, + "pressure_in": 30.2, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 93, + "cloud": 26, + "feelslike_c": 8.4, + "feelslike_f": 47.2, + "windchill_c": 8.4, + "windchill_f": 47.2, + "heatindex_c": 9.5, + "heatindex_f": 49.1, + "dewpoint_c": 8.5, + "dewpoint_f": 47.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 9.8, + "gust_kph": 15.8, + "uv": 0 + } + ] + } + ] + } +} \ No newline at end of file diff --git a/src/test/resources/WeatherAPI/Bordeaux-3-partial-sunny-rain.json b/src/test/resources/WeatherAPI/Bordeaux-3-partial-sunny-rain.json new file mode 100644 index 0000000..6e10f39 --- /dev/null +++ b/src/test/resources/WeatherAPI/Bordeaux-3-partial-sunny-rain.json @@ -0,0 +1,3057 @@ +{ + "location": { + "name": "Bordeaux", + "region": "Aquitaine", + "country": "France", + "lat": 44.8333, + "lon": -0.5667, + "tz_id": "Europe/Paris", + "localtime_epoch": 1732356248, + "localtime": "2024-11-23 11:04" + }, + "current": { + "last_updated_epoch": 1732356000, + "last_updated": "2024-11-23 11:00", + "temp_c": 7.4, + "temp_f": 45.3, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 11.4, + "wind_kph": 18.4, + "wind_degree": 148, + "wind_dir": "SSE", + "pressure_mb": 1018, + "pressure_in": 30.06, + "precip_mm": 0, + "precip_in": 0, + "humidity": 76, + "cloud": 0, + "feelslike_c": 4.3, + "feelslike_f": 39.7, + "windchill_c": 6, + "windchill_f": 42.7, + "heatindex_c": 8.7, + "heatindex_f": 47.7, + "dewpoint_c": 1.3, + "dewpoint_f": 34.3, + "vis_km": 10, + "vis_miles": 6, + "uv": 0.9, + "gust_mph": 15.9, + "gust_kph": 25.5 + }, + "forecast": { + "forecastday": [ + { + "date": "2024-11-23", + "date_epoch": 1732320000, + "day": { + "maxtemp_c": 13.1, + "maxtemp_f": 55.7, + "mintemp_c": 4, + "mintemp_f": 39.3, + "avgtemp_c": 8.1, + "avgtemp_f": 46.6, + "maxwind_mph": 13, + "maxwind_kph": 20.9, + "totalprecip_mm": 0, + "totalprecip_in": 0, + "totalsnow_cm": 0, + "avgvis_km": 10, + "avgvis_miles": 6, + "avghumidity": 69, + "daily_will_it_rain": 0, + "daily_chance_of_rain": 0, + "daily_will_it_snow": 0, + "daily_chance_of_snow": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/day/116.png", + "code": 1003 + }, + "uv": 0.3 + }, + "astro": { + "sunrise": "08:10 AM", + "sunset": "05:27 PM", + "moonrise": "12:08 AM", + "moonset": "02:13 PM", + "moon_phase": "Last Quarter", + "moon_illumination": 51, + "is_moon_up": 0, + "is_sun_up": 0 + }, + "hour": [ + { + "time_epoch": 1732316400, + "time": "2024-11-23 00:00", + "temp_c": 5.6, + "temp_f": 42, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 6.7, + "wind_kph": 10.8, + "wind_degree": 150, + "wind_dir": "SSE", + "pressure_mb": 1022, + "pressure_in": 30.19, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 81, + "cloud": 17, + "feelslike_c": 3.2, + "feelslike_f": 37.7, + "windchill_c": 3.2, + "windchill_f": 37.7, + "heatindex_c": 5.6, + "heatindex_f": 42, + "dewpoint_c": 2.6, + "dewpoint_f": 36.6, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 13.7, + "gust_kph": 22, + "uv": 0 + }, + { + "time_epoch": 1732320000, + "time": "2024-11-23 01:00", + "temp_c": 5.8, + "temp_f": 42.4, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 6.9, + "wind_kph": 11.2, + "wind_degree": 141, + "wind_dir": "SE", + "pressure_mb": 1022, + "pressure_in": 30.17, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 80, + "cloud": 47, + "feelslike_c": 3.3, + "feelslike_f": 38, + "windchill_c": 3.3, + "windchill_f": 38, + "heatindex_c": 5.8, + "heatindex_f": 42.4, + "dewpoint_c": 2.5, + "dewpoint_f": 36.6, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 13.4, + "gust_kph": 21.6, + "uv": 0 + }, + { + "time_epoch": 1732323600, + "time": "2024-11-23 02:00", + "temp_c": 5.6, + "temp_f": 42.1, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 8.3, + "wind_kph": 13.3, + "wind_degree": 137, + "wind_dir": "SE", + "pressure_mb": 1021, + "pressure_in": 30.14, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 79, + "cloud": 56, + "feelslike_c": 2.8, + "feelslike_f": 37, + "windchill_c": 2.8, + "windchill_f": 37, + "heatindex_c": 5.6, + "heatindex_f": 42.1, + "dewpoint_c": 2.3, + "dewpoint_f": 36.1, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 15.3, + "gust_kph": 24.7, + "uv": 0 + }, + { + "time_epoch": 1732327200, + "time": "2024-11-23 03:00", + "temp_c": 5.1, + "temp_f": 41.3, + "is_day": 0, + "condition": { + "text": "Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/119.png", + "code": 1006 + }, + "wind_mph": 9.8, + "wind_kph": 15.8, + "wind_degree": 133, + "wind_dir": "SE", + "pressure_mb": 1020, + "pressure_in": 30.13, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 79, + "cloud": 86, + "feelslike_c": 1.8, + "feelslike_f": 35.3, + "windchill_c": 1.8, + "windchill_f": 35.3, + "heatindex_c": 5.2, + "heatindex_f": 41.3, + "dewpoint_c": 1.9, + "dewpoint_f": 35.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 17.6, + "gust_kph": 28.4, + "uv": 0 + }, + { + "time_epoch": 1732330800, + "time": "2024-11-23 04:00", + "temp_c": 4.8, + "temp_f": 40.7, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 10.5, + "wind_kph": 16.9, + "wind_degree": 130, + "wind_dir": "SE", + "pressure_mb": 1020, + "pressure_in": 30.12, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 79, + "cloud": 44, + "feelslike_c": 1.3, + "feelslike_f": 34.3, + "windchill_c": 1.3, + "windchill_f": 34.3, + "heatindex_c": 4.9, + "heatindex_f": 40.7, + "dewpoint_c": 1.5, + "dewpoint_f": 34.7, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 18.1, + "gust_kph": 29.1, + "uv": 0 + }, + { + "time_epoch": 1732334400, + "time": "2024-11-23 05:00", + "temp_c": 4.3, + "temp_f": 39.7, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 11, + "wind_kph": 17.6, + "wind_degree": 130, + "wind_dir": "SE", + "pressure_mb": 1019, + "pressure_in": 30.1, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 80, + "cloud": 46, + "feelslike_c": 0.5, + "feelslike_f": 32.8, + "windchill_c": 0.5, + "windchill_f": 32.8, + "heatindex_c": 4.3, + "heatindex_f": 39.7, + "dewpoint_c": 1.2, + "dewpoint_f": 34.1, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 19.1, + "gust_kph": 30.8, + "uv": 0 + }, + { + "time_epoch": 1732338000, + "time": "2024-11-23 06:00", + "temp_c": 4, + "temp_f": 39.3, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 11.2, + "wind_kph": 18, + "wind_degree": 138, + "wind_dir": "SE", + "pressure_mb": 1019, + "pressure_in": 30.08, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 80, + "cloud": 39, + "feelslike_c": 0.1, + "feelslike_f": 32.2, + "windchill_c": 0.1, + "windchill_f": 32.2, + "heatindex_c": 4, + "heatindex_f": 39.3, + "dewpoint_c": 0.9, + "dewpoint_f": 33.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 19.8, + "gust_kph": 31.8, + "uv": 0 + }, + { + "time_epoch": 1732341600, + "time": "2024-11-23 07:00", + "temp_c": 4, + "temp_f": 39.3, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 11.2, + "wind_kph": 18, + "wind_degree": 138, + "wind_dir": "SE", + "pressure_mb": 1019, + "pressure_in": 30.08, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 78, + "cloud": 26, + "feelslike_c": 0.1, + "feelslike_f": 32.2, + "windchill_c": 0.1, + "windchill_f": 32.2, + "heatindex_c": 4, + "heatindex_f": 39.3, + "dewpoint_c": 0.6, + "dewpoint_f": 33, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 19.9, + "gust_kph": 32, + "uv": 0 + }, + { + "time_epoch": 1732345200, + "time": "2024-11-23 08:00", + "temp_c": 4.1, + "temp_f": 39.4, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 10.5, + "wind_kph": 16.9, + "wind_degree": 140, + "wind_dir": "SE", + "pressure_mb": 1019, + "pressure_in": 30.09, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 75, + "cloud": 7, + "feelslike_c": 0.4, + "feelslike_f": 32.6, + "windchill_c": 0.4, + "windchill_f": 32.6, + "heatindex_c": 4.1, + "heatindex_f": 39.4, + "dewpoint_c": 0.1, + "dewpoint_f": 32.1, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 18.8, + "gust_kph": 30.2, + "uv": 0 + }, + { + "time_epoch": 1732348800, + "time": "2024-11-23 09:00", + "temp_c": 4.6, + "temp_f": 40.3, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 10.7, + "wind_kph": 17.3, + "wind_degree": 143, + "wind_dir": "SE", + "pressure_mb": 1019, + "pressure_in": 30.09, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 71, + "cloud": 13, + "feelslike_c": 1, + "feelslike_f": 33.7, + "windchill_c": 1, + "windchill_f": 33.7, + "heatindex_c": 4.6, + "heatindex_f": 40.4, + "dewpoint_c": -0.2, + "dewpoint_f": 31.7, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 19, + "gust_kph": 30.5, + "uv": 0 + }, + { + "time_epoch": 1732352400, + "time": "2024-11-23 10:00", + "temp_c": 6.6, + "temp_f": 43.9, + "is_day": 1, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/day/116.png", + "code": 1003 + }, + "wind_mph": 11.2, + "wind_kph": 18, + "wind_degree": 146, + "wind_dir": "SSE", + "pressure_mb": 1019, + "pressure_in": 30.08, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 63, + "cloud": 49, + "feelslike_c": 3.4, + "feelslike_f": 38, + "windchill_c": 3.4, + "windchill_f": 38, + "heatindex_c": 6.6, + "heatindex_f": 43.9, + "dewpoint_c": 0.2, + "dewpoint_f": 32.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 17.5, + "gust_kph": 28.2, + "uv": 0.4 + }, + { + "time_epoch": 1732356000, + "time": "2024-11-23 11:00", + "temp_c": 7.4, + "temp_f": 45.3, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 11.4, + "wind_kph": 18.4, + "wind_degree": 148, + "wind_dir": "SSE", + "pressure_mb": 1018, + "pressure_in": 30.06, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 76, + "cloud": 0, + "feelslike_c": 6, + "feelslike_f": 42.7, + "windchill_c": 6, + "windchill_f": 42.7, + "heatindex_c": 8.7, + "heatindex_f": 47.7, + "dewpoint_c": 1.3, + "dewpoint_f": 34.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 15.9, + "gust_kph": 25.5, + "uv": 0.9 + }, + { + "time_epoch": 1732359600, + "time": "2024-11-23 12:00", + "temp_c": 10.4, + "temp_f": 50.8, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 11, + "wind_kph": 17.6, + "wind_degree": 149, + "wind_dir": "SSE", + "pressure_mb": 1018, + "pressure_in": 30.07, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 56, + "cloud": 21, + "feelslike_c": 8.2, + "feelslike_f": 46.7, + "windchill_c": 8.2, + "windchill_f": 46.7, + "heatindex_c": 10.5, + "heatindex_f": 50.8, + "dewpoint_c": 2.2, + "dewpoint_f": 35.9, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 14.7, + "gust_kph": 23.6, + "uv": 1.3 + }, + { + "time_epoch": 1732363200, + "time": "2024-11-23 13:00", + "temp_c": 12.3, + "temp_f": 54.2, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 11.4, + "wind_kph": 18.4, + "wind_degree": 150, + "wind_dir": "SSE", + "pressure_mb": 1017, + "pressure_in": 30.04, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 52, + "cloud": 22, + "feelslike_c": 10.5, + "feelslike_f": 50.8, + "windchill_c": 10.5, + "windchill_f": 50.8, + "heatindex_c": 12.3, + "heatindex_f": 54.2, + "dewpoint_c": 2.8, + "dewpoint_f": 37, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 14.9, + "gust_kph": 23.9, + "uv": 1.6 + }, + { + "time_epoch": 1732366800, + "time": "2024-11-23 14:00", + "temp_c": 13, + "temp_f": 55.4, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 10.3, + "wind_kph": 16.6, + "wind_degree": 149, + "wind_dir": "SSE", + "pressure_mb": 1017, + "pressure_in": 30.03, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 54, + "cloud": 12, + "feelslike_c": 11.5, + "feelslike_f": 52.7, + "windchill_c": 11.5, + "windchill_f": 52.7, + "heatindex_c": 13, + "heatindex_f": 55.4, + "dewpoint_c": 3.8, + "dewpoint_f": 38.9, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 14, + "gust_kph": 22.6, + "uv": 1.4 + }, + { + "time_epoch": 1732370400, + "time": "2024-11-23 15:00", + "temp_c": 13.1, + "temp_f": 55.7, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 11.4, + "wind_kph": 18.4, + "wind_degree": 150, + "wind_dir": "SSE", + "pressure_mb": 1016, + "pressure_in": 30.01, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 54, + "cloud": 22, + "feelslike_c": 11.5, + "feelslike_f": 52.7, + "windchill_c": 11.5, + "windchill_f": 52.7, + "heatindex_c": 13.2, + "heatindex_f": 55.7, + "dewpoint_c": 4.2, + "dewpoint_f": 39.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 16.8, + "gust_kph": 27.1, + "uv": 0.9 + }, + { + "time_epoch": 1732374000, + "time": "2024-11-23 16:00", + "temp_c": 12.5, + "temp_f": 54.4, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 10.7, + "wind_kph": 17.3, + "wind_degree": 148, + "wind_dir": "SSE", + "pressure_mb": 1017, + "pressure_in": 30.02, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 57, + "cloud": 14, + "feelslike_c": 10.7, + "feelslike_f": 51.3, + "windchill_c": 10.7, + "windchill_f": 51.3, + "heatindex_c": 12.5, + "heatindex_f": 54.4, + "dewpoint_c": 4.1, + "dewpoint_f": 39.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 17.5, + "gust_kph": 28.1, + "uv": 0.4 + }, + { + "time_epoch": 1732377600, + "time": "2024-11-23 17:00", + "temp_c": 11.2, + "temp_f": 52.1, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 11.2, + "wind_kph": 18, + "wind_degree": 141, + "wind_dir": "SE", + "pressure_mb": 1017, + "pressure_in": 30.02, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 61, + "cloud": 22, + "feelslike_c": 9, + "feelslike_f": 48.2, + "windchill_c": 9, + "windchill_f": 48.2, + "heatindex_c": 11.2, + "heatindex_f": 52.1, + "dewpoint_c": 4, + "dewpoint_f": 39.2, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 20.2, + "gust_kph": 32.5, + "uv": 0 + }, + { + "time_epoch": 1732381200, + "time": "2024-11-23 18:00", + "temp_c": 10.2, + "temp_f": 50.4, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 11.6, + "wind_kph": 18.7, + "wind_degree": 141, + "wind_dir": "SE", + "pressure_mb": 1016, + "pressure_in": 30.02, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 64, + "cloud": 47, + "feelslike_c": 7.8, + "feelslike_f": 46, + "windchill_c": 7.8, + "windchill_f": 46, + "heatindex_c": 10.2, + "heatindex_f": 50.4, + "dewpoint_c": 3.8, + "dewpoint_f": 38.9, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 22.2, + "gust_kph": 35.7, + "uv": 0 + }, + { + "time_epoch": 1732384800, + "time": "2024-11-23 19:00", + "temp_c": 10, + "temp_f": 50.1, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 12.3, + "wind_kph": 19.8, + "wind_degree": 144, + "wind_dir": "SE", + "pressure_mb": 1017, + "pressure_in": 30.02, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 66, + "cloud": 62, + "feelslike_c": 7.5, + "feelslike_f": 45.4, + "windchill_c": 7.5, + "windchill_f": 45.4, + "heatindex_c": 10, + "heatindex_f": 50.1, + "dewpoint_c": 4, + "dewpoint_f": 39.2, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 22.8, + "gust_kph": 36.8, + "uv": 0 + }, + { + "time_epoch": 1732388400, + "time": "2024-11-23 20:00", + "temp_c": 10, + "temp_f": 50, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 12.5, + "wind_kph": 20.2, + "wind_degree": 143, + "wind_dir": "SE", + "pressure_mb": 1017, + "pressure_in": 30.03, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 68, + "cloud": 57, + "feelslike_c": 7.3, + "feelslike_f": 45.2, + "windchill_c": 7.3, + "windchill_f": 45.2, + "heatindex_c": 10, + "heatindex_f": 50, + "dewpoint_c": 4.3, + "dewpoint_f": 39.8, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 22.6, + "gust_kph": 36.3, + "uv": 0 + }, + { + "time_epoch": 1732392000, + "time": "2024-11-23 21:00", + "temp_c": 9.7, + "temp_f": 49.5, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 12.5, + "wind_kph": 20.2, + "wind_degree": 142, + "wind_dir": "SE", + "pressure_mb": 1017, + "pressure_in": 30.03, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 71, + "cloud": 46, + "feelslike_c": 7, + "feelslike_f": 44.6, + "windchill_c": 7, + "windchill_f": 44.6, + "heatindex_c": 9.7, + "heatindex_f": 49.5, + "dewpoint_c": 4.6, + "dewpoint_f": 40.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 22.3, + "gust_kph": 35.9, + "uv": 0 + }, + { + "time_epoch": 1732395600, + "time": "2024-11-23 22:00", + "temp_c": 9.5, + "temp_f": 49.1, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 13, + "wind_kph": 20.9, + "wind_degree": 139, + "wind_dir": "SE", + "pressure_mb": 1017, + "pressure_in": 30.03, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 73, + "cloud": 24, + "feelslike_c": 6.6, + "feelslike_f": 44, + "windchill_c": 6.6, + "windchill_f": 44, + "heatindex_c": 9.5, + "heatindex_f": 49.1, + "dewpoint_c": 5, + "dewpoint_f": 41, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 23, + "gust_kph": 37, + "uv": 0 + }, + { + "time_epoch": 1732399200, + "time": "2024-11-23 23:00", + "temp_c": 9.3, + "temp_f": 48.8, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 12.8, + "wind_kph": 20.5, + "wind_degree": 140, + "wind_dir": "SE", + "pressure_mb": 1017, + "pressure_in": 30.03, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 76, + "cloud": 21, + "feelslike_c": 6.5, + "feelslike_f": 43.7, + "windchill_c": 6.5, + "windchill_f": 43.7, + "heatindex_c": 9.3, + "heatindex_f": 48.8, + "dewpoint_c": 5.3, + "dewpoint_f": 41.6, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 23, + "gust_kph": 37, + "uv": 0 + } + ] + }, + { + "date": "2024-11-24", + "date_epoch": 1732406400, + "day": { + "maxtemp_c": 17.2, + "maxtemp_f": 63, + "mintemp_c": 9.3, + "mintemp_f": 48.8, + "avgtemp_c": 13, + "avgtemp_f": 55.3, + "maxwind_mph": 16.3, + "maxwind_kph": 26.3, + "totalprecip_mm": 0, + "totalprecip_in": 0, + "totalsnow_cm": 0, + "avgvis_km": 10, + "avgvis_miles": 6, + "avghumidity": 70, + "daily_will_it_rain": 0, + "daily_chance_of_rain": 0, + "daily_will_it_snow": 0, + "daily_chance_of_snow": 0, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "uv": 0.3 + }, + "astro": { + "sunrise": "08:12 AM", + "sunset": "05:26 PM", + "moonrise": "01:14 AM", + "moonset": "02:30 PM", + "moon_phase": "Waning Crescent", + "moon_illumination": 41, + "is_moon_up": 0, + "is_sun_up": 0 + }, + "hour": [ + { + "time_epoch": 1732402800, + "time": "2024-11-24 00:00", + "temp_c": 9.3, + "temp_f": 48.8, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 12.5, + "wind_kph": 20.2, + "wind_degree": 140, + "wind_dir": "SE", + "pressure_mb": 1017, + "pressure_in": 30.03, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 77, + "cloud": 41, + "feelslike_c": 6.5, + "feelslike_f": 43.8, + "windchill_c": 6.5, + "windchill_f": 43.8, + "heatindex_c": 9.3, + "heatindex_f": 48.8, + "dewpoint_c": 5.6, + "dewpoint_f": 42, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 23.1, + "gust_kph": 37.2, + "uv": 0 + }, + { + "time_epoch": 1732406400, + "time": "2024-11-24 01:00", + "temp_c": 9.5, + "temp_f": 49.1, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 12.5, + "wind_kph": 20.2, + "wind_degree": 139, + "wind_dir": "SE", + "pressure_mb": 1017, + "pressure_in": 30.02, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 77, + "cloud": 33, + "feelslike_c": 6.7, + "feelslike_f": 44.1, + "windchill_c": 6.7, + "windchill_f": 44.1, + "heatindex_c": 9.5, + "heatindex_f": 49.1, + "dewpoint_c": 5.7, + "dewpoint_f": 42.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 23.4, + "gust_kph": 37.7, + "uv": 0 + }, + { + "time_epoch": 1732410000, + "time": "2024-11-24 02:00", + "temp_c": 9.7, + "temp_f": 49.4, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 12.3, + "wind_kph": 19.8, + "wind_degree": 139, + "wind_dir": "SE", + "pressure_mb": 1016, + "pressure_in": 30, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 77, + "cloud": 22, + "feelslike_c": 7, + "feelslike_f": 44.5, + "windchill_c": 7, + "windchill_f": 44.5, + "heatindex_c": 9.7, + "heatindex_f": 49.4, + "dewpoint_c": 5.8, + "dewpoint_f": 42.4, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 23.2, + "gust_kph": 37.3, + "uv": 0 + }, + { + "time_epoch": 1732413600, + "time": "2024-11-24 03:00", + "temp_c": 9.7, + "temp_f": 49.5, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 12.8, + "wind_kph": 20.5, + "wind_degree": 138, + "wind_dir": "SE", + "pressure_mb": 1016, + "pressure_in": 30, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 76, + "cloud": 14, + "feelslike_c": 7, + "feelslike_f": 44.6, + "windchill_c": 7, + "windchill_f": 44.6, + "heatindex_c": 9.7, + "heatindex_f": 49.5, + "dewpoint_c": 5.7, + "dewpoint_f": 42.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 24.3, + "gust_kph": 39.2, + "uv": 0 + }, + { + "time_epoch": 1732417200, + "time": "2024-11-24 04:00", + "temp_c": 9.8, + "temp_f": 49.6, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 13, + "wind_kph": 20.9, + "wind_degree": 137, + "wind_dir": "SE", + "pressure_mb": 1015, + "pressure_in": 29.98, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 75, + "cloud": 10, + "feelslike_c": 7, + "feelslike_f": 44.6, + "windchill_c": 7, + "windchill_f": 44.6, + "heatindex_c": 9.8, + "heatindex_f": 49.6, + "dewpoint_c": 5.7, + "dewpoint_f": 42.2, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 24.6, + "gust_kph": 39.6, + "uv": 0 + }, + { + "time_epoch": 1732420800, + "time": "2024-11-24 05:00", + "temp_c": 9.7, + "temp_f": 49.4, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 13.4, + "wind_kph": 21.6, + "wind_degree": 136, + "wind_dir": "SE", + "pressure_mb": 1015, + "pressure_in": 29.98, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 76, + "cloud": 10, + "feelslike_c": 6.8, + "feelslike_f": 44.3, + "windchill_c": 6.8, + "windchill_f": 44.3, + "heatindex_c": 9.7, + "heatindex_f": 49.4, + "dewpoint_c": 5.7, + "dewpoint_f": 42.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 25.3, + "gust_kph": 40.7, + "uv": 0 + }, + { + "time_epoch": 1732424400, + "time": "2024-11-24 06:00", + "temp_c": 9.7, + "temp_f": 49.5, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 13.6, + "wind_kph": 22, + "wind_degree": 137, + "wind_dir": "SE", + "pressure_mb": 1015, + "pressure_in": 29.98, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 77, + "cloud": 11, + "feelslike_c": 6.8, + "feelslike_f": 44.3, + "windchill_c": 6.8, + "windchill_f": 44.3, + "heatindex_c": 9.7, + "heatindex_f": 49.5, + "dewpoint_c": 5.8, + "dewpoint_f": 42.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 25.7, + "gust_kph": 41.4, + "uv": 0 + }, + { + "time_epoch": 1732428000, + "time": "2024-11-24 07:00", + "temp_c": 9.7, + "temp_f": 49.4, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 13.6, + "wind_kph": 22, + "wind_degree": 138, + "wind_dir": "SE", + "pressure_mb": 1015, + "pressure_in": 29.97, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 78, + "cloud": 8, + "feelslike_c": 6.8, + "feelslike_f": 44.2, + "windchill_c": 6.8, + "windchill_f": 44.2, + "heatindex_c": 9.7, + "heatindex_f": 49.4, + "dewpoint_c": 6, + "dewpoint_f": 42.7, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 25.5, + "gust_kph": 41, + "uv": 0 + }, + { + "time_epoch": 1732431600, + "time": "2024-11-24 08:00", + "temp_c": 9.7, + "temp_f": 49.5, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 13.2, + "wind_kph": 21.2, + "wind_degree": 138, + "wind_dir": "SE", + "pressure_mb": 1015, + "pressure_in": 29.98, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 78, + "cloud": 7, + "feelslike_c": 6.9, + "feelslike_f": 44.4, + "windchill_c": 6.9, + "windchill_f": 44.4, + "heatindex_c": 9.7, + "heatindex_f": 49.5, + "dewpoint_c": 6.1, + "dewpoint_f": 43, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 24.8, + "gust_kph": 40, + "uv": 0 + }, + { + "time_epoch": 1732435200, + "time": "2024-11-24 09:00", + "temp_c": 10.1, + "temp_f": 50.1, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 13.6, + "wind_kph": 22, + "wind_degree": 137, + "wind_dir": "SE", + "pressure_mb": 1015, + "pressure_in": 29.98, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 78, + "cloud": 7, + "feelslike_c": 7.3, + "feelslike_f": 45.1, + "windchill_c": 7.3, + "windchill_f": 45.1, + "heatindex_c": 10.1, + "heatindex_f": 50.1, + "dewpoint_c": 6.4, + "dewpoint_f": 43.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 24.5, + "gust_kph": 39.5, + "uv": 0 + }, + { + "time_epoch": 1732438800, + "time": "2024-11-24 10:00", + "temp_c": 11.4, + "temp_f": 52.5, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 14.5, + "wind_kph": 23.4, + "wind_degree": 137, + "wind_dir": "SE", + "pressure_mb": 1015, + "pressure_in": 29.97, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 74, + "cloud": 5, + "feelslike_c": 8.9, + "feelslike_f": 47.9, + "windchill_c": 8.9, + "windchill_f": 47.9, + "heatindex_c": 11.4, + "heatindex_f": 52.5, + "dewpoint_c": 7, + "dewpoint_f": 44.6, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 23.2, + "gust_kph": 37.3, + "uv": 0.4 + }, + { + "time_epoch": 1732442400, + "time": "2024-11-24 11:00", + "temp_c": 13.2, + "temp_f": 55.7, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 15.7, + "wind_kph": 25.2, + "wind_degree": 137, + "wind_dir": "SE", + "pressure_mb": 1015, + "pressure_in": 29.96, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 70, + "cloud": 4, + "feelslike_c": 11, + "feelslike_f": 51.8, + "windchill_c": 11, + "windchill_f": 51.8, + "heatindex_c": 13.2, + "heatindex_f": 55.7, + "dewpoint_c": 7.8, + "dewpoint_f": 46, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 22.5, + "gust_kph": 36.3, + "uv": 0.8 + }, + { + "time_epoch": 1732446000, + "time": "2024-11-24 12:00", + "temp_c": 15, + "temp_f": 58.9, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 16.1, + "wind_kph": 25.9, + "wind_degree": 140, + "wind_dir": "SE", + "pressure_mb": 1014, + "pressure_in": 29.95, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 65, + "cloud": 4, + "feelslike_c": 13.3, + "feelslike_f": 55.9, + "windchill_c": 13.3, + "windchill_f": 55.9, + "heatindex_c": 15, + "heatindex_f": 58.9, + "dewpoint_c": 8.4, + "dewpoint_f": 47.1, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 21.9, + "gust_kph": 35.2, + "uv": 1.3 + }, + { + "time_epoch": 1732449600, + "time": "2024-11-24 13:00", + "temp_c": 16.4, + "temp_f": 61.6, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 16.1, + "wind_kph": 25.9, + "wind_degree": 142, + "wind_dir": "SE", + "pressure_mb": 1013, + "pressure_in": 29.91, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 60, + "cloud": 5, + "feelslike_c": 16.4, + "feelslike_f": 61.6, + "windchill_c": 16.4, + "windchill_f": 61.6, + "heatindex_c": 16.4, + "heatindex_f": 61.6, + "dewpoint_c": 8.8, + "dewpoint_f": 47.8, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 21.6, + "gust_kph": 34.7, + "uv": 1.4 + }, + { + "time_epoch": 1732453200, + "time": "2024-11-24 14:00", + "temp_c": 17.2, + "temp_f": 62.9, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 16.3, + "wind_kph": 26.3, + "wind_degree": 143, + "wind_dir": "SE", + "pressure_mb": 1012, + "pressure_in": 29.89, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 59, + "cloud": 4, + "feelslike_c": 17.2, + "feelslike_f": 62.9, + "windchill_c": 17.2, + "windchill_f": 62.9, + "heatindex_c": 17.2, + "heatindex_f": 62.9, + "dewpoint_c": 9, + "dewpoint_f": 48.2, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 22.9, + "gust_kph": 36.8, + "uv": 1.3 + }, + { + "time_epoch": 1732456800, + "time": "2024-11-24 15:00", + "temp_c": 17.2, + "temp_f": 63, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 16.3, + "wind_kph": 26.3, + "wind_degree": 145, + "wind_dir": "SE", + "pressure_mb": 1012, + "pressure_in": 29.87, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 59, + "cloud": 1, + "feelslike_c": 17.2, + "feelslike_f": 63, + "windchill_c": 17.2, + "windchill_f": 63, + "heatindex_c": 17.2, + "heatindex_f": 63, + "dewpoint_c": 9.2, + "dewpoint_f": 48.6, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 25, + "gust_kph": 40.2, + "uv": 0.8 + }, + { + "time_epoch": 1732460400, + "time": "2024-11-24 16:00", + "temp_c": 16.6, + "temp_f": 61.9, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 15.7, + "wind_kph": 25.2, + "wind_degree": 147, + "wind_dir": "SSE", + "pressure_mb": 1011, + "pressure_in": 29.86, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 61, + "cloud": 3, + "feelslike_c": 16.6, + "feelslike_f": 61.9, + "windchill_c": 16.6, + "windchill_f": 61.9, + "heatindex_c": 16.6, + "heatindex_f": 61.9, + "dewpoint_c": 9.2, + "dewpoint_f": 48.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 25.9, + "gust_kph": 41.7, + "uv": 0.4 + }, + { + "time_epoch": 1732464000, + "time": "2024-11-24 17:00", + "temp_c": 15.7, + "temp_f": 60.3, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 15.4, + "wind_kph": 24.8, + "wind_degree": 147, + "wind_dir": "SSE", + "pressure_mb": 1011, + "pressure_in": 29.86, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 65, + "cloud": 22, + "feelslike_c": 15.7, + "feelslike_f": 60.3, + "windchill_c": 15.7, + "windchill_f": 60.3, + "heatindex_c": 15.7, + "heatindex_f": 60.3, + "dewpoint_c": 9.1, + "dewpoint_f": 48.4, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 27, + "gust_kph": 43.5, + "uv": 0 + }, + { + "time_epoch": 1732467600, + "time": "2024-11-24 18:00", + "temp_c": 15.3, + "temp_f": 59.5, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 15.4, + "wind_kph": 24.8, + "wind_degree": 149, + "wind_dir": "SSE", + "pressure_mb": 1011, + "pressure_in": 29.86, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 66, + "cloud": 15, + "feelslike_c": 15.3, + "feelslike_f": 59.5, + "windchill_c": 15.3, + "windchill_f": 59.5, + "heatindex_c": 15.3, + "heatindex_f": 59.5, + "dewpoint_c": 9.1, + "dewpoint_f": 48.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 27.8, + "gust_kph": 44.8, + "uv": 0 + }, + { + "time_epoch": 1732471200, + "time": "2024-11-24 19:00", + "temp_c": 15.2, + "temp_f": 59.4, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 15.7, + "wind_kph": 25.2, + "wind_degree": 150, + "wind_dir": "SSE", + "pressure_mb": 1011, + "pressure_in": 29.86, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 67, + "cloud": 47, + "feelslike_c": 15.2, + "feelslike_f": 59.4, + "windchill_c": 15.2, + "windchill_f": 59.4, + "heatindex_c": 15.2, + "heatindex_f": 59.4, + "dewpoint_c": 9.1, + "dewpoint_f": 48.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 28.4, + "gust_kph": 45.7, + "uv": 0 + }, + { + "time_epoch": 1732474800, + "time": "2024-11-24 20:00", + "temp_c": 15.2, + "temp_f": 59.4, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 14.8, + "wind_kph": 23.8, + "wind_degree": 149, + "wind_dir": "SSE", + "pressure_mb": 1012, + "pressure_in": 29.87, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 67, + "cloud": 56, + "feelslike_c": 15.2, + "feelslike_f": 59.4, + "windchill_c": 15.2, + "windchill_f": 59.4, + "heatindex_c": 15.2, + "heatindex_f": 59.4, + "dewpoint_c": 9.2, + "dewpoint_f": 48.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 26.5, + "gust_kph": 42.6, + "uv": 0 + }, + { + "time_epoch": 1732478400, + "time": "2024-11-24 21:00", + "temp_c": 15.4, + "temp_f": 59.7, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 14.3, + "wind_kph": 23, + "wind_degree": 151, + "wind_dir": "SSE", + "pressure_mb": 1011, + "pressure_in": 29.87, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 67, + "cloud": 49, + "feelslike_c": 15.4, + "feelslike_f": 59.7, + "windchill_c": 15.4, + "windchill_f": 59.7, + "heatindex_c": 15.4, + "heatindex_f": 59.7, + "dewpoint_c": 9.3, + "dewpoint_f": 48.8, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 24.7, + "gust_kph": 39.8, + "uv": 0 + }, + { + "time_epoch": 1732482000, + "time": "2024-11-24 22:00", + "temp_c": 15.3, + "temp_f": 59.5, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 13, + "wind_kph": 20.9, + "wind_degree": 155, + "wind_dir": "SSE", + "pressure_mb": 1011, + "pressure_in": 29.87, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 67, + "cloud": 47, + "feelslike_c": 15.3, + "feelslike_f": 59.5, + "windchill_c": 15.3, + "windchill_f": 59.5, + "heatindex_c": 15.3, + "heatindex_f": 59.5, + "dewpoint_c": 9.2, + "dewpoint_f": 48.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 22.7, + "gust_kph": 36.5, + "uv": 0 + }, + { + "time_epoch": 1732485600, + "time": "2024-11-24 23:00", + "temp_c": 15.3, + "temp_f": 59.5, + "is_day": 0, + "condition": { + "text": "Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/119.png", + "code": 1006 + }, + "wind_mph": 13.4, + "wind_kph": 21.6, + "wind_degree": 151, + "wind_dir": "SSE", + "pressure_mb": 1011, + "pressure_in": 29.87, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 65, + "cloud": 66, + "feelslike_c": 15.3, + "feelslike_f": 59.5, + "windchill_c": 15.3, + "windchill_f": 59.5, + "heatindex_c": 15.3, + "heatindex_f": 59.5, + "dewpoint_c": 8.8, + "dewpoint_f": 47.9, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 23.8, + "gust_kph": 38.3, + "uv": 0 + } + ] + }, + { + "date": "2024-11-25", + "date_epoch": 1732492800, + "day": { + "maxtemp_c": 15.5, + "maxtemp_f": 59.8, + "mintemp_c": 9.5, + "mintemp_f": 49.1, + "avgtemp_c": 12.7, + "avgtemp_f": 54.8, + "maxwind_mph": 15, + "maxwind_kph": 24.1, + "totalprecip_mm": 7.56, + "totalprecip_in": 0.3, + "totalsnow_cm": 0, + "avgvis_km": 8.6, + "avgvis_miles": 5, + "avghumidity": 80, + "daily_will_it_rain": 1, + "daily_chance_of_rain": 89, + "daily_will_it_snow": 0, + "daily_chance_of_snow": 0, + "condition": { + "text": "Moderate rain", + "icon": "//cdn.weatherapi.com/weather/64x64/day/302.png", + "code": 1189 + }, + "uv": 0 + }, + "astro": { + "sunrise": "08:13 AM", + "sunset": "05:26 PM", + "moonrise": "02:17 AM", + "moonset": "02:46 PM", + "moon_phase": "Waning Crescent", + "moon_illumination": 32, + "is_moon_up": 0, + "is_sun_up": 0 + }, + "hour": [ + { + "time_epoch": 1732489200, + "time": "2024-11-25 00:00", + "temp_c": 15.5, + "temp_f": 59.8, + "is_day": 0, + "condition": { + "text": "Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/119.png", + "code": 1006 + }, + "wind_mph": 13.4, + "wind_kph": 21.6, + "wind_degree": 153, + "wind_dir": "SSE", + "pressure_mb": 1011, + "pressure_in": 29.85, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 63, + "cloud": 66, + "feelslike_c": 15.5, + "feelslike_f": 59.8, + "windchill_c": 15.5, + "windchill_f": 59.8, + "heatindex_c": 15.5, + "heatindex_f": 59.8, + "dewpoint_c": 8.5, + "dewpoint_f": 47.2, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 24.7, + "gust_kph": 39.7, + "uv": 0 + }, + { + "time_epoch": 1732492800, + "time": "2024-11-25 01:00", + "temp_c": 15.3, + "temp_f": 59.5, + "is_day": 0, + "condition": { + "text": "Patchy rain nearby", + "icon": "//cdn.weatherapi.com/weather/64x64/night/176.png", + "code": 1063 + }, + "wind_mph": 15, + "wind_kph": 24.1, + "wind_degree": 150, + "wind_dir": "SSE", + "pressure_mb": 1011, + "pressure_in": 29.86, + "precip_mm": 0.04, + "precip_in": 0, + "snow_cm": 0, + "humidity": 64, + "cloud": 56, + "feelslike_c": 15.3, + "feelslike_f": 59.5, + "windchill_c": 15.3, + "windchill_f": 59.5, + "heatindex_c": 15.3, + "heatindex_f": 59.5, + "dewpoint_c": 8.5, + "dewpoint_f": 47.3, + "will_it_rain": 0, + "chance_of_rain": 69, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 26.1, + "gust_kph": 42.1, + "uv": 0 + }, + { + "time_epoch": 1732496400, + "time": "2024-11-25 02:00", + "temp_c": 15.3, + "temp_f": 59.5, + "is_day": 0, + "condition": { + "text": "Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/119.png", + "code": 1006 + }, + "wind_mph": 14.5, + "wind_kph": 23.4, + "wind_degree": 148, + "wind_dir": "SSE", + "pressure_mb": 1010, + "pressure_in": 29.82, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 62, + "cloud": 67, + "feelslike_c": 15.3, + "feelslike_f": 59.5, + "windchill_c": 15.3, + "windchill_f": 59.5, + "heatindex_c": 15.3, + "heatindex_f": 59.5, + "dewpoint_c": 8.1, + "dewpoint_f": 46.6, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 26.6, + "gust_kph": 42.7, + "uv": 0 + }, + { + "time_epoch": 1732500000, + "time": "2024-11-25 03:00", + "temp_c": 15.5, + "temp_f": 60, + "is_day": 0, + "condition": { + "text": "Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/119.png", + "code": 1006 + }, + "wind_mph": 15, + "wind_kph": 24.1, + "wind_degree": 154, + "wind_dir": "SSE", + "pressure_mb": 1010, + "pressure_in": 29.81, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 59, + "cloud": 82, + "feelslike_c": 15.5, + "feelslike_f": 60, + "windchill_c": 15.5, + "windchill_f": 60, + "heatindex_c": 15.5, + "heatindex_f": 60, + "dewpoint_c": 7.5, + "dewpoint_f": 45.4, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 26.2, + "gust_kph": 42.1, + "uv": 0 + }, + { + "time_epoch": 1732503600, + "time": "2024-11-25 04:00", + "temp_c": 16.4, + "temp_f": 61.6, + "is_day": 0, + "condition": { + "text": "Patchy rain nearby", + "icon": "//cdn.weatherapi.com/weather/64x64/night/176.png", + "code": 1063 + }, + "wind_mph": 15, + "wind_kph": 24.1, + "wind_degree": 160, + "wind_dir": "SSE", + "pressure_mb": 1009, + "pressure_in": 29.79, + "precip_mm": 0.01, + "precip_in": 0, + "snow_cm": 0, + "humidity": 54, + "cloud": 85, + "feelslike_c": 16.4, + "feelslike_f": 61.6, + "windchill_c": 16.4, + "windchill_f": 61.6, + "heatindex_c": 16.4, + "heatindex_f": 61.6, + "dewpoint_c": 7, + "dewpoint_f": 44.7, + "will_it_rain": 0, + "chance_of_rain": 70, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 26.5, + "gust_kph": 42.7, + "uv": 0 + }, + { + "time_epoch": 1732507200, + "time": "2024-11-25 05:00", + "temp_c": 15.5, + "temp_f": 59.9, + "is_day": 0, + "condition": { + "text": "Patchy rain nearby", + "icon": "//cdn.weatherapi.com/weather/64x64/night/176.png", + "code": 1063 + }, + "wind_mph": 12.5, + "wind_kph": 20.2, + "wind_degree": 154, + "wind_dir": "SSE", + "pressure_mb": 1009, + "pressure_in": 29.8, + "precip_mm": 0.01, + "precip_in": 0, + "snow_cm": 0, + "humidity": 59, + "cloud": 86, + "feelslike_c": 15.5, + "feelslike_f": 59.9, + "windchill_c": 15.5, + "windchill_f": 59.9, + "heatindex_c": 15.5, + "heatindex_f": 59.9, + "dewpoint_c": 7.5, + "dewpoint_f": 45.5, + "will_it_rain": 0, + "chance_of_rain": 67, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 21.5, + "gust_kph": 34.5, + "uv": 0 + }, + { + "time_epoch": 1732510800, + "time": "2024-11-25 06:00", + "temp_c": 15.5, + "temp_f": 59.8, + "is_day": 0, + "condition": { + "text": "Patchy rain nearby", + "icon": "//cdn.weatherapi.com/weather/64x64/night/176.png", + "code": 1063 + }, + "wind_mph": 8.5, + "wind_kph": 13.7, + "wind_degree": 181, + "wind_dir": "S", + "pressure_mb": 1010, + "pressure_in": 29.83, + "precip_mm": 0.04, + "precip_in": 0, + "snow_cm": 0, + "humidity": 60, + "cloud": 85, + "feelslike_c": 15.5, + "feelslike_f": 59.8, + "windchill_c": 15.5, + "windchill_f": 59.8, + "heatindex_c": 15.5, + "heatindex_f": 59.8, + "dewpoint_c": 7.8, + "dewpoint_f": 46.1, + "will_it_rain": 0, + "chance_of_rain": 64, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 15.4, + "gust_kph": 24.8, + "uv": 0 + }, + { + "time_epoch": 1732514400, + "time": "2024-11-25 07:00", + "temp_c": 15.1, + "temp_f": 59.1, + "is_day": 0, + "condition": { + "text": "Patchy light drizzle", + "icon": "//cdn.weatherapi.com/weather/64x64/night/263.png", + "code": 1150 + }, + "wind_mph": 7.2, + "wind_kph": 11.5, + "wind_degree": 238, + "wind_dir": "WSW", + "pressure_mb": 1011, + "pressure_in": 29.84, + "precip_mm": 0.32, + "precip_in": 0.01, + "snow_cm": 0, + "humidity": 73, + "cloud": 71, + "feelslike_c": 15.1, + "feelslike_f": 59.1, + "windchill_c": 15.1, + "windchill_f": 59.1, + "heatindex_c": 15.1, + "heatindex_f": 59.1, + "dewpoint_c": 10.2, + "dewpoint_f": 50.3, + "will_it_rain": 1, + "chance_of_rain": 100, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 5, + "vis_miles": 3, + "gust_mph": 12.5, + "gust_kph": 20.1, + "uv": 0 + }, + { + "time_epoch": 1732518000, + "time": "2024-11-25 08:00", + "temp_c": 13.6, + "temp_f": 56.5, + "is_day": 0, + "condition": { + "text": "Light rain", + "icon": "//cdn.weatherapi.com/weather/64x64/night/296.png", + "code": 1183 + }, + "wind_mph": 8.5, + "wind_kph": 13.7, + "wind_degree": 301, + "wind_dir": "WNW", + "pressure_mb": 1012, + "pressure_in": 29.88, + "precip_mm": 0.84, + "precip_in": 0.03, + "snow_cm": 0, + "humidity": 91, + "cloud": 100, + "feelslike_c": 12.5, + "feelslike_f": 54.5, + "windchill_c": 12.5, + "windchill_f": 54.5, + "heatindex_c": 13.6, + "heatindex_f": 56.5, + "dewpoint_c": 12.2, + "dewpoint_f": 53.9, + "will_it_rain": 1, + "chance_of_rain": 100, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 9, + "vis_miles": 5, + "gust_mph": 12.6, + "gust_kph": 20.3, + "uv": 0 + }, + { + "time_epoch": 1732521600, + "time": "2024-11-25 09:00", + "temp_c": 12.4, + "temp_f": 54.3, + "is_day": 1, + "condition": { + "text": "Light drizzle", + "icon": "//cdn.weatherapi.com/weather/64x64/day/266.png", + "code": 1153 + }, + "wind_mph": 6.7, + "wind_kph": 10.8, + "wind_degree": 316, + "wind_dir": "NW", + "pressure_mb": 1013, + "pressure_in": 29.92, + "precip_mm": 0.73, + "precip_in": 0.03, + "snow_cm": 0, + "humidity": 92, + "cloud": 100, + "feelslike_c": 11.4, + "feelslike_f": 52.5, + "windchill_c": 11.4, + "windchill_f": 52.5, + "heatindex_c": 12.4, + "heatindex_f": 54.3, + "dewpoint_c": 11.1, + "dewpoint_f": 52.1, + "will_it_rain": 1, + "chance_of_rain": 100, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 2, + "vis_miles": 1, + "gust_mph": 9.8, + "gust_kph": 15.7, + "uv": 0 + }, + { + "time_epoch": 1732525200, + "time": "2024-11-25 10:00", + "temp_c": 12.4, + "temp_f": 54.4, + "is_day": 1, + "condition": { + "text": "Light rain", + "icon": "//cdn.weatherapi.com/weather/64x64/day/296.png", + "code": 1183 + }, + "wind_mph": 5.1, + "wind_kph": 8.3, + "wind_degree": 290, + "wind_dir": "WNW", + "pressure_mb": 1014, + "pressure_in": 29.95, + "precip_mm": 0.89, + "precip_in": 0.04, + "snow_cm": 0, + "humidity": 92, + "cloud": 100, + "feelslike_c": 11.8, + "feelslike_f": 53.3, + "windchill_c": 11.8, + "windchill_f": 53.3, + "heatindex_c": 12.5, + "heatindex_f": 54.4, + "dewpoint_c": 11.2, + "dewpoint_f": 52.1, + "will_it_rain": 1, + "chance_of_rain": 100, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 9, + "vis_miles": 5, + "gust_mph": 7.5, + "gust_kph": 12.1, + "uv": 0 + }, + { + "time_epoch": 1732528800, + "time": "2024-11-25 11:00", + "temp_c": 12.5, + "temp_f": 54.4, + "is_day": 1, + "condition": { + "text": "Light rain", + "icon": "//cdn.weatherapi.com/weather/64x64/day/296.png", + "code": 1183 + }, + "wind_mph": 6, + "wind_kph": 9.7, + "wind_degree": 311, + "wind_dir": "NW", + "pressure_mb": 1015, + "pressure_in": 29.96, + "precip_mm": 1.36, + "precip_in": 0.05, + "snow_cm": 0, + "humidity": 93, + "cloud": 100, + "feelslike_c": 11.6, + "feelslike_f": 52.9, + "windchill_c": 11.6, + "windchill_f": 52.9, + "heatindex_c": 12.5, + "heatindex_f": 54.4, + "dewpoint_c": 11.3, + "dewpoint_f": 52.3, + "will_it_rain": 1, + "chance_of_rain": 100, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 9, + "vis_miles": 5, + "gust_mph": 8.6, + "gust_kph": 13.8, + "uv": 0.1 + }, + { + "time_epoch": 1732532400, + "time": "2024-11-25 12:00", + "temp_c": 11.9, + "temp_f": 53.4, + "is_day": 1, + "condition": { + "text": "Light rain", + "icon": "//cdn.weatherapi.com/weather/64x64/day/296.png", + "code": 1183 + }, + "wind_mph": 8.3, + "wind_kph": 13.3, + "wind_degree": 308, + "wind_dir": "NW", + "pressure_mb": 1016, + "pressure_in": 29.99, + "precip_mm": 2.14, + "precip_in": 0.08, + "snow_cm": 0, + "humidity": 93, + "cloud": 100, + "feelslike_c": 10.4, + "feelslike_f": 50.8, + "windchill_c": 10.4, + "windchill_f": 50.8, + "heatindex_c": 11.9, + "heatindex_f": 53.4, + "dewpoint_c": 10.8, + "dewpoint_f": 51.4, + "will_it_rain": 1, + "chance_of_rain": 100, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 9, + "vis_miles": 5, + "gust_mph": 11.6, + "gust_kph": 18.6, + "uv": 0.1 + }, + { + "time_epoch": 1732536000, + "time": "2024-11-25 13:00", + "temp_c": 11.3, + "temp_f": 52.4, + "is_day": 1, + "condition": { + "text": "Light rain shower", + "icon": "//cdn.weatherapi.com/weather/64x64/day/353.png", + "code": 1240 + }, + "wind_mph": 6.7, + "wind_kph": 10.8, + "wind_degree": 302, + "wind_dir": "WNW", + "pressure_mb": 1016, + "pressure_in": 30.01, + "precip_mm": 0.24, + "precip_in": 0.01, + "snow_cm": 0, + "humidity": 89, + "cloud": 100, + "feelslike_c": 10.1, + "feelslike_f": 50.2, + "windchill_c": 10.1, + "windchill_f": 50.2, + "heatindex_c": 11.3, + "heatindex_f": 52.4, + "dewpoint_c": 9.6, + "dewpoint_f": 49.3, + "will_it_rain": 1, + "chance_of_rain": 100, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 9.5, + "gust_kph": 15.2, + "uv": 0.1 + }, + { + "time_epoch": 1732539600, + "time": "2024-11-25 14:00", + "temp_c": 11.3, + "temp_f": 52.4, + "is_day": 1, + "condition": { + "text": "Light drizzle", + "icon": "//cdn.weatherapi.com/weather/64x64/day/266.png", + "code": 1153 + }, + "wind_mph": 5.6, + "wind_kph": 9, + "wind_degree": 283, + "wind_dir": "WNW", + "pressure_mb": 1017, + "pressure_in": 30.03, + "precip_mm": 0.37, + "precip_in": 0.01, + "snow_cm": 0, + "humidity": 89, + "cloud": 100, + "feelslike_c": 10.4, + "feelslike_f": 50.7, + "windchill_c": 10.4, + "windchill_f": 50.7, + "heatindex_c": 11.3, + "heatindex_f": 52.4, + "dewpoint_c": 9.5, + "dewpoint_f": 49.2, + "will_it_rain": 1, + "chance_of_rain": 100, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 2, + "vis_miles": 1, + "gust_mph": 8, + "gust_kph": 12.9, + "uv": 0.1 + }, + { + "time_epoch": 1732543200, + "time": "2024-11-25 15:00", + "temp_c": 11.3, + "temp_f": 52.3, + "is_day": 1, + "condition": { + "text": "Light drizzle", + "icon": "//cdn.weatherapi.com/weather/64x64/day/266.png", + "code": 1153 + }, + "wind_mph": 7.4, + "wind_kph": 11.9, + "wind_degree": 269, + "wind_dir": "W", + "pressure_mb": 1018, + "pressure_in": 30.06, + "precip_mm": 0.45, + "precip_in": 0.02, + "snow_cm": 0, + "humidity": 88, + "cloud": 100, + "feelslike_c": 9.9, + "feelslike_f": 49.7, + "windchill_c": 9.9, + "windchill_f": 49.7, + "heatindex_c": 11.3, + "heatindex_f": 52.3, + "dewpoint_c": 9.4, + "dewpoint_f": 49, + "will_it_rain": 1, + "chance_of_rain": 100, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 2, + "vis_miles": 1, + "gust_mph": 11, + "gust_kph": 17.6, + "uv": 0.1 + }, + { + "time_epoch": 1732546800, + "time": "2024-11-25 16:00", + "temp_c": 11.4, + "temp_f": 52.5, + "is_day": 1, + "condition": { + "text": "Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/day/119.png", + "code": 1006 + }, + "wind_mph": 3.6, + "wind_kph": 5.8, + "wind_degree": 244, + "wind_dir": "WSW", + "pressure_mb": 1018, + "pressure_in": 30.07, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 82, + "cloud": 65, + "feelslike_c": 11.2, + "feelslike_f": 52.1, + "windchill_c": 11.2, + "windchill_f": 52.1, + "heatindex_c": 11.4, + "heatindex_f": 52.5, + "dewpoint_c": 8.4, + "dewpoint_f": 47.1, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 5.6, + "gust_kph": 9, + "uv": 0.1 + }, + { + "time_epoch": 1732550400, + "time": "2024-11-25 17:00", + "temp_c": 11.4, + "temp_f": 52.4, + "is_day": 1, + "condition": { + "text": "Patchy rain nearby", + "icon": "//cdn.weatherapi.com/weather/64x64/day/176.png", + "code": 1063 + }, + "wind_mph": 5.1, + "wind_kph": 8.3, + "wind_degree": 191, + "wind_dir": "SSW", + "pressure_mb": 1019, + "pressure_in": 30.09, + "precip_mm": 0.02, + "precip_in": 0, + "snow_cm": 0, + "humidity": 82, + "cloud": 58, + "feelslike_c": 10.5, + "feelslike_f": 51, + "windchill_c": 10.5, + "windchill_f": 51, + "heatindex_c": 11.4, + "heatindex_f": 52.4, + "dewpoint_c": 8.4, + "dewpoint_f": 47, + "will_it_rain": 1, + "chance_of_rain": 89, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 8.4, + "gust_kph": 13.5, + "uv": 0 + }, + { + "time_epoch": 1732554000, + "time": "2024-11-25 18:00", + "temp_c": 11.1, + "temp_f": 51.9, + "is_day": 0, + "condition": { + "text": "Patchy rain nearby", + "icon": "//cdn.weatherapi.com/weather/64x64/night/176.png", + "code": 1063 + }, + "wind_mph": 5.6, + "wind_kph": 9, + "wind_degree": 207, + "wind_dir": "SSW", + "pressure_mb": 1020, + "pressure_in": 30.11, + "precip_mm": 0.02, + "precip_in": 0, + "snow_cm": 0, + "humidity": 85, + "cloud": 89, + "feelslike_c": 10.1, + "feelslike_f": 50.1, + "windchill_c": 10.1, + "windchill_f": 50.1, + "heatindex_c": 11.1, + "heatindex_f": 51.9, + "dewpoint_c": 8.6, + "dewpoint_f": 47.4, + "will_it_rain": 1, + "chance_of_rain": 76, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 9.4, + "gust_kph": 15.2, + "uv": 0 + }, + { + "time_epoch": 1732557600, + "time": "2024-11-25 19:00", + "temp_c": 10.6, + "temp_f": 51.1, + "is_day": 0, + "condition": { + "text": "Patchy rain nearby", + "icon": "//cdn.weatherapi.com/weather/64x64/night/176.png", + "code": 1063 + }, + "wind_mph": 6, + "wind_kph": 9.7, + "wind_degree": 198, + "wind_dir": "SSW", + "pressure_mb": 1020, + "pressure_in": 30.13, + "precip_mm": 0.04, + "precip_in": 0, + "snow_cm": 0, + "humidity": 88, + "cloud": 88, + "feelslike_c": 9.4, + "feelslike_f": 48.9, + "windchill_c": 9.4, + "windchill_f": 48.9, + "heatindex_c": 10.6, + "heatindex_f": 51.1, + "dewpoint_c": 8.8, + "dewpoint_f": 47.8, + "will_it_rain": 1, + "chance_of_rain": 76, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 10.6, + "gust_kph": 17.1, + "uv": 0 + }, + { + "time_epoch": 1732561200, + "time": "2024-11-25 20:00", + "temp_c": 10.1, + "temp_f": 50.2, + "is_day": 0, + "condition": { + "text": "Patchy rain nearby", + "icon": "//cdn.weatherapi.com/weather/64x64/night/176.png", + "code": 1063 + }, + "wind_mph": 5.6, + "wind_kph": 9, + "wind_degree": 198, + "wind_dir": "SSW", + "pressure_mb": 1021, + "pressure_in": 30.16, + "precip_mm": 0.04, + "precip_in": 0, + "snow_cm": 0, + "humidity": 92, + "cloud": 87, + "feelslike_c": 8.9, + "feelslike_f": 48.1, + "windchill_c": 8.9, + "windchill_f": 48.1, + "heatindex_c": 10.1, + "heatindex_f": 50.2, + "dewpoint_c": 8.8, + "dewpoint_f": 47.9, + "will_it_rain": 1, + "chance_of_rain": 75, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 10.5, + "gust_kph": 16.9, + "uv": 0 + }, + { + "time_epoch": 1732564800, + "time": "2024-11-25 21:00", + "temp_c": 9.8, + "temp_f": 49.6, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 5.1, + "wind_kph": 8.3, + "wind_degree": 198, + "wind_dir": "SSW", + "pressure_mb": 1022, + "pressure_in": 30.17, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 93, + "cloud": 18, + "feelslike_c": 8.7, + "feelslike_f": 47.7, + "windchill_c": 8.7, + "windchill_f": 47.7, + "heatindex_c": 9.8, + "heatindex_f": 49.6, + "dewpoint_c": 8.7, + "dewpoint_f": 47.7, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 10.2, + "gust_kph": 16.3, + "uv": 0 + }, + { + "time_epoch": 1732568400, + "time": "2024-11-25 22:00", + "temp_c": 9.6, + "temp_f": 49.4, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 5.1, + "wind_kph": 8.3, + "wind_degree": 198, + "wind_dir": "SSW", + "pressure_mb": 1022, + "pressure_in": 30.19, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 93, + "cloud": 24, + "feelslike_c": 8.5, + "feelslike_f": 47.4, + "windchill_c": 8.5, + "windchill_f": 47.4, + "heatindex_c": 9.6, + "heatindex_f": 49.4, + "dewpoint_c": 8.6, + "dewpoint_f": 47.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 10.2, + "gust_kph": 16.5, + "uv": 0 + }, + { + "time_epoch": 1732572000, + "time": "2024-11-25 23:00", + "temp_c": 9.5, + "temp_f": 49.1, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 4.9, + "wind_kph": 7.9, + "wind_degree": 198, + "wind_dir": "SSW", + "pressure_mb": 1023, + "pressure_in": 30.2, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 93, + "cloud": 26, + "feelslike_c": 8.4, + "feelslike_f": 47.2, + "windchill_c": 8.4, + "windchill_f": 47.2, + "heatindex_c": 9.5, + "heatindex_f": 49.1, + "dewpoint_c": 8.5, + "dewpoint_f": 47.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 9.8, + "gust_kph": 15.8, + "uv": 0 + } + ] + } + ] + } +} \ No newline at end of file diff --git a/src/test/resources/WeatherAPI/Bordeaux-4-partial-sunny-rain-cloudy.json b/src/test/resources/WeatherAPI/Bordeaux-4-partial-sunny-rain-cloudy.json new file mode 100644 index 0000000..a5781ec --- /dev/null +++ b/src/test/resources/WeatherAPI/Bordeaux-4-partial-sunny-rain-cloudy.json @@ -0,0 +1,4059 @@ +{ + "location": { + "name": "Bordeaux", + "region": "Aquitaine", + "country": "France", + "lat": 44.8333, + "lon": -0.5667, + "tz_id": "Europe/Paris", + "localtime_epoch": 1732356248, + "localtime": "2024-11-23 11:04" + }, + "current": { + "last_updated_epoch": 1732356000, + "last_updated": "2024-11-23 11:00", + "temp_c": 7.4, + "temp_f": 45.3, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 11.4, + "wind_kph": 18.4, + "wind_degree": 148, + "wind_dir": "SSE", + "pressure_mb": 1018, + "pressure_in": 30.06, + "precip_mm": 0, + "precip_in": 0, + "humidity": 76, + "cloud": 0, + "feelslike_c": 4.3, + "feelslike_f": 39.7, + "windchill_c": 6, + "windchill_f": 42.7, + "heatindex_c": 8.7, + "heatindex_f": 47.7, + "dewpoint_c": 1.3, + "dewpoint_f": 34.3, + "vis_km": 10, + "vis_miles": 6, + "uv": 0.9, + "gust_mph": 15.9, + "gust_kph": 25.5 + }, + "forecast": { + "forecastday": [ + { + "date": "2024-11-23", + "date_epoch": 1732320000, + "day": { + "maxtemp_c": 13.1, + "maxtemp_f": 55.7, + "mintemp_c": 4, + "mintemp_f": 39.3, + "avgtemp_c": 8.1, + "avgtemp_f": 46.6, + "maxwind_mph": 13, + "maxwind_kph": 20.9, + "totalprecip_mm": 0, + "totalprecip_in": 0, + "totalsnow_cm": 0, + "avgvis_km": 10, + "avgvis_miles": 6, + "avghumidity": 69, + "daily_will_it_rain": 0, + "daily_chance_of_rain": 0, + "daily_will_it_snow": 0, + "daily_chance_of_snow": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/day/116.png", + "code": 1003 + }, + "uv": 0.3 + }, + "astro": { + "sunrise": "08:10 AM", + "sunset": "05:27 PM", + "moonrise": "12:08 AM", + "moonset": "02:13 PM", + "moon_phase": "Last Quarter", + "moon_illumination": 51, + "is_moon_up": 0, + "is_sun_up": 0 + }, + "hour": [ + { + "time_epoch": 1732316400, + "time": "2024-11-23 00:00", + "temp_c": 5.6, + "temp_f": 42, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 6.7, + "wind_kph": 10.8, + "wind_degree": 150, + "wind_dir": "SSE", + "pressure_mb": 1022, + "pressure_in": 30.19, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 81, + "cloud": 17, + "feelslike_c": 3.2, + "feelslike_f": 37.7, + "windchill_c": 3.2, + "windchill_f": 37.7, + "heatindex_c": 5.6, + "heatindex_f": 42, + "dewpoint_c": 2.6, + "dewpoint_f": 36.6, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 13.7, + "gust_kph": 22, + "uv": 0 + }, + { + "time_epoch": 1732320000, + "time": "2024-11-23 01:00", + "temp_c": 5.8, + "temp_f": 42.4, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 6.9, + "wind_kph": 11.2, + "wind_degree": 141, + "wind_dir": "SE", + "pressure_mb": 1022, + "pressure_in": 30.17, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 80, + "cloud": 47, + "feelslike_c": 3.3, + "feelslike_f": 38, + "windchill_c": 3.3, + "windchill_f": 38, + "heatindex_c": 5.8, + "heatindex_f": 42.4, + "dewpoint_c": 2.5, + "dewpoint_f": 36.6, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 13.4, + "gust_kph": 21.6, + "uv": 0 + }, + { + "time_epoch": 1732323600, + "time": "2024-11-23 02:00", + "temp_c": 5.6, + "temp_f": 42.1, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 8.3, + "wind_kph": 13.3, + "wind_degree": 137, + "wind_dir": "SE", + "pressure_mb": 1021, + "pressure_in": 30.14, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 79, + "cloud": 56, + "feelslike_c": 2.8, + "feelslike_f": 37, + "windchill_c": 2.8, + "windchill_f": 37, + "heatindex_c": 5.6, + "heatindex_f": 42.1, + "dewpoint_c": 2.3, + "dewpoint_f": 36.1, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 15.3, + "gust_kph": 24.7, + "uv": 0 + }, + { + "time_epoch": 1732327200, + "time": "2024-11-23 03:00", + "temp_c": 5.1, + "temp_f": 41.3, + "is_day": 0, + "condition": { + "text": "Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/119.png", + "code": 1006 + }, + "wind_mph": 9.8, + "wind_kph": 15.8, + "wind_degree": 133, + "wind_dir": "SE", + "pressure_mb": 1020, + "pressure_in": 30.13, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 79, + "cloud": 86, + "feelslike_c": 1.8, + "feelslike_f": 35.3, + "windchill_c": 1.8, + "windchill_f": 35.3, + "heatindex_c": 5.2, + "heatindex_f": 41.3, + "dewpoint_c": 1.9, + "dewpoint_f": 35.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 17.6, + "gust_kph": 28.4, + "uv": 0 + }, + { + "time_epoch": 1732330800, + "time": "2024-11-23 04:00", + "temp_c": 4.8, + "temp_f": 40.7, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 10.5, + "wind_kph": 16.9, + "wind_degree": 130, + "wind_dir": "SE", + "pressure_mb": 1020, + "pressure_in": 30.12, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 79, + "cloud": 44, + "feelslike_c": 1.3, + "feelslike_f": 34.3, + "windchill_c": 1.3, + "windchill_f": 34.3, + "heatindex_c": 4.9, + "heatindex_f": 40.7, + "dewpoint_c": 1.5, + "dewpoint_f": 34.7, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 18.1, + "gust_kph": 29.1, + "uv": 0 + }, + { + "time_epoch": 1732334400, + "time": "2024-11-23 05:00", + "temp_c": 4.3, + "temp_f": 39.7, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 11, + "wind_kph": 17.6, + "wind_degree": 130, + "wind_dir": "SE", + "pressure_mb": 1019, + "pressure_in": 30.1, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 80, + "cloud": 46, + "feelslike_c": 0.5, + "feelslike_f": 32.8, + "windchill_c": 0.5, + "windchill_f": 32.8, + "heatindex_c": 4.3, + "heatindex_f": 39.7, + "dewpoint_c": 1.2, + "dewpoint_f": 34.1, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 19.1, + "gust_kph": 30.8, + "uv": 0 + }, + { + "time_epoch": 1732338000, + "time": "2024-11-23 06:00", + "temp_c": 4, + "temp_f": 39.3, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 11.2, + "wind_kph": 18, + "wind_degree": 138, + "wind_dir": "SE", + "pressure_mb": 1019, + "pressure_in": 30.08, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 80, + "cloud": 39, + "feelslike_c": 0.1, + "feelslike_f": 32.2, + "windchill_c": 0.1, + "windchill_f": 32.2, + "heatindex_c": 4, + "heatindex_f": 39.3, + "dewpoint_c": 0.9, + "dewpoint_f": 33.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 19.8, + "gust_kph": 31.8, + "uv": 0 + }, + { + "time_epoch": 1732341600, + "time": "2024-11-23 07:00", + "temp_c": 4, + "temp_f": 39.3, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 11.2, + "wind_kph": 18, + "wind_degree": 138, + "wind_dir": "SE", + "pressure_mb": 1019, + "pressure_in": 30.08, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 78, + "cloud": 26, + "feelslike_c": 0.1, + "feelslike_f": 32.2, + "windchill_c": 0.1, + "windchill_f": 32.2, + "heatindex_c": 4, + "heatindex_f": 39.3, + "dewpoint_c": 0.6, + "dewpoint_f": 33, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 19.9, + "gust_kph": 32, + "uv": 0 + }, + { + "time_epoch": 1732345200, + "time": "2024-11-23 08:00", + "temp_c": 4.1, + "temp_f": 39.4, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 10.5, + "wind_kph": 16.9, + "wind_degree": 140, + "wind_dir": "SE", + "pressure_mb": 1019, + "pressure_in": 30.09, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 75, + "cloud": 7, + "feelslike_c": 0.4, + "feelslike_f": 32.6, + "windchill_c": 0.4, + "windchill_f": 32.6, + "heatindex_c": 4.1, + "heatindex_f": 39.4, + "dewpoint_c": 0.1, + "dewpoint_f": 32.1, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 18.8, + "gust_kph": 30.2, + "uv": 0 + }, + { + "time_epoch": 1732348800, + "time": "2024-11-23 09:00", + "temp_c": 4.6, + "temp_f": 40.3, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 10.7, + "wind_kph": 17.3, + "wind_degree": 143, + "wind_dir": "SE", + "pressure_mb": 1019, + "pressure_in": 30.09, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 71, + "cloud": 13, + "feelslike_c": 1, + "feelslike_f": 33.7, + "windchill_c": 1, + "windchill_f": 33.7, + "heatindex_c": 4.6, + "heatindex_f": 40.4, + "dewpoint_c": -0.2, + "dewpoint_f": 31.7, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 19, + "gust_kph": 30.5, + "uv": 0 + }, + { + "time_epoch": 1732352400, + "time": "2024-11-23 10:00", + "temp_c": 6.6, + "temp_f": 43.9, + "is_day": 1, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/day/116.png", + "code": 1003 + }, + "wind_mph": 11.2, + "wind_kph": 18, + "wind_degree": 146, + "wind_dir": "SSE", + "pressure_mb": 1019, + "pressure_in": 30.08, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 63, + "cloud": 49, + "feelslike_c": 3.4, + "feelslike_f": 38, + "windchill_c": 3.4, + "windchill_f": 38, + "heatindex_c": 6.6, + "heatindex_f": 43.9, + "dewpoint_c": 0.2, + "dewpoint_f": 32.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 17.5, + "gust_kph": 28.2, + "uv": 0.4 + }, + { + "time_epoch": 1732356000, + "time": "2024-11-23 11:00", + "temp_c": 7.4, + "temp_f": 45.3, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 11.4, + "wind_kph": 18.4, + "wind_degree": 148, + "wind_dir": "SSE", + "pressure_mb": 1018, + "pressure_in": 30.06, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 76, + "cloud": 0, + "feelslike_c": 6, + "feelslike_f": 42.7, + "windchill_c": 6, + "windchill_f": 42.7, + "heatindex_c": 8.7, + "heatindex_f": 47.7, + "dewpoint_c": 1.3, + "dewpoint_f": 34.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 15.9, + "gust_kph": 25.5, + "uv": 0.9 + }, + { + "time_epoch": 1732359600, + "time": "2024-11-23 12:00", + "temp_c": 10.4, + "temp_f": 50.8, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 11, + "wind_kph": 17.6, + "wind_degree": 149, + "wind_dir": "SSE", + "pressure_mb": 1018, + "pressure_in": 30.07, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 56, + "cloud": 21, + "feelslike_c": 8.2, + "feelslike_f": 46.7, + "windchill_c": 8.2, + "windchill_f": 46.7, + "heatindex_c": 10.5, + "heatindex_f": 50.8, + "dewpoint_c": 2.2, + "dewpoint_f": 35.9, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 14.7, + "gust_kph": 23.6, + "uv": 1.3 + }, + { + "time_epoch": 1732363200, + "time": "2024-11-23 13:00", + "temp_c": 12.3, + "temp_f": 54.2, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 11.4, + "wind_kph": 18.4, + "wind_degree": 150, + "wind_dir": "SSE", + "pressure_mb": 1017, + "pressure_in": 30.04, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 52, + "cloud": 22, + "feelslike_c": 10.5, + "feelslike_f": 50.8, + "windchill_c": 10.5, + "windchill_f": 50.8, + "heatindex_c": 12.3, + "heatindex_f": 54.2, + "dewpoint_c": 2.8, + "dewpoint_f": 37, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 14.9, + "gust_kph": 23.9, + "uv": 1.6 + }, + { + "time_epoch": 1732366800, + "time": "2024-11-23 14:00", + "temp_c": 13, + "temp_f": 55.4, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 10.3, + "wind_kph": 16.6, + "wind_degree": 149, + "wind_dir": "SSE", + "pressure_mb": 1017, + "pressure_in": 30.03, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 54, + "cloud": 12, + "feelslike_c": 11.5, + "feelslike_f": 52.7, + "windchill_c": 11.5, + "windchill_f": 52.7, + "heatindex_c": 13, + "heatindex_f": 55.4, + "dewpoint_c": 3.8, + "dewpoint_f": 38.9, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 14, + "gust_kph": 22.6, + "uv": 1.4 + }, + { + "time_epoch": 1732370400, + "time": "2024-11-23 15:00", + "temp_c": 13.1, + "temp_f": 55.7, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 11.4, + "wind_kph": 18.4, + "wind_degree": 150, + "wind_dir": "SSE", + "pressure_mb": 1016, + "pressure_in": 30.01, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 54, + "cloud": 22, + "feelslike_c": 11.5, + "feelslike_f": 52.7, + "windchill_c": 11.5, + "windchill_f": 52.7, + "heatindex_c": 13.2, + "heatindex_f": 55.7, + "dewpoint_c": 4.2, + "dewpoint_f": 39.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 16.8, + "gust_kph": 27.1, + "uv": 0.9 + }, + { + "time_epoch": 1732374000, + "time": "2024-11-23 16:00", + "temp_c": 12.5, + "temp_f": 54.4, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 10.7, + "wind_kph": 17.3, + "wind_degree": 148, + "wind_dir": "SSE", + "pressure_mb": 1017, + "pressure_in": 30.02, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 57, + "cloud": 14, + "feelslike_c": 10.7, + "feelslike_f": 51.3, + "windchill_c": 10.7, + "windchill_f": 51.3, + "heatindex_c": 12.5, + "heatindex_f": 54.4, + "dewpoint_c": 4.1, + "dewpoint_f": 39.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 17.5, + "gust_kph": 28.1, + "uv": 0.4 + }, + { + "time_epoch": 1732377600, + "time": "2024-11-23 17:00", + "temp_c": 11.2, + "temp_f": 52.1, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 11.2, + "wind_kph": 18, + "wind_degree": 141, + "wind_dir": "SE", + "pressure_mb": 1017, + "pressure_in": 30.02, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 61, + "cloud": 22, + "feelslike_c": 9, + "feelslike_f": 48.2, + "windchill_c": 9, + "windchill_f": 48.2, + "heatindex_c": 11.2, + "heatindex_f": 52.1, + "dewpoint_c": 4, + "dewpoint_f": 39.2, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 20.2, + "gust_kph": 32.5, + "uv": 0 + }, + { + "time_epoch": 1732381200, + "time": "2024-11-23 18:00", + "temp_c": 10.2, + "temp_f": 50.4, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 11.6, + "wind_kph": 18.7, + "wind_degree": 141, + "wind_dir": "SE", + "pressure_mb": 1016, + "pressure_in": 30.02, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 64, + "cloud": 47, + "feelslike_c": 7.8, + "feelslike_f": 46, + "windchill_c": 7.8, + "windchill_f": 46, + "heatindex_c": 10.2, + "heatindex_f": 50.4, + "dewpoint_c": 3.8, + "dewpoint_f": 38.9, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 22.2, + "gust_kph": 35.7, + "uv": 0 + }, + { + "time_epoch": 1732384800, + "time": "2024-11-23 19:00", + "temp_c": 10, + "temp_f": 50.1, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 12.3, + "wind_kph": 19.8, + "wind_degree": 144, + "wind_dir": "SE", + "pressure_mb": 1017, + "pressure_in": 30.02, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 66, + "cloud": 62, + "feelslike_c": 7.5, + "feelslike_f": 45.4, + "windchill_c": 7.5, + "windchill_f": 45.4, + "heatindex_c": 10, + "heatindex_f": 50.1, + "dewpoint_c": 4, + "dewpoint_f": 39.2, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 22.8, + "gust_kph": 36.8, + "uv": 0 + }, + { + "time_epoch": 1732388400, + "time": "2024-11-23 20:00", + "temp_c": 10, + "temp_f": 50, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 12.5, + "wind_kph": 20.2, + "wind_degree": 143, + "wind_dir": "SE", + "pressure_mb": 1017, + "pressure_in": 30.03, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 68, + "cloud": 57, + "feelslike_c": 7.3, + "feelslike_f": 45.2, + "windchill_c": 7.3, + "windchill_f": 45.2, + "heatindex_c": 10, + "heatindex_f": 50, + "dewpoint_c": 4.3, + "dewpoint_f": 39.8, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 22.6, + "gust_kph": 36.3, + "uv": 0 + }, + { + "time_epoch": 1732392000, + "time": "2024-11-23 21:00", + "temp_c": 9.7, + "temp_f": 49.5, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 12.5, + "wind_kph": 20.2, + "wind_degree": 142, + "wind_dir": "SE", + "pressure_mb": 1017, + "pressure_in": 30.03, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 71, + "cloud": 46, + "feelslike_c": 7, + "feelslike_f": 44.6, + "windchill_c": 7, + "windchill_f": 44.6, + "heatindex_c": 9.7, + "heatindex_f": 49.5, + "dewpoint_c": 4.6, + "dewpoint_f": 40.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 22.3, + "gust_kph": 35.9, + "uv": 0 + }, + { + "time_epoch": 1732395600, + "time": "2024-11-23 22:00", + "temp_c": 9.5, + "temp_f": 49.1, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 13, + "wind_kph": 20.9, + "wind_degree": 139, + "wind_dir": "SE", + "pressure_mb": 1017, + "pressure_in": 30.03, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 73, + "cloud": 24, + "feelslike_c": 6.6, + "feelslike_f": 44, + "windchill_c": 6.6, + "windchill_f": 44, + "heatindex_c": 9.5, + "heatindex_f": 49.1, + "dewpoint_c": 5, + "dewpoint_f": 41, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 23, + "gust_kph": 37, + "uv": 0 + }, + { + "time_epoch": 1732399200, + "time": "2024-11-23 23:00", + "temp_c": 9.3, + "temp_f": 48.8, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 12.8, + "wind_kph": 20.5, + "wind_degree": 140, + "wind_dir": "SE", + "pressure_mb": 1017, + "pressure_in": 30.03, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 76, + "cloud": 21, + "feelslike_c": 6.5, + "feelslike_f": 43.7, + "windchill_c": 6.5, + "windchill_f": 43.7, + "heatindex_c": 9.3, + "heatindex_f": 48.8, + "dewpoint_c": 5.3, + "dewpoint_f": 41.6, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 23, + "gust_kph": 37, + "uv": 0 + } + ] + }, + { + "date": "2024-11-24", + "date_epoch": 1732406400, + "day": { + "maxtemp_c": 17.2, + "maxtemp_f": 63, + "mintemp_c": 9.3, + "mintemp_f": 48.8, + "avgtemp_c": 13, + "avgtemp_f": 55.3, + "maxwind_mph": 16.3, + "maxwind_kph": 26.3, + "totalprecip_mm": 0, + "totalprecip_in": 0, + "totalsnow_cm": 0, + "avgvis_km": 10, + "avgvis_miles": 6, + "avghumidity": 70, + "daily_will_it_rain": 0, + "daily_chance_of_rain": 0, + "daily_will_it_snow": 0, + "daily_chance_of_snow": 0, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "uv": 0.3 + }, + "astro": { + "sunrise": "08:12 AM", + "sunset": "05:26 PM", + "moonrise": "01:14 AM", + "moonset": "02:30 PM", + "moon_phase": "Waning Crescent", + "moon_illumination": 41, + "is_moon_up": 0, + "is_sun_up": 0 + }, + "hour": [ + { + "time_epoch": 1732402800, + "time": "2024-11-24 00:00", + "temp_c": 9.3, + "temp_f": 48.8, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 12.5, + "wind_kph": 20.2, + "wind_degree": 140, + "wind_dir": "SE", + "pressure_mb": 1017, + "pressure_in": 30.03, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 77, + "cloud": 41, + "feelslike_c": 6.5, + "feelslike_f": 43.8, + "windchill_c": 6.5, + "windchill_f": 43.8, + "heatindex_c": 9.3, + "heatindex_f": 48.8, + "dewpoint_c": 5.6, + "dewpoint_f": 42, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 23.1, + "gust_kph": 37.2, + "uv": 0 + }, + { + "time_epoch": 1732406400, + "time": "2024-11-24 01:00", + "temp_c": 9.5, + "temp_f": 49.1, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 12.5, + "wind_kph": 20.2, + "wind_degree": 139, + "wind_dir": "SE", + "pressure_mb": 1017, + "pressure_in": 30.02, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 77, + "cloud": 33, + "feelslike_c": 6.7, + "feelslike_f": 44.1, + "windchill_c": 6.7, + "windchill_f": 44.1, + "heatindex_c": 9.5, + "heatindex_f": 49.1, + "dewpoint_c": 5.7, + "dewpoint_f": 42.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 23.4, + "gust_kph": 37.7, + "uv": 0 + }, + { + "time_epoch": 1732410000, + "time": "2024-11-24 02:00", + "temp_c": 9.7, + "temp_f": 49.4, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 12.3, + "wind_kph": 19.8, + "wind_degree": 139, + "wind_dir": "SE", + "pressure_mb": 1016, + "pressure_in": 30, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 77, + "cloud": 22, + "feelslike_c": 7, + "feelslike_f": 44.5, + "windchill_c": 7, + "windchill_f": 44.5, + "heatindex_c": 9.7, + "heatindex_f": 49.4, + "dewpoint_c": 5.8, + "dewpoint_f": 42.4, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 23.2, + "gust_kph": 37.3, + "uv": 0 + }, + { + "time_epoch": 1732413600, + "time": "2024-11-24 03:00", + "temp_c": 9.7, + "temp_f": 49.5, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 12.8, + "wind_kph": 20.5, + "wind_degree": 138, + "wind_dir": "SE", + "pressure_mb": 1016, + "pressure_in": 30, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 76, + "cloud": 14, + "feelslike_c": 7, + "feelslike_f": 44.6, + "windchill_c": 7, + "windchill_f": 44.6, + "heatindex_c": 9.7, + "heatindex_f": 49.5, + "dewpoint_c": 5.7, + "dewpoint_f": 42.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 24.3, + "gust_kph": 39.2, + "uv": 0 + }, + { + "time_epoch": 1732417200, + "time": "2024-11-24 04:00", + "temp_c": 9.8, + "temp_f": 49.6, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 13, + "wind_kph": 20.9, + "wind_degree": 137, + "wind_dir": "SE", + "pressure_mb": 1015, + "pressure_in": 29.98, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 75, + "cloud": 10, + "feelslike_c": 7, + "feelslike_f": 44.6, + "windchill_c": 7, + "windchill_f": 44.6, + "heatindex_c": 9.8, + "heatindex_f": 49.6, + "dewpoint_c": 5.7, + "dewpoint_f": 42.2, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 24.6, + "gust_kph": 39.6, + "uv": 0 + }, + { + "time_epoch": 1732420800, + "time": "2024-11-24 05:00", + "temp_c": 9.7, + "temp_f": 49.4, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 13.4, + "wind_kph": 21.6, + "wind_degree": 136, + "wind_dir": "SE", + "pressure_mb": 1015, + "pressure_in": 29.98, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 76, + "cloud": 10, + "feelslike_c": 6.8, + "feelslike_f": 44.3, + "windchill_c": 6.8, + "windchill_f": 44.3, + "heatindex_c": 9.7, + "heatindex_f": 49.4, + "dewpoint_c": 5.7, + "dewpoint_f": 42.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 25.3, + "gust_kph": 40.7, + "uv": 0 + }, + { + "time_epoch": 1732424400, + "time": "2024-11-24 06:00", + "temp_c": 9.7, + "temp_f": 49.5, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 13.6, + "wind_kph": 22, + "wind_degree": 137, + "wind_dir": "SE", + "pressure_mb": 1015, + "pressure_in": 29.98, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 77, + "cloud": 11, + "feelslike_c": 6.8, + "feelslike_f": 44.3, + "windchill_c": 6.8, + "windchill_f": 44.3, + "heatindex_c": 9.7, + "heatindex_f": 49.5, + "dewpoint_c": 5.8, + "dewpoint_f": 42.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 25.7, + "gust_kph": 41.4, + "uv": 0 + }, + { + "time_epoch": 1732428000, + "time": "2024-11-24 07:00", + "temp_c": 9.7, + "temp_f": 49.4, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 13.6, + "wind_kph": 22, + "wind_degree": 138, + "wind_dir": "SE", + "pressure_mb": 1015, + "pressure_in": 29.97, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 78, + "cloud": 8, + "feelslike_c": 6.8, + "feelslike_f": 44.2, + "windchill_c": 6.8, + "windchill_f": 44.2, + "heatindex_c": 9.7, + "heatindex_f": 49.4, + "dewpoint_c": 6, + "dewpoint_f": 42.7, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 25.5, + "gust_kph": 41, + "uv": 0 + }, + { + "time_epoch": 1732431600, + "time": "2024-11-24 08:00", + "temp_c": 9.7, + "temp_f": 49.5, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 13.2, + "wind_kph": 21.2, + "wind_degree": 138, + "wind_dir": "SE", + "pressure_mb": 1015, + "pressure_in": 29.98, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 78, + "cloud": 7, + "feelslike_c": 6.9, + "feelslike_f": 44.4, + "windchill_c": 6.9, + "windchill_f": 44.4, + "heatindex_c": 9.7, + "heatindex_f": 49.5, + "dewpoint_c": 6.1, + "dewpoint_f": 43, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 24.8, + "gust_kph": 40, + "uv": 0 + }, + { + "time_epoch": 1732435200, + "time": "2024-11-24 09:00", + "temp_c": 10.1, + "temp_f": 50.1, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 13.6, + "wind_kph": 22, + "wind_degree": 137, + "wind_dir": "SE", + "pressure_mb": 1015, + "pressure_in": 29.98, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 78, + "cloud": 7, + "feelslike_c": 7.3, + "feelslike_f": 45.1, + "windchill_c": 7.3, + "windchill_f": 45.1, + "heatindex_c": 10.1, + "heatindex_f": 50.1, + "dewpoint_c": 6.4, + "dewpoint_f": 43.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 24.5, + "gust_kph": 39.5, + "uv": 0 + }, + { + "time_epoch": 1732438800, + "time": "2024-11-24 10:00", + "temp_c": 11.4, + "temp_f": 52.5, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 14.5, + "wind_kph": 23.4, + "wind_degree": 137, + "wind_dir": "SE", + "pressure_mb": 1015, + "pressure_in": 29.97, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 74, + "cloud": 5, + "feelslike_c": 8.9, + "feelslike_f": 47.9, + "windchill_c": 8.9, + "windchill_f": 47.9, + "heatindex_c": 11.4, + "heatindex_f": 52.5, + "dewpoint_c": 7, + "dewpoint_f": 44.6, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 23.2, + "gust_kph": 37.3, + "uv": 0.4 + }, + { + "time_epoch": 1732442400, + "time": "2024-11-24 11:00", + "temp_c": 13.2, + "temp_f": 55.7, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 15.7, + "wind_kph": 25.2, + "wind_degree": 137, + "wind_dir": "SE", + "pressure_mb": 1015, + "pressure_in": 29.96, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 70, + "cloud": 4, + "feelslike_c": 11, + "feelslike_f": 51.8, + "windchill_c": 11, + "windchill_f": 51.8, + "heatindex_c": 13.2, + "heatindex_f": 55.7, + "dewpoint_c": 7.8, + "dewpoint_f": 46, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 22.5, + "gust_kph": 36.3, + "uv": 0.8 + }, + { + "time_epoch": 1732446000, + "time": "2024-11-24 12:00", + "temp_c": 15, + "temp_f": 58.9, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 16.1, + "wind_kph": 25.9, + "wind_degree": 140, + "wind_dir": "SE", + "pressure_mb": 1014, + "pressure_in": 29.95, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 65, + "cloud": 4, + "feelslike_c": 13.3, + "feelslike_f": 55.9, + "windchill_c": 13.3, + "windchill_f": 55.9, + "heatindex_c": 15, + "heatindex_f": 58.9, + "dewpoint_c": 8.4, + "dewpoint_f": 47.1, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 21.9, + "gust_kph": 35.2, + "uv": 1.3 + }, + { + "time_epoch": 1732449600, + "time": "2024-11-24 13:00", + "temp_c": 16.4, + "temp_f": 61.6, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 16.1, + "wind_kph": 25.9, + "wind_degree": 142, + "wind_dir": "SE", + "pressure_mb": 1013, + "pressure_in": 29.91, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 60, + "cloud": 5, + "feelslike_c": 16.4, + "feelslike_f": 61.6, + "windchill_c": 16.4, + "windchill_f": 61.6, + "heatindex_c": 16.4, + "heatindex_f": 61.6, + "dewpoint_c": 8.8, + "dewpoint_f": 47.8, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 21.6, + "gust_kph": 34.7, + "uv": 1.4 + }, + { + "time_epoch": 1732453200, + "time": "2024-11-24 14:00", + "temp_c": 17.2, + "temp_f": 62.9, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 16.3, + "wind_kph": 26.3, + "wind_degree": 143, + "wind_dir": "SE", + "pressure_mb": 1012, + "pressure_in": 29.89, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 59, + "cloud": 4, + "feelslike_c": 17.2, + "feelslike_f": 62.9, + "windchill_c": 17.2, + "windchill_f": 62.9, + "heatindex_c": 17.2, + "heatindex_f": 62.9, + "dewpoint_c": 9, + "dewpoint_f": 48.2, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 22.9, + "gust_kph": 36.8, + "uv": 1.3 + }, + { + "time_epoch": 1732456800, + "time": "2024-11-24 15:00", + "temp_c": 17.2, + "temp_f": 63, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 16.3, + "wind_kph": 26.3, + "wind_degree": 145, + "wind_dir": "SE", + "pressure_mb": 1012, + "pressure_in": 29.87, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 59, + "cloud": 1, + "feelslike_c": 17.2, + "feelslike_f": 63, + "windchill_c": 17.2, + "windchill_f": 63, + "heatindex_c": 17.2, + "heatindex_f": 63, + "dewpoint_c": 9.2, + "dewpoint_f": 48.6, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 25, + "gust_kph": 40.2, + "uv": 0.8 + }, + { + "time_epoch": 1732460400, + "time": "2024-11-24 16:00", + "temp_c": 16.6, + "temp_f": 61.9, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 15.7, + "wind_kph": 25.2, + "wind_degree": 147, + "wind_dir": "SSE", + "pressure_mb": 1011, + "pressure_in": 29.86, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 61, + "cloud": 3, + "feelslike_c": 16.6, + "feelslike_f": 61.9, + "windchill_c": 16.6, + "windchill_f": 61.9, + "heatindex_c": 16.6, + "heatindex_f": 61.9, + "dewpoint_c": 9.2, + "dewpoint_f": 48.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 25.9, + "gust_kph": 41.7, + "uv": 0.4 + }, + { + "time_epoch": 1732464000, + "time": "2024-11-24 17:00", + "temp_c": 15.7, + "temp_f": 60.3, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 15.4, + "wind_kph": 24.8, + "wind_degree": 147, + "wind_dir": "SSE", + "pressure_mb": 1011, + "pressure_in": 29.86, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 65, + "cloud": 22, + "feelslike_c": 15.7, + "feelslike_f": 60.3, + "windchill_c": 15.7, + "windchill_f": 60.3, + "heatindex_c": 15.7, + "heatindex_f": 60.3, + "dewpoint_c": 9.1, + "dewpoint_f": 48.4, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 27, + "gust_kph": 43.5, + "uv": 0 + }, + { + "time_epoch": 1732467600, + "time": "2024-11-24 18:00", + "temp_c": 15.3, + "temp_f": 59.5, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 15.4, + "wind_kph": 24.8, + "wind_degree": 149, + "wind_dir": "SSE", + "pressure_mb": 1011, + "pressure_in": 29.86, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 66, + "cloud": 15, + "feelslike_c": 15.3, + "feelslike_f": 59.5, + "windchill_c": 15.3, + "windchill_f": 59.5, + "heatindex_c": 15.3, + "heatindex_f": 59.5, + "dewpoint_c": 9.1, + "dewpoint_f": 48.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 27.8, + "gust_kph": 44.8, + "uv": 0 + }, + { + "time_epoch": 1732471200, + "time": "2024-11-24 19:00", + "temp_c": 15.2, + "temp_f": 59.4, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 15.7, + "wind_kph": 25.2, + "wind_degree": 150, + "wind_dir": "SSE", + "pressure_mb": 1011, + "pressure_in": 29.86, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 67, + "cloud": 47, + "feelslike_c": 15.2, + "feelslike_f": 59.4, + "windchill_c": 15.2, + "windchill_f": 59.4, + "heatindex_c": 15.2, + "heatindex_f": 59.4, + "dewpoint_c": 9.1, + "dewpoint_f": 48.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 28.4, + "gust_kph": 45.7, + "uv": 0 + }, + { + "time_epoch": 1732474800, + "time": "2024-11-24 20:00", + "temp_c": 15.2, + "temp_f": 59.4, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 14.8, + "wind_kph": 23.8, + "wind_degree": 149, + "wind_dir": "SSE", + "pressure_mb": 1012, + "pressure_in": 29.87, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 67, + "cloud": 56, + "feelslike_c": 15.2, + "feelslike_f": 59.4, + "windchill_c": 15.2, + "windchill_f": 59.4, + "heatindex_c": 15.2, + "heatindex_f": 59.4, + "dewpoint_c": 9.2, + "dewpoint_f": 48.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 26.5, + "gust_kph": 42.6, + "uv": 0 + }, + { + "time_epoch": 1732478400, + "time": "2024-11-24 21:00", + "temp_c": 15.4, + "temp_f": 59.7, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 14.3, + "wind_kph": 23, + "wind_degree": 151, + "wind_dir": "SSE", + "pressure_mb": 1011, + "pressure_in": 29.87, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 67, + "cloud": 49, + "feelslike_c": 15.4, + "feelslike_f": 59.7, + "windchill_c": 15.4, + "windchill_f": 59.7, + "heatindex_c": 15.4, + "heatindex_f": 59.7, + "dewpoint_c": 9.3, + "dewpoint_f": 48.8, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 24.7, + "gust_kph": 39.8, + "uv": 0 + }, + { + "time_epoch": 1732482000, + "time": "2024-11-24 22:00", + "temp_c": 15.3, + "temp_f": 59.5, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 13, + "wind_kph": 20.9, + "wind_degree": 155, + "wind_dir": "SSE", + "pressure_mb": 1011, + "pressure_in": 29.87, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 67, + "cloud": 47, + "feelslike_c": 15.3, + "feelslike_f": 59.5, + "windchill_c": 15.3, + "windchill_f": 59.5, + "heatindex_c": 15.3, + "heatindex_f": 59.5, + "dewpoint_c": 9.2, + "dewpoint_f": 48.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 22.7, + "gust_kph": 36.5, + "uv": 0 + }, + { + "time_epoch": 1732485600, + "time": "2024-11-24 23:00", + "temp_c": 15.3, + "temp_f": 59.5, + "is_day": 0, + "condition": { + "text": "Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/119.png", + "code": 1006 + }, + "wind_mph": 13.4, + "wind_kph": 21.6, + "wind_degree": 151, + "wind_dir": "SSE", + "pressure_mb": 1011, + "pressure_in": 29.87, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 65, + "cloud": 66, + "feelslike_c": 15.3, + "feelslike_f": 59.5, + "windchill_c": 15.3, + "windchill_f": 59.5, + "heatindex_c": 15.3, + "heatindex_f": 59.5, + "dewpoint_c": 8.8, + "dewpoint_f": 47.9, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 23.8, + "gust_kph": 38.3, + "uv": 0 + } + ] + }, + { + "date": "2024-11-25", + "date_epoch": 1732492800, + "day": { + "maxtemp_c": 15.5, + "maxtemp_f": 59.8, + "mintemp_c": 9.5, + "mintemp_f": 49.1, + "avgtemp_c": 12.7, + "avgtemp_f": 54.8, + "maxwind_mph": 15, + "maxwind_kph": 24.1, + "totalprecip_mm": 7.56, + "totalprecip_in": 0.3, + "totalsnow_cm": 0, + "avgvis_km": 8.6, + "avgvis_miles": 5, + "avghumidity": 80, + "daily_will_it_rain": 1, + "daily_chance_of_rain": 89, + "daily_will_it_snow": 0, + "daily_chance_of_snow": 0, + "condition": { + "text": "Moderate rain", + "icon": "//cdn.weatherapi.com/weather/64x64/day/302.png", + "code": 1189 + }, + "uv": 0 + }, + "astro": { + "sunrise": "08:13 AM", + "sunset": "05:26 PM", + "moonrise": "02:17 AM", + "moonset": "02:46 PM", + "moon_phase": "Waning Crescent", + "moon_illumination": 32, + "is_moon_up": 0, + "is_sun_up": 0 + }, + "hour": [ + { + "time_epoch": 1732489200, + "time": "2024-11-25 00:00", + "temp_c": 15.5, + "temp_f": 59.8, + "is_day": 0, + "condition": { + "text": "Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/119.png", + "code": 1006 + }, + "wind_mph": 13.4, + "wind_kph": 21.6, + "wind_degree": 153, + "wind_dir": "SSE", + "pressure_mb": 1011, + "pressure_in": 29.85, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 63, + "cloud": 66, + "feelslike_c": 15.5, + "feelslike_f": 59.8, + "windchill_c": 15.5, + "windchill_f": 59.8, + "heatindex_c": 15.5, + "heatindex_f": 59.8, + "dewpoint_c": 8.5, + "dewpoint_f": 47.2, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 24.7, + "gust_kph": 39.7, + "uv": 0 + }, + { + "time_epoch": 1732492800, + "time": "2024-11-25 01:00", + "temp_c": 15.3, + "temp_f": 59.5, + "is_day": 0, + "condition": { + "text": "Patchy rain nearby", + "icon": "//cdn.weatherapi.com/weather/64x64/night/176.png", + "code": 1063 + }, + "wind_mph": 15, + "wind_kph": 24.1, + "wind_degree": 150, + "wind_dir": "SSE", + "pressure_mb": 1011, + "pressure_in": 29.86, + "precip_mm": 0.04, + "precip_in": 0, + "snow_cm": 0, + "humidity": 64, + "cloud": 56, + "feelslike_c": 15.3, + "feelslike_f": 59.5, + "windchill_c": 15.3, + "windchill_f": 59.5, + "heatindex_c": 15.3, + "heatindex_f": 59.5, + "dewpoint_c": 8.5, + "dewpoint_f": 47.3, + "will_it_rain": 0, + "chance_of_rain": 69, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 26.1, + "gust_kph": 42.1, + "uv": 0 + }, + { + "time_epoch": 1732496400, + "time": "2024-11-25 02:00", + "temp_c": 15.3, + "temp_f": 59.5, + "is_day": 0, + "condition": { + "text": "Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/119.png", + "code": 1006 + }, + "wind_mph": 14.5, + "wind_kph": 23.4, + "wind_degree": 148, + "wind_dir": "SSE", + "pressure_mb": 1010, + "pressure_in": 29.82, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 62, + "cloud": 67, + "feelslike_c": 15.3, + "feelslike_f": 59.5, + "windchill_c": 15.3, + "windchill_f": 59.5, + "heatindex_c": 15.3, + "heatindex_f": 59.5, + "dewpoint_c": 8.1, + "dewpoint_f": 46.6, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 26.6, + "gust_kph": 42.7, + "uv": 0 + }, + { + "time_epoch": 1732500000, + "time": "2024-11-25 03:00", + "temp_c": 15.5, + "temp_f": 60, + "is_day": 0, + "condition": { + "text": "Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/119.png", + "code": 1006 + }, + "wind_mph": 15, + "wind_kph": 24.1, + "wind_degree": 154, + "wind_dir": "SSE", + "pressure_mb": 1010, + "pressure_in": 29.81, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 59, + "cloud": 82, + "feelslike_c": 15.5, + "feelslike_f": 60, + "windchill_c": 15.5, + "windchill_f": 60, + "heatindex_c": 15.5, + "heatindex_f": 60, + "dewpoint_c": 7.5, + "dewpoint_f": 45.4, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 26.2, + "gust_kph": 42.1, + "uv": 0 + }, + { + "time_epoch": 1732503600, + "time": "2024-11-25 04:00", + "temp_c": 16.4, + "temp_f": 61.6, + "is_day": 0, + "condition": { + "text": "Patchy rain nearby", + "icon": "//cdn.weatherapi.com/weather/64x64/night/176.png", + "code": 1063 + }, + "wind_mph": 15, + "wind_kph": 24.1, + "wind_degree": 160, + "wind_dir": "SSE", + "pressure_mb": 1009, + "pressure_in": 29.79, + "precip_mm": 0.01, + "precip_in": 0, + "snow_cm": 0, + "humidity": 54, + "cloud": 85, + "feelslike_c": 16.4, + "feelslike_f": 61.6, + "windchill_c": 16.4, + "windchill_f": 61.6, + "heatindex_c": 16.4, + "heatindex_f": 61.6, + "dewpoint_c": 7, + "dewpoint_f": 44.7, + "will_it_rain": 0, + "chance_of_rain": 70, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 26.5, + "gust_kph": 42.7, + "uv": 0 + }, + { + "time_epoch": 1732507200, + "time": "2024-11-25 05:00", + "temp_c": 15.5, + "temp_f": 59.9, + "is_day": 0, + "condition": { + "text": "Patchy rain nearby", + "icon": "//cdn.weatherapi.com/weather/64x64/night/176.png", + "code": 1063 + }, + "wind_mph": 12.5, + "wind_kph": 20.2, + "wind_degree": 154, + "wind_dir": "SSE", + "pressure_mb": 1009, + "pressure_in": 29.8, + "precip_mm": 0.01, + "precip_in": 0, + "snow_cm": 0, + "humidity": 59, + "cloud": 86, + "feelslike_c": 15.5, + "feelslike_f": 59.9, + "windchill_c": 15.5, + "windchill_f": 59.9, + "heatindex_c": 15.5, + "heatindex_f": 59.9, + "dewpoint_c": 7.5, + "dewpoint_f": 45.5, + "will_it_rain": 0, + "chance_of_rain": 67, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 21.5, + "gust_kph": 34.5, + "uv": 0 + }, + { + "time_epoch": 1732510800, + "time": "2024-11-25 06:00", + "temp_c": 15.5, + "temp_f": 59.8, + "is_day": 0, + "condition": { + "text": "Patchy rain nearby", + "icon": "//cdn.weatherapi.com/weather/64x64/night/176.png", + "code": 1063 + }, + "wind_mph": 8.5, + "wind_kph": 13.7, + "wind_degree": 181, + "wind_dir": "S", + "pressure_mb": 1010, + "pressure_in": 29.83, + "precip_mm": 0.04, + "precip_in": 0, + "snow_cm": 0, + "humidity": 60, + "cloud": 85, + "feelslike_c": 15.5, + "feelslike_f": 59.8, + "windchill_c": 15.5, + "windchill_f": 59.8, + "heatindex_c": 15.5, + "heatindex_f": 59.8, + "dewpoint_c": 7.8, + "dewpoint_f": 46.1, + "will_it_rain": 0, + "chance_of_rain": 64, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 15.4, + "gust_kph": 24.8, + "uv": 0 + }, + { + "time_epoch": 1732514400, + "time": "2024-11-25 07:00", + "temp_c": 15.1, + "temp_f": 59.1, + "is_day": 0, + "condition": { + "text": "Patchy light drizzle", + "icon": "//cdn.weatherapi.com/weather/64x64/night/263.png", + "code": 1150 + }, + "wind_mph": 7.2, + "wind_kph": 11.5, + "wind_degree": 238, + "wind_dir": "WSW", + "pressure_mb": 1011, + "pressure_in": 29.84, + "precip_mm": 0.32, + "precip_in": 0.01, + "snow_cm": 0, + "humidity": 73, + "cloud": 71, + "feelslike_c": 15.1, + "feelslike_f": 59.1, + "windchill_c": 15.1, + "windchill_f": 59.1, + "heatindex_c": 15.1, + "heatindex_f": 59.1, + "dewpoint_c": 10.2, + "dewpoint_f": 50.3, + "will_it_rain": 1, + "chance_of_rain": 100, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 5, + "vis_miles": 3, + "gust_mph": 12.5, + "gust_kph": 20.1, + "uv": 0 + }, + { + "time_epoch": 1732518000, + "time": "2024-11-25 08:00", + "temp_c": 13.6, + "temp_f": 56.5, + "is_day": 0, + "condition": { + "text": "Light rain", + "icon": "//cdn.weatherapi.com/weather/64x64/night/296.png", + "code": 1183 + }, + "wind_mph": 8.5, + "wind_kph": 13.7, + "wind_degree": 301, + "wind_dir": "WNW", + "pressure_mb": 1012, + "pressure_in": 29.88, + "precip_mm": 0.84, + "precip_in": 0.03, + "snow_cm": 0, + "humidity": 91, + "cloud": 100, + "feelslike_c": 12.5, + "feelslike_f": 54.5, + "windchill_c": 12.5, + "windchill_f": 54.5, + "heatindex_c": 13.6, + "heatindex_f": 56.5, + "dewpoint_c": 12.2, + "dewpoint_f": 53.9, + "will_it_rain": 1, + "chance_of_rain": 100, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 9, + "vis_miles": 5, + "gust_mph": 12.6, + "gust_kph": 20.3, + "uv": 0 + }, + { + "time_epoch": 1732521600, + "time": "2024-11-25 09:00", + "temp_c": 12.4, + "temp_f": 54.3, + "is_day": 1, + "condition": { + "text": "Light drizzle", + "icon": "//cdn.weatherapi.com/weather/64x64/day/266.png", + "code": 1153 + }, + "wind_mph": 6.7, + "wind_kph": 10.8, + "wind_degree": 316, + "wind_dir": "NW", + "pressure_mb": 1013, + "pressure_in": 29.92, + "precip_mm": 0.73, + "precip_in": 0.03, + "snow_cm": 0, + "humidity": 92, + "cloud": 100, + "feelslike_c": 11.4, + "feelslike_f": 52.5, + "windchill_c": 11.4, + "windchill_f": 52.5, + "heatindex_c": 12.4, + "heatindex_f": 54.3, + "dewpoint_c": 11.1, + "dewpoint_f": 52.1, + "will_it_rain": 1, + "chance_of_rain": 100, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 2, + "vis_miles": 1, + "gust_mph": 9.8, + "gust_kph": 15.7, + "uv": 0 + }, + { + "time_epoch": 1732525200, + "time": "2024-11-25 10:00", + "temp_c": 12.4, + "temp_f": 54.4, + "is_day": 1, + "condition": { + "text": "Light rain", + "icon": "//cdn.weatherapi.com/weather/64x64/day/296.png", + "code": 1183 + }, + "wind_mph": 5.1, + "wind_kph": 8.3, + "wind_degree": 290, + "wind_dir": "WNW", + "pressure_mb": 1014, + "pressure_in": 29.95, + "precip_mm": 0.89, + "precip_in": 0.04, + "snow_cm": 0, + "humidity": 92, + "cloud": 100, + "feelslike_c": 11.8, + "feelslike_f": 53.3, + "windchill_c": 11.8, + "windchill_f": 53.3, + "heatindex_c": 12.5, + "heatindex_f": 54.4, + "dewpoint_c": 11.2, + "dewpoint_f": 52.1, + "will_it_rain": 1, + "chance_of_rain": 100, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 9, + "vis_miles": 5, + "gust_mph": 7.5, + "gust_kph": 12.1, + "uv": 0 + }, + { + "time_epoch": 1732528800, + "time": "2024-11-25 11:00", + "temp_c": 12.5, + "temp_f": 54.4, + "is_day": 1, + "condition": { + "text": "Light rain", + "icon": "//cdn.weatherapi.com/weather/64x64/day/296.png", + "code": 1183 + }, + "wind_mph": 6, + "wind_kph": 9.7, + "wind_degree": 311, + "wind_dir": "NW", + "pressure_mb": 1015, + "pressure_in": 29.96, + "precip_mm": 1.36, + "precip_in": 0.05, + "snow_cm": 0, + "humidity": 93, + "cloud": 100, + "feelslike_c": 11.6, + "feelslike_f": 52.9, + "windchill_c": 11.6, + "windchill_f": 52.9, + "heatindex_c": 12.5, + "heatindex_f": 54.4, + "dewpoint_c": 11.3, + "dewpoint_f": 52.3, + "will_it_rain": 1, + "chance_of_rain": 100, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 9, + "vis_miles": 5, + "gust_mph": 8.6, + "gust_kph": 13.8, + "uv": 0.1 + }, + { + "time_epoch": 1732532400, + "time": "2024-11-25 12:00", + "temp_c": 11.9, + "temp_f": 53.4, + "is_day": 1, + "condition": { + "text": "Light rain", + "icon": "//cdn.weatherapi.com/weather/64x64/day/296.png", + "code": 1183 + }, + "wind_mph": 8.3, + "wind_kph": 13.3, + "wind_degree": 308, + "wind_dir": "NW", + "pressure_mb": 1016, + "pressure_in": 29.99, + "precip_mm": 2.14, + "precip_in": 0.08, + "snow_cm": 0, + "humidity": 93, + "cloud": 100, + "feelslike_c": 10.4, + "feelslike_f": 50.8, + "windchill_c": 10.4, + "windchill_f": 50.8, + "heatindex_c": 11.9, + "heatindex_f": 53.4, + "dewpoint_c": 10.8, + "dewpoint_f": 51.4, + "will_it_rain": 1, + "chance_of_rain": 100, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 9, + "vis_miles": 5, + "gust_mph": 11.6, + "gust_kph": 18.6, + "uv": 0.1 + }, + { + "time_epoch": 1732536000, + "time": "2024-11-25 13:00", + "temp_c": 11.3, + "temp_f": 52.4, + "is_day": 1, + "condition": { + "text": "Light rain shower", + "icon": "//cdn.weatherapi.com/weather/64x64/day/353.png", + "code": 1240 + }, + "wind_mph": 6.7, + "wind_kph": 10.8, + "wind_degree": 302, + "wind_dir": "WNW", + "pressure_mb": 1016, + "pressure_in": 30.01, + "precip_mm": 0.24, + "precip_in": 0.01, + "snow_cm": 0, + "humidity": 89, + "cloud": 100, + "feelslike_c": 10.1, + "feelslike_f": 50.2, + "windchill_c": 10.1, + "windchill_f": 50.2, + "heatindex_c": 11.3, + "heatindex_f": 52.4, + "dewpoint_c": 9.6, + "dewpoint_f": 49.3, + "will_it_rain": 1, + "chance_of_rain": 100, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 9.5, + "gust_kph": 15.2, + "uv": 0.1 + }, + { + "time_epoch": 1732539600, + "time": "2024-11-25 14:00", + "temp_c": 11.3, + "temp_f": 52.4, + "is_day": 1, + "condition": { + "text": "Light drizzle", + "icon": "//cdn.weatherapi.com/weather/64x64/day/266.png", + "code": 1153 + }, + "wind_mph": 5.6, + "wind_kph": 9, + "wind_degree": 283, + "wind_dir": "WNW", + "pressure_mb": 1017, + "pressure_in": 30.03, + "precip_mm": 0.37, + "precip_in": 0.01, + "snow_cm": 0, + "humidity": 89, + "cloud": 100, + "feelslike_c": 10.4, + "feelslike_f": 50.7, + "windchill_c": 10.4, + "windchill_f": 50.7, + "heatindex_c": 11.3, + "heatindex_f": 52.4, + "dewpoint_c": 9.5, + "dewpoint_f": 49.2, + "will_it_rain": 1, + "chance_of_rain": 100, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 2, + "vis_miles": 1, + "gust_mph": 8, + "gust_kph": 12.9, + "uv": 0.1 + }, + { + "time_epoch": 1732543200, + "time": "2024-11-25 15:00", + "temp_c": 11.3, + "temp_f": 52.3, + "is_day": 1, + "condition": { + "text": "Light drizzle", + "icon": "//cdn.weatherapi.com/weather/64x64/day/266.png", + "code": 1153 + }, + "wind_mph": 7.4, + "wind_kph": 11.9, + "wind_degree": 269, + "wind_dir": "W", + "pressure_mb": 1018, + "pressure_in": 30.06, + "precip_mm": 0.45, + "precip_in": 0.02, + "snow_cm": 0, + "humidity": 88, + "cloud": 100, + "feelslike_c": 9.9, + "feelslike_f": 49.7, + "windchill_c": 9.9, + "windchill_f": 49.7, + "heatindex_c": 11.3, + "heatindex_f": 52.3, + "dewpoint_c": 9.4, + "dewpoint_f": 49, + "will_it_rain": 1, + "chance_of_rain": 100, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 2, + "vis_miles": 1, + "gust_mph": 11, + "gust_kph": 17.6, + "uv": 0.1 + }, + { + "time_epoch": 1732546800, + "time": "2024-11-25 16:00", + "temp_c": 11.4, + "temp_f": 52.5, + "is_day": 1, + "condition": { + "text": "Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/day/119.png", + "code": 1006 + }, + "wind_mph": 3.6, + "wind_kph": 5.8, + "wind_degree": 244, + "wind_dir": "WSW", + "pressure_mb": 1018, + "pressure_in": 30.07, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 82, + "cloud": 65, + "feelslike_c": 11.2, + "feelslike_f": 52.1, + "windchill_c": 11.2, + "windchill_f": 52.1, + "heatindex_c": 11.4, + "heatindex_f": 52.5, + "dewpoint_c": 8.4, + "dewpoint_f": 47.1, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 5.6, + "gust_kph": 9, + "uv": 0.1 + }, + { + "time_epoch": 1732550400, + "time": "2024-11-25 17:00", + "temp_c": 11.4, + "temp_f": 52.4, + "is_day": 1, + "condition": { + "text": "Patchy rain nearby", + "icon": "//cdn.weatherapi.com/weather/64x64/day/176.png", + "code": 1063 + }, + "wind_mph": 5.1, + "wind_kph": 8.3, + "wind_degree": 191, + "wind_dir": "SSW", + "pressure_mb": 1019, + "pressure_in": 30.09, + "precip_mm": 0.02, + "precip_in": 0, + "snow_cm": 0, + "humidity": 82, + "cloud": 58, + "feelslike_c": 10.5, + "feelslike_f": 51, + "windchill_c": 10.5, + "windchill_f": 51, + "heatindex_c": 11.4, + "heatindex_f": 52.4, + "dewpoint_c": 8.4, + "dewpoint_f": 47, + "will_it_rain": 1, + "chance_of_rain": 89, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 8.4, + "gust_kph": 13.5, + "uv": 0 + }, + { + "time_epoch": 1732554000, + "time": "2024-11-25 18:00", + "temp_c": 11.1, + "temp_f": 51.9, + "is_day": 0, + "condition": { + "text": "Patchy rain nearby", + "icon": "//cdn.weatherapi.com/weather/64x64/night/176.png", + "code": 1063 + }, + "wind_mph": 5.6, + "wind_kph": 9, + "wind_degree": 207, + "wind_dir": "SSW", + "pressure_mb": 1020, + "pressure_in": 30.11, + "precip_mm": 0.02, + "precip_in": 0, + "snow_cm": 0, + "humidity": 85, + "cloud": 89, + "feelslike_c": 10.1, + "feelslike_f": 50.1, + "windchill_c": 10.1, + "windchill_f": 50.1, + "heatindex_c": 11.1, + "heatindex_f": 51.9, + "dewpoint_c": 8.6, + "dewpoint_f": 47.4, + "will_it_rain": 1, + "chance_of_rain": 76, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 9.4, + "gust_kph": 15.2, + "uv": 0 + }, + { + "time_epoch": 1732557600, + "time": "2024-11-25 19:00", + "temp_c": 10.6, + "temp_f": 51.1, + "is_day": 0, + "condition": { + "text": "Patchy rain nearby", + "icon": "//cdn.weatherapi.com/weather/64x64/night/176.png", + "code": 1063 + }, + "wind_mph": 6, + "wind_kph": 9.7, + "wind_degree": 198, + "wind_dir": "SSW", + "pressure_mb": 1020, + "pressure_in": 30.13, + "precip_mm": 0.04, + "precip_in": 0, + "snow_cm": 0, + "humidity": 88, + "cloud": 88, + "feelslike_c": 9.4, + "feelslike_f": 48.9, + "windchill_c": 9.4, + "windchill_f": 48.9, + "heatindex_c": 10.6, + "heatindex_f": 51.1, + "dewpoint_c": 8.8, + "dewpoint_f": 47.8, + "will_it_rain": 1, + "chance_of_rain": 76, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 10.6, + "gust_kph": 17.1, + "uv": 0 + }, + { + "time_epoch": 1732561200, + "time": "2024-11-25 20:00", + "temp_c": 10.1, + "temp_f": 50.2, + "is_day": 0, + "condition": { + "text": "Patchy rain nearby", + "icon": "//cdn.weatherapi.com/weather/64x64/night/176.png", + "code": 1063 + }, + "wind_mph": 5.6, + "wind_kph": 9, + "wind_degree": 198, + "wind_dir": "SSW", + "pressure_mb": 1021, + "pressure_in": 30.16, + "precip_mm": 0.04, + "precip_in": 0, + "snow_cm": 0, + "humidity": 92, + "cloud": 87, + "feelslike_c": 8.9, + "feelslike_f": 48.1, + "windchill_c": 8.9, + "windchill_f": 48.1, + "heatindex_c": 10.1, + "heatindex_f": 50.2, + "dewpoint_c": 8.8, + "dewpoint_f": 47.9, + "will_it_rain": 1, + "chance_of_rain": 75, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 10.5, + "gust_kph": 16.9, + "uv": 0 + }, + { + "time_epoch": 1732564800, + "time": "2024-11-25 21:00", + "temp_c": 9.8, + "temp_f": 49.6, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 5.1, + "wind_kph": 8.3, + "wind_degree": 198, + "wind_dir": "SSW", + "pressure_mb": 1022, + "pressure_in": 30.17, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 93, + "cloud": 18, + "feelslike_c": 8.7, + "feelslike_f": 47.7, + "windchill_c": 8.7, + "windchill_f": 47.7, + "heatindex_c": 9.8, + "heatindex_f": 49.6, + "dewpoint_c": 8.7, + "dewpoint_f": 47.7, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 10.2, + "gust_kph": 16.3, + "uv": 0 + }, + { + "time_epoch": 1732568400, + "time": "2024-11-25 22:00", + "temp_c": 9.6, + "temp_f": 49.4, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 5.1, + "wind_kph": 8.3, + "wind_degree": 198, + "wind_dir": "SSW", + "pressure_mb": 1022, + "pressure_in": 30.19, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 93, + "cloud": 24, + "feelslike_c": 8.5, + "feelslike_f": 47.4, + "windchill_c": 8.5, + "windchill_f": 47.4, + "heatindex_c": 9.6, + "heatindex_f": 49.4, + "dewpoint_c": 8.6, + "dewpoint_f": 47.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 10.2, + "gust_kph": 16.5, + "uv": 0 + }, + { + "time_epoch": 1732572000, + "time": "2024-11-25 23:00", + "temp_c": 9.5, + "temp_f": 49.1, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 4.9, + "wind_kph": 7.9, + "wind_degree": 198, + "wind_dir": "SSW", + "pressure_mb": 1023, + "pressure_in": 30.2, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 93, + "cloud": 26, + "feelslike_c": 8.4, + "feelslike_f": 47.2, + "windchill_c": 8.4, + "windchill_f": 47.2, + "heatindex_c": 9.5, + "heatindex_f": 49.1, + "dewpoint_c": 8.5, + "dewpoint_f": 47.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 9.8, + "gust_kph": 15.8, + "uv": 0 + } + ] + }, + { + "date": "2024-11-23", + "date_epoch": 1732320000, + "day": { + "maxtemp_c": 13.1, + "maxtemp_f": 55.7, + "mintemp_c": 4, + "mintemp_f": 39.3, + "avgtemp_c": 8.1, + "avgtemp_f": 46.6, + "maxwind_mph": 13, + "maxwind_kph": 20.9, + "totalprecip_mm": 0, + "totalprecip_in": 0, + "totalsnow_cm": 0, + "avgvis_km": 10, + "avgvis_miles": 6, + "avghumidity": 69, + "daily_will_it_rain": 0, + "daily_chance_of_rain": 0, + "daily_will_it_snow": 0, + "daily_chance_of_snow": 0, + "condition": { + "text": "Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/day/116.png", + "code": 1003 + }, + "uv": 0.3 + }, + "astro": { + "sunrise": "08:10 AM", + "sunset": "05:27 PM", + "moonrise": "12:08 AM", + "moonset": "02:13 PM", + "moon_phase": "Last Quarter", + "moon_illumination": 51, + "is_moon_up": 0, + "is_sun_up": 0 + }, + "hour": [ + { + "time_epoch": 1732316400, + "time": "2024-11-23 00:00", + "temp_c": 5.6, + "temp_f": 42, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 6.7, + "wind_kph": 10.8, + "wind_degree": 150, + "wind_dir": "SSE", + "pressure_mb": 1022, + "pressure_in": 30.19, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 81, + "cloud": 17, + "feelslike_c": 3.2, + "feelslike_f": 37.7, + "windchill_c": 3.2, + "windchill_f": 37.7, + "heatindex_c": 5.6, + "heatindex_f": 42, + "dewpoint_c": 2.6, + "dewpoint_f": 36.6, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 13.7, + "gust_kph": 22, + "uv": 0 + }, + { + "time_epoch": 1732320000, + "time": "2024-11-23 01:00", + "temp_c": 5.8, + "temp_f": 42.4, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 6.9, + "wind_kph": 11.2, + "wind_degree": 141, + "wind_dir": "SE", + "pressure_mb": 1022, + "pressure_in": 30.17, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 80, + "cloud": 47, + "feelslike_c": 3.3, + "feelslike_f": 38, + "windchill_c": 3.3, + "windchill_f": 38, + "heatindex_c": 5.8, + "heatindex_f": 42.4, + "dewpoint_c": 2.5, + "dewpoint_f": 36.6, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 13.4, + "gust_kph": 21.6, + "uv": 0 + }, + { + "time_epoch": 1732323600, + "time": "2024-11-23 02:00", + "temp_c": 5.6, + "temp_f": 42.1, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 8.3, + "wind_kph": 13.3, + "wind_degree": 137, + "wind_dir": "SE", + "pressure_mb": 1021, + "pressure_in": 30.14, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 79, + "cloud": 56, + "feelslike_c": 2.8, + "feelslike_f": 37, + "windchill_c": 2.8, + "windchill_f": 37, + "heatindex_c": 5.6, + "heatindex_f": 42.1, + "dewpoint_c": 2.3, + "dewpoint_f": 36.1, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 15.3, + "gust_kph": 24.7, + "uv": 0 + }, + { + "time_epoch": 1732327200, + "time": "2024-11-23 03:00", + "temp_c": 5.1, + "temp_f": 41.3, + "is_day": 0, + "condition": { + "text": "Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/119.png", + "code": 1006 + }, + "wind_mph": 9.8, + "wind_kph": 15.8, + "wind_degree": 133, + "wind_dir": "SE", + "pressure_mb": 1020, + "pressure_in": 30.13, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 79, + "cloud": 86, + "feelslike_c": 1.8, + "feelslike_f": 35.3, + "windchill_c": 1.8, + "windchill_f": 35.3, + "heatindex_c": 5.2, + "heatindex_f": 41.3, + "dewpoint_c": 1.9, + "dewpoint_f": 35.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 17.6, + "gust_kph": 28.4, + "uv": 0 + }, + { + "time_epoch": 1732330800, + "time": "2024-11-23 04:00", + "temp_c": 4.8, + "temp_f": 40.7, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 10.5, + "wind_kph": 16.9, + "wind_degree": 130, + "wind_dir": "SE", + "pressure_mb": 1020, + "pressure_in": 30.12, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 79, + "cloud": 44, + "feelslike_c": 1.3, + "feelslike_f": 34.3, + "windchill_c": 1.3, + "windchill_f": 34.3, + "heatindex_c": 4.9, + "heatindex_f": 40.7, + "dewpoint_c": 1.5, + "dewpoint_f": 34.7, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 18.1, + "gust_kph": 29.1, + "uv": 0 + }, + { + "time_epoch": 1732334400, + "time": "2024-11-23 05:00", + "temp_c": 4.3, + "temp_f": 39.7, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 11, + "wind_kph": 17.6, + "wind_degree": 130, + "wind_dir": "SE", + "pressure_mb": 1019, + "pressure_in": 30.1, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 80, + "cloud": 46, + "feelslike_c": 0.5, + "feelslike_f": 32.8, + "windchill_c": 0.5, + "windchill_f": 32.8, + "heatindex_c": 4.3, + "heatindex_f": 39.7, + "dewpoint_c": 1.2, + "dewpoint_f": 34.1, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 19.1, + "gust_kph": 30.8, + "uv": 0 + }, + { + "time_epoch": 1732338000, + "time": "2024-11-23 06:00", + "temp_c": 4, + "temp_f": 39.3, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 11.2, + "wind_kph": 18, + "wind_degree": 138, + "wind_dir": "SE", + "pressure_mb": 1019, + "pressure_in": 30.08, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 80, + "cloud": 39, + "feelslike_c": 0.1, + "feelslike_f": 32.2, + "windchill_c": 0.1, + "windchill_f": 32.2, + "heatindex_c": 4, + "heatindex_f": 39.3, + "dewpoint_c": 0.9, + "dewpoint_f": 33.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 19.8, + "gust_kph": 31.8, + "uv": 0 + }, + { + "time_epoch": 1732341600, + "time": "2024-11-23 07:00", + "temp_c": 4, + "temp_f": 39.3, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 11.2, + "wind_kph": 18, + "wind_degree": 138, + "wind_dir": "SE", + "pressure_mb": 1019, + "pressure_in": 30.08, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 78, + "cloud": 26, + "feelslike_c": 0.1, + "feelslike_f": 32.2, + "windchill_c": 0.1, + "windchill_f": 32.2, + "heatindex_c": 4, + "heatindex_f": 39.3, + "dewpoint_c": 0.6, + "dewpoint_f": 33, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 19.9, + "gust_kph": 32, + "uv": 0 + }, + { + "time_epoch": 1732345200, + "time": "2024-11-23 08:00", + "temp_c": 4.1, + "temp_f": 39.4, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 10.5, + "wind_kph": 16.9, + "wind_degree": 140, + "wind_dir": "SE", + "pressure_mb": 1019, + "pressure_in": 30.09, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 75, + "cloud": 7, + "feelslike_c": 0.4, + "feelslike_f": 32.6, + "windchill_c": 0.4, + "windchill_f": 32.6, + "heatindex_c": 4.1, + "heatindex_f": 39.4, + "dewpoint_c": 0.1, + "dewpoint_f": 32.1, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 18.8, + "gust_kph": 30.2, + "uv": 0 + }, + { + "time_epoch": 1732348800, + "time": "2024-11-23 09:00", + "temp_c": 4.6, + "temp_f": 40.3, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 10.7, + "wind_kph": 17.3, + "wind_degree": 143, + "wind_dir": "SE", + "pressure_mb": 1019, + "pressure_in": 30.09, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 71, + "cloud": 13, + "feelslike_c": 1, + "feelslike_f": 33.7, + "windchill_c": 1, + "windchill_f": 33.7, + "heatindex_c": 4.6, + "heatindex_f": 40.4, + "dewpoint_c": -0.2, + "dewpoint_f": 31.7, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 19, + "gust_kph": 30.5, + "uv": 0 + }, + { + "time_epoch": 1732352400, + "time": "2024-11-23 10:00", + "temp_c": 6.6, + "temp_f": 43.9, + "is_day": 1, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/day/116.png", + "code": 1003 + }, + "wind_mph": 11.2, + "wind_kph": 18, + "wind_degree": 146, + "wind_dir": "SSE", + "pressure_mb": 1019, + "pressure_in": 30.08, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 63, + "cloud": 49, + "feelslike_c": 3.4, + "feelslike_f": 38, + "windchill_c": 3.4, + "windchill_f": 38, + "heatindex_c": 6.6, + "heatindex_f": 43.9, + "dewpoint_c": 0.2, + "dewpoint_f": 32.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 17.5, + "gust_kph": 28.2, + "uv": 0.4 + }, + { + "time_epoch": 1732356000, + "time": "2024-11-23 11:00", + "temp_c": 7.4, + "temp_f": 45.3, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 11.4, + "wind_kph": 18.4, + "wind_degree": 148, + "wind_dir": "SSE", + "pressure_mb": 1018, + "pressure_in": 30.06, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 76, + "cloud": 0, + "feelslike_c": 6, + "feelslike_f": 42.7, + "windchill_c": 6, + "windchill_f": 42.7, + "heatindex_c": 8.7, + "heatindex_f": 47.7, + "dewpoint_c": 1.3, + "dewpoint_f": 34.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 15.9, + "gust_kph": 25.5, + "uv": 0.9 + }, + { + "time_epoch": 1732359600, + "time": "2024-11-23 12:00", + "temp_c": 10.4, + "temp_f": 50.8, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 11, + "wind_kph": 17.6, + "wind_degree": 149, + "wind_dir": "SSE", + "pressure_mb": 1018, + "pressure_in": 30.07, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 56, + "cloud": 21, + "feelslike_c": 8.2, + "feelslike_f": 46.7, + "windchill_c": 8.2, + "windchill_f": 46.7, + "heatindex_c": 10.5, + "heatindex_f": 50.8, + "dewpoint_c": 2.2, + "dewpoint_f": 35.9, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 14.7, + "gust_kph": 23.6, + "uv": 1.3 + }, + { + "time_epoch": 1732363200, + "time": "2024-11-23 13:00", + "temp_c": 12.3, + "temp_f": 54.2, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 11.4, + "wind_kph": 18.4, + "wind_degree": 150, + "wind_dir": "SSE", + "pressure_mb": 1017, + "pressure_in": 30.04, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 52, + "cloud": 22, + "feelslike_c": 10.5, + "feelslike_f": 50.8, + "windchill_c": 10.5, + "windchill_f": 50.8, + "heatindex_c": 12.3, + "heatindex_f": 54.2, + "dewpoint_c": 2.8, + "dewpoint_f": 37, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 14.9, + "gust_kph": 23.9, + "uv": 1.6 + }, + { + "time_epoch": 1732366800, + "time": "2024-11-23 14:00", + "temp_c": 13, + "temp_f": 55.4, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 10.3, + "wind_kph": 16.6, + "wind_degree": 149, + "wind_dir": "SSE", + "pressure_mb": 1017, + "pressure_in": 30.03, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 54, + "cloud": 12, + "feelslike_c": 11.5, + "feelslike_f": 52.7, + "windchill_c": 11.5, + "windchill_f": 52.7, + "heatindex_c": 13, + "heatindex_f": 55.4, + "dewpoint_c": 3.8, + "dewpoint_f": 38.9, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 14, + "gust_kph": 22.6, + "uv": 1.4 + }, + { + "time_epoch": 1732370400, + "time": "2024-11-23 15:00", + "temp_c": 13.1, + "temp_f": 55.7, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 11.4, + "wind_kph": 18.4, + "wind_degree": 150, + "wind_dir": "SSE", + "pressure_mb": 1016, + "pressure_in": 30.01, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 54, + "cloud": 22, + "feelslike_c": 11.5, + "feelslike_f": 52.7, + "windchill_c": 11.5, + "windchill_f": 52.7, + "heatindex_c": 13.2, + "heatindex_f": 55.7, + "dewpoint_c": 4.2, + "dewpoint_f": 39.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 16.8, + "gust_kph": 27.1, + "uv": 0.9 + }, + { + "time_epoch": 1732374000, + "time": "2024-11-23 16:00", + "temp_c": 12.5, + "temp_f": 54.4, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 10.7, + "wind_kph": 17.3, + "wind_degree": 148, + "wind_dir": "SSE", + "pressure_mb": 1017, + "pressure_in": 30.02, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 57, + "cloud": 14, + "feelslike_c": 10.7, + "feelslike_f": 51.3, + "windchill_c": 10.7, + "windchill_f": 51.3, + "heatindex_c": 12.5, + "heatindex_f": 54.4, + "dewpoint_c": 4.1, + "dewpoint_f": 39.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 17.5, + "gust_kph": 28.1, + "uv": 0.4 + }, + { + "time_epoch": 1732377600, + "time": "2024-11-23 17:00", + "temp_c": 11.2, + "temp_f": 52.1, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 11.2, + "wind_kph": 18, + "wind_degree": 141, + "wind_dir": "SE", + "pressure_mb": 1017, + "pressure_in": 30.02, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 61, + "cloud": 22, + "feelslike_c": 9, + "feelslike_f": 48.2, + "windchill_c": 9, + "windchill_f": 48.2, + "heatindex_c": 11.2, + "heatindex_f": 52.1, + "dewpoint_c": 4, + "dewpoint_f": 39.2, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 20.2, + "gust_kph": 32.5, + "uv": 0 + }, + { + "time_epoch": 1732381200, + "time": "2024-11-23 18:00", + "temp_c": 10.2, + "temp_f": 50.4, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 11.6, + "wind_kph": 18.7, + "wind_degree": 141, + "wind_dir": "SE", + "pressure_mb": 1016, + "pressure_in": 30.02, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 64, + "cloud": 47, + "feelslike_c": 7.8, + "feelslike_f": 46, + "windchill_c": 7.8, + "windchill_f": 46, + "heatindex_c": 10.2, + "heatindex_f": 50.4, + "dewpoint_c": 3.8, + "dewpoint_f": 38.9, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 22.2, + "gust_kph": 35.7, + "uv": 0 + }, + { + "time_epoch": 1732384800, + "time": "2024-11-23 19:00", + "temp_c": 10, + "temp_f": 50.1, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 12.3, + "wind_kph": 19.8, + "wind_degree": 144, + "wind_dir": "SE", + "pressure_mb": 1017, + "pressure_in": 30.02, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 66, + "cloud": 62, + "feelslike_c": 7.5, + "feelslike_f": 45.4, + "windchill_c": 7.5, + "windchill_f": 45.4, + "heatindex_c": 10, + "heatindex_f": 50.1, + "dewpoint_c": 4, + "dewpoint_f": 39.2, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 22.8, + "gust_kph": 36.8, + "uv": 0 + }, + { + "time_epoch": 1732388400, + "time": "2024-11-23 20:00", + "temp_c": 10, + "temp_f": 50, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 12.5, + "wind_kph": 20.2, + "wind_degree": 143, + "wind_dir": "SE", + "pressure_mb": 1017, + "pressure_in": 30.03, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 68, + "cloud": 57, + "feelslike_c": 7.3, + "feelslike_f": 45.2, + "windchill_c": 7.3, + "windchill_f": 45.2, + "heatindex_c": 10, + "heatindex_f": 50, + "dewpoint_c": 4.3, + "dewpoint_f": 39.8, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 22.6, + "gust_kph": 36.3, + "uv": 0 + }, + { + "time_epoch": 1732392000, + "time": "2024-11-23 21:00", + "temp_c": 9.7, + "temp_f": 49.5, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 12.5, + "wind_kph": 20.2, + "wind_degree": 142, + "wind_dir": "SE", + "pressure_mb": 1017, + "pressure_in": 30.03, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 71, + "cloud": 46, + "feelslike_c": 7, + "feelslike_f": 44.6, + "windchill_c": 7, + "windchill_f": 44.6, + "heatindex_c": 9.7, + "heatindex_f": 49.5, + "dewpoint_c": 4.6, + "dewpoint_f": 40.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 22.3, + "gust_kph": 35.9, + "uv": 0 + }, + { + "time_epoch": 1732395600, + "time": "2024-11-23 22:00", + "temp_c": 9.5, + "temp_f": 49.1, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 13, + "wind_kph": 20.9, + "wind_degree": 139, + "wind_dir": "SE", + "pressure_mb": 1017, + "pressure_in": 30.03, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 73, + "cloud": 24, + "feelslike_c": 6.6, + "feelslike_f": 44, + "windchill_c": 6.6, + "windchill_f": 44, + "heatindex_c": 9.5, + "heatindex_f": 49.1, + "dewpoint_c": 5, + "dewpoint_f": 41, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 23, + "gust_kph": 37, + "uv": 0 + }, + { + "time_epoch": 1732399200, + "time": "2024-11-23 23:00", + "temp_c": 9.3, + "temp_f": 48.8, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 12.8, + "wind_kph": 20.5, + "wind_degree": 140, + "wind_dir": "SE", + "pressure_mb": 1017, + "pressure_in": 30.03, + "precip_mm": 0, + "precip_in": 0, + "snow_cm": 0, + "humidity": 76, + "cloud": 21, + "feelslike_c": 6.5, + "feelslike_f": 43.7, + "windchill_c": 6.5, + "windchill_f": 43.7, + "heatindex_c": 9.3, + "heatindex_f": 48.8, + "dewpoint_c": 5.3, + "dewpoint_f": 41.6, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10, + "vis_miles": 6, + "gust_mph": 23, + "gust_kph": 37, + "uv": 0 + } + ] + } + ] + } +} \ No newline at end of file diff --git a/src/test/resources/WeatherAPI/wrong-apikey.json b/src/test/resources/WeatherAPI/wrong-apikey.json new file mode 100644 index 0000000..2e08784 --- /dev/null +++ b/src/test/resources/WeatherAPI/wrong-apikey.json @@ -0,0 +1,6 @@ +{ + "error": { + "code": 2008, + "message": "API key has been disabled." + } +} \ No newline at end of file From f683f120801577fbe0352e386d50fef5e503b897 Mon Sep 17 00:00:00 2001 From: Martin Eyben Date: Sat, 23 Nov 2024 18:59:52 +0100 Subject: [PATCH 10/29] feat: move fake json fetcher and remove sample test --- src/test/java/eirb/pg203/SampleTest.java | 11 ----------- src/test/java/eirb/pg203/WeatherAPITest.java | 2 +- .../FakeJSONFetcherWeatherAPI.java | 3 +-- 3 files changed, 2 insertions(+), 14 deletions(-) delete mode 100644 src/test/java/eirb/pg203/SampleTest.java rename src/test/java/eirb/pg203/{ => fakeJSONFetcher}/FakeJSONFetcherWeatherAPI.java (97%) diff --git a/src/test/java/eirb/pg203/SampleTest.java b/src/test/java/eirb/pg203/SampleTest.java deleted file mode 100644 index ea913e4..0000000 --- a/src/test/java/eirb/pg203/SampleTest.java +++ /dev/null @@ -1,11 +0,0 @@ -package eirb.pg203; - -import org.json.JSONObject; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import java.io.IOException; - -public class SampleTest { - -} diff --git a/src/test/java/eirb/pg203/WeatherAPITest.java b/src/test/java/eirb/pg203/WeatherAPITest.java index c8b800c..248870e 100644 --- a/src/test/java/eirb/pg203/WeatherAPITest.java +++ b/src/test/java/eirb/pg203/WeatherAPITest.java @@ -1,6 +1,6 @@ package eirb.pg203; -import org.json.JSONObject; +import eirb.pg203.fakeJSONFetcher.FakeJSONFetcherWeatherAPI; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; diff --git a/src/test/java/eirb/pg203/FakeJSONFetcherWeatherAPI.java b/src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherWeatherAPI.java similarity index 97% rename from src/test/java/eirb/pg203/FakeJSONFetcherWeatherAPI.java rename to src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherWeatherAPI.java index 045381a..31d30e2 100644 --- a/src/test/java/eirb/pg203/FakeJSONFetcherWeatherAPI.java +++ b/src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherWeatherAPI.java @@ -1,4 +1,4 @@ -package eirb.pg203; +package eirb.pg203.fakeJSONFetcher; import eirb.pg203.utils.FileResourcesUtils; import eirb.pg203.utils.JSONFetcherInterface; @@ -6,7 +6,6 @@ import eirb.pg203.utils.SplitQueryUrl; import org.json.JSONArray; import org.json.JSONObject; -import java.io.File; import java.io.IOException; import java.net.URL; import java.util.ArrayList; From d3ad85e26793e6a534a0d0e3df2d540b43ce70b5 Mon Sep 17 00:00:00 2001 From: Martin Eyben Date: Sat, 23 Nov 2024 20:50:11 +0100 Subject: [PATCH 11/29] feat: city test --- src/main/java/eirb/pg203/City.java | 20 ++++--- src/test/java/eirb/pg203/CityTest.java | 52 +++++++++++++++++++ .../fakeJSONFetcher/FakeJSONFetcherCity.java | 40 ++++++++++++++ src/test/resources/City/bordeaux.json | 37 +++++++++++++ src/test/resources/City/fakeCity.json | 9 ++++ src/test/resources/City/paris.json | 36 +++++++++++++ 6 files changed, 187 insertions(+), 7 deletions(-) create mode 100644 src/test/java/eirb/pg203/CityTest.java create mode 100644 src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherCity.java create mode 100644 src/test/resources/City/bordeaux.json create mode 100644 src/test/resources/City/fakeCity.json create mode 100644 src/test/resources/City/paris.json diff --git a/src/main/java/eirb/pg203/City.java b/src/main/java/eirb/pg203/City.java index e89e682..77fab5d 100644 --- a/src/main/java/eirb/pg203/City.java +++ b/src/main/java/eirb/pg203/City.java @@ -11,6 +11,7 @@ import java.util.Locale; import eirb.pg203.utils.JSONFetcher; import eirb.pg203.utils.JSONFetcherInterface; import org.json.JSONArray; +import org.json.JSONException; import org.json.JSONObject; import eirb.pg203.utils.Coords; @@ -22,13 +23,13 @@ import eirb.pg203.utils.Coords; public class City { private String cityName; private Coords cityCoords; - private static final JSONFetcherInterface JSONFetcher = new JSONFetcher(); + JSONFetcherInterface JSONFetcher = new JSONFetcher(); /** * Fetch data from adresse.data.gouv.fr * @throws IOException if the request fails */ - private static JSONObject getDataFromName(String cityName) throws IOException { + private JSONObject getDataFromName(String cityName) throws IOException { StringBuilder result = new StringBuilder(); URL url = URI.create( String.format(Locale.ENGLISH, "https://api-adresse.data.gouv.fr/search/?q=%s&autocomplete=0&limit=1", @@ -39,12 +40,17 @@ public class City { return JSONFetcher.fetch(url); } - private static Coords getCoordsFromName(String cityName) throws IOException { + private Coords getCoordsFromName(String cityName) throws IOException { JSONObject data = getDataFromName(cityName); - JSONArray rawCoords = data.getJSONArray("features") - .getJSONObject(0) - .getJSONObject("geometry") - .getJSONArray("coordinates"); + JSONArray rawCoords; + try { + rawCoords = data.getJSONArray("features") + .getJSONObject(0) + .getJSONObject("geometry") + .getJSONArray("coordinates"); + } catch (JSONException e) { + throw new IOException(); + } final float lon = rawCoords.getFloat(0); final float lat = rawCoords.getFloat(1); diff --git a/src/test/java/eirb/pg203/CityTest.java b/src/test/java/eirb/pg203/CityTest.java new file mode 100644 index 0000000..bc3af92 --- /dev/null +++ b/src/test/java/eirb/pg203/CityTest.java @@ -0,0 +1,52 @@ +package eirb.pg203; + +import eirb.pg203.fakeJSONFetcher.FakeJSONFetcherCity; +import eirb.pg203.utils.Coords; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import java.io.IOException; + +public class CityTest { + private final static float epsilon = 0.001F; + @ParameterizedTest(name = "{0} is located at [lat:{1}, lon:{2}]") + @CsvSource({ + "Paris,48.859F,2.347F,'City(Paris, lat: 48.859001, lon: 2.347000)'", + "Bordeaux,44.851895F,-0.587877F,'City(Bordeaux, lat: 44.851894, lon: -0.587877)'" + }) + void testRealCity(String cityName, float expectedLat, float expectedLon, String expectedString) throws IOException { + City city = new City(cityName); + city.JSONFetcher = new FakeJSONFetcherCity(); + + /* Name coherence */ + Assertions.assertEquals(cityName, city.getCityName()); + /* Localisation */ + Coords coords = city.getCityCoords(); + float lat = coords.getLat(); + float lon = coords.getLon(); + System.out.println(city); + + Assertions.assertTrue(Math.abs(lat - expectedLat) < epsilon); + Assertions.assertTrue(Math.abs(lon - expectedLon) < epsilon); + + /* String representation */ + Assertions.assertEquals(expectedString, city.toString()); + } + + @Test + void testFakeCity(){ + String fakeCity = "farlmjmjfkl"; + String fakeCityRepresentation = "City("+ fakeCity +", lat: Request failed, lon: Request Failed)"; + City city = new City(fakeCity); + + /* String representation */ + Assertions.assertEquals(fakeCityRepresentation, city.toString()); + + /* Throw exception */ + Assertions.assertThrows(IOException.class, city::getCityCoords); + + } + +} diff --git a/src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherCity.java b/src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherCity.java new file mode 100644 index 0000000..6259e41 --- /dev/null +++ b/src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherCity.java @@ -0,0 +1,40 @@ +package eirb.pg203.fakeJSONFetcher; + +import eirb.pg203.utils.FileResourcesUtils; +import eirb.pg203.utils.JSONFetcher; +import eirb.pg203.utils.JSONFetcherInterface; +import eirb.pg203.utils.SplitQueryUrl; +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.IOException; +import java.net.URL; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +public class FakeJSONFetcherCity implements JSONFetcherInterface { + JSONObject unknownCity = FileResourcesUtils.getFileFromResourceAsJson("City/fakeCity.json"); + + private static HashMap cities(){ + HashMap cities = new HashMap<>(); + cities.put("bordeaux", FileResourcesUtils.getFileFromResourceAsJson("City/bordeaux.json")); + cities.put("paris", FileResourcesUtils.getFileFromResourceAsJson("City/paris.json")); + cities.put("unknown", FileResourcesUtils.getFileFromResourceAsJson("City/fakeCity.json")); + return cities; + } + + + @Override + public JSONObject fetch(URL url) throws IOException { + Map params = SplitQueryUrl.splitQuery(url); + + String city = params.get("q").toLowerCase(Locale.ENGLISH); + return cities().getOrDefault(city, unknownCity); + } + + @Override + public JSONArray fetchArray(URL url) throws IOException { + return null; + } +} diff --git a/src/test/resources/City/bordeaux.json b/src/test/resources/City/bordeaux.json new file mode 100644 index 0000000..76577c9 --- /dev/null +++ b/src/test/resources/City/bordeaux.json @@ -0,0 +1,37 @@ +{ + "type": "FeatureCollection", + "version": "draft", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -0.587877, + 44.851895 + ] + }, + "properties": { + "label": "Bordeaux", + "score": 0.9608036363636362, + "id": "33063", + "banId": "f38fe08f-c87b-4b3f-8f05-1566b8da9c39", + "type": "municipality", + "name": "Bordeaux", + "postcode": "33000", + "citycode": "33063", + "x": 416627.63, + "y": 6423408.37, + "population": 261804, + "city": "Bordeaux", + "context": "33, Gironde, Nouvelle-Aquitaine", + "importance": 0.56884, + "municipality": "Bordeaux" + } + } + ], + "attribution": "BAN", + "licence": "ETALAB-2.0", + "query": "Bordeaux", + "limit": 1 +} \ No newline at end of file diff --git a/src/test/resources/City/fakeCity.json b/src/test/resources/City/fakeCity.json new file mode 100644 index 0000000..21a422a --- /dev/null +++ b/src/test/resources/City/fakeCity.json @@ -0,0 +1,9 @@ +{ + "type": "FeatureCollection", + "version": "draft", + "features": [], + "attribution": "BAN", + "licence": "ETALAB-2.0", + "query": "farmjfakj", + "limit": 1 +} \ No newline at end of file diff --git a/src/test/resources/City/paris.json b/src/test/resources/City/paris.json new file mode 100644 index 0000000..d1e0e1f --- /dev/null +++ b/src/test/resources/City/paris.json @@ -0,0 +1,36 @@ +{ + "type": "FeatureCollection", + "version": "draft", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 2.347, + 48.859 + ] + }, + "properties": { + "label": "Paris", + "score": 0.9703381818181818, + "id": "75056", + "type": "municipality", + "name": "Paris", + "postcode": "75001", + "citycode": "75056", + "x": 652089.7, + "y": 6862305.26, + "population": 2133111, + "city": "Paris", + "context": "75, Paris, Île-de-France", + "importance": 0.67372, + "municipality": "Paris" + } + } + ], + "attribution": "BAN", + "licence": "ETALAB-2.0", + "query": "Paris", + "limit": 1 +} \ No newline at end of file From 69d5512220207d2cdbf4224536970eba1da0883c Mon Sep 17 00:00:00 2001 From: Martin Eyben Date: Sat, 23 Nov 2024 20:54:35 +0100 Subject: [PATCH 12/29] minor: remove debug --- src/test/java/eirb/pg203/CityTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/eirb/pg203/CityTest.java b/src/test/java/eirb/pg203/CityTest.java index bc3af92..2dad7b6 100644 --- a/src/test/java/eirb/pg203/CityTest.java +++ b/src/test/java/eirb/pg203/CityTest.java @@ -26,7 +26,6 @@ public class CityTest { Coords coords = city.getCityCoords(); float lat = coords.getLat(); float lon = coords.getLon(); - System.out.println(city); Assertions.assertTrue(Math.abs(lat - expectedLat) < epsilon); Assertions.assertTrue(Math.abs(lon - expectedLon) < epsilon); From 423e1a44f30a55f16870df711e63a4f3b089ba41 Mon Sep 17 00:00:00 2001 From: Martin Eyben Date: Sat, 23 Nov 2024 22:01:37 +0100 Subject: [PATCH 13/29] feat: refactor tests --- src/main/java/eirb/pg203/WeatherAPI.java | 5 ++ src/test/java/eirb/pg203/WeatherAPITest.java | 57 ------------ .../java/eirb/pg203/WeatherDataAPITest.java | 86 +++++++++++++++++++ 3 files changed, 91 insertions(+), 57 deletions(-) create mode 100644 src/test/java/eirb/pg203/WeatherDataAPITest.java diff --git a/src/main/java/eirb/pg203/WeatherAPI.java b/src/main/java/eirb/pg203/WeatherAPI.java index 6e30274..d4f1d83 100644 --- a/src/main/java/eirb/pg203/WeatherAPI.java +++ b/src/main/java/eirb/pg203/WeatherAPI.java @@ -111,4 +111,9 @@ public class WeatherAPI implements WeatherDataAPI{ public String getAPIName() { return "WeatherAPI"; } + + @Override + public String toString() { + return this.getAPIName(); + } } diff --git a/src/test/java/eirb/pg203/WeatherAPITest.java b/src/test/java/eirb/pg203/WeatherAPITest.java index 248870e..0a29db9 100644 --- a/src/test/java/eirb/pg203/WeatherAPITest.java +++ b/src/test/java/eirb/pg203/WeatherAPITest.java @@ -23,35 +23,6 @@ public class WeatherAPITest { this.weatherAPI.JSONFetcher = new FakeJSONFetcherWeatherAPI(); } - /** - * List of args for Temperature testing - * @return Args for testing - */ - private static Stream testGetTemperature(){ - return Stream.of( - Arguments.arguments(0, 8.1F,WeatherData.Condition.PARTIAL, 17.45F, 142.08F), - Arguments.arguments(1, 13F, WeatherData.Condition.SUNNY, 23.03F, 142.58F), - Arguments.arguments(2, 12.7F, WeatherData.Condition.RAINY, 13.19F, 222.92F), - Arguments.arguments(3, 8.1F,WeatherData.Condition.CLOUDY, 17.45F, 142.08F) - ); - } - - @ParameterizedTest(name="Temperature fetch at Bordeaux D+{0}") - @MethodSource - public void testGetTemperature(int day, float expectedTemp, WeatherData.Condition expectedCond, float expectedWindSpeed, float expectedWindAngle) throws IOException { - String city = "Bordeaux"; - WeatherData weatherData; - weatherData = weatherAPI.getTemperature(day, city); - - /* Temperatures */ - Assertions.assertEquals(expectedTemp, weatherData.getTemp()); - /* Condition */ - Assertions.assertEquals(expectedCond, weatherData.getCondition()); - /* Wind */ - Assertions.assertTrue(expectedWindSpeed - weatherData.getWindSpeed() < epsilon); - Assertions.assertTrue(expectedWindAngle - weatherData.getWindDirectionAngle() < epsilon); - } - /** * For coverage (hour not yet implemented) * @throws IOException never @@ -65,34 +36,6 @@ public class WeatherAPITest { } - @Test - @DisplayName("Multiple day temperature fetch") - public void testGetTemperatures() throws IOException { - String city = "Bordeaux"; - int days = 3; - float[] expectedTemperatures = {8.1F, 13F, 12.7F, 8.1F}; - WeatherData.Condition[] expectedConditions = {WeatherData.Condition.PARTIAL, WeatherData.Condition.SUNNY, WeatherData.Condition.RAINY, WeatherData.Condition.CLOUDY}; - float[] expectedWindSpeed = {17.45F, 23.03F, 13.19F, 17.45F}; - float[] expectedWindDirection = {142.08F, 142.58F, 222.92F, 142.08F}; - - ArrayList weatherDatas; - WeatherData weatherData; - weatherDatas = weatherAPI.getTemperatures(days, city); - - for (int index = 0; index < days; index++) { - weatherData = weatherDatas.get(index); - - - /* Temperatures */ - Assertions.assertEquals(expectedTemperatures[index], weatherData.getTemp()); - /* Weather condition */ - Assertions.assertEquals(expectedConditions[index], weatherData.getCondition()); - /* Wind */ - Assertions.assertTrue(expectedWindSpeed[index] - weatherData.getWindSpeed() < epsilon); - Assertions.assertTrue(expectedWindDirection[index] - weatherData.getWindDirectionAngle() < epsilon); - } - } - @Test public void testGetAPIName() { Assertions.assertEquals("WeatherAPI", weatherAPI.getAPIName()); diff --git a/src/test/java/eirb/pg203/WeatherDataAPITest.java b/src/test/java/eirb/pg203/WeatherDataAPITest.java new file mode 100644 index 0000000..8826cc7 --- /dev/null +++ b/src/test/java/eirb/pg203/WeatherDataAPITest.java @@ -0,0 +1,86 @@ +package eirb.pg203; + +import eirb.pg203.fakeJSONFetcher.FakeJSONFetcherWeatherAPI; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.stream.Stream; + +public class WeatherDataAPITest { + private static final float epsilon = 0.01F; + private static final String APIKey = "realKey"; + + private static WeatherAPI weatherAPI(){ + WeatherAPI weatherAPI = new WeatherAPI(APIKey); + weatherAPI.JSONFetcher = new FakeJSONFetcherWeatherAPI(); + return weatherAPI; + } + + /** + * List of args for Temperature testing + * @return Args for testing + */ + private static Stream testGetTemperature(){ + + return Stream.of( + Arguments.arguments(weatherAPI(), 0, 8.1F,WeatherData.Condition.PARTIAL, 17.45F, 142.08F), + Arguments.arguments(weatherAPI(), 1, 13F, WeatherData.Condition.SUNNY, 23.03F, 142.58F), + Arguments.arguments(weatherAPI(), 2, 12.7F, WeatherData.Condition.RAINY, 13.19F, 222.92F), + Arguments.arguments(weatherAPI(), 3, 8.1F,WeatherData.Condition.CLOUDY, 17.45F, 142.08F) + ); + } + + @ParameterizedTest(name="[{0}] Temperature fetch at Bordeaux D+{1}") + @MethodSource + public void testGetTemperature(WeatherDataAPI weatherDataAPI, int day, float expectedTemp, WeatherData.Condition expectedCond, float expectedWindSpeed, float expectedWindAngle) throws IOException { + String city = "Bordeaux"; + WeatherData weatherData; + weatherData = weatherDataAPI.getTemperature(day, city); + + /* Temperatures */ + Assertions.assertEquals(expectedTemp, weatherData.getTemp()); + /* Condition */ + Assertions.assertEquals(expectedCond, weatherData.getCondition()); + /* Wind */ + Assertions.assertTrue(expectedWindSpeed - weatherData.getWindSpeed() < epsilon); + Assertions.assertTrue(expectedWindAngle - weatherData.getWindDirectionAngle() < epsilon); + } + + private static Stream testGetTemperatures() { + float[] weatherAPIExpectedTemperatures = {8.1F, 13F, 12.7F, 8.1F}; + WeatherData.Condition[] weatherAPIExpectedConditions = {WeatherData.Condition.PARTIAL, WeatherData.Condition.SUNNY, WeatherData.Condition.RAINY, WeatherData.Condition.CLOUDY}; + float[] weatherAPIExpectedWindSpeed = {17.45F, 23.03F, 13.19F, 17.45F}; + float[] weatherAPIExpectedWindDirection = {142.08F, 142.58F, 222.92F, 142.08F}; + return Stream.of( + Arguments.arguments(weatherAPI(), 4, weatherAPIExpectedTemperatures, weatherAPIExpectedConditions, weatherAPIExpectedWindSpeed, weatherAPIExpectedWindDirection) + ); + + } + @ParameterizedTest(name = "[{0}] Fetch temperatures for {1} days") + @MethodSource + public void testGetTemperatures(WeatherDataAPI weatherDataAPI, int days, float[] expectedTemperatures, WeatherData.Condition[] expectedConditions, float[] expectedWindSpeed, float[] expectedWindDirection) throws IOException { + String city = "Bordeaux"; + + ArrayList weatherDatas; + WeatherData weatherData; + weatherDatas = weatherDataAPI.getTemperatures(days, city); + + for (int index = 0; index < days; index++) { + weatherData = weatherDatas.get(index); + + /* Temperatures */ + Assertions.assertEquals(expectedTemperatures[index], weatherData.getTemp()); + /* Weather condition */ + Assertions.assertEquals(expectedConditions[index], weatherData.getCondition()); + /* Wind */ + Assertions.assertTrue(expectedWindSpeed[index] - weatherData.getWindSpeed() < epsilon); + Assertions.assertTrue(expectedWindDirection[index] - weatherData.getWindDirectionAngle() < epsilon); + } + } +} From 68abfe66fa536286093ebab5ae8eaf03695ac2c3 Mon Sep 17 00:00:00 2001 From: Martin Eyben Date: Sun, 24 Nov 2024 00:18:19 +0100 Subject: [PATCH 14/29] feat: OpenWeatherMap tests --- src/main/java/eirb/pg203/OpenWeatherMap.java | 24 +- src/test/java/eirb/pg203/WeatherAPITest.java | 14 - .../java/eirb/pg203/WeatherDataAPITest.java | 60 +- .../FakeJSONFetcherOpenWeatherMap.java | 33 + .../Bordeaux-partial-cloudy-rain-sunny.json | 1475 +++++++++++++++++ .../OpenWeatherMap/wrong-apikey.json | 4 + 6 files changed, 1579 insertions(+), 31 deletions(-) create mode 100644 src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherOpenWeatherMap.java create mode 100644 src/test/resources/OpenWeatherMap/Bordeaux-partial-cloudy-rain-sunny.json create mode 100644 src/test/resources/OpenWeatherMap/wrong-apikey.json diff --git a/src/main/java/eirb/pg203/OpenWeatherMap.java b/src/main/java/eirb/pg203/OpenWeatherMap.java index 9a54da3..580b57f 100644 --- a/src/main/java/eirb/pg203/OpenWeatherMap.java +++ b/src/main/java/eirb/pg203/OpenWeatherMap.java @@ -9,6 +9,7 @@ import eirb.pg203.utils.JSONFetcher; import java.io.IOException; import java.net.URI; import java.net.URL; +import java.time.Clock; import java.time.DayOfWeek; import java.time.Instant; import java.time.ZoneId; @@ -23,29 +24,29 @@ import eirb.pg203.WeatherData.Condition; public class OpenWeatherMap implements WeatherDataAPI { private static final String forecastBaseURL = "https://api.openweathermap.org/data/2.5/forecast"; private String APIKey; - private static final JSONFetcherInterface JSONFetcher = new JSONFetcher(); + Clock clock = Clock.systemUTC(); + JSONFetcherInterface JSONFetcher = new JSONFetcher(); OpenWeatherMap(String APIKey) { this.APIKey = APIKey; } - private JSONObject fetchWeather(int days, City city) throws IOException { + private JSONObject fetchWeather(City city) throws IOException { URL url = URI.create( String.format(Locale.ENGLISH, forecastBaseURL + "?appid=%s&lat=%.2f&lon=%.2f&units=metric", APIKey, city.getCityCoords().getLat(), - city.getCityCoords().getLon(), - days + city.getCityCoords().getLon() ) ).toURL(); return JSONFetcher.fetch(url); } - private static WeatherData getWeatherDataFromForecast(JSONObject response, int day, String city) { + private WeatherData getWeatherDataFromForecast(JSONObject response, int day, String city) { JSONArray list = response.getJSONArray("list"); - DayOfWeek targetedDay = Instant.now().plusSeconds(day * 24 * 3600).atZone(ZoneId.systemDefault()).getDayOfWeek(); + DayOfWeek targetedDay = Instant.now(clock).plusSeconds(day * 24 * 3600).atZone(ZoneId.systemDefault()).getDayOfWeek(); DayOfWeek dayOfWeek; int dataCount = 0; float temp_c = 0; @@ -91,7 +92,7 @@ public class OpenWeatherMap implements WeatherDataAPI { return new WeatherData( new City(city), - Instant.now().plusSeconds(day * 24 * 3600), + Instant.now(clock).plusSeconds(day * 24 * 3600), temp_c, windSpeed, windDirection, @@ -104,7 +105,7 @@ public class OpenWeatherMap implements WeatherDataAPI { */ @Override public WeatherData getTemperature(int day, String city) throws IOException { - JSONObject result = fetchWeather(day+1, new City(city)); + JSONObject result = fetchWeather(new City(city)); return getWeatherDataFromForecast(result, day, city); } @@ -116,7 +117,7 @@ public class OpenWeatherMap implements WeatherDataAPI { @Override public ArrayList getTemperatures(int days, String city) throws IOException { - JSONObject result = fetchWeather(days, new City(city)); + JSONObject result = fetchWeather(new City(city)); ArrayList weatherDatas = new ArrayList<>(); @@ -133,4 +134,9 @@ public class OpenWeatherMap implements WeatherDataAPI { public String getAPIName() { return "OpenWeatherMap"; } + + @Override + public String toString() { + return this.getAPIName(); + } } diff --git a/src/test/java/eirb/pg203/WeatherAPITest.java b/src/test/java/eirb/pg203/WeatherAPITest.java index 0a29db9..907ab10 100644 --- a/src/test/java/eirb/pg203/WeatherAPITest.java +++ b/src/test/java/eirb/pg203/WeatherAPITest.java @@ -14,7 +14,6 @@ import java.util.stream.Stream; public class WeatherAPITest { private static final String APIKey = "realKey"; - private static final float epsilon = 0.01F; private WeatherAPI weatherAPI; @BeforeEach @@ -23,19 +22,6 @@ public class WeatherAPITest { this.weatherAPI.JSONFetcher = new FakeJSONFetcherWeatherAPI(); } - /** - * For coverage (hour not yet implemented) - * @throws IOException never - */ - @Test - public void testGetTemperatureByHour() throws IOException { - String city = "Bordeaux"; - Assertions.assertAll( - () -> weatherAPI.getTemperature(0,1, city) - ); - - } - @Test public void testGetAPIName() { Assertions.assertEquals("WeatherAPI", weatherAPI.getAPIName()); diff --git a/src/test/java/eirb/pg203/WeatherDataAPITest.java b/src/test/java/eirb/pg203/WeatherDataAPITest.java index 8826cc7..21df664 100644 --- a/src/test/java/eirb/pg203/WeatherDataAPITest.java +++ b/src/test/java/eirb/pg203/WeatherDataAPITest.java @@ -1,5 +1,6 @@ package eirb.pg203; +import eirb.pg203.fakeJSONFetcher.FakeJSONFetcherOpenWeatherMap; import eirb.pg203.fakeJSONFetcher.FakeJSONFetcherWeatherAPI; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; @@ -9,6 +10,9 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import java.io.IOException; +import java.time.Clock; +import java.time.Instant; +import java.time.ZoneId; import java.util.ArrayList; import java.util.stream.Stream; @@ -22,6 +26,18 @@ public class WeatherDataAPITest { return weatherAPI; } + private static OpenWeatherMap openWeatherMap(){ + // Fix clock for testing + String instantExpected = "2024-11-24T00:00:00.00Z"; + Clock clock = Clock.fixed(Instant.parse(instantExpected), ZoneId.systemDefault()); + + OpenWeatherMap openWeatherMap= new OpenWeatherMap(APIKey); + openWeatherMap.JSONFetcher = new FakeJSONFetcherOpenWeatherMap(); + openWeatherMap.clock = clock; + return openWeatherMap; + } + + /** * List of args for Temperature testing * @return Args for testing @@ -32,7 +48,11 @@ public class WeatherDataAPITest { Arguments.arguments(weatherAPI(), 0, 8.1F,WeatherData.Condition.PARTIAL, 17.45F, 142.08F), Arguments.arguments(weatherAPI(), 1, 13F, WeatherData.Condition.SUNNY, 23.03F, 142.58F), Arguments.arguments(weatherAPI(), 2, 12.7F, WeatherData.Condition.RAINY, 13.19F, 222.92F), - Arguments.arguments(weatherAPI(), 3, 8.1F,WeatherData.Condition.CLOUDY, 17.45F, 142.08F) + Arguments.arguments(weatherAPI(), 3, 8.1F,WeatherData.Condition.CLOUDY, 17.45F, 142.08F), + Arguments.arguments(openWeatherMap(), 0, 13.41F,WeatherData.Condition.PARTIAL, 5.74F, 142.13F), + Arguments.arguments(openWeatherMap(), 1, 13.29F,WeatherData.Condition.CLOUDY, 3.62F, 225.25F), + Arguments.arguments(openWeatherMap(), 2, 10.06F,WeatherData.Condition.RAINY, 2.22F, 191.75F), + Arguments.arguments(openWeatherMap(), 3, 9.88F,WeatherData.Condition.SUNNY, 2.00F, 160.00F) ); } @@ -44,12 +64,12 @@ public class WeatherDataAPITest { weatherData = weatherDataAPI.getTemperature(day, city); /* Temperatures */ - Assertions.assertEquals(expectedTemp, weatherData.getTemp()); + Assertions.assertEquals(expectedTemp, weatherData.getTemp(), epsilon); /* Condition */ Assertions.assertEquals(expectedCond, weatherData.getCondition()); /* Wind */ - Assertions.assertTrue(expectedWindSpeed - weatherData.getWindSpeed() < epsilon); - Assertions.assertTrue(expectedWindAngle - weatherData.getWindDirectionAngle() < epsilon); + Assertions.assertEquals(expectedWindSpeed, weatherData.getWindSpeed(), epsilon); + Assertions.assertEquals(expectedWindAngle, weatherData.getWindDirectionAngle(),epsilon); } private static Stream testGetTemperatures() { @@ -57,8 +77,15 @@ public class WeatherDataAPITest { WeatherData.Condition[] weatherAPIExpectedConditions = {WeatherData.Condition.PARTIAL, WeatherData.Condition.SUNNY, WeatherData.Condition.RAINY, WeatherData.Condition.CLOUDY}; float[] weatherAPIExpectedWindSpeed = {17.45F, 23.03F, 13.19F, 17.45F}; float[] weatherAPIExpectedWindDirection = {142.08F, 142.58F, 222.92F, 142.08F}; + + float[] openWeatherMapExpectedTemperatures = {13.41F, 13.29F, 10.06F, 9.88F}; + WeatherData.Condition[] openWeatherMapExpectedConditions = {WeatherData.Condition.PARTIAL, WeatherData.Condition.CLOUDY, WeatherData.Condition.RAINY, WeatherData.Condition.SUNNY}; + float[] openWeatherMapExpectedWindSpeed = {5.74F, 3.62F, 2.22F, 2.00F}; + float[] openWeatherMapExpectedWindDirection = {142.13F, 225.25F, 191.75F, 160F}; + return Stream.of( - Arguments.arguments(weatherAPI(), 4, weatherAPIExpectedTemperatures, weatherAPIExpectedConditions, weatherAPIExpectedWindSpeed, weatherAPIExpectedWindDirection) + Arguments.arguments(weatherAPI(), 4, weatherAPIExpectedTemperatures, weatherAPIExpectedConditions, weatherAPIExpectedWindSpeed, weatherAPIExpectedWindDirection), + Arguments.arguments(openWeatherMap(), 4, openWeatherMapExpectedTemperatures, openWeatherMapExpectedConditions, openWeatherMapExpectedWindSpeed, openWeatherMapExpectedWindDirection) ); } @@ -75,12 +102,29 @@ public class WeatherDataAPITest { weatherData = weatherDatas.get(index); /* Temperatures */ - Assertions.assertEquals(expectedTemperatures[index], weatherData.getTemp()); + Assertions.assertEquals(expectedTemperatures[index], weatherData.getTemp(), epsilon); /* Weather condition */ Assertions.assertEquals(expectedConditions[index], weatherData.getCondition()); /* Wind */ - Assertions.assertTrue(expectedWindSpeed[index] - weatherData.getWindSpeed() < epsilon); - Assertions.assertTrue(expectedWindDirection[index] - weatherData.getWindDirectionAngle() < epsilon); + Assertions.assertEquals(expectedWindSpeed[index],weatherData.getWindSpeed(), epsilon); + Assertions.assertEquals(expectedWindDirection[index], weatherData.getWindDirectionAngle(), epsilon); } } + + private static Stream testGetTemperatureByHours() { + + return Stream.of( + Arguments.arguments(weatherAPI()), + Arguments.arguments(openWeatherMap()) + ); + } + @ParameterizedTest(name = "[{0}] Get temperature for a specific hour") + @MethodSource + public void testGetTemperatureByHours(WeatherDataAPI weatherDataAPI) { + String city = "Bordeaux"; + Assertions.assertAll( + () -> weatherDataAPI.getTemperature(0,1, city) + ); + + } } diff --git a/src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherOpenWeatherMap.java b/src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherOpenWeatherMap.java new file mode 100644 index 0000000..5583f31 --- /dev/null +++ b/src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherOpenWeatherMap.java @@ -0,0 +1,33 @@ +package eirb.pg203.fakeJSONFetcher; + +import eirb.pg203.utils.FileResourcesUtils; +import eirb.pg203.utils.JSONFetcherInterface; +import eirb.pg203.utils.SplitQueryUrl; +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.IOException; +import java.net.URL; +import java.time.Instant; +import java.util.Map; + +public class FakeJSONFetcherOpenWeatherMap implements JSONFetcherInterface { + private final String apiKey = "realKey"; + private final JSONObject wrongKeyResponse = FileResourcesUtils.getFileFromResourceAsJson("OpenWeatherMap/wrong-apikey.json"); + + private JSONObject responseExample() { + return FileResourcesUtils.getFileFromResourceAsJson("OpenWeatherMap/Bordeaux-partial-cloudy-rain-sunny.json"); + } + @Override + public JSONObject fetch(URL url) throws IOException { + Map params = SplitQueryUrl.splitQuery(url); + if (!params.getOrDefault("appid", "").contentEquals(apiKey)) + return wrongKeyResponse; + return responseExample(); + } + + @Override + public JSONArray fetchArray(URL url) throws IOException { + return null; + } +} diff --git a/src/test/resources/OpenWeatherMap/Bordeaux-partial-cloudy-rain-sunny.json b/src/test/resources/OpenWeatherMap/Bordeaux-partial-cloudy-rain-sunny.json new file mode 100644 index 0000000..21735d5 --- /dev/null +++ b/src/test/resources/OpenWeatherMap/Bordeaux-partial-cloudy-rain-sunny.json @@ -0,0 +1,1475 @@ +{ + "cod": "200", + "message": 0, + "cnt": 40, + "list": [ + { + "dt": 1732406400, + "main": { + "temp": 10.2, + "feels_like": 9.23, + "temp_min": 10.18, + "temp_max": 10.2, + "pressure": 1017, + "sea_level": 1017, + "grnd_level": 1012, + "humidity": 75, + "temp_kf": 0.02 + }, + "weather": [ + { + "id": 802, + "main": "Clouds", + "description": "scattered clouds", + "icon": "03n" + } + ], + "clouds": { + "all": 33 + }, + "wind": { + "speed": 4.79, + "deg": 140, + "gust": 13.02 + }, + "visibility": 10000, + "pop": 0, + "sys": { + "pod": "n" + }, + "dt_txt": "2024-11-24 00:00:00" + }, + { + "dt": 1732417200, + "main": { + "temp": 10.26, + "feels_like": 9.22, + "temp_min": 10.26, + "temp_max": 10.29, + "pressure": 1016, + "sea_level": 1016, + "grnd_level": 1011, + "humidity": 72, + "temp_kf": -0.03 + }, + "weather": [ + { + "id": 803, + "main": "Clouds", + "description": "broken clouds", + "icon": "04n" + } + ], + "clouds": { + "all": 67 + }, + "wind": { + "speed": 5.15, + "deg": 135, + "gust": 13.89 + }, + "visibility": 10000, + "pop": 0, + "sys": { + "pod": "n" + }, + "dt_txt": "2024-11-24 03:00:00" + }, + { + "dt": 1732428000, + "main": { + "temp": 10.15, + "feels_like": 9.18, + "temp_min": 10.15, + "temp_max": 10.15, + "pressure": 1016, + "sea_level": 1016, + "grnd_level": 1011, + "humidity": 75, + "temp_kf": 0 + }, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04n" + } + ], + "clouds": { + "all": 93 + }, + "wind": { + "speed": 5.71, + "deg": 138, + "gust": 14.58 + }, + "visibility": 10000, + "pop": 0, + "sys": { + "pod": "n" + }, + "dt_txt": "2024-11-24 06:00:00" + }, + { + "dt": 1732438800, + "main": { + "temp": 11.77, + "feels_like": 10.91, + "temp_min": 11.77, + "temp_max": 11.77, + "pressure": 1016, + "sea_level": 1016, + "grnd_level": 1011, + "humidity": 73, + "temp_kf": 0 + }, + "weather": [ + { + "id": 801, + "main": "Clouds", + "description": "few clouds", + "icon": "02d" + } + ], + "clouds": { + "all": 13 + }, + "wind": { + "speed": 5.93, + "deg": 134, + "gust": 14.45 + }, + "visibility": 10000, + "pop": 0, + "sys": { + "pod": "d" + }, + "dt_txt": "2024-11-24 09:00:00" + }, + { + "dt": 1732449600, + "main": { + "temp": 16.48, + "feels_like": 15.72, + "temp_min": 16.48, + "temp_max": 16.48, + "pressure": 1013, + "sea_level": 1013, + "grnd_level": 1009, + "humidity": 59, + "temp_kf": 0 + }, + "weather": [ + { + "id": 800, + "main": "Clear", + "description": "clear sky", + "icon": "01d" + } + ], + "clouds": { + "all": 10 + }, + "wind": { + "speed": 6.38, + "deg": 140, + "gust": 13.55 + }, + "visibility": 10000, + "pop": 0, + "sys": { + "pod": "d" + }, + "dt_txt": "2024-11-24 12:00:00" + }, + { + "dt": 1732460400, + "main": { + "temp": 17.25, + "feels_like": 16.55, + "temp_min": 17.25, + "temp_max": 17.25, + "pressure": 1011, + "sea_level": 1011, + "grnd_level": 1007, + "humidity": 58, + "temp_kf": 0 + }, + "weather": [ + { + "id": 803, + "main": "Clouds", + "description": "broken clouds", + "icon": "04d" + } + ], + "clouds": { + "all": 65 + }, + "wind": { + "speed": 6.31, + "deg": 146, + "gust": 15.59 + }, + "visibility": 10000, + "pop": 0, + "sys": { + "pod": "d" + }, + "dt_txt": "2024-11-24 15:00:00" + }, + { + "dt": 1732471200, + "main": { + "temp": 15.26, + "feels_like": 14.59, + "temp_min": 15.26, + "temp_max": 15.26, + "pressure": 1012, + "sea_level": 1012, + "grnd_level": 1007, + "humidity": 67, + "temp_kf": 0 + }, + "weather": [ + { + "id": 803, + "main": "Clouds", + "description": "broken clouds", + "icon": "04n" + } + ], + "clouds": { + "all": 84 + }, + "wind": { + "speed": 5.73, + "deg": 149, + "gust": 15.31 + }, + "visibility": 10000, + "pop": 0, + "sys": { + "pod": "n" + }, + "dt_txt": "2024-11-24 18:00:00" + }, + { + "dt": 1732482000, + "main": { + "temp": 15.9, + "feels_like": 15.14, + "temp_min": 15.9, + "temp_max": 15.9, + "pressure": 1011, + "sea_level": 1011, + "grnd_level": 1006, + "humidity": 61, + "temp_kf": 0 + }, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04n" + } + ], + "clouds": { + "all": 100 + }, + "wind": { + "speed": 5.9, + "deg": 155, + "gust": 17.26 + }, + "visibility": 10000, + "pop": 0, + "sys": { + "pod": "n" + }, + "dt_txt": "2024-11-24 21:00:00" + }, + { + "dt": 1732492800, + "main": { + "temp": 16.01, + "feels_like": 15.13, + "temp_min": 16.01, + "temp_max": 16.01, + "pressure": 1009, + "sea_level": 1009, + "grnd_level": 1004, + "humidity": 56, + "temp_kf": 0 + }, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04n" + } + ], + "clouds": { + "all": 100 + }, + "wind": { + "speed": 6.95, + "deg": 150, + "gust": 18.68 + }, + "visibility": 10000, + "pop": 0, + "sys": { + "pod": "n" + }, + "dt_txt": "2024-11-25 00:00:00" + }, + { + "dt": 1732503600, + "main": { + "temp": 16.52, + "feels_like": 15.59, + "temp_min": 16.52, + "temp_max": 16.52, + "pressure": 1007, + "sea_level": 1007, + "grnd_level": 1002, + "humidity": 52, + "temp_kf": 0 + }, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04n" + } + ], + "clouds": { + "all": 99 + }, + "wind": { + "speed": 5.13, + "deg": 161, + "gust": 15.28 + }, + "visibility": 10000, + "pop": 0, + "sys": { + "pod": "n" + }, + "dt_txt": "2024-11-25 03:00:00" + }, + { + "dt": 1732514400, + "main": { + "temp": 16.06, + "feels_like": 15.37, + "temp_min": 16.06, + "temp_max": 16.06, + "pressure": 1008, + "sea_level": 1008, + "grnd_level": 1003, + "humidity": 63, + "temp_kf": 0 + }, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04n" + } + ], + "clouds": { + "all": 100 + }, + "wind": { + "speed": 2.59, + "deg": 215, + "gust": 7.05 + }, + "visibility": 10000, + "pop": 0, + "sys": { + "pod": "n" + }, + "dt_txt": "2024-11-25 06:00:00" + }, + { + "dt": 1732525200, + "main": { + "temp": 13.58, + "feels_like": 13.4, + "temp_min": 13.58, + "temp_max": 13.58, + "pressure": 1011, + "sea_level": 1011, + "grnd_level": 1007, + "humidity": 92, + "temp_kf": 0 + }, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10d" + } + ], + "clouds": { + "all": 100 + }, + "wind": { + "speed": 3.32, + "deg": 296, + "gust": 8.35 + }, + "visibility": 10000, + "pop": 1, + "rain": { + "3h": 1.97 + }, + "sys": { + "pod": "d" + }, + "dt_txt": "2024-11-25 09:00:00" + }, + { + "dt": 1732536000, + "main": { + "temp": 11.61, + "feels_like": 11.18, + "temp_min": 11.61, + "temp_max": 11.61, + "pressure": 1015, + "sea_level": 1015, + "grnd_level": 1010, + "humidity": 90, + "temp_kf": 0 + }, + "weather": [ + { + "id": 501, + "main": "Rain", + "description": "moderate rain", + "icon": "10d" + } + ], + "clouds": { + "all": 100 + }, + "wind": { + "speed": 2.99, + "deg": 286, + "gust": 8.29 + }, + "visibility": 8895, + "pop": 1, + "rain": { + "3h": 5.34 + }, + "sys": { + "pod": "d" + }, + "dt_txt": "2024-11-25 12:00:00" + }, + { + "dt": 1732546800, + "main": { + "temp": 12.13, + "feels_like": 11.57, + "temp_min": 12.13, + "temp_max": 12.13, + "pressure": 1016, + "sea_level": 1016, + "grnd_level": 1012, + "humidity": 83, + "temp_kf": 0 + }, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10d" + } + ], + "clouds": { + "all": 100 + }, + "wind": { + "speed": 3.33, + "deg": 253, + "gust": 7.07 + }, + "visibility": 10000, + "pop": 1, + "rain": { + "3h": 2.12 + }, + "sys": { + "pod": "d" + }, + "dt_txt": "2024-11-25 15:00:00" + }, + { + "dt": 1732557600, + "main": { + "temp": 10.97, + "feels_like": 10.34, + "temp_min": 10.97, + "temp_max": 10.97, + "pressure": 1019, + "sea_level": 1019, + "grnd_level": 1014, + "humidity": 85, + "temp_kf": 0 + }, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10n" + } + ], + "clouds": { + "all": 99 + }, + "wind": { + "speed": 2.57, + "deg": 224, + "gust": 6.92 + }, + "visibility": 10000, + "pop": 1, + "rain": { + "3h": 0.73 + }, + "sys": { + "pod": "n" + }, + "dt_txt": "2024-11-25 18:00:00" + }, + { + "dt": 1732568400, + "main": { + "temp": 9.43, + "feels_like": 8.47, + "temp_min": 9.43, + "temp_max": 9.43, + "pressure": 1021, + "sea_level": 1021, + "grnd_level": 1017, + "humidity": 91, + "temp_kf": 0 + }, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10n" + } + ], + "clouds": { + "all": 40 + }, + "wind": { + "speed": 2.07, + "deg": 217, + "gust": 3.77 + }, + "visibility": 10000, + "pop": 0.36, + "rain": { + "3h": 0.24 + }, + "sys": { + "pod": "n" + }, + "dt_txt": "2024-11-25 21:00:00" + }, + { + "dt": 1732579200, + "main": { + "temp": 8.75, + "feels_like": 7.73, + "temp_min": 8.75, + "temp_max": 8.75, + "pressure": 1022, + "sea_level": 1022, + "grnd_level": 1017, + "humidity": 91, + "temp_kf": 0 + }, + "weather": [ + { + "id": 801, + "main": "Clouds", + "description": "few clouds", + "icon": "02n" + } + ], + "clouds": { + "all": 21 + }, + "wind": { + "speed": 2.01, + "deg": 206, + "gust": 3.19 + }, + "visibility": 10000, + "pop": 9000, + "sys": { + "pod": "n" + }, + "dt_txt": "2024-11-26 00:00:00" + }, + { + "dt": 1732590000, + "main": { + "temp": 8.39, + "feels_like": 7.37, + "temp_min": 8.39, + "temp_max": 8.39, + "pressure": 1023, + "sea_level": 1023, + "grnd_level": 1018, + "humidity": 92, + "temp_kf": 0 + }, + "weather": [ + { + "id": 802, + "main": "Clouds", + "description": "scattered clouds", + "icon": "03n" + } + ], + "clouds": { + "all": 31 + }, + "wind": { + "speed": 1.94, + "deg": 213, + "gust": 2.61 + }, + "visibility": 10000, + "pop": 0, + "sys": { + "pod": "n" + }, + "dt_txt": "2024-11-26 03:00:00" + }, + { + "dt": 1732600800, + "main": { + "temp": 8.51, + "feels_like": 7.16, + "temp_min": 8.51, + "temp_max": 8.51, + "pressure": 1023, + "sea_level": 1023, + "grnd_level": 1018, + "humidity": 93, + "temp_kf": 0 + }, + "weather": [ + { + "id": 803, + "main": "Clouds", + "description": "broken clouds", + "icon": "04n" + } + ], + "clouds": { + "all": 61 + }, + "wind": { + "speed": 2.36, + "deg": 181, + "gust": 4.11 + }, + "visibility": 10000, + "pop": 0, + "sys": { + "pod": "n" + }, + "dt_txt": "2024-11-26 06:00:00" + }, + { + "dt": 1732611600, + "main": { + "temp": 10.55, + "feels_like": 9.96, + "temp_min": 10.55, + "temp_max": 10.55, + "pressure": 1024, + "sea_level": 1024, + "grnd_level": 1019, + "humidity": 88, + "temp_kf": 0 + }, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04d" + } + ], + "clouds": { + "all": 100 + }, + "wind": { + "speed": 2.54, + "deg": 202, + "gust": 6.63 + }, + "visibility": 10000, + "pop": 0, + "sys": { + "pod": "d" + }, + "dt_txt": "2024-11-26 09:00:00" + }, + { + "dt": 1732622400, + "main": { + "temp": 12.51, + "feels_like": 11.88, + "temp_min": 12.51, + "temp_max": 12.51, + "pressure": 1023, + "sea_level": 1023, + "grnd_level": 1018, + "humidity": 79, + "temp_kf": 0 + }, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04d" + } + ], + "clouds": { + "all": 100 + }, + "wind": { + "speed": 2.43, + "deg": 205, + "gust": 4.14 + }, + "visibility": 10000, + "pop": 0, + "sys": { + "pod": "d" + }, + "dt_txt": "2024-11-26 12:00:00" + }, + { + "dt": 1732633200, + "main": { + "temp": 12.24, + "feels_like": 11.63, + "temp_min": 12.24, + "temp_max": 12.24, + "pressure": 1022, + "sea_level": 1022, + "grnd_level": 1018, + "humidity": 81, + "temp_kf": 0 + }, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04d" + } + ], + "clouds": { + "all": 100 + }, + "wind": { + "speed": 2.67, + "deg": 189, + "gust": 4.81 + }, + "visibility": 10000, + "pop": 0, + "sys": { + "pod": "d" + }, + "dt_txt": "2024-11-26 15:00:00" + }, + { + "dt": 1732644000, + "main": { + "temp": 10.6, + "feels_like": 9.88, + "temp_min": 10.6, + "temp_max": 10.6, + "pressure": 1023, + "sea_level": 1023, + "grnd_level": 1018, + "humidity": 83, + "temp_kf": 0 + }, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04n" + } + ], + "clouds": { + "all": 100 + }, + "wind": { + "speed": 1.63, + "deg": 172, + "gust": 2.51 + }, + "visibility": 10000, + "pop": 0, + "sys": { + "pod": "n" + }, + "dt_txt": "2024-11-26 18:00:00" + }, + { + "dt": 1732654800, + "main": { + "temp": 8.95, + "feels_like": 7.85, + "temp_min": 8.95, + "temp_max": 8.95, + "pressure": 1023, + "sea_level": 1023, + "grnd_level": 1018, + "humidity": 85, + "temp_kf": 0 + }, + "weather": [ + { + "id": 803, + "main": "Clouds", + "description": "broken clouds", + "icon": "04n" + } + ], + "clouds": { + "all": 72 + }, + "wind": { + "speed": 2.14, + "deg": 166, + "gust": 3.57 + }, + "visibility": 10000, + "pop": 0, + "sys": { + "pod": "n" + }, + "dt_txt": "2024-11-26 21:00:00" + }, + { + "dt": 1732665600, + "main": { + "temp": 8.1, + "feels_like": 6.71, + "temp_min": 8.1, + "temp_max": 8.1, + "pressure": 1022, + "sea_level": 1022, + "grnd_level": 1017, + "humidity": 89, + "temp_kf": 0 + }, + "weather": [ + { + "id": 802, + "main": "Clouds", + "description": "scattered clouds", + "icon": "03n" + } + ], + "clouds": { + "all": 0 + }, + "wind": { + "speed": 2.32, + "deg": 151, + "gust": 4.57 + }, + "visibility": 10000, + "pop": 0, + "sys": { + "pod": "n" + }, + "dt_txt": "2024-11-27 00:00:00" + }, + { + "dt": 1732676400, + "main": { + "temp": 7.55, + "feels_like": 5.92, + "temp_min": 7.55, + "temp_max": 7.55, + "pressure": 1021, + "sea_level": 1021, + "grnd_level": 1016, + "humidity": 88, + "temp_kf": 0 + }, + "weather": [ + { + "id": 803, + "main": "Clouds", + "description": "broken clouds", + "icon": "04n" + } + ], + "clouds": { + "all": 0 + }, + "wind": { + "speed": 2.5, + "deg": 154, + "gust": 5.91 + }, + "visibility": 10000, + "pop": 0, + "sys": { + "pod": "n" + }, + "dt_txt": "2024-11-27 03:00:00" + }, + { + "dt": 1732687200, + "main": { + "temp": 7.47, + "feels_like": 5.8, + "temp_min": 7.47, + "temp_max": 7.47, + "pressure": 1021, + "sea_level": 1021, + "grnd_level": 1016, + "humidity": 84, + "temp_kf": 0 + }, + "weather": [ + { + "id": 803, + "main": "Clouds", + "description": "broken clouds", + "icon": "04n" + } + ], + "clouds": { + "all": 0 + }, + "wind": { + "speed": 2.54, + "deg": 159, + "gust": 5.65 + }, + "visibility": 10000, + "pop": 0, + "sys": { + "pod": "n" + }, + "dt_txt": "2024-11-27 06:00:00" + }, + { + "dt": 1732698000, + "main": { + "temp": 9.35, + "feels_like": 8.38, + "temp_min": 9.35, + "temp_max": 9.35, + "pressure": 1022, + "sea_level": 1022, + "grnd_level": 1017, + "humidity": 74, + "temp_kf": 0 + }, + "weather": [ + { + "id": 803, + "main": "Clouds", + "description": "broken clouds", + "icon": "04d" + } + ], + "clouds": { + "all": 0 + }, + "wind": { + "speed": 2.07, + "deg": 161, + "gust": 4.74 + }, + "visibility": 10000, + "pop": 0, + "sys": { + "pod": "d" + }, + "dt_txt": "2024-11-27 09:00:00" + }, + { + "dt": 1732708800, + "main": { + "temp": 11.8, + "feels_like": 10.73, + "temp_min": 11.8, + "temp_max": 11.8, + "pressure": 1021, + "sea_level": 1021, + "grnd_level": 1016, + "humidity": 65, + "temp_kf": 0 + }, + "weather": [ + { + "id": 803, + "main": "Clouds", + "description": "broken clouds", + "icon": "04d" + } + ], + "clouds": { + "all": 0 + }, + "wind": { + "speed": 1.46, + "deg": 173, + "gust": 3.12 + }, + "visibility": 10000, + "pop": 0, + "sys": { + "pod": "d" + }, + "dt_txt": "2024-11-27 12:00:00" + }, + { + "dt": 1732719600, + "main": { + "temp": 14.28, + "feels_like": 13.23, + "temp_min": 14.28, + "temp_max": 14.28, + "pressure": 1021, + "sea_level": 1021, + "grnd_level": 1016, + "humidity": 56, + "temp_kf": 0 + }, + "weather": [ + { + "id": 802, + "main": "Clouds", + "description": "scattered clouds", + "icon": "03d" + } + ], + "clouds": { + "all": 0 + }, + "wind": { + "speed": 1.34, + "deg": 158, + "gust": 1.71 + }, + "visibility": 10000, + "pop": 0, + "sys": { + "pod": "d" + }, + "dt_txt": "2024-11-27 15:00:00" + }, + { + "dt": 1732730400, + "main": { + "temp": 10.81, + "feels_like": 9.7, + "temp_min": 10.81, + "temp_max": 10.81, + "pressure": 1021, + "sea_level": 1021, + "grnd_level": 1016, + "humidity": 67, + "temp_kf": 0 + }, + "weather": [ + { + "id": 802, + "main": "Clouds", + "description": "scattered clouds", + "icon": "03n" + } + ], + "clouds": { + "all": 0 + }, + "wind": { + "speed": 1.81, + "deg": 164, + "gust": 1.94 + }, + "visibility": 10000, + "pop": 0, + "sys": { + "pod": "n" + }, + "dt_txt": "2024-11-27 18:00:00" + }, + { + "dt": 1732741200, + "main": { + "temp": 9.66, + "feels_like": 8.85, + "temp_min": 9.66, + "temp_max": 9.66, + "pressure": 1022, + "sea_level": 1022, + "grnd_level": 1017, + "humidity": 72, + "temp_kf": 0 + }, + "weather": [ + { + "id": 802, + "main": "Clouds", + "description": "scattered clouds", + "icon": "03n" + } + ], + "clouds": { + "all": 48 + }, + "wind": { + "speed": 1.93, + "deg": 160, + "gust": 2.05 + }, + "visibility": 10000, + "pop": 0, + "sys": { + "pod": "n" + }, + "dt_txt": "2024-11-27 21:00:00" + }, + { + "dt": 1732752000, + "main": { + "temp": 9.16, + "feels_like": 8.53, + "temp_min": 9.16, + "temp_max": 9.16, + "pressure": 1021, + "sea_level": 1021, + "grnd_level": 1016, + "humidity": 74, + "temp_kf": 0 + }, + "weather": [ + { + "id": 803, + "main": "Clouds", + "description": "broken clouds", + "icon": "04n" + } + ], + "clouds": { + "all": 57 + }, + "wind": { + "speed": 1.66, + "deg": 167, + "gust": 1.66 + }, + "visibility": 10000, + "pop": 0, + "sys": { + "pod": "n" + }, + "dt_txt": "2024-11-28 00:00:00" + }, + { + "dt": 1732762800, + "main": { + "temp": 9.04, + "feels_like": 8.02, + "temp_min": 9.04, + "temp_max": 9.04, + "pressure": 1021, + "sea_level": 1021, + "grnd_level": 1016, + "humidity": 73, + "temp_kf": 0 + }, + "weather": [ + { + "id": 803, + "main": "Clouds", + "description": "broken clouds", + "icon": "04n" + } + ], + "clouds": { + "all": 82 + }, + "wind": { + "speed": 2.06, + "deg": 172, + "gust": 2.47 + }, + "visibility": 10000, + "pop": 0, + "sys": { + "pod": "n" + }, + "dt_txt": "2024-11-28 03:00:00" + }, + { + "dt": 1732773600, + "main": { + "temp": 8.56, + "feels_like": 7.61, + "temp_min": 8.56, + "temp_max": 8.56, + "pressure": 1021, + "sea_level": 1021, + "grnd_level": 1016, + "humidity": 74, + "temp_kf": 0 + }, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04n" + } + ], + "clouds": { + "all": 86 + }, + "wind": { + "speed": 1.89, + "deg": 193, + "gust": 1.99 + }, + "visibility": 10000, + "pop": 0, + "sys": { + "pod": "n" + }, + "dt_txt": "2024-11-28 06:00:00" + }, + { + "dt": 1732784400, + "main": { + "temp": 10.51, + "feels_like": 9.52, + "temp_min": 10.51, + "temp_max": 10.51, + "pressure": 1022, + "sea_level": 1022, + "grnd_level": 1017, + "humidity": 73, + "temp_kf": 0 + }, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04d" + } + ], + "clouds": { + "all": 93 + }, + "wind": { + "speed": 1.5, + "deg": 203, + "gust": 2.77 + }, + "visibility": 10000, + "pop": 0, + "sys": { + "pod": "d" + }, + "dt_txt": "2024-11-28 09:00:00" + }, + { + "dt": 1732795200, + "main": { + "temp": 13.48, + "feels_like": 12.79, + "temp_min": 13.48, + "temp_max": 13.48, + "pressure": 1022, + "sea_level": 1022, + "grnd_level": 1017, + "humidity": 73, + "temp_kf": 0 + }, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04d" + } + ], + "clouds": { + "all": 91 + }, + "wind": { + "speed": 2.13, + "deg": 223, + "gust": 3.44 + }, + "visibility": 10000, + "pop": 0, + "sys": { + "pod": "d" + }, + "dt_txt": "2024-11-28 12:00:00" + }, + { + "dt": 1732806000, + "main": { + "temp": 15.76, + "feels_like": 15.27, + "temp_min": 15.76, + "temp_max": 15.76, + "pressure": 1022, + "sea_level": 1022, + "grnd_level": 1017, + "humidity": 72, + "temp_kf": 0 + }, + "weather": [ + { + "id": 803, + "main": "Clouds", + "description": "broken clouds", + "icon": "04d" + } + ], + "clouds": { + "all": 64 + }, + "wind": { + "speed": 1.82, + "deg": 262, + "gust": 3.62 + }, + "visibility": 10000, + "pop": 0, + "sys": { + "pod": "d" + }, + "dt_txt": "2024-11-28 15:00:00" + }, + { + "dt": 1732816800, + "main": { + "temp": 12.48, + "feels_like": 12.16, + "temp_min": 12.48, + "temp_max": 12.48, + "pressure": 1023, + "sea_level": 1023, + "grnd_level": 1018, + "humidity": 91, + "temp_kf": 0 + }, + "weather": [ + { + "id": 803, + "main": "Clouds", + "description": "broken clouds", + "icon": "04n" + } + ], + "clouds": { + "all": 63 + }, + "wind": { + "speed": 1.09, + "deg": 273, + "gust": 0.73 + }, + "visibility": 10000, + "pop": 0, + "sys": { + "pod": "n" + }, + "dt_txt": "2024-11-28 18:00:00" + }, + { + "dt": 1732827600, + "main": { + "temp": 12.51, + "feels_like": 12.04, + "temp_min": 12.51, + "temp_max": 12.51, + "pressure": 1024, + "sea_level": 1024, + "grnd_level": 1019, + "humidity": 85, + "temp_kf": 0 + }, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04n" + } + ], + "clouds": { + "all": 89 + }, + "wind": { + "speed": 2.32, + "deg": 96, + "gust": 4.52 + }, + "visibility": 10000, + "pop": 0, + "sys": { + "pod": "n" + }, + "dt_txt": "2024-11-28 21:00:00" + } + ], + "city": { + "id": 3031582, + "name": "Bordeaux", + "coord": { + "lat": 44.85, + "lon": -0.59 + }, + "country": "FR", + "population": 231844, + "timezone": 3600, + "sunrise": 1732345841, + "sunset": 1732379244 + } +} \ No newline at end of file diff --git a/src/test/resources/OpenWeatherMap/wrong-apikey.json b/src/test/resources/OpenWeatherMap/wrong-apikey.json new file mode 100644 index 0000000..0ba9c1e --- /dev/null +++ b/src/test/resources/OpenWeatherMap/wrong-apikey.json @@ -0,0 +1,4 @@ +{ + "cod": 401, + "message": "Invalid API key. Please see https://openweathermap.org/faq#error401 for more info." +} \ No newline at end of file From eb56bbfe733562cc37e86e0da4210db51e7b09a1 Mon Sep 17 00:00:00 2001 From: Martin Eyben Date: Sun, 24 Nov 2024 00:50:44 +0100 Subject: [PATCH 15/29] feat: openMeteo tests --- src/main/java/eirb/pg203/OpenMeteo.java | 16 +++++- .../java/eirb/pg203/WeatherDataAPITest.java | 37 ++++++++++++- .../FakeJSONFetcherOpenMeteo.java | 27 +++++++++ .../Bordeaux-partial-cloudy-rain-sunny.json | 55 +++++++++++++++++++ 4 files changed, 129 insertions(+), 6 deletions(-) create mode 100644 src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherOpenMeteo.java create mode 100644 src/test/resources/OpenMeteo/Bordeaux-partial-cloudy-rain-sunny.json diff --git a/src/main/java/eirb/pg203/OpenMeteo.java b/src/main/java/eirb/pg203/OpenMeteo.java index dab51de..32a9464 100644 --- a/src/main/java/eirb/pg203/OpenMeteo.java +++ b/src/main/java/eirb/pg203/OpenMeteo.java @@ -9,6 +9,7 @@ import eirb.pg203.utils.JSONFetcher; import java.io.IOException; import java.net.URI; import java.net.URL; +import java.time.Clock; import java.time.Instant; import java.util.ArrayList; import java.util.Locale; @@ -23,7 +24,8 @@ import eirb.pg203.WeatherData.Condition; public class OpenMeteo implements WeatherDataAPI { private static final String forecastBaseURL = "https://api.open-meteo.com/v1/forecast"; private static final String dailyQuery = "weather_code,temperature_2m_max,temperature_2m_min,wind_speed_10m_max,wind_direction_10m_dominant"; - private static final JSONFetcherInterface JSONFetcher = new JSONFetcher(); + JSONFetcherInterface JSONFetcher = new JSONFetcher(); + Clock clock = Clock.systemUTC(); /** * Default constructor @@ -45,6 +47,9 @@ public class OpenMeteo implements WeatherDataAPI { } private static Condition getConditionFromCode(int WMOCode) { + // TODO Wesh Nemo c'est quoi cette merde ? + if (WMOCode == 2 ) + return Condition.PARTIAL; if (WMOCode < 20) return Condition.SUNNY; else if (WMOCode < 30) @@ -56,7 +61,7 @@ public class OpenMeteo implements WeatherDataAPI { } - private static WeatherData getWeatherDataFromForecast(JSONObject response, int day, String city) { + private WeatherData getWeatherDataFromForecast(JSONObject response, int day, String city) { JSONObject daily = response.getJSONObject("daily"); float max_temp = daily.getJSONArray("temperature_2m_max").getFloat(day); float min_temp = daily.getJSONArray("temperature_2m_min").getFloat(day); @@ -68,7 +73,7 @@ public class OpenMeteo implements WeatherDataAPI { return new WeatherData( new City(city), - Instant.now(), + Instant.now(clock), temp_c, windSpeed, windDirection, @@ -109,4 +114,9 @@ public class OpenMeteo implements WeatherDataAPI { public String getAPIName() { return "OpenMeteo"; } + + @Override + public String toString() { + return this.getAPIName(); + } } diff --git a/src/test/java/eirb/pg203/WeatherDataAPITest.java b/src/test/java/eirb/pg203/WeatherDataAPITest.java index 21df664..804a284 100644 --- a/src/test/java/eirb/pg203/WeatherDataAPITest.java +++ b/src/test/java/eirb/pg203/WeatherDataAPITest.java @@ -1,5 +1,6 @@ package eirb.pg203; +import eirb.pg203.fakeJSONFetcher.FakeJSONFetcherOpenMeteo; import eirb.pg203.fakeJSONFetcher.FakeJSONFetcherOpenWeatherMap; import eirb.pg203.fakeJSONFetcher.FakeJSONFetcherWeatherAPI; import org.junit.jupiter.api.Assertions; @@ -37,6 +38,17 @@ public class WeatherDataAPITest { return openWeatherMap; } + private static OpenMeteo openMeteo() { + // Fix clock for testing + String instantExpected = "2024-11-24T00:00:00.00Z"; + Clock clock = Clock.fixed(Instant.parse(instantExpected), ZoneId.systemDefault()); + + OpenMeteo openMeteo = new OpenMeteo(); + openMeteo.clock = clock; + openMeteo.JSONFetcher = new FakeJSONFetcherOpenMeteo(); + return openMeteo; + } + /** * List of args for Temperature testing @@ -45,14 +57,21 @@ public class WeatherDataAPITest { private static Stream testGetTemperature(){ return Stream.of( + /* WeatherAPI */ Arguments.arguments(weatherAPI(), 0, 8.1F,WeatherData.Condition.PARTIAL, 17.45F, 142.08F), Arguments.arguments(weatherAPI(), 1, 13F, WeatherData.Condition.SUNNY, 23.03F, 142.58F), Arguments.arguments(weatherAPI(), 2, 12.7F, WeatherData.Condition.RAINY, 13.19F, 222.92F), Arguments.arguments(weatherAPI(), 3, 8.1F,WeatherData.Condition.CLOUDY, 17.45F, 142.08F), + /* Open Weather Map */ Arguments.arguments(openWeatherMap(), 0, 13.41F,WeatherData.Condition.PARTIAL, 5.74F, 142.13F), Arguments.arguments(openWeatherMap(), 1, 13.29F,WeatherData.Condition.CLOUDY, 3.62F, 225.25F), Arguments.arguments(openWeatherMap(), 2, 10.06F,WeatherData.Condition.RAINY, 2.22F, 191.75F), - Arguments.arguments(openWeatherMap(), 3, 9.88F,WeatherData.Condition.SUNNY, 2.00F, 160.00F) + Arguments.arguments(openWeatherMap(), 3, 9.88F,WeatherData.Condition.SUNNY, 2.00F, 160.00F), + /* Open Meteo */ + Arguments.arguments(openMeteo(), 0, 7.6F,WeatherData.Condition.PARTIAL, 17.6F, 151F), + Arguments.arguments(openMeteo(), 1, 13.20F,WeatherData.Condition.CLOUDY, 20.6F, 149F), + Arguments.arguments(openMeteo(), 2, 12.3F,WeatherData.Condition.RAINY, 21.70F, 187F), + Arguments.arguments(openMeteo(), 3, 10.80F,WeatherData.Condition.SUNNY, 9.4F, 177F) ); } @@ -83,9 +102,15 @@ public class WeatherDataAPITest { float[] openWeatherMapExpectedWindSpeed = {5.74F, 3.62F, 2.22F, 2.00F}; float[] openWeatherMapExpectedWindDirection = {142.13F, 225.25F, 191.75F, 160F}; + float[] openMeteoExpectedTemperatures = {7.6F, 13.2F, 12.3F, 10.80F}; + WeatherData.Condition[] openMeteoExpectedConditions = {WeatherData.Condition.PARTIAL, WeatherData.Condition.CLOUDY, WeatherData.Condition.RAINY, WeatherData.Condition.SUNNY}; + float[] openMeteoExpectedWindSpeed = {17.6F, 20.6F, 21.7F, 9.40F}; + float[] openMeteoExpectedWindDirection = {157.00F, 149F, 187F, 177F}; + return Stream.of( Arguments.arguments(weatherAPI(), 4, weatherAPIExpectedTemperatures, weatherAPIExpectedConditions, weatherAPIExpectedWindSpeed, weatherAPIExpectedWindDirection), - Arguments.arguments(openWeatherMap(), 4, openWeatherMapExpectedTemperatures, openWeatherMapExpectedConditions, openWeatherMapExpectedWindSpeed, openWeatherMapExpectedWindDirection) + Arguments.arguments(openWeatherMap(), 4, openWeatherMapExpectedTemperatures, openWeatherMapExpectedConditions, openWeatherMapExpectedWindSpeed, openWeatherMapExpectedWindDirection), + Arguments.arguments(openMeteo(), 4, openMeteoExpectedTemperatures, openMeteoExpectedConditions, openMeteoExpectedWindSpeed, openMeteoExpectedWindDirection) ); } @@ -115,9 +140,15 @@ public class WeatherDataAPITest { return Stream.of( Arguments.arguments(weatherAPI()), - Arguments.arguments(openWeatherMap()) + Arguments.arguments(openWeatherMap()), + Arguments.arguments(openMeteo()) ); } + + /** + * For coverage, not yet implemented + * @param weatherDataAPI api to test + */ @ParameterizedTest(name = "[{0}] Get temperature for a specific hour") @MethodSource public void testGetTemperatureByHours(WeatherDataAPI weatherDataAPI) { diff --git a/src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherOpenMeteo.java b/src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherOpenMeteo.java new file mode 100644 index 0000000..7d0e75e --- /dev/null +++ b/src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherOpenMeteo.java @@ -0,0 +1,27 @@ +package eirb.pg203.fakeJSONFetcher; + +import eirb.pg203.utils.FileResourcesUtils; +import eirb.pg203.utils.JSONFetcherInterface; +import eirb.pg203.utils.SplitQueryUrl; +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.IOException; +import java.net.URL; +import java.util.Map; + +public class FakeJSONFetcherOpenMeteo implements JSONFetcherInterface { + private JSONObject responseExample() { + return FileResourcesUtils.getFileFromResourceAsJson("OpenMeteo/Bordeaux-partial-cloudy-rain-sunny.json"); + } + + @Override + public JSONObject fetch(URL url) throws IOException { + return responseExample(); + } + + @Override + public JSONArray fetchArray(URL url) throws IOException { + return null; + } +} diff --git a/src/test/resources/OpenMeteo/Bordeaux-partial-cloudy-rain-sunny.json b/src/test/resources/OpenMeteo/Bordeaux-partial-cloudy-rain-sunny.json new file mode 100644 index 0000000..c30d533 --- /dev/null +++ b/src/test/resources/OpenMeteo/Bordeaux-partial-cloudy-rain-sunny.json @@ -0,0 +1,55 @@ +{ + "latitude": 44.84, + "longitude": -0.58000016, + "generationtime_ms": 0.1360177993774414, + "utc_offset_seconds": 0, + "timezone": "GMT", + "timezone_abbreviation": "GMT", + "elevation": 15, + "daily_units": { + "time": "iso8601", + "weather_code": "wmo code", + "temperature_2m_max": "°C", + "temperature_2m_min": "°C", + "wind_speed_10m_max": "km/h", + "wind_direction_10m_dominant": "°" + }, + "daily": { + "time": [ + "2024-11-23", + "2024-11-24", + "2024-11-25", + "2024-11-26" + ], + "weather_code": [ + 2, + 3, + 63, + 0 + ], + "temperature_2m_max": [ + 12.3, + 17.2, + 15.4, + 13.7 + ], + "temperature_2m_min": [ + 2.9, + 9.2, + 9.2, + 7.9 + ], + "wind_speed_10m_max": [ + 17.6, + 20.6, + 21.7, + 9.4 + ], + "wind_direction_10m_dominant": [ + 151, + 149, + 187, + 177 + ] + } +} \ No newline at end of file From 8f1e2649e163f7358b58ef29a5fee23d42b5e924 Mon Sep 17 00:00:00 2001 From: Martin Eyben Date: Sun, 24 Nov 2024 12:45:45 +0100 Subject: [PATCH 16/29] feat: add weather data tests --- src/main/java/eirb/pg203/WeatherData.java | 5 +- .../java/eirb/pg203/WeatherDataAPITest.java | 2 +- src/test/java/eirb/pg203/WeatherDataTest.java | 108 ++++++++++++++++++ .../fakeJSONFetcher/FakeJSONFetcherCity.java | 2 +- 4 files changed, 113 insertions(+), 4 deletions(-) create mode 100644 src/test/java/eirb/pg203/WeatherDataTest.java diff --git a/src/main/java/eirb/pg203/WeatherData.java b/src/main/java/eirb/pg203/WeatherData.java index 602bb55..1a03b68 100644 --- a/src/main/java/eirb/pg203/WeatherData.java +++ b/src/main/java/eirb/pg203/WeatherData.java @@ -101,7 +101,7 @@ public class WeatherData { private Condition condition; // cloudly, sunny ... private float windSpeed; private float windDirectionAngle; - private final WindDirection windDirection; + private WindDirection windDirection; /** @@ -110,7 +110,7 @@ public class WeatherData { * @return wind direction representation */ private WindDirection getWindDirection(float windDirectionAngle) { - if (windDirectionAngle >= 337.5 || windDirectionAngle <= 22.5) + if ((windDirectionAngle >= 337.5 && windDirectionAngle <= 360) || (windDirectionAngle >= 0 && windDirectionAngle <= 22.5)) return WindDirection.N; if (windDirectionAngle > 22.5 && windDirectionAngle <= 67.5) return WindDirection.NE; @@ -241,6 +241,7 @@ public class WeatherData { */ public void setWindDirectionAngle(float windDirectionAngle) { this.windDirectionAngle = windDirectionAngle; + this.windDirection = this.getWindDirection(windDirectionAngle); } /** diff --git a/src/test/java/eirb/pg203/WeatherDataAPITest.java b/src/test/java/eirb/pg203/WeatherDataAPITest.java index 804a284..89215ab 100644 --- a/src/test/java/eirb/pg203/WeatherDataAPITest.java +++ b/src/test/java/eirb/pg203/WeatherDataAPITest.java @@ -105,7 +105,7 @@ public class WeatherDataAPITest { float[] openMeteoExpectedTemperatures = {7.6F, 13.2F, 12.3F, 10.80F}; WeatherData.Condition[] openMeteoExpectedConditions = {WeatherData.Condition.PARTIAL, WeatherData.Condition.CLOUDY, WeatherData.Condition.RAINY, WeatherData.Condition.SUNNY}; float[] openMeteoExpectedWindSpeed = {17.6F, 20.6F, 21.7F, 9.40F}; - float[] openMeteoExpectedWindDirection = {157.00F, 149F, 187F, 177F}; + float[] openMeteoExpectedWindDirection = {151.00F, 149F, 187F, 177F}; return Stream.of( Arguments.arguments(weatherAPI(), 4, weatherAPIExpectedTemperatures, weatherAPIExpectedConditions, weatherAPIExpectedWindSpeed, weatherAPIExpectedWindDirection), diff --git a/src/test/java/eirb/pg203/WeatherDataTest.java b/src/test/java/eirb/pg203/WeatherDataTest.java new file mode 100644 index 0000000..5dac2bc --- /dev/null +++ b/src/test/java/eirb/pg203/WeatherDataTest.java @@ -0,0 +1,108 @@ +package eirb.pg203; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.MethodSource; + +import java.time.Instant; +import java.util.stream.Stream; + +public class WeatherDataTest { + private static final float epsilon = 0.01F; + private WeatherData weatherData; + + @BeforeEach + public void setWeatherData() { + this.weatherData = new WeatherData(new City("f"), Instant.now(), 0f, 0, 0, WeatherData.Condition.SUNNY); + } + + @Test + void city() { + String cityName = "Paris"; + City city = new City(cityName); + this.weatherData.setCity(city); + + /* check if the setter works */ + Assertions.assertEquals(weatherData.getCity(), city); + } + + @Test + void temp() { + float temp = 69.69f; + this.weatherData.setTemp(temp); + + /* check if the setter works */ + Assertions.assertEquals(temp, weatherData.getTemp(), epsilon); + } + + @ParameterizedTest(name = "Set {0}") + @EnumSource + void condition(WeatherData.Condition condition) { + this.weatherData.setCondition(condition); + + /* check if the setter works */ + Assertions.assertEquals(condition, weatherData.getCondition()); + } + + @Test + void windSpeed() { + float windSpeed = 69.69f; + this.weatherData.setWindSpeed(windSpeed); + + /* check if the setter works */ + Assertions.assertEquals(windSpeed, weatherData.getWindSpeed()); + } + + @Test + void windAngle() { + float windAngle = 69.69f; + this.weatherData.setWindDirectionAngle(windAngle); + + /* check if the setter works */ + Assertions.assertEquals(windAngle, weatherData.getWindDirectionAngle()); + } + + @Test + void date() { + Instant instant = Instant.now(); + + this.weatherData.setDate(instant); + + /* check if the setter works */ + Assertions.assertEquals(instant, weatherData.getDate()); + + } + + @Test + void stringRepresentation() { + this.weatherData = new WeatherData(new City("f"), Instant.now(), 0f, 0, 0, WeatherData.Condition.SUNNY); + + Assertions.assertEquals("00.00° ☀️ 00.00km/h 000.00° 🡩", weatherData.toString()); + } + + private static Stream windDirectionRepresentation() { + return Stream.of( + Arguments.arguments(0f, WeatherData.WindDirection.N), + Arguments.arguments(45f, WeatherData.WindDirection.NE), + Arguments.arguments(90f, WeatherData.WindDirection.E), + Arguments.arguments(135f, WeatherData.WindDirection.SE), + Arguments.arguments(180f, WeatherData.WindDirection.S), + Arguments.arguments(225f, WeatherData.WindDirection.SW), + Arguments.arguments(270f, WeatherData.WindDirection.W), + Arguments.arguments(315f, WeatherData.WindDirection.NW), + Arguments.arguments(999f, WeatherData.WindDirection.ERROR) + ); + + } + + @ParameterizedTest + @MethodSource + void windDirectionRepresentation(float windDirection, WeatherData.WindDirection expectedWindDirection){ + weatherData.setWindDirectionAngle(windDirection); + Assertions.assertEquals(expectedWindDirection, weatherData.getWindDirection()); + } +} diff --git a/src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherCity.java b/src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherCity.java index 6259e41..b24b0b5 100644 --- a/src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherCity.java +++ b/src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherCity.java @@ -37,4 +37,4 @@ public class FakeJSONFetcherCity implements JSONFetcherInterface { public JSONArray fetchArray(URL url) throws IOException { return null; } -} +} \ No newline at end of file From 10e36d97c415893f39c4cf968e9ec9072db96610 Mon Sep 17 00:00:00 2001 From: Martin Eyben Date: Sun, 24 Nov 2024 12:59:26 +0100 Subject: [PATCH 17/29] feat: coords tests --- .../java/eirb/pg203/utils/CoordsTest.java | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 src/test/java/eirb/pg203/utils/CoordsTest.java diff --git a/src/test/java/eirb/pg203/utils/CoordsTest.java b/src/test/java/eirb/pg203/utils/CoordsTest.java new file mode 100644 index 0000000..374e731 --- /dev/null +++ b/src/test/java/eirb/pg203/utils/CoordsTest.java @@ -0,0 +1,54 @@ +package eirb.pg203.utils; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class CoordsTest { + private static final float epsilon = 0.01F; + + @Test + void getLat() { + float lat = 1f; + float lon = 2f; + Coords coords = new Coords(lat, lon); + + Assertions.assertEquals(lat, coords.getLat(), epsilon); + } + + @Test + void getLon() { + float lat = 1f; + float lon = 2f; + Coords coords = new Coords(lat, lon); + + Assertions.assertEquals(lon, coords.getLon(), epsilon); + } + + @Test + void setLat() { + float lat = 1f; + float lon = 2f; + Coords coords = new Coords(lat, lon); + + float sndLat = 3f; + coords.setLat(sndLat); + + Assertions.assertEquals(sndLat, coords.getLat(), epsilon); + + } + + @Test + void setLon() { + float lat = 1f; + float lon = 2f; + Coords coords = new Coords(lat, lon); + + float sndLon = 4f; + coords.setLon(sndLon); + + Assertions.assertEquals(sndLon, coords.getLon(), epsilon); + + } +} \ No newline at end of file From 00f77897c98180272dc81f875cb9c3ce0a1a6be3 Mon Sep 17 00:00:00 2001 From: Martin Eyben Date: Sun, 24 Nov 2024 13:12:17 +0100 Subject: [PATCH 18/29] feat: refactor JSON fetcher --- src/main/java/eirb/pg203/City.java | 6 +----- src/main/java/eirb/pg203/OpenMeteo.java | 4 +--- src/main/java/eirb/pg203/OpenWeatherMap.java | 3 +-- src/main/java/eirb/pg203/WeatherAPI.java | 3 +-- src/main/java/eirb/pg203/utils/JSONFetcher.java | 4 +--- .../eirb/pg203/utils/JSONFetcherInterface.java | 16 ---------------- .../fakeJSONFetcher/FakeJSONFetcherCity.java | 3 +-- .../FakeJSONFetcherOpenMeteo.java | 6 ++---- .../FakeJSONFetcherOpenWeatherMap.java | 5 ++--- .../FakeJSONFetcherWeatherAPI.java | 4 ++-- 10 files changed, 12 insertions(+), 42 deletions(-) delete mode 100644 src/main/java/eirb/pg203/utils/JSONFetcherInterface.java diff --git a/src/main/java/eirb/pg203/City.java b/src/main/java/eirb/pg203/City.java index 77fab5d..5084fae 100644 --- a/src/main/java/eirb/pg203/City.java +++ b/src/main/java/eirb/pg203/City.java @@ -1,15 +1,11 @@ package eirb.pg203; -import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStreamReader; -import java.net.HttpURLConnection; import java.net.URI; import java.net.URL; import java.util.Locale; import eirb.pg203.utils.JSONFetcher; -import eirb.pg203.utils.JSONFetcherInterface; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -23,7 +19,7 @@ import eirb.pg203.utils.Coords; public class City { private String cityName; private Coords cityCoords; - JSONFetcherInterface JSONFetcher = new JSONFetcher(); + JSONFetcher JSONFetcher = new JSONFetcher(); /** * Fetch data from adresse.data.gouv.fr diff --git a/src/main/java/eirb/pg203/OpenMeteo.java b/src/main/java/eirb/pg203/OpenMeteo.java index 32a9464..b3ff99e 100644 --- a/src/main/java/eirb/pg203/OpenMeteo.java +++ b/src/main/java/eirb/pg203/OpenMeteo.java @@ -1,7 +1,5 @@ package eirb.pg203; -import eirb.pg203.utils.JSONFetcherInterface; -import org.json.JSONArray; import org.json.JSONObject; import eirb.pg203.utils.JSONFetcher; @@ -24,7 +22,7 @@ import eirb.pg203.WeatherData.Condition; public class OpenMeteo implements WeatherDataAPI { private static final String forecastBaseURL = "https://api.open-meteo.com/v1/forecast"; private static final String dailyQuery = "weather_code,temperature_2m_max,temperature_2m_min,wind_speed_10m_max,wind_direction_10m_dominant"; - JSONFetcherInterface JSONFetcher = new JSONFetcher(); + JSONFetcher JSONFetcher = new JSONFetcher(); Clock clock = Clock.systemUTC(); /** diff --git a/src/main/java/eirb/pg203/OpenWeatherMap.java b/src/main/java/eirb/pg203/OpenWeatherMap.java index 580b57f..333b77c 100644 --- a/src/main/java/eirb/pg203/OpenWeatherMap.java +++ b/src/main/java/eirb/pg203/OpenWeatherMap.java @@ -1,6 +1,5 @@ package eirb.pg203; -import eirb.pg203.utils.JSONFetcherInterface; import org.json.JSONObject; import org.json.JSONArray; @@ -25,7 +24,7 @@ public class OpenWeatherMap implements WeatherDataAPI { private static final String forecastBaseURL = "https://api.openweathermap.org/data/2.5/forecast"; private String APIKey; Clock clock = Clock.systemUTC(); - JSONFetcherInterface JSONFetcher = new JSONFetcher(); + JSONFetcher JSONFetcher = new JSONFetcher(); OpenWeatherMap(String APIKey) { this.APIKey = APIKey; diff --git a/src/main/java/eirb/pg203/WeatherAPI.java b/src/main/java/eirb/pg203/WeatherAPI.java index d4f1d83..3431c86 100644 --- a/src/main/java/eirb/pg203/WeatherAPI.java +++ b/src/main/java/eirb/pg203/WeatherAPI.java @@ -1,6 +1,5 @@ package eirb.pg203; -import eirb.pg203.utils.JSONFetcherInterface; import org.json.JSONArray; import org.json.JSONObject; @@ -19,7 +18,7 @@ import java.util.Locale; */ public class WeatherAPI implements WeatherDataAPI{ private final String weatherAPIKey; - JSONFetcherInterface JSONFetcher = new JSONFetcher(); + JSONFetcher JSONFetcher = new JSONFetcher(); private static final String forecastBaseURL = "https://api.weatherapi.com/v1/forecast.json"; WeatherAPI(String weatherAPIKey) { diff --git a/src/main/java/eirb/pg203/utils/JSONFetcher.java b/src/main/java/eirb/pg203/utils/JSONFetcher.java index 16484a9..49d7799 100644 --- a/src/main/java/eirb/pg203/utils/JSONFetcher.java +++ b/src/main/java/eirb/pg203/utils/JSONFetcher.java @@ -12,7 +12,7 @@ import org.json.JSONObject; /** * Util for http calls */ -public class JSONFetcher implements JSONFetcherInterface{ +public class JSONFetcher { /** * No need for constructor @@ -44,7 +44,6 @@ public class JSONFetcher implements JSONFetcherInterface{ * @return Json object of the response * @throws IOException if the request failed */ - @Override public JSONObject fetch(URL url) throws IOException { String result = fetchString(url); @@ -57,7 +56,6 @@ public class JSONFetcher implements JSONFetcherInterface{ * @return JSON array * @throws IOException when request failed */ - @Override public JSONArray fetchArray(URL url) throws IOException { String result = fetchString(url); diff --git a/src/main/java/eirb/pg203/utils/JSONFetcherInterface.java b/src/main/java/eirb/pg203/utils/JSONFetcherInterface.java deleted file mode 100644 index 0adbd33..0000000 --- a/src/main/java/eirb/pg203/utils/JSONFetcherInterface.java +++ /dev/null @@ -1,16 +0,0 @@ -package eirb.pg203.utils; - -import org.json.JSONArray; -import org.json.JSONObject; - -import java.io.IOException; -import java.net.URL; - -/** - * Interface use to be overrided by a fake jsonFetcher during tests - */ -public interface JSONFetcherInterface { - JSONObject fetch(URL url) throws IOException; - - JSONArray fetchArray(URL url) throws IOException; -} diff --git a/src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherCity.java b/src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherCity.java index b24b0b5..8634f12 100644 --- a/src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherCity.java +++ b/src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherCity.java @@ -2,7 +2,6 @@ package eirb.pg203.fakeJSONFetcher; import eirb.pg203.utils.FileResourcesUtils; import eirb.pg203.utils.JSONFetcher; -import eirb.pg203.utils.JSONFetcherInterface; import eirb.pg203.utils.SplitQueryUrl; import org.json.JSONArray; import org.json.JSONObject; @@ -13,7 +12,7 @@ import java.util.HashMap; import java.util.Locale; import java.util.Map; -public class FakeJSONFetcherCity implements JSONFetcherInterface { +public class FakeJSONFetcherCity extends JSONFetcher{ JSONObject unknownCity = FileResourcesUtils.getFileFromResourceAsJson("City/fakeCity.json"); private static HashMap cities(){ diff --git a/src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherOpenMeteo.java b/src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherOpenMeteo.java index 7d0e75e..9630f7f 100644 --- a/src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherOpenMeteo.java +++ b/src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherOpenMeteo.java @@ -1,16 +1,14 @@ package eirb.pg203.fakeJSONFetcher; import eirb.pg203.utils.FileResourcesUtils; -import eirb.pg203.utils.JSONFetcherInterface; -import eirb.pg203.utils.SplitQueryUrl; +import eirb.pg203.utils.JSONFetcher; import org.json.JSONArray; import org.json.JSONObject; import java.io.IOException; import java.net.URL; -import java.util.Map; -public class FakeJSONFetcherOpenMeteo implements JSONFetcherInterface { +public class FakeJSONFetcherOpenMeteo extends JSONFetcher { private JSONObject responseExample() { return FileResourcesUtils.getFileFromResourceAsJson("OpenMeteo/Bordeaux-partial-cloudy-rain-sunny.json"); } diff --git a/src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherOpenWeatherMap.java b/src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherOpenWeatherMap.java index 5583f31..2561930 100644 --- a/src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherOpenWeatherMap.java +++ b/src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherOpenWeatherMap.java @@ -1,17 +1,16 @@ package eirb.pg203.fakeJSONFetcher; import eirb.pg203.utils.FileResourcesUtils; -import eirb.pg203.utils.JSONFetcherInterface; +import eirb.pg203.utils.JSONFetcher; import eirb.pg203.utils.SplitQueryUrl; import org.json.JSONArray; import org.json.JSONObject; import java.io.IOException; import java.net.URL; -import java.time.Instant; import java.util.Map; -public class FakeJSONFetcherOpenWeatherMap implements JSONFetcherInterface { +public class FakeJSONFetcherOpenWeatherMap extends JSONFetcher { private final String apiKey = "realKey"; private final JSONObject wrongKeyResponse = FileResourcesUtils.getFileFromResourceAsJson("OpenWeatherMap/wrong-apikey.json"); diff --git a/src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherWeatherAPI.java b/src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherWeatherAPI.java index 31d30e2..e5d4170 100644 --- a/src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherWeatherAPI.java +++ b/src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherWeatherAPI.java @@ -1,7 +1,7 @@ package eirb.pg203.fakeJSONFetcher; import eirb.pg203.utils.FileResourcesUtils; -import eirb.pg203.utils.JSONFetcherInterface; +import eirb.pg203.utils.JSONFetcher; import eirb.pg203.utils.SplitQueryUrl; import org.json.JSONArray; import org.json.JSONObject; @@ -11,7 +11,7 @@ import java.net.URL; import java.util.ArrayList; import java.util.Map; -public class FakeJSONFetcherWeatherAPI implements JSONFetcherInterface { +public class FakeJSONFetcherWeatherAPI extends JSONFetcher { private final static String baseUrlFormat = "https://api.weatherapi.com/v1/forecast.json?key=%s&q=%s&days=%d"; private final String apiKey = "realKey"; private static final JSONObject wrongKeyRequest = FileResourcesUtils.getFileFromResourceAsJson("WeatherAPI/wrong-apikey.json"); From 288895f0656e7c1e7736b51a4ce7b76cb76fe5d6 Mon Sep 17 00:00:00 2001 From: Martin Eyben Date: Sun, 24 Nov 2024 14:39:55 +0100 Subject: [PATCH 19/29] feat: display tests --- .../java/eirb/pg203/WeatherDataAPITest.java | 6 +- .../eirb/pg203/WeatherDisplayBasicTest.java | 82 +++++++++++++++++++ 2 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 src/test/java/eirb/pg203/WeatherDisplayBasicTest.java diff --git a/src/test/java/eirb/pg203/WeatherDataAPITest.java b/src/test/java/eirb/pg203/WeatherDataAPITest.java index 89215ab..1c48fa9 100644 --- a/src/test/java/eirb/pg203/WeatherDataAPITest.java +++ b/src/test/java/eirb/pg203/WeatherDataAPITest.java @@ -21,13 +21,13 @@ public class WeatherDataAPITest { private static final float epsilon = 0.01F; private static final String APIKey = "realKey"; - private static WeatherAPI weatherAPI(){ + static WeatherAPI weatherAPI(){ WeatherAPI weatherAPI = new WeatherAPI(APIKey); weatherAPI.JSONFetcher = new FakeJSONFetcherWeatherAPI(); return weatherAPI; } - private static OpenWeatherMap openWeatherMap(){ + static OpenWeatherMap openWeatherMap(){ // Fix clock for testing String instantExpected = "2024-11-24T00:00:00.00Z"; Clock clock = Clock.fixed(Instant.parse(instantExpected), ZoneId.systemDefault()); @@ -38,7 +38,7 @@ public class WeatherDataAPITest { return openWeatherMap; } - private static OpenMeteo openMeteo() { + static OpenMeteo openMeteo() { // Fix clock for testing String instantExpected = "2024-11-24T00:00:00.00Z"; Clock clock = Clock.fixed(Instant.parse(instantExpected), ZoneId.systemDefault()); diff --git a/src/test/java/eirb/pg203/WeatherDisplayBasicTest.java b/src/test/java/eirb/pg203/WeatherDisplayBasicTest.java new file mode 100644 index 0000000..23656f2 --- /dev/null +++ b/src/test/java/eirb/pg203/WeatherDisplayBasicTest.java @@ -0,0 +1,82 @@ +package eirb.pg203; + +import org.junit.jupiter.api.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.stream.Stream; + +import static eirb.pg203.WeatherDataAPITest.*; + +public class WeatherDisplayBasicTest { + private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + private final ByteArrayOutputStream errContent = new ByteArrayOutputStream(); + private final PrintStream originalOut = System.out; + private final PrintStream originalErr = System.err; + + @BeforeEach + public void setUpStreams() { + System.setOut(new PrintStream(outContent)); + System.setErr(new PrintStream(errContent)); + } + + @AfterEach + public void restoreStreams() { + System.setOut(originalOut); + System.setErr(originalErr); + } + + private WeatherDisplayBasic weatherDisplayBasic; + + private WeatherDisplayBasic setWeatherDisplayBasic() { + /* Fake apis */ + OpenMeteo openMeteo = openMeteo(); + OpenWeatherMap openWeatherMap = openWeatherMap(); + WeatherAPI weatherAPI = weatherAPI(); + + WeatherDisplayBasic weatherDisplay = new WeatherDisplayBasic(); + weatherDisplay.addAPI(openMeteo); + weatherDisplay.addAPI(openWeatherMap); + weatherDisplay.addAPI(weatherAPI); + + return weatherDisplay; + } + + private static Stream display() { + return Stream.of( + Arguments.arguments(1, new StringBuilder() + .append("Source J + 0 \n") + .append("------------------------------+-----------------------------+\n") + .append("OpenMeteo | 07.60° 🌤 17.60km/h 151.00° 🡮| \n") + .append("------------------------------+-----------------------------+\n") + .append("WeatherAPI | 08.10° 🌤 17.45km/h 142.08° 🡮| \n") + .append("------------------------------+-----------------------------+\n") + .append("OpenWeatherMap | 13.41° 🌤 05.74km/h 142.13° 🡮| \n") + .append("------------------------------+-----------------------------+\n") + .toString() + ) + ); + + } + + @ParameterizedTest + @MethodSource + void display(int days, String expectedDisplay) { + String city = "Bordeaux"; + WeatherDisplayBasic weatherDisplayBasic = setWeatherDisplayBasic(); + weatherDisplayBasic.display(days, city); + + Assertions.assertEquals(expectedDisplay, outContent.toString()); + + } + + @Test + void addAPI() { + + } +} From dbd289762ad9cf701bd121e9b1f0c734577cb6ee Mon Sep 17 00:00:00 2001 From: Martin Eyben Date: Wed, 27 Nov 2024 20:27:21 +0000 Subject: [PATCH 20/29] fix: WMOcode for openmeteo implmentation --- src/main/java/eirb/pg203/OpenMeteo.java | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/main/java/eirb/pg203/OpenMeteo.java b/src/main/java/eirb/pg203/OpenMeteo.java index b3ff99e..6191992 100644 --- a/src/main/java/eirb/pg203/OpenMeteo.java +++ b/src/main/java/eirb/pg203/OpenMeteo.java @@ -44,18 +44,20 @@ public class OpenMeteo implements WeatherDataAPI { return JSONFetcher.fetch(url); } + /** + * return condition based on the WMOCode + * table can be find here : https://open-meteo.com/en/docs (at the end) + * @param WMOCode id code + * @return weather condition + */ private static Condition getConditionFromCode(int WMOCode) { - // TODO Wesh Nemo c'est quoi cette merde ? - if (WMOCode == 2 ) - return Condition.PARTIAL; - if (WMOCode < 20) - return Condition.SUNNY; - else if (WMOCode < 30) - return Condition.RAINY; - else if (WMOCode < 50) - return Condition.CLOUDY; - else - return Condition.RAINY; + return switch (WMOCode) { + case 0, 1 -> Condition.SUNNY; + case 2 -> Condition.PARTIAL; + case 3 -> Condition.CLOUDY; + case 61, 63, 65, 80, 81, 82 -> Condition.RAINY; + default -> Condition.ERROR; + }; } From 5dfed9ff4c52f4f4472897d97ef13b0567a9b84f Mon Sep 17 00:00:00 2001 From: Martin Eyben Date: Wed, 27 Nov 2024 20:28:34 +0000 Subject: [PATCH 21/29] fix: typo --- src/main/java/eirb/pg203/OpenMeteo.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/eirb/pg203/OpenMeteo.java b/src/main/java/eirb/pg203/OpenMeteo.java index 6191992..0031692 100644 --- a/src/main/java/eirb/pg203/OpenMeteo.java +++ b/src/main/java/eirb/pg203/OpenMeteo.java @@ -46,7 +46,7 @@ public class OpenMeteo implements WeatherDataAPI { /** * return condition based on the WMOCode - * table can be find here : https://open-meteo.com/en/docs (at the end) + * table can be found here : https://open-meteo.com/en/docs (at the end) * @param WMOCode id code * @return weather condition */ From bc584b3ff03d4d7b14a5fdf3bb06f33f963cb363 Mon Sep 17 00:00:00 2001 From: Martin Eyben Date: Wed, 27 Nov 2024 21:03:25 +0000 Subject: [PATCH 22/29] feat: use WeatherFetching exception --- src/main/java/eirb/pg203/OpenMeteo.java | 35 +++++++++++------- src/main/java/eirb/pg203/OpenWeatherMap.java | 36 ++++++++++++------- src/main/java/eirb/pg203/WeatherAPI.java | 36 ++++++++++++------- src/main/java/eirb/pg203/WeatherDataAPI.java | 8 +++-- .../java/eirb/pg203/WeatherDisplayBasic.java | 7 +++- .../WeatherFetchingExceptionApi.java | 7 ++++ .../WeatherFetchingExceptionCityCoords.java | 7 ++++ 7 files changed, 96 insertions(+), 40 deletions(-) create mode 100644 src/main/java/eirb/pg203/exceptions/WeatherFetchingExceptionApi.java create mode 100644 src/main/java/eirb/pg203/exceptions/WeatherFetchingExceptionCityCoords.java diff --git a/src/main/java/eirb/pg203/OpenMeteo.java b/src/main/java/eirb/pg203/OpenMeteo.java index 0031692..75fa51f 100644 --- a/src/main/java/eirb/pg203/OpenMeteo.java +++ b/src/main/java/eirb/pg203/OpenMeteo.java @@ -1,5 +1,7 @@ package eirb.pg203; +import eirb.pg203.exceptions.WeatherFetchingException; +import eirb.pg203.exceptions.WeatherFetchingExceptionApi; import org.json.JSONObject; import eirb.pg203.utils.JSONFetcher; @@ -32,16 +34,25 @@ public class OpenMeteo implements WeatherDataAPI { // https://www.nodc.noaa.gov/archive/arc0021/0002199/1.1/data/0-data/HTML/WMO-CODE/WMO4677.HTM - private JSONObject fetchWeather(int days, City city) throws IOException { - URL url = URI.create( - String.format(Locale.ENGLISH, forecastBaseURL + "?latitude=%.2f&longitude=%.2f&forecast_days=%d&daily=" + dailyQuery, - city.getCityCoords().getLat(), - city.getCityCoords().getLon(), - days - ) - ).toURL(); + private JSONObject fetchWeather(int days, City city) throws WeatherFetchingException{ + URL url = null; + try { + url = URI.create( + String.format(Locale.ENGLISH, forecastBaseURL + "?latitude=%.2f&longitude=%.2f&forecast_days=%d&daily=" + dailyQuery, + city.getCityCoords().getLat(), + city.getCityCoords().getLon(), + days + ) + ).toURL(); + } catch (IOException e) { + throw new WeatherFetchingException("Impossible to get city coords"); + } - return JSONFetcher.fetch(url); + try { + return JSONFetcher.fetch(url); + } catch (IOException e) { + throw new WeatherFetchingExceptionApi(); + } } /** @@ -85,19 +96,19 @@ public class OpenMeteo implements WeatherDataAPI { * @param day Day, 0 ≤ day ≤ 14 */ @Override - public WeatherData getTemperature(int day, String city) throws IOException { + public WeatherData getTemperature(int day, String city) throws WeatherFetchingException { JSONObject result = fetchWeather(day + 1, new City(city)); return getWeatherDataFromForecast(result, day, city); } @Override - public WeatherData getTemperature(int day, int hour, String city) throws IOException{ + public WeatherData getTemperature(int day, int hour, String city) throws WeatherFetchingException{ return getTemperature(day, city); } @Override - public ArrayList getTemperatures(int days, String city) throws IOException { + public ArrayList getTemperatures(int days, String city) throws WeatherFetchingException{ JSONObject result = fetchWeather(days, new City(city)); ArrayList weatherDatas = new ArrayList<>(); diff --git a/src/main/java/eirb/pg203/OpenWeatherMap.java b/src/main/java/eirb/pg203/OpenWeatherMap.java index 333b77c..2e4e424 100644 --- a/src/main/java/eirb/pg203/OpenWeatherMap.java +++ b/src/main/java/eirb/pg203/OpenWeatherMap.java @@ -1,5 +1,8 @@ package eirb.pg203; +import eirb.pg203.exceptions.WeatherFetchingException; +import eirb.pg203.exceptions.WeatherFetchingExceptionApi; +import eirb.pg203.exceptions.WeatherFetchingExceptionCityCoords; import org.json.JSONObject; import org.json.JSONArray; @@ -30,16 +33,25 @@ public class OpenWeatherMap implements WeatherDataAPI { this.APIKey = APIKey; } - private JSONObject fetchWeather(City city) throws IOException { - URL url = URI.create( - String.format(Locale.ENGLISH, forecastBaseURL + "?appid=%s&lat=%.2f&lon=%.2f&units=metric", - APIKey, - city.getCityCoords().getLat(), - city.getCityCoords().getLon() - ) - ).toURL(); + private JSONObject fetchWeather(City city) throws WeatherFetchingException { + URL url = null; + try { + url = URI.create( + String.format(Locale.ENGLISH, forecastBaseURL + "?appid=%s&lat=%.2f&lon=%.2f&units=metric", + APIKey, + city.getCityCoords().getLat(), + city.getCityCoords().getLon() + ) + ).toURL(); + } catch (IOException e) { + throw new WeatherFetchingExceptionCityCoords(); + } - return JSONFetcher.fetch(url); + try { + return JSONFetcher.fetch(url); + } catch (IOException e) { + throw new WeatherFetchingExceptionApi(); + } } private WeatherData getWeatherDataFromForecast(JSONObject response, int day, String city) { @@ -103,19 +115,19 @@ public class OpenWeatherMap implements WeatherDataAPI { * @param day Day, 0 ≤ day ≤ 14 */ @Override - public WeatherData getTemperature(int day, String city) throws IOException { + public WeatherData getTemperature(int day, String city) throws WeatherFetchingException{ JSONObject result = fetchWeather(new City(city)); return getWeatherDataFromForecast(result, day, city); } @Override - public WeatherData getTemperature(int day, int hour, String city) throws IOException{ + public WeatherData getTemperature(int day, int hour, String city) throws WeatherFetchingException{ return getTemperature(day, city); } @Override - public ArrayList getTemperatures(int days, String city) throws IOException { + public ArrayList getTemperatures(int days, String city) throws WeatherFetchingException{ JSONObject result = fetchWeather(new City(city)); ArrayList weatherDatas = new ArrayList<>(); diff --git a/src/main/java/eirb/pg203/WeatherAPI.java b/src/main/java/eirb/pg203/WeatherAPI.java index 3431c86..570b169 100644 --- a/src/main/java/eirb/pg203/WeatherAPI.java +++ b/src/main/java/eirb/pg203/WeatherAPI.java @@ -1,5 +1,7 @@ package eirb.pg203; +import eirb.pg203.exceptions.WeatherFetchingException; +import eirb.pg203.exceptions.WeatherFetchingExceptionApi; import org.json.JSONArray; import org.json.JSONObject; @@ -7,6 +9,7 @@ import eirb.pg203.WeatherData.Condition; import eirb.pg203.utils.JSONFetcher; import java.io.IOException; +import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.time.Instant; @@ -25,16 +28,25 @@ public class WeatherAPI implements WeatherDataAPI{ this.weatherAPIKey = weatherAPIKey; } - private JSONObject fetchWeather(int days, String city) throws IOException { - URL url = URI.create( - String.format(Locale.ENGLISH, forecastBaseURL + "?key=%s&q=%s&days=%d", - this.weatherAPIKey, - city, - days - ) - ).toURL(); + private JSONObject fetchWeather(int days, String city) throws WeatherFetchingException { + URL url = null; + try { + url = URI.create( + String.format(Locale.ENGLISH, forecastBaseURL + "?key=%s&q=%s&days=%d", + this.weatherAPIKey, + city, + days + ) + ).toURL(); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } - return JSONFetcher.fetch(url); + try { + return JSONFetcher.fetch(url); + } catch (IOException e) { + throw new WeatherFetchingExceptionApi(); + } } private static WeatherData.Condition getConditionFromString(String str) { @@ -82,18 +94,18 @@ public class WeatherAPI implements WeatherDataAPI{ * @param day Day, 0 ≤ day ≤ 14 */ @Override - public WeatherData getTemperature(int day, String city) throws IOException { + public WeatherData getTemperature(int day, String city) throws WeatherFetchingException{ JSONObject result = fetchWeather(day+1, city); return getWeatherDataFromForecast(result, day, city); } @Override - public WeatherData getTemperature(int day, int hour, String city) throws IOException{ + public WeatherData getTemperature(int day, int hour, String city) throws WeatherFetchingException{ return getTemperature(day, city); } @Override - public ArrayList getTemperatures(int days, String city) throws IOException { + public ArrayList getTemperatures(int days, String city) throws WeatherFetchingException{ JSONObject result = fetchWeather(days, city); ArrayList weatherDatas = new ArrayList<>(); diff --git a/src/main/java/eirb/pg203/WeatherDataAPI.java b/src/main/java/eirb/pg203/WeatherDataAPI.java index c91c416..3f9ec02 100644 --- a/src/main/java/eirb/pg203/WeatherDataAPI.java +++ b/src/main/java/eirb/pg203/WeatherDataAPI.java @@ -1,5 +1,7 @@ package eirb.pg203; +import eirb.pg203.exceptions.WeatherFetchingException; + import java.io.IOException; import java.util.ArrayList; @@ -15,7 +17,7 @@ public interface WeatherDataAPI { * @return Temperature of the day from the city * @throws IOException when request failed */ - WeatherData getTemperature(int day, String city) throws IOException; + WeatherData getTemperature(int day, String city) throws WeatherFetchingException; /** * Get WeatherData for a specific day @@ -25,7 +27,7 @@ public interface WeatherDataAPI { * @return Temperature of the day for a hour from the city * @throws IOException when request failed */ - WeatherData getTemperature(int day, int hour, String city) throws IOException; + WeatherData getTemperature(int day, int hour, String city) throws WeatherFetchingException; /** * Fetch the temperature for multiple day since today @@ -34,7 +36,7 @@ public interface WeatherDataAPI { * @return List of WeatherData * @throws IOException when request failed */ - ArrayList getTemperatures(int days, String city) throws IOException; + ArrayList getTemperatures(int days, String city) throws WeatherFetchingException; /*** * Name of the API diff --git a/src/main/java/eirb/pg203/WeatherDisplayBasic.java b/src/main/java/eirb/pg203/WeatherDisplayBasic.java index dddc38c..887a27f 100644 --- a/src/main/java/eirb/pg203/WeatherDisplayBasic.java +++ b/src/main/java/eirb/pg203/WeatherDisplayBasic.java @@ -1,5 +1,7 @@ package eirb.pg203; +import eirb.pg203.exceptions.WeatherFetchingException; + import java.util.ArrayList; import java.util.HashMap; @@ -149,7 +151,10 @@ class WeatherDisplayBasic implements WeatherDisplay { for (WeatherDataAPI w: apis) { try { weatherDatasMap.put(w, w.getTemperatures(days, city)); - } catch (Exception e) { + } catch (WeatherFetchingException e) { + System.err.println(w.getAPIName() + " failed to fetch meteo"); + } + catch (Exception e) { System.err.println(e); } } diff --git a/src/main/java/eirb/pg203/exceptions/WeatherFetchingExceptionApi.java b/src/main/java/eirb/pg203/exceptions/WeatherFetchingExceptionApi.java new file mode 100644 index 0000000..c6657e0 --- /dev/null +++ b/src/main/java/eirb/pg203/exceptions/WeatherFetchingExceptionApi.java @@ -0,0 +1,7 @@ +package eirb.pg203.exceptions; + +public class WeatherFetchingExceptionApi extends WeatherFetchingException{ + public WeatherFetchingExceptionApi() { + super("An error occurred during API fetching"); + } +} diff --git a/src/main/java/eirb/pg203/exceptions/WeatherFetchingExceptionCityCoords.java b/src/main/java/eirb/pg203/exceptions/WeatherFetchingExceptionCityCoords.java new file mode 100644 index 0000000..75f472c --- /dev/null +++ b/src/main/java/eirb/pg203/exceptions/WeatherFetchingExceptionCityCoords.java @@ -0,0 +1,7 @@ +package eirb.pg203.exceptions; + +public class WeatherFetchingExceptionCityCoords extends WeatherFetchingException{ + public WeatherFetchingExceptionCityCoords() { + super("Impossible to get city coords"); + } +} From 57081c4f43f9195b05a4a819458f7ac787b4c1b2 Mon Sep 17 00:00:00 2001 From: Martin Eyben Date: Wed, 27 Nov 2024 21:05:48 +0000 Subject: [PATCH 23/29] fix tests --- src/test/java/eirb/pg203/WeatherDataAPITest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/java/eirb/pg203/WeatherDataAPITest.java b/src/test/java/eirb/pg203/WeatherDataAPITest.java index 1c48fa9..738b262 100644 --- a/src/test/java/eirb/pg203/WeatherDataAPITest.java +++ b/src/test/java/eirb/pg203/WeatherDataAPITest.java @@ -1,5 +1,6 @@ package eirb.pg203; +import eirb.pg203.exceptions.WeatherFetchingException; import eirb.pg203.fakeJSONFetcher.FakeJSONFetcherOpenMeteo; import eirb.pg203.fakeJSONFetcher.FakeJSONFetcherOpenWeatherMap; import eirb.pg203.fakeJSONFetcher.FakeJSONFetcherWeatherAPI; @@ -77,7 +78,7 @@ public class WeatherDataAPITest { @ParameterizedTest(name="[{0}] Temperature fetch at Bordeaux D+{1}") @MethodSource - public void testGetTemperature(WeatherDataAPI weatherDataAPI, int day, float expectedTemp, WeatherData.Condition expectedCond, float expectedWindSpeed, float expectedWindAngle) throws IOException { + public void testGetTemperature(WeatherDataAPI weatherDataAPI, int day, float expectedTemp, WeatherData.Condition expectedCond, float expectedWindSpeed, float expectedWindAngle) throws WeatherFetchingException { String city = "Bordeaux"; WeatherData weatherData; weatherData = weatherDataAPI.getTemperature(day, city); @@ -116,7 +117,7 @@ public class WeatherDataAPITest { } @ParameterizedTest(name = "[{0}] Fetch temperatures for {1} days") @MethodSource - public void testGetTemperatures(WeatherDataAPI weatherDataAPI, int days, float[] expectedTemperatures, WeatherData.Condition[] expectedConditions, float[] expectedWindSpeed, float[] expectedWindDirection) throws IOException { + public void testGetTemperatures(WeatherDataAPI weatherDataAPI, int days, float[] expectedTemperatures, WeatherData.Condition[] expectedConditions, float[] expectedWindSpeed, float[] expectedWindDirection) throws WeatherFetchingException{ String city = "Bordeaux"; ArrayList weatherDatas; From d2175120d47045e27fbe57b0abf67a9df1d2ec50 Mon Sep 17 00:00:00 2001 From: Martin Eyben Date: Fri, 29 Nov 2024 09:03:43 +0000 Subject: [PATCH 24/29] fix: documentation --- src/main/java/eirb/pg203/WeatherDataAPI.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/eirb/pg203/WeatherDataAPI.java b/src/main/java/eirb/pg203/WeatherDataAPI.java index 3f9ec02..e9bc5c6 100644 --- a/src/main/java/eirb/pg203/WeatherDataAPI.java +++ b/src/main/java/eirb/pg203/WeatherDataAPI.java @@ -15,7 +15,7 @@ public interface WeatherDataAPI { * @param day Since D+0 * @param city Localisation * @return Temperature of the day from the city - * @throws IOException when request failed + * @throws WeatherFetchingException when request failed */ WeatherData getTemperature(int day, String city) throws WeatherFetchingException; @@ -25,7 +25,7 @@ public interface WeatherDataAPI { * @param hour Since H+0 * @param city Localisation * @return Temperature of the day for a hour from the city - * @throws IOException when request failed + * @throws WeatherFetchingException when request failed */ WeatherData getTemperature(int day, int hour, String city) throws WeatherFetchingException; @@ -34,7 +34,7 @@ public interface WeatherDataAPI { * @param days number of days to fetch * @param city name of te city * @return List of WeatherData - * @throws IOException when request failed + * @throws WeatherFetchingException when request failed */ ArrayList getTemperatures(int days, String city) throws WeatherFetchingException; From d446f4c4464cc15b57d1dbfd519c78e13c33dc71 Mon Sep 17 00:00:00 2001 From: Martin Eyben Date: Fri, 29 Nov 2024 09:04:38 +0000 Subject: [PATCH 25/29] feat: class diagram --- weather-app.png | Bin 0 -> 162563 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 weather-app.png diff --git a/weather-app.png b/weather-app.png new file mode 100644 index 0000000000000000000000000000000000000000..c1756d9b28fa6586e9eb3dc38b86c2eaa877ecfa GIT binary patch literal 162563 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

sp6d1)mjt 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

#$*hA-hkE1$)<>x9vBYf?R_ z2cY{*N=`0+G7bgaArd7D(KZyXii?vf(c)MZr=4Z|h=qLKz>aVN<6f((}$(fdBgBeqRO1yqnvIFmLTvpyYl~;K9 z9aSOG?v9-A!59Y=TL@~|xM`DZcb?G3GOu#|A>g)6KA>A$-OY7$nthF#C$8{xbAsk3#%NxaVgsS`2B~ah~>*vUb;9aB1k2#^nv-} zYv1el`aqL-r%H+lWXo)&Y%Yqjd9K`cFe_7<>@{Vdi;c4OmDQqx+o#p)j5Wpry2cz( zRo3_Oon-0vC@;D=6#A<=lrN;@v+v*ag}~IzVX7oIu`KDCst+UYzbA_D^IzNgYVz#E zqMcU6*h;TiNXr@0tWZ0m5SpxceQGfn+%E1=M3R>mk_ehF@}%M1QYeyEst?_F{WE`b zKU89-FC1CpE3RH#vy&^OVB?5j#&sNMzfs3kow=1fR*hL2R6Wayyt@bYPLq7AOxP9a zY|1d6R&|a(Q~;TG4j%waL-_&e9)C(f6pjYi4VZal<;gm~Nfjnvq(38d`qZgwp;iLk z92ZtApew68O;siK>FCf4t5a06*XL|0*yQsufNb$zQ2Ck=e}9WTE(=0Jh1C-tN&a2u ztwxL=)P1#y^D(W;OLyLUJS0W7xU}wTPnbGcF^kqn%0ZO;sscP`OQ)|08PG|_92|af zfmS@jp)mB9*^+m|z6}8ZUt@Y*b+tH4HW_gzy}Mp>+JBXw6H+zgBuD|S&*?nvnX~db zG~E_C*A7tSZCF0`03x!nuH0R@laShzr?i2M8RhA@gP{rF$w^~2+=8%^A|fPF_d$J) z-5$7xLD*_(QDz(E;M}*Mp@~r-`$3h<1S)vLC>~0IP9C}d4h{=U2CTC7-@+uihGZzX z0RtbD*kdGZ*I9EOB7tz3&f@yS;90CqQBi93Ys*)LF}V@4sm?2l;JBcdm<B{K(ArGnD74tW{23GPyL*4*JLW(Y{-NY@cagkBV>()8 z@D-GR6eVLj@+wiL7XzCDT~j^^WTXJ|q}bU-&^5(~va+o$i9 zyic#MBrhv`u_m9~B3*PYBISy*vsTt^Yya{hKaZ2&e`65J)$SWU%sSY8AD@`*VInnp zxGzxMJ}IA7i3(PE`5I-D7nl4tS2hZqEc3k1LV5CP_SFNgTZ+fOwtn!aimTA-xm@P8 zSnEYXN~&xkD%LTPLYV1M{5^w?BhkQ zdXC=6gn4l3oGQL$qBJq7C7}9vd3}99JYu+95(CFsdHpu`jjp};ocyz+RAu?e ztHP3Jdlrl5dQMbtOMjHcd^zc|gs$y%m=%m2>z3s7>+6%Fs03Co8YPs%ZA2cXRSX$s z{7^P;9x7hBWYTY4vLB*8AEv>R8kFd^a9U8eh{=#;r4^WH(gUWj!R}OKH9p_D!I^H~;Y8+sal9@k{Ja!dWV>6Oi5 zfJ5r73zIbwx%$l;eclF(H>a(wE1s8n&OdXDQaMhg(oXIcGmC=p(pXjA`Az>2Hw9J~ z{gOBKKdL%+7iNJ9O^a0C4HXZb>^yhrm599KDNr+_OP|!_kXSDuu$gy_JXK$P<=mS@ z;f`wKMuaPTfpX)*gqax!8VP{eT3#0*nV5?Z?T(9*DEqY9!s#Bd8JrjJqN#4^2|Gya;?gq~4&$mF zS9nrhu+7L_3=h-!oN?4$M@qsp!sy3^$kCE02gblY`u>e)mqjbQwK@im&X(8BBNR;GU64qwl%1HfFD!95Y2xXehPl{f@T+n{1wC zca@zZ3<5+qip48>!sS6rn2?Bo%;XhaA3{7rKf19v%g797Q(jNGuY`z0ZT=6fev1 z^Ka7MFpKernx_ox1o-X<9ke^ayM~&UeqGm^*67RqZ;Is8@2BftT~-zqEwy!M>PP#Y zAk9Q$d>S=j=V2t(Xg73rS24^?rtV$Z#C;=$lWHUIZG&z8WXgG25^QK#dsEZB11{#|@YS@~MV5m_}JX|)p8jy5Zu+>T-upFZoY zuT1mL^?@G#(pLCYaVb+{`)!S|@A1ovsq))qu7*1y%chA@Pvub!-uGIMz< z#B1Q5GboMlCz9BjD0xU@)hG}<79E)0%-1DD7cT8@yeWOq_rp__@cZ)7^9i&BW<{@U ze_Z1VvSTlMdMfGLkVddr9TpXdC7KxB3m;BkhsMYY$ez;gw~b??pRc}Z$o$vNw6d}y zG_Bh1zrV)PbpN56$W74UoN#3?>Zx4Mi4@0@%k6D#4Y7L|k8~(t2uuBsYM`2#9&7FFV4MYo<9PS96votWtws^qZt>~CJXS-&VUy^5f zZ=qYU3}<0z#gnd&EiH^Pp98q;-UcL|xxMp4GX+La7|7lp)0Ik{X4Gx?qF{TnDZhE9 zknCNa_Kk^SRvzQZPtPBd-6ZwKE_{Q5MCGZdQ^)=OhR}%XG%zveze8w9p5VD^L|zkV z?>I#$I(|-lcJ(^jbyB*EYx#9^1KS?=X>KNML9-#CYYHwSp^L*scTs(5M@t^iyg-sQ zu9OG$_hii11@*=ghA&AQUMJV{hpfxK{7Dw0NDn_sPxq!lM5DhY&lA$N7R3B4-0Kxj zFNuto_t2l+bEA(Mf}@)GGV-m=*+RWre9u>|XRjAtObdA;{g5AYOBr(xN;br$o= zh6e_eHZS&K!V;ol810+h`idc*mGe9t;-*&0&wayL-tCalu)CBE<`Fn2I@j-38mg)d z_hrM_RN?@Es|+Cp);|UE0DAxCpz-%O>so;DtjnOGPm zScLO7%?qp)><6gEXEd~TE-EPv0W(*1+{fU%HMJ~u%WRpa+t{O2@P^e~c?QMZC|wZ= zIL{0vh8XhxcVFc*Fg7;Eh*+RTI5@NPx2?+|pUydKIhEB@Z`M@DVX=Fcz7&*g#>a(5 zu7x~NqJHpX6>xq!N)&Y7Hj_zGtb+H@mEMl+nzc18a?^F~jjDBY>ZFr`BdMIzdPJ6U z^ZTUaG=Y0g2hRJZoop&>Zb(i)uGDko!X{8M%yN%s+4Kt~J{XL=Fx$zH5}L8$-_++m zqFYq0F*uZNGVX53l{dbAy7#@@L0j8Vz*}$D0& z8*=;|B&z!yif~z>jl?jw_sy2Id<#ku_xK&4Q@ok4ZuIvbd zqP3|P=8>W}It{^kPAARb{`rYxD%&s6zDwNDp4lxdpfK`ZxH~F!`%AYFOpWI1=A&EG zNCVimEx`5edndH|YuA&3tKBJyQXf2+8Gj#g zj;7l>5Kpd>%MgxQk;X`|WGKI9B1wy||L1ONuo)pk!h!T83_WOr3Xs5QV>5z}Y9x08 zFz}aOo#Km6g#dfhdMZQ@B{1r6{<mFUqJ1qWR?}>s z-n-^UR-VJ&x3%CAfa<+>#}G}pOA4W2IAe0|s|I#R+k5Y?n7ld$l$IdGJ&Z95c>8wE z9eM6IdKBh9oELlxd1$n>g9jKwH$89pgqq%)oy6P{-|wLnbJ}0M6Dd5&8Z(;OGM+L> zu2~1aM&v_|jYs%a)OhWG$@wM&r4-P){@#}9me@0iVI>GXxf;BlEX9q|-DZ-DFVWE_DmLeQ{OG_#z47$R$~ynu;IZ!Jz(pFWL;dcAnQhUKghVH58A z`5kh_;>h5?fY42h*_UTVbg@QZNN31)NZBn}Te|@fmGeJ;Vh%5@@TXbqvCw5A;tO{l z;>08*BsNemQE%OsXwD$wtR3H*ZoKSb(MFxT3&YcO@ zx|#~{upkI$AVWuLjd3v^+k}so$U~Bc3wGZ@;OZ2Vwje1<0_W@Kp26W^a7HaULsym-v?4{XbtJuooT0v! z?UFrMEu8pp%35AX*J)G<-IhRs@y3w9XBwB`atbzz4gF8cnTZ=_6$l^_M# zC{{H`#K}tXhlCTgVj=z$@z(%n#U(5SWK2QT1ohyh&oz{Ij4yhxnAC-ahYyaAo9OB) z=leTY{c=7Jn8Tm1jUr4%1Yfaksp&4Xx|nm1Eu^$m6-SZKf<_bhy-*BJVl=&|@`NM^|Z-fOTM~_R*o<&-j+tO|1ebs#YS>KXYe+R8^023Hj^1Ys+Eyes&!U3I9 zS@f#0kyX*t4gWii^`@Db?M!l1jDm(7=2?jWk9qC3F-03CJ~BEkM0qaNtK}-ke%3$Q zS-BNmfbiN?%_l*oyCFvl0% z3jvR$|H5z%PIgM>c2-9{`%ew$o=ZP6+&UjaiZi&^zUG|yA@Gw-AnoJ(@i?oTjie}qCQslt;p@x76nMDhMa1f z01qkOtJWvp@uR_V{|@tPD<1@V5t2AJlpE0hd@4QZ2?Gt1{y^S0>nU2XFnm}QV&+~G zvg+2HI?3{dAeEp#92FyX!~K!|^y$;Z-~k{T$_0II)m1t%?n+BD^Asl|K{-?qv>9D3 z#UjRKYoo;2zyBwAdfQjH{s;|!MMcl$IkTkjwQU<;$wOJ8;In@BDY3?VG6q&QV|+&* z;fBm^PK<r@ntr{*O0WZ~zt>5zkR}Z6aDk8-6D!@z$SN zd#_rKS;oixFXio;B6*b6hZ}U4C$r)L=F+02$`ihYek}`gcu8IEApCRK;LWass1PkJ z`w%*T6bZ+kqWa@WU7wCKDY1mE6X_OI(=(MQzdseD@H}6|_RS?nsS&<7vWqkNWyW+K z@j_2VeQn~_4^5TrJK^sWyeTZQH|E{(%v~Q+w^i_K^j=&tD0Fjs`LugDaq^CZ5mP^x z`+e2ro$A#0EZnvH+H>%_&c*mMuIyi@8^Et*Y+HyaXZPI7l?6%mvRD}sB4h;^Ah0^k zG;`Np*Vvo#(C=-^qekIH;YXw>C)!cA!p{z=i2@SQp}r@E#Y?zH;3XJEC~uAE3-aZ+ zcz8Xq<5ARR)-nHj+t0lB&zkw$TkMwIJH8$)hW90I)Ceokh5!c#hm@4Tx5)1&pGv*R ziZUAG{u(1&wbkx_IC~4Itha7&6cw;gN>V{U0Ragqr9@H$2}$YhkPxJ#ltuw*6p)68 z?v@aw8w8}gl@`f6|KPjNd(L_9cklSd*kkN5wr=Bj)>?DUU(J`YyqATf)hLUfjIeM0 zcfbQ(z|3B^-VVamDhX`%6V^ z|3OS7$~5iSEqLHd9#pPE7GpQXC^SDJ!os=w(XQrywV=aQkOYBfL2p3lk3cZ+#rUds zcfm+2#0HUerzqb#EXCc}zxZFMN%R8)Ex>rsaJuX*(njY4&H&=dt{!;mjr_#DK$CK( z6ms5l{GG^IwA=y%vHgt+>wA6TQE_5*F9vfdVIeG-9FvjryL{stA1CJyj5(H|Le;Uq z|Ay$<>tAjFN!GkH=}Bsa91)`ys6udV((D14@|@KAWe-9^idgN>o6t^qym{oI#VUg|;mI5O>*Ubjb^%meUMj)#YfzRr8nM-`=fL{}t%>_x(+v*#^; z#0#AdcXyF6gUfxlffD_9%r{AGa(f3f%2PqHmWbQqEhs%82Dv@QR!8|NA!NEQF-1w(~aetUvx1e!5!BF=yU^MEl$`5i_1J2w$Y5xJ+z8L7cLCZbq%EAfm3Qh37Tf@~0gylD&zyyqY z%@xw2lX5cz>vtfai}L!-y9vx!S!*sofqfMqL_KqMYjrq6WMm`f3N)WjrKNxW{*4W4 zT`a7($B^Mc2?F_LXo+~`FE_wIx!cCp*>wQvpi+=wKFVdcLM*9=knAnK@_{HW$s25W zRWt(YFa_jaTewvzj)WM$zWWY~CH-?2%#Iu|xIA?#ZPAIwSZ9aVnMhngQL(NcI;(IU zRvp!xO#mYyCGk8uIt;dtsKMm1B_iL;bLnXxWc20b2qKjmB%q#DC)pKg-9Y&YXgMNO zSr<1C!2{=Amcq+6nXt*_v$IjK1^Y&ooUK5Rz#|TZmG7A-P8u2mNZS&m@}P)qjEd3n zmdRSAiO&EU;LN|EpT?4A_cvSEd?6y8C#Vdl9TqV#pox)(>I<)l=e3>3uVk<*3u;xb?Cab|z=2W; zn#h1ZQJa=iaIDuymYzxSde?7a&588vn^{Sw@)Dbt&%!&NX2ECLQ%)do`<1EJozQ9P zc6;kGB|dE5>wDC+>mqZqd*FKp+Z77kB`QW z@!=Hb;T)LxX!Bu<)$hPV{Z{iA?8BIaNnvpv;0ZJ@IY2ZZ#nA?qFoe^B6N9N+*IvN8 z7jY_X_sM}Wh_!HGYiRceP-^Ie2LBrPxoriY{v888?xj=+h92-pg-4B4OYDKn&n=k< zw9*a`&VB^3yIos=h9R+N1eC?&XX0Q_O=QvMB?wg7h_DAadCfy#07Y*3d6F5ueRi=% zoDn3uOwe0Mg^{GVKzXP!bE?HWlqGwH^;RvlrE8W0EBWD{nGiVG;C0=C4MP#*xz&#V zhSAcZIKJMSDX3tMde6eXSu4XP%=cdSo;kab3hgEHbC794Gs3*`5=5n4R*p|&G&eGF z5|k83=eoBVWLVzGZ(vatV;}MTm#`HJpTX$P^_S;wcOSg_5-;YQvprQib7uW>Dhp1~ z71EZD4zGA5R}NYRWx9U$-tjuKaB_Z^PaQ&-PW2=^DsLJMuI8~j$WJMkrh+W}-u=C8 z=LG=~XEjo!63mzrjKsKJ* ztMMwm?fAI3NcgTJmUJCVgfK_B0vOMZ(Y6M2RIhGWdWQAork^}ZDM9U)0A$ymq28yR z{6hkvr;geh{ATJ0HIY}PdWL1E1j97Fa_F*c4g+Rjtn@gknbZY z_Lj(g$IzR3=*SFjuK6E4uJqe%)30kdMddz8_saI24W=e6o0jxBi2WoBduzH}jRw(0 zsKYL?4KvtnCvJ@3HSqS?TX3Y&#Kzli1Vt^T9|bc|jK9hRB=vO!$3s=MUfpWG)W}g^}2hm`=K9hJ@tdK zl;h6h18iO~*GEX3R&O;yu#R+SrKKvf=NsR|UAdPzf5vwZ5@`^HF(1j;9L$WD6|dxp z3~(6tpuY!4RvcI;LCBse3Te#6p zO`teKA`E!dZ*yYbnz?yT2VLy!HmpZ>3#JIg06`UB?sM<~foLZ(G_)^6i8tTEPuc`FqW6RZ6d6BEcwkZQ}H?UMccJjf18O&5p4 z91xE$e~pm^Y<8M;y;kdg)MSgoQrD1>LBT2oaE^>X+4R7;3?*;zcfb50HD@R8^VgZ2 zbqZ;y_S{=QGH9idB#=MuFYNwote$NC8Kw%vbD4quR8di!gTcPtl)bHMON2U7gI?P! z^LgG`J*VZY5{Ol7jN z_YvM}8I8Nj*j8T(n@v)SoOdU^;o-drqTOd(Drv$sKxI65=-{Km`}h}KtB`ctI~Ts2 zdyt`sC%fCuiA+%1eUQaOqUHXPRsYg+hq}gqWo!rpfldPJ@@3r@4}ECMU}2uKGjHW3 zKO$a2VYef%{C4a6ho&gb&_)*8JdQJsB_;sx z0}#kUY8d?FaS)2Jf@4!BtuWQv55;YyOuTrh?4i%Zt#NQx)M3 zgS+_+^uC1>fCsQO#Ju8aAq}aJwqb3<_KtTnzCxe3-TUavM|nx3q#i8;r-p17`B>G2 zNN@U1#ta$H$$GYH(&VgceEq!E7&FSkr8IiMTk79;p@Pzi?#aLV{N<=zy_fODQzPz- zvTgQ);A)dBb@F!gMAFLwNdVeB@5SoAlrH;Tl_OE*=Pd09ly&&f&kWDWf4`FqmA4<< z`g{yJh6NzYxgq5Ji`F>7&+cAF+cV2726#_k9Nd%71ui|u0R8lT z7a}*;3z0>v{Pw90*KIc+%sqppWwPmf3MmHRuap3Q$BIp{^%oet$cOO_2rHAmI|&3{ zk8#k%0oYOA9_VzQi~oJR6l6d#!+oWSR@l1Gy}LnNAZPMJN_7qJmNi}a5lx5T@oLxk z2)XMLMKIOti&&7cPeL+i1uuDZNRHbJ?S7O^-G}sYVj>-S~`&LjiO) zrh30AQ-u$$;Z-Z=4Ws($dp6R_|I&f|qtQLNI`$&^&3<|M=RuITp*wWM03;#Pjq%5!A ziJHYTvN_W1EX{Q9I{8v7e0+)V@mbMbZ0tDvXrr3$7yw(D1ICo~dT{jy!$+er?qL3$})K1;rL z*mjTWhH#cf*q~jiyYv0@=q|D3+Mhd@_`WYRCe4SB@MB-$EBQUkDaf&|w?32VHyUd~ zUUn9xTz{X^KWQ=lo1wocH{WZt?=M@gS6FFWF~+bgT(b*nlfv?pi|g z4|wVJ>hOJfdK6G8L55KAwq(I!75S*ItoKx%+^4wnk@5TwSf1de_^9drnEjhU7wik~ zH63Qdz-xtcM*)v;h*8uk+U#c^rNgM4FB{$FA(BLLp-f8X*TrBgi7zf`l+@HBy_!1y ziy%(mW)S>Yf;IbNA1j^Rhh{rKMa#5=`oCwqo;n|7yyrEho?FA&%Hzvk1SOM401q>f5hpA|Jk} z2m{V&@gsnlpQnRd1vcKWr)5B-E+~pXSLWIBk;7C`T|Mi*Er-P!F4nJISOWvh4?bfZ zhJYB{9=@){JO>AME)sUjx5vtFpsABPexq@6(u-A7#kGbcO$7RB)N2joVY+X*C|Tdo z5cb`AvJN9!6G!ydUDm}5tY)ni?UlsztLdK-gh#EyLKJ0URyc_;8@e2lH!97C@-ccK z3R$|;XZ0V!cJ9gZ|B?&EPSbgVJmjlD-;#o_`76?mTdw z6|2fdEXAwL`zKweBsKbRwwnpzx?~fTae3xCf(J13p_!I1{Socvy(AygKkm3U)Re7a z6!F+*V4=~2s5#%jM@8(N^$3dO>ufBc)t?NEXCP*MR*imU5paKzWa;8cojj)y-hqRhk6r+z?uq;dFZ}9RHcQxvMuHR>B#EM_wK+_Ywf+CjLh| zPdL-EDl66Q3_c zHHykj!H`Q+SZ9RpCRW9@%^6%L9p`%F@bKq%%hShRckuK;cgJ^zLy5|}<|ob> zRNvY80>U-~UHn9EyL^EJMLIfT*bKN5%A6KuaCU`|=C{9qSvAx7&ii{&Bndw`TA8Pt zql6y(k@Q31c6K|p{7bJM>qx(NM2Jj87qsd*k$>|N+ndoo2OR34OW5z8jJL+JisEdgS0B! zI<46f706R9fAMaK{=Xey<3aS`x7|GcPq{s?hON^K!gjzLknr=tvj?Y7^j8g;$W1@T zv40a7zknp2NrXxY$)KS1ALNu9c@mK*uDj7T36Jy^D&A2+hll;j;HciwyAq{^ zJW+nqPR3`QLPDKciMhVC{yi`&fO6l?$7osOWBVG@g+wFj*l7sLiaRfBkuTU&v2@N7biQvnhhnhNn3 zE0@o=vB17;H#3NRxvL6ML1t(R1?vXb=Q2G3GIviFH&Z-Q!^8-L6|A=;FUW^n1GWWZ za}PV&)rrviyF`pC*T~4Od>4Tw<G@rU2Rpaiylazgy_Fx6WShdPl>ywNNym zq07Z_j`F$$z0g3mcKRYS3z)G1nO>)}V-$+^{&gYH;Gq$_P{dyeknLmPg56D+I|z~kynZ`vkK8-417|ko*l)=R|2m`Vkfs33 zfqWtqdrb*q!9~8_oQT)?gHIoTxU%k?c_B?%*{S*Y$G=OE*-IGhA4i2QPc4dLv~rjb z&6FE5fG8;^!AF=x2dyRGY1pvNkg)JuUw;J#07@-b1AWp-viYvoK#pq}K^iMQ*h}X1 zMk*b8frteeNMb6Ge*pKT9-Xfm%wO)wj`b~;dRJquN+{2GjZ!L2CFpU8oP`I~g~V5c zQ7_R+if3ioPoCK5_nT`{Dgw6o!y~yw*Tx(;fR6!~X04jQ;e?L4pm_ae-fQ3`{+*1m zqE&xS|E`%Ws&U5hLgWypwGnC4kuI=9woBmn)EAH(dd%p7GE-6uJMn1_LG0#UMWo#8~mT{d5g4K(< zm;&dry8}HbK5(?-6a|samMXAqBqdxBVGqNnD_H6md^9N1!;j}comQSmqNcO;p=LUW zFsYiVbUg!H@ zZpM)GNx$F``SqXc=;r;z?H!C6zA0uW3hi8YN1RMtpNZ=)EsdV_*q74x!nZvXW*yUX zT%=~nkdEQTQ2fJxv>sS}xcUM(o8{hA>?`!F%-FnI99#}GPa|<~aq~>ipquroa0NB)G=F<0DJAh67tt)JV9%A3wg$e~xNL@(_clzU-gi`A;+ck-I zAjeB|xo8^=|6ZNgV7@4INvLhL)%5M;GsfUO!wVvt|8WL}OS!tVv;=_+?4`%vW`LZd zE8;&lLSX5hq>G0ijW4}O17r0=(DVkZf~MF0^}MUB-{X*X{g|SQiGdG)KxQ=1iSQa5 z?XFE~t>J(Cb0!K5{ofNrE(fK;3*JW1AhCgLH*YC6=|>^EoA6mg&}m&jg$8W=pbPzA zp4N!qz-Ou5o8=Ic>bmnrAc&EVZ*+Y8CKi3#rK8UF_LFYE$?F~cCWKqm7AI}ofL-)y z=LNIzu|_d}3z1bAU`Z&r`nIp0O$8DyGnAxN<|k5p(%8S^AcJr?XmYc9b~u zO7V!nraxPIwA;wPkt|=}F;FS`6-zPGfXkZ}8NyRe<{G?05tsNBCD{%ESprS1r=_NhYfuV^`KR-ugl@#~ z3Nywg{=U{wcjEGk^CtK4^;Ir^Nyj6ClqK?CeuHDMTkW|6a6bTqLombIhoIR(K2{QoI5_`B#PtubWPk~Z-r6#K2@-D34+g2KET8Fh2G89PoYW>sfuMWxU(cQV$ zv*PyrdZ{!7`VYE7K`~w9KmP8ArQHL!UMMcPSWr;k5H$d>A@?R)`N_4QGJ!08N#zm? z_2nG{(ChGZBgjq!}*_0hPn-v`}0AaJ@*yG-a!@eol1Ea{av z!12jnW$1RCCs+eA<6C3n8?m)Iv}15i3tp`i9pD6!38|rzeb4$87=38aF7vV?C_w~_k4SsQV^{w&42w*2H}s{?h%gYydaVM&m;*1 zvdK3}Ohk)DTBUC*%nG;s-``TO%&Q*J8stBQ5wWtOHTz~PR3b84FxBWNX~Z{Jo_P!NcLl6UW}Eszl>;b3h_2}Drk+=QaqvEFVr z9VWwo&4UPlt;pM*yG*}X-k3Fp5tDc*QV-Y|6LTRt;+H#B_es7g*CzI`1db3*exQg{ zPe*TQZH>p9?6neR7=~g#(F3f++m{JAzKze;Z-}^F9&X4Th;EisV)MQuOd#WHI2oJp zFS#z6P&IU|z;D%2Gu=Xp(!qNo5i>BOe1%B`Bmi6ce2{^knw-4ul+mF@ECm`*nIol- zOXu>%Y!gUyZu^F$6?2WcON6HMU}0DCfo}4I$koDr=V&Y@{FgEGra61=+$3L6ik_k2 zc?^tN9?G;rVEMrP0*YF^D)e9FmW$1_ae9<;@O%Il3o!Z?S$;~20U&-sb;kz&wEg{{ zxa$uzx^*F;@uX;}?+mF}4!00Tm)FOt*6^bC$Qmkn=WJvj^o%*4C=~Fcp)ngUa-Qe1Z6BiQ$k^@tWWa^!IlH>Ut<$eZC*$`uf3bx+SG6);EAO zn%I#~_rJqFcL%knzrFNI!{<$}8!<<1P;$?Mx9Xm7UJEz7x@GTw7JS~8h3Z0Bzev5m)pQ%n@}F?hefQe6Uk}IeUXZBPIA~V}L!p4%S3$SZ!+Z7J zG`ON5a)K&_%I6|^Z1Z#%-!=;im?g0v?ml{oym~<@K#KE#YJ`Sn4wPn77}s;hw*OmX zkpv=krj2txn{)neC{Tn*8a?P(0SS@j+wKFo&90@anD-|LxCD`iZ$N-E5RJZej=l;y zgV`z1{*A3_S%a+;LUeW<%*-!9TnPz85O}qP|Hg}IkwfSPQBSskPw;I6sHUqZs^a*j zQ-yU8Xwl&9!*vQ1lFHqFw2SBqH9^-6T`5Ftmq4smxLgjq!XarpPU8AmU?a|*JGUrp zCTA~_UPze{L69o^SK|GKiyxBwbC>z|7H&ne!a6+wW7L1ppQhmQAN2YO zTY27*vpwP;tvDtRnY?Fv24IMJ{u*Bj9|zuI{FQ^~_k$qzeGOeKWCeFY3I=v(Ct#Te z5FM>UgKmZ0X09PC3ppU6fc_J^T>SD-Mp+qPrVg-{+ACel>BnBOn@=X^e#;R%%pLkY zD^N`#VjCgDf*s$h{vu7X=*G_W{{4Ns?m^%wE3IpKs(G_$5L_#u^xD>{FP;~Zl#Jnb z{Svu!0cIxfaxmSy{fP)OFW_<`t^kh5?`z1teIOR|*sS&DPu2rFB_KEh0|QYX;6V3| zhk#qd^=H{UC>UzLq{I4}|B(ARhD@E;u7yGhcSOVhlm(C=T;!*&gs&X*eAr~%TWr_2 z5_t|fn@~Uc+38sfJ3c`-$qa8iK!}jaKBe#WI|{lqk02Ssd;uW{vkVpKG4*&qcFK`t-gZzTeWpH3Lxt%!&F2y+eDPJNDd-r1}V)S)l&5x%;A7u!&aTPc)r)8=!a0N16yE=ByVYGvP}qcySJ5YJ)Ia zAyWOkc-8UIK6_O{0*ooo-%I>WIyFkLeA1hZdgcy+6!7e3!KR5=2h@?sE8+wE z0^B{?Q9ySgj0cr(Qa4uK?W8B|I8X4{qA8c`d>Jbv$LV#)?Y*cV|0qRdMGH0ny?r^|0S1*`SR|})=fb6ue9Xf*XUZC(!z6WeJ^si!@flKA zpaTMt{9B3hS8seKCzidQcSSA(Kq~mp`(Vq3!Sp38UM9@D;_HRFLr?Fq1tT6#C+hM8 z5YMo`f)>z@KmaG?6y`#bM=ng^otoPr+3^4T@s8o;yd=8Q-wB*z3s%34H_4d^80B02=-MJDZ%I9^n)B- zSa}uMg4SP`tfb+f5BMD*j~#7o=<5rHA25R1qbbARvIE2NL`RfOc$6v6lc@1uh_51g zez&eSy)>+FaP=a;whKTuZdMQbwkdle33HG(WZ-392nOHCkDz&SY3MZyp*MUkBlGB7 z5d#AQjJWnYE6SwmIO1o@9M`9IE@t7v*N(4FQ2F1N+T?gnp8gfcD&kTVJ?JNeuJk0=>)1hz^UvVN24Abc5?JYlAP zzALGXI}CPKNzhh8n!pgrDO4Wr3?F9~hC^7XrsgssC*cNPHG2v(Oynov1&2N&?)44{UsiUu*Hu}Pnj?to{_C>U(DNew(BLZiI74A z*~VgW5L_P>8%s_|_^rH|s@R?XU=kbZc%{>CrV|*r^(UyGOKs+_@?lU&3k#z&7dQ4{ zfKLaJlWJL^3*4wXMJ9NYlamkb??T)mWbSGLpiRTf{0p1|YTw4epaO<~)1=umcViBy zm&j-0h=C+?ySA5^Xyj+A7RqGRiE`_x!zU+i_^;5}+*T!Ouf#PeB~b;1endOYh8`XU z{`K&mDcbppjO9MK=vI_t`6*7*Dsm-G{@?$RAG;|D+zatv(^F*hN#!_^qI-^iDS8S-_Ch7l z1I`J#Wm18Yo`VoiOA-$874QLRB_rXq3ab!ZnEap~awnkgs87A%)brgQB3vIqi;)X} z0GwcOhJ&sMHrPCAqor)sJ(DYoY?njywIGmq>S|4TVlx0erRNK`mhG`@-Ebz>rW|j z$01KoNN(3>m4|cT9)Pqkp`W5dNcfyo{B)Nj2$iGV?Pb(!AQ$uVH9k?CdP->IGC_v* zm48he*9_1k;DaXP4?+6*wZP-m2*~W1?%vG=$OHuT9%gI8vtM~WliM%kRS#1Z$6bT- zBtx}>oTrXbyX`NHa6xMlVZVtD*ss8BOvr4ru&kZtIH?_xBaZcWGA26|oYS>~z#)QX z1nf!fJ6f;f8E)}ErRmqlH0@9DClfZevZEsM|HY8_g=}q~+W(Dv* zm|vk87nd|B@Ry(}p7g{EU;;DAi|WI0YEn_Nh_JBQ1!(oUwoc+}Em=5*ewRU96o{MT zUqsmjyS6Cn`bK$|&;xP=z{DVghJXVmv$FEwD1a)T_dS5|d@|i&z!@}#*GD1lybuDV zz+AES^tI1m__1%42^XkKpW}m~9n^@Jx~bs8=(BZ!I1O;*jPf>{slS|e_#`mVld!_aS9LY3w}T$F|n7WB5BR3IlunB}UM z&?Ieby#(2HDLN+UiTON0n98IbM?x^V8*rL?i6jZ;1MockY2m;f7S3XqgsCCz8!)?g z?ie7o*i5Z9#tIev^bGDmuF5C}{>nM9{^hHm+8&ajMn4! zkNDQm+`aMkpHtkts{^`LwNO^a@#+5&QhV}D30;s<(t`Gkutn%PeO11!wEXgwY^YHRtbm}3+BR!-u1iKf6%+;c>J5*RkkY3 zn%*&iY<&>HPR#COT;B>&@557tE$-mH11jmy7krUJd36v#c(y?J&f@?xT^TbF z!7lqaIAGb}HPoEZk`)v>_$44j_f_5UT2E7uUq(Zd@ z%rhKLJ7z2|%G9<)fot(tYun(cA6@!!Dk7ykJPH7e9;t*KIg4#sub@kS853Ct)~1~Z zlNs0m@bRXnrpjOak_ucLPln`tSY+*yvQ|rF=PyT;9PbqVY8B73;=`cJ$i$Fg+&e!% zM~E?mJ&HbcznftHfL9x=IMC3hFf;8sxfh>;iJA}T$fE=FXIIga$ z!2Xa>3M;xB$tW7xd_-}|pryETXM^|(F0T89P+<&7w0rKv6nNs9Xq2SXBA;0tYo7V~ zM7#M4hc@~L*MH{yWa)O6lH&7*o_*=`pS_R#nKOZXzvPDbe^@z|zp@+Rg#^Tj$qiNq>8G_yG<9!Wk=5#OG?TCY`QWG$5 zw^U8iFA)}x+U_(hC6K&j$Y@+?$0k&y`+2p(N%BgtHp4G%it~|&1J{P$&iS=a4;p9B zeb@=4U8LT4uodX1u6ABS&whu=zaaT2X9Q>e7t?iocN3n}b7*dv;_s1<6+=-Hx0xHT)z|wDfhvceQbBLOWcUzN-divXg4yX0kohn3v@i<-W=PqMD1`2j z;J2>rwJ_uav<>cDV=HPFP6Lh(RG^_xVghDhAyBFLFlSigJ28i-X0h9{(qIhG?;=rA z0#~HEmlp%r)4@Cq>?e|sT7|gMzbbylS6KCAsAQ2oy?8@A|TQ+(~T6EaO z$TkLzCtW4#`opS4Cbr|Rf5Xe}>Ij~BpeOWW@6Vw$6@z2$++%*8`>6aG*W5Q&o$!kR zE@b?%O&_w!Qt_08^cIefKR&gGmXNw{A_>`YlZI=e^KGXg31&=y48e8)!p#m4=IQx4 z0x4oi*=GrFJL`0~vh(h_=b-SrUcP*h;)_WI{t9>M@POHBYq82&z6LxPu!G|Ed~H{? zB)8?nBUl{)clXWZ2Ix5T|G>o4SEd&XxIUl;@DM0sd=Qz+B;VY~T(;JK^29fD1Kv0Q z7m%f?-=!}FRPGodcSY(q2#Pj>*_YBsvT5_VW~;hLH$UVsV9u7Bpc3jf-%h6Uziht$ zvU?kJ>-mQ2jUO+qq{#4s1o+E*i-U|_PB|*9eM>WB9`Byp({zFPEz(!nPkxOh`1v&| zunJEZLzFK>4Ts-V?t$5AB1=zc&`$N<{*YX%wORCN_{MuCwQZ$ibG0xdL5-XI^N>W~ zSVk5=%lC*V9Hrs3S_D~2jW8fUK?S|?+e$JnYdu)p0I`OA=G0kS#9kS{sm>$EKDZ=z zHM`)2q2`S@mIVE}J9En)L=Q%BhpNgkl?IQ#)*P8yb4>cS+!{0vZl`P1RP2&})gv)u zwQ!TbNtoO|cyg%XZb!J|7i*!FL@|9sDjD;6*clk4V0@eZv0rAYfu7m%c#U@LF%>iZ z%hey-@UHjgKySq~%@`#U^YC>!sPE?<2hEzp6R3}qIUbE0iItl)2oivdh?`v2NzW2R zceFY*+xm(D9UR&L*dUUCgXA$GAt6j)iq(MAAJ`74r)r+}{wU!1LD%Jqvgkod8Gz(1 zYok~>02u9t#2pAzAEhSn+~ zk%b_|OB}$1XbJ>*X;-~PY@)eLtE)GxA7D!Uo+^Z!%s16Vc@~;}?1$3S)+m1My0_rW z@gzj)t)oZ5Ii`hCAK%%!ENDa49*TRdC!FFM&m2)bvw5`A+n&{z(d-q5W3&di_~!Ig zw;Ng=VOoR`qYwOlAm*CqS7q-EUualZa~=3US2z3d;QCUw+M4)C5e5@Mxr76odg-=* zy)s|Ntn$EY3k(?GD0yHt%IGFW-AHjS|M^I%D8NaOqnd zIfW-EvXeAh>0v%(SMC60F*v^Nm&&(p`{m!mO)IwHMnh{tv44oWCGy>7UC#_$)%$)7*+EuWm0+F94an{*!?gn# zm5$SScMTZH#Q_6!pxij=b|B(EQU?Zw{PQ+PI<+JXklMFxnq4M}`4b@G(DgVbis_~a z_9ry-Wo2c1+Jm)u6L6dK_kMtI@47{4JTwrp2F)L+_Y$GAZh5YVjjqqV1t#6)*?e~{ z_s0Pe3}8gZp2A?|X@YI2Km0}lG)i=A+_PEmj_Mc?%tkIr(6+h^EB6#l%TJA;3%Gut zNyZ&ga072|K!1)wbQX>u(HGL-JXrqr0aeUXn{DIR)>_B6dc$dr7(w76Br(#~O z-n$c-w~A78CCzN?{j@BY;rFU};HJ=wg4|(nj0h2lSM^aB^>~+v(ts2@F9|_5!@NMY z#;sYLCk}CfjW_ zM4eP!{jkJJA;A~0;9tCtrRvL_?-08>J~GnPXV)ROVoYiPzJuYh>J!teUXx3F18%}) z_eX3m^Yq#7tgiTL;be~pJ=kx$=vT%QTH&OwP0NS1Qca_TADhN2=+I_ovsaYrdhmof zd%BB_e#f78ZDnocMOc#R(w~+qR%9YWkr(8LxnQxOo&sreXE4=rnLTSUHUu^d=|@x@ z?JWZo03i4ngeamq4tb`bQ;gJsZH3b7<%Z7ECzEr6q{yv_rLB9LWB z(1+HpLjuj#25i-6SxMXH(mY&Jn(cKxn7$bw{U%4`*|Tn7Z<)?n95SdRKol4xb9k7A z20Y&WfUHdO2yOn_dj`hXJvpVc9ZSVxz6YaWEPJ%FOMKhjiqF1h%7*-jf19P*7zoA4 zy3v>CXX*6*__0eaCSOSR=2K+F8YZRgbu>4Bp`y`?b}_xW8pR6lTOEG@@&K&>EG<|B zzz)jw;S^Ez1%gP3W`*Q_P}n7$ZKk4MKPXeRC~NqyXH5KcnG+112DV?n>=19ggkDLZ z?1=q&U@F&ury}Db@_uD!q_q5asziLWCuHi$RK66m+b1U-MNJtSaA_cHlR_!S5v4E| zT_%i0A!pLXrQTm?BRNBHyQ4}(zhRorcp<3$xH$O5BBw;%BuBm*S8BoZVij48z}uMJ82PHtp1**rmQZLmXpV7 zCm8>l&8r3JTNMKYcH-Xde@y+%>TZTfSgtIYNIC2`#%)b;U*$`j+HG?;^fq+OxIHS{ zsbt}^e5d7`@aG>LuveENqesl;{WayzhLAi)=m5WfHwoezW}tZ;IObf350up&7@lA{ zn_=^kDAy~-qFSt~K{_zya5MnbVv0R?(BLu6C4@OXp#CZ*pg=+A23^}@P;?_Zd|b@G zhxxwS8vdT8x^3-1`Qi=!5j4^u_}BQB2xqWQLQg{930j1sVK;c{=0HM_zF}Y{F~j-< zLd!Ek2PBXcH=vb<-l*;1eJ6CLdYt;l70+j{D3I=T2fAFldxOhMacpl@po}jgR6FnK zS%|5Km-TIw>$&jr!9A0{G@v5SUk(iq7w>h9Mb`B7QE*V}I>EFSe?G2hctS`Hwsn9x zML?^)al`T048W?~bi4Vv+FDr8VEPjScyK|KjLzRvh5N}u z;zbkQVWLU2p>5|G5Y}TUQ&(Qo7+NTtnwkQN&nv>f=eTCG>07v0(m*|Utq{%aT8|BC zRVsJ+6Zvs|)@$WXD2C1L?fv6UuSRW^pnZA6?@a5u53tZ&xf+YIu$>i5aWLV33=t#r zEpYtQT9Dlu20E+JL8T`20RnPuNv*M9cGsB9MA zZ3a`u{RC5BC}E}h5@JLniY|#a9gVjr$@%16>e#QDWj)YFt%DXN`=(%GV6!;B1)E)-Xsgn;ay^#do~T;%6l{E7yf|pV@GkpE)?)gHq*1)q z!Qz?tIy!x`RQZC8-)iUZI=#+gywLV8?;uwevG7!s!<=jx)!hlc0Vn-!{gYL(MV1Ti z`umAK`j9Ay!9h3f=;e=1B09EOASGxtyvobNyRS;Uc+wQ|K7RI%_H4hM)Oj>CFLC0687we^eFGozqAO!T zpcpVnb>1dcC#4vwER>=ZXoKkQb%bB^4VaOK^FGbVmoQr8RB{)wUkWV)74|(_-3eF& z`g@A7Y$^i%#-{+X2bPbD^E9d)SwHnlAQB~UVpt7zPalC_!S+Xe@2zOmTu~&ytIO(J zWW|^x)N%=k5zuSQ0?_oD(^~EM%a<>=GakV2pQ%mWo1c~}5#fIS1B#C-Yse6X(3 z;SYsszB>^^pI;sU!mDqwRsu4*dH!Pu2r$);O=liL9#kmR^^i&qLkWt%C)R%I;>B;3 z7;rUA{GvbPG2(|qs(eTZJ@X9G6id1Tf*n;E`9~cEf;fH51A3FQA26c|#Dbs*n8J#t zr@g?g!Qvesu+Dh4id<|6QK&6OmBsZ(jP~2{GWEReQ^Q#Ukr9|UD|ma9brE#EN)4&nMS@Bd`b{L_+EGDhE^S5DTJ7w$L{bopp2 z*TF8rWm$97#%&ae$Q<`m)Jl7`aQ#YQ=$;`+D1!$^I^vdV{P8u~82N72(oDTgf&j_| zXq5-59baUe_8teV#d8c((B4>MHFW?HS6Vq*>0kpd00g-v_%{!O)gIVSSgBe?AI!u8 zEQB&ut0W(dvM?+aOaX1_90S$jk`+@Y{qNZpp#59v;bWBbWxw{vt_NeqG+v-KzDTT==wyX+J+W%p9?G1TX^bRe z6?^5?Xx&xY7{2`S2XMc-Pwtm1(0T*-b_ZV{9dndL%lu4((3E6U)7uHHR1wZp&Heqx zu&pVst6@&^D}LW?koNaq(-&1>g4t7a)as|JXlVKL?`G!3g17qu0?hZTE#u$93!rA( zufU~wa2O27q*-2AGzuWx@Vw9??@AR*bQ(DyIIXQ{g)oh=PFz0PkO)eROqcZPQ&O`M zfm?`Nk8`Dv^3~L(Nc}BXb~ojtg~?cDS$!{Tf?_kfBD%4xd93ms*>0dubL%zL*Osog znxY}B4IJ3POW+7E`Ldm*b;kDRI8RF#KTKdng9V0xp~ge`$;wpgP4$T|<<;&#>gj^V z9s`TL;F6Rk%3GWdHhc>7wkO47ZHIV4GNB`^m9Gg#Bsoa3UhO1yne!Y>ygm2 z9ss$35}vB;k*2DY^qR{UxSl}KI0hI1()b_uWW*h|ejLI#%ZT1rem$ga-Z(u~&hzmK zRGuG#>Plg|`hzfPMpM^j84VY&!k)A?{9?ObW9CeKO>Nxw@4tez4tt-u;gx~(H9g-l zT!!r?bTyizIp5LRfYqc7!x^-K27YTMNxE(69oAy?^BUrl3W2+n&Er3S6I*p$P&w_b zR!xhuTalLV3*Wk1IjC5GNlk@^3{=EDM9a{by`SiO4uXp>&=YLDVl=$@#}%duJ>Ng< zp?C9Lk6q{tzEH{M_v>oJ+Nx*sf`R7Mmrb&CMa3K#9$_!nONad*x-xf=o-2RJietX4 z3pJ&_v0abj&@bPOc&;Na2@UQ)^`vSW) zZgG6n5vY~&H(f?Yg|9wwp4vAS2jD?Bg#|VB2MttkG9O)$kz*}J zI(4x3rD(S5nbiB>vSv6!D(vCpS|=#4lF?Xjfd^9^gRn% zYy!(VTo-4dlUGu=RRAjOm*il@tDM-Jb_hmw#m~bMj@YM*_JT zef-Y5misPapv+Q}kRUx=DI+PQLnWG(B(YYA=v z=9lKBLCqV0p7Fd~pwPy?&iQoj`_FR7qZIdE_D(i7V0eF%KNey=ru>%c8|#&Za)_S`#!!jFRq%74cWhNu)S))!K!(B-;#ZB4M{fiSi}|R zx1-%X=v=fm<6o;eH@6Cz{SXC^2bT8zo=-a?nzPG}biC zm|nRlZX)^3GMe<=^?K&?CtC0~?Uv5qU?s2%%IJ_kXJ^$PheCd65to*af{(6$WvQy^ zjIZHW^^)8-g10jKjd8MX@P*SW9oqe-kwh9jnP-y3cJi!;*xOIPIegIIZH(umS8Ps@ zbezx^pHZ?9BYhlP)qLdps3@lQ4B8{bU9?~GZ%W2TcQ7$-G+BT=#}&tG&Hy9DT&U(;+rG0j&=h=*nD~}@ks0@#DGk}%jtEM z425@}074Y^S~ZVksjW!>e&z@1V^2>He4GOrN-w6%#_<46+=Esf@UlFk?gaP^#-V9< z`~%W42s)VqjQ7h2OGq({%7JHd3EIs4-ngSJm?8U73az}MO>)(l{jg6Ct{uSB6YuVi z`LtC4_TdBytDMs@_ym*9Yr~H?g=86WDtl%G%Ik`U{f0VYxx;jOSBm8mdSzFaWcV2# z``PqlzYl=2B+gEm6BvhN5>|~|HGubN&sQAY=_=@R0Yd9BH_0$0XBHtP2izT&hh>{LADsBh9{ur?OAVZ?F>1G|oSKf-D zsiGV{>XBex)TyPVEi0E0*^aFGQ=WwD`byX?C%cxQ`1rpdI==1}3n{Lne2=6CptG(K z5mf^$VjYBDolX__71Z%y3yr!N_Rw}I)GYWoHiOV%Ld47PSN?+&ge5PKiomGuYIAin zj60~6%nAM=mcR3s_tqF}6qpHQ-=GX7@&Ly<09z*m>1aGR26K6A;EFoetA1n@B9Xt< zvFHPRE|wD+;0f82$-<-?2YL`Yt&V2uG+ba4evMF0n^)n)#K|A?(W7h=sF!8 zz$yPjaB)KiFh}?{44^ZeNd#DLfo>iOHZY+ZhOrLcGCAPafHd9XO_h%Bve>!DjDURb zfE0Xl5h5R5fSdwgZm|TuuGEf<%>iapb#M_GWHqr*!HOw%)9-f+WvKmW@5uK&u$mGE zg(e(}tzr03K=iY`CEz+3Up%!{Ag*XA!=mL>(X#-jF=KPVgeUg1R`aizeE~zp-3#+t zEO>T+FKz|WESCTu75l&{fo>#Sj`mPR5$VdBtsg?D4S?6KzbnS+PVh8O+#x4uDn2Me zN}Nvk<4YF1@Vw6!-N1}zjn4*;P$y)|oQ-qUP6cM9JS|OZiSEvwAa%2!)#3jO%XTU5 z7!3ah%hoe%R91IP$<1AZnQku;QME6M5X^`+q>}k|gx;V_)wm zA0kVl-@$gdr!TLPd&7KELRVlpSqBAl*mr_+;D5j`mpHY(XNL3^@rTktd+=Ig@6#z~ z)QI2-F*j}=O?a$>1hh5ymeb>-r*(1hb4_Dmn=AcTXrk{Pq|(wbprK7%7EEpZ?<}95 z**Q2CHPlI%`ewg|3Lejefy7m_*eptlA{(?`a;Y5vX50z8Z#K9F{1fyKvUBJ8cWYr9 z0v`c8++FDEMlHWyDfa>?oOC2hJS@A&PlA~jAZJi?cHbN#8Llk>>JKKQ&y{O%IG#Ky zP}Cz`2bMagbn4O}w+nCV2d|FF%MWK~vnzh~Ty#A}>F1Md%g+`U;Pw7<9>|rR7$?9Z z%hUM>z~YyGWLUU)6g}=szyg6W$Xht%F3f}9_sYY#Cn}$Sr?D;!E1c6J&K`rFBk^va z^ZR_$udt5u5W0`UXw&TABV=JCyfW|$4Kx=63f)nu@LdXAfru3eMz`DYzLT{4e|+!u zt%G9Bb=SG*EohiqH{gYp#$YWjF&p|25uv1_V!~QHoypXWA=j(~#K*@J*QCLj*79Q6;Mq*);Eq|oGp(2>aH& zV5|>;0Rmubi5dVOr%6vD$TA)*d7n^{?}(Bl}E0p>xnzaz1St%ZmAq<4@Y@C z*;l5{=q1PQxS=Ie9||Ia>i$BZRs{af1w?Yc$mSD*_9*Y3H0GZP+x5BK)`Qb zr__5hhU94Vs#N9-K+ z157nfX5jFAn1Berfh<)+#lX%R_ICC=CE`7d+s?CIrSGX+zZDyX*nY1#x_^=K|NO

fM493lX4)KThu4|I^;J$3va2aXBaIqh;4J<(#5?4nlN3ltPTt zQ700G#u7#u+0f<_3T;Mo*>)W>MAl|1p^i&gm!(O$Zj8(FSuJLE%B3jRl1p-(^L^cR zCV!lN_OpMT_n+bYUFP?D=Y5~|dA`r{d>`(aI`i1bBd?>`9%B#6ZGrmT-tSwcd1SY8 z>=u?lDPXh#^>-Mbq8f$I)%@c^8q;B%RWT1QyFkf(!9D2U`2idlbF5t6io1=QpYK@Q zHd11>A|0?0g1w$FBLRiKfYG%bVH5tJ{HI>Rk+;mt4+d`QOuRSw0WEfezFCVpDQEyr zCpxd?DTs-Q8UUebi|u=WZRrWT{G6LNEnZ%O0o1ft^~=FBFc$*w=q*I8J0#(zJw%LY zoU->%-=!&PN5bU?4&$xda5!LIH{ZQBLJF1RLDYwbM|_>8YifEYTgQr9@M6eFCjqM` zn3KW=K!lhdVZ+zjpmUwK*0_#fW4r2`kni`Q;=W;uCZU3+$@HjPG4Eb!nm?f9PSril zxxOz?Xq}Qd@U$ks$V7>CCskL(mw$;|ni9Ahf?6w3jcDW6hmagG7aVH%wDezK{cZ~$ z^f2B7qa{_b`X^HTI=+yw*8kuq$b}h|VHusBMX0a5f}YKTir>IALS%+9gZHuVCYBmi z{{&bqNp1+>kM1Kw1@L;plME8xZ0pW``M+%0bCq|}fmO|?fMF*6P~p95zU_ktBR1~t z*IOy$MeO%#XGac9ANV`HxykK+;Z&(1brV07Fd1sqYW>I`(8-3^*Ey4>H@&aRSpG5i zC>Sl!*RxR$et)8&O9R=I7ZkwDJHC$C$KRkmrb&q88_$(#><0XCwX6d8?4Chssr7$l z3(FHjUhD$Zk zrNB?nxCsd$D4y;PqIg&*DG8|RiCPhx5Kc*|H!oj4BogF#_pun6eZQm!07Y-kri+|P zCgQ*JIk^n-^=G82gpT%F&4N<8RJ)MALc@Xp#lMw92^;JF*%M*|;f_YjpI5!mRtdyX zfZMyse)p}|Ju(15P~34kMhl)zGA9=C57R<}fqMl23$jHJ@VV@UP zUwG=eitw#3?RBf^9Du`l`qZ^&qo9$vu!rt_)CMR(*~xW-8bopi#Lvn7pcU20>|-Qv zIe=YNdJSkp2QJ^Stc~t|a)%Wc#|s*VfcHdJ&FxO8NJW7?>KjmG!eXWkYM}EG^N*mw zw3X1m?nBpL4K~tajQz!lsyPsT*EKVlAAI* zE89s36*guzFJ&ViMl^+6jcDm$WEe#3jq<7GQw+|t&z)Csgba`R7WN+EmMdO8^Lq}| z)56G6H0zzU3$1P_fR`aF>NuGRrH{+vb2u7Mq@H1jum~blDxc4TH%~ru=$b`oF4OI= zlwvLtv)HJXtp<5<>>h7#dS@Q3g&Bi6QeRSGA4*N<1iJuqv|k7L*M{VcIjpr^_)x4 zj-him99z5Von|%`EW*KZTO#EC-CM_(A@7XVZqH8de8mgp&Gj(cw|e{0zawTsJ?D_S z`%Jy*8)WpPB5D`jKl#+e%jIuT+Ppc&>%l z)i@3XFwEu@Whnzz)PT?TcnIjd$`nf2fG@oW8X@2=RsCg*HuyfVy_R^CMGVTWj)HMi z1krj4DF-m8DsH%~ft5Leo%~{gd-=R!W_NoiK|Pbs?Ky6sBAowDDF*E}!a@3ozZ#7l zLcW}RaQ+cUk8`4RSajBS(V+gT`Ja_W z;=TAge6Nf6=!hLH)zEW5pu;GegpNqG_Smc9lS}NFzg4aHtBW1EpDHnkVnolYw0DuK;3oq^}%2~v%3D0tsBGO6veRMF)8YX6rsM6fDbRZe={y2fr((* zjxF7RcQ0F4)_<|&$Cq+dSHF@|y(l#n?h(G~E1OEq#Z|k|EoHFYK;_!pGFj}yAG~lL z!*9+q67OFBr~lvl_Zb$`q#QRIP4faR+kKr2JbaREFc{>mD24&9fJnIt6(Q0eV|S9- zss}SxvIP^N4`GYkFlYb6+dU6KIeGi5Z}v2h)!c=>WAPGp9TX7N;P!J#NlA!|*dKbz zvhp3YUP7n6WaWJIN^FK&H$?OJ6w%CB2?`;p*-kCF^=EikDyXIXT~6%`Tskk?r+yQ^ ni;pANx%5Pa+)^pX))OhPK6W}(*|+Koa Date: Fri, 29 Nov 2024 14:52:18 +0100 Subject: [PATCH 26/29] draft: first DESIGN.md --- DESIGN.md | 48 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/DESIGN.md b/DESIGN.md index 9388859..c09b412 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -7,24 +7,58 @@ Nous vous conseillons d'utiliser le logiciel PlantUML pour générer vos diagram ## Schéma général -Décrivez ici le schéma général de votre programme. Quels sont les composants principaux et comment interagissent-ils? +Décrivez ici le schéma général de votre programme. Quels sont les composants principaux et comment interagissent-ils ? + +On a 2 principaux composants : + +* WeatherDataAPI +* WeatherDisplay + +WeatherDisplay contient un ensemble d'instances de WeatherDisplay. Chacune de ces instances retournent des données ayant l'interface WeatherData, ce qui permet au WeatherDisplay de les afficher. ## Utilisation du polymorphisme -Comment utilisez-vous le polymorphisme dans votre programme? +Comment utilisez-vous le polymorphisme dans votre programme ? -## Utilisation de la déléguation +Nous utilisons les interfaces suivantes servant à définir les parties publiques de nos Classes : -Comment utilisez-vous la délégation dans votre programme? +* WeatherDataAPI +* WeatherDisplay + +## Utilisation de la délégation + +Comment utilisez-vous la délégation dans votre programme ? + +Nous avons essayé de mettre en oeuvre un maximum de forme de délégation dans le projet. + +Voici les principales formes de délégations qui se trouvent dans le projet : + +### JSONFetcher + +Les requêtes HTTP(S) et la transformation de la réponse en JSONObject est abstraite grâce à la classe JSONFetcher + +### City + +Nous utilisons une classe City afin de stocker le nom d'une ville, et de faire le lien avec ses coordonnées. + +Cela permet d'abstraire un appel à l'API api-adresse.data.gouv.fr pour obtenir les coordonnées depuis le nom de la ville. ## Utilisation de l'héritage -Comment utilisez-vous l'héritage dans votre programme? +Comment utilisez-vous l'héritage dans votre programme ? + +Nous avons limité au maximum l'héritage dans le projet et nous sommes concentrés sur des relations de composition. + +Au final, pour permettre un système de cache, les trois classes WeatherAPI, OpenMeteo et OpenWeatherMap héritent d'une classe WeatherDataCachedAPI. ## Utilisation de la généricité -Comment utilisez-vous la généricité dans votre programme? +Comment utilisez-vous la généricité dans votre programme ? + +Nous n'avons pas eu besoin de généricité dans notre programme. ## Utilisation des exceptions -Comment utilisez-vous les exceptions dans votre programme? \ No newline at end of file +Comment utilisez-vous les exceptions dans votre programme ? + +Nous utilisons WeatherFetchingException qui est une Exception qui est envoyée lorsqu' From f89bc7ae98668c320d9fb15df4db75bf034b2270 Mon Sep 17 00:00:00 2001 From: Martin Eyben Date: Mon, 2 Dec 2024 10:07:16 +0000 Subject: [PATCH 27/29] feat: package refactor --- src/main/java/eirb/pg203/Main.java | 6 +++++ .../WeatherFetchingExceptionApi.java | 7 ------ .../WeatherFetchingExceptionCityCoords.java | 7 ------ .../pg203/{ => weather/data}/WeatherData.java | 7 +++--- .../{ => weather/data/api}/OpenMeteo.java | 12 +++++----- .../data/api}/OpenWeatherMap.java | 16 ++++++++------ .../{ => weather/data/api}/WeatherAPI.java | 16 ++++++++------ .../data/api}/WeatherDataAPI.java | 6 ++--- .../{ => weather/display}/WeatherDisplay.java | 4 +++- .../display}/WeatherDisplayBasic.java | 8 ++++--- .../exceptions/WeatherFetchingException.java | 2 +- .../WeatherFetchingExceptionApi.java | 7 ++++++ .../WeatherFetchingExceptionCityCoords.java | 7 ++++++ .../eirb/pg203/{ => weather/utils}/City.java | 9 ++++---- .../pg203/{ => weather}/utils/Coords.java | 2 +- .../{ => weather}/utils/JSONFetcher.java | 14 +----------- .../WeatherDisplayBasicTest.java | 10 +++++---- .../{ => weather/data}/WeatherDataTest.java | 3 ++- .../data/api}/WeatherAPITest.java | 11 ++-------- .../data/api}/WeatherDataAPITest.java | 22 +++++++++---------- .../fakeJSONFetcher/FakeJSONFetcherCity.java | 14 ++++++------ .../FakeJSONFetcherOpenMeteo.java | 6 ++--- .../FakeJSONFetcherOpenWeatherMap.java | 10 ++++----- .../FakeJSONFetcherWeatherAPI.java | 16 +++++++------- .../pg203/{ => weather/utils}/CityTest.java | 5 ++--- .../pg203/{ => weather}/utils/CoordsTest.java | 11 +++++----- .../utils/FileResourcesUtils.java | 2 +- .../{ => weather}/utils/SplitQueryUrl.java | 2 +- 28 files changed, 119 insertions(+), 123 deletions(-) delete mode 100644 src/main/java/eirb/pg203/exceptions/WeatherFetchingExceptionApi.java delete mode 100644 src/main/java/eirb/pg203/exceptions/WeatherFetchingExceptionCityCoords.java rename src/main/java/eirb/pg203/{ => weather/data}/WeatherData.java (96%) rename src/main/java/eirb/pg203/{ => weather/data/api}/OpenMeteo.java (91%) rename src/main/java/eirb/pg203/{ => weather/data/api}/OpenWeatherMap.java (89%) rename src/main/java/eirb/pg203/{ => weather/data/api}/WeatherAPI.java (88%) rename src/main/java/eirb/pg203/{ => weather/data/api}/WeatherDataAPI.java (89%) rename src/main/java/eirb/pg203/{ => weather/display}/WeatherDisplay.java (84%) rename src/main/java/eirb/pg203/{ => weather/display}/WeatherDisplayBasic.java (95%) rename src/main/java/eirb/pg203/{ => weather}/exceptions/WeatherFetchingException.java (88%) create mode 100644 src/main/java/eirb/pg203/weather/exceptions/WeatherFetchingExceptionApi.java create mode 100644 src/main/java/eirb/pg203/weather/exceptions/WeatherFetchingExceptionCityCoords.java rename src/main/java/eirb/pg203/{ => weather/utils}/City.java (93%) rename src/main/java/eirb/pg203/{ => weather}/utils/Coords.java (95%) rename src/main/java/eirb/pg203/{ => weather}/utils/JSONFetcher.java (80%) rename src/test/java/eirb/pg203/{ => weather}/WeatherDisplayBasicTest.java (90%) rename src/test/java/eirb/pg203/{ => weather/data}/WeatherDataTest.java (97%) rename src/test/java/eirb/pg203/{ => weather/data/api}/WeatherAPITest.java (62%) rename src/test/java/eirb/pg203/{ => weather/data/api}/WeatherDataAPITest.java (92%) rename src/test/java/eirb/pg203/{ => weather}/fakeJSONFetcher/FakeJSONFetcherCity.java (60%) rename src/test/java/eirb/pg203/{ => weather}/fakeJSONFetcher/FakeJSONFetcherOpenMeteo.java (79%) rename src/test/java/eirb/pg203/{ => weather}/fakeJSONFetcher/FakeJSONFetcherOpenWeatherMap.java (70%) rename src/test/java/eirb/pg203/{ => weather}/fakeJSONFetcher/FakeJSONFetcherWeatherAPI.java (59%) rename src/test/java/eirb/pg203/{ => weather/utils}/CityTest.java (94%) rename src/test/java/eirb/pg203/{ => weather}/utils/CoordsTest.java (70%) rename src/test/java/eirb/pg203/{ => weather}/utils/FileResourcesUtils.java (97%) rename src/test/java/eirb/pg203/{ => weather}/utils/SplitQueryUrl.java (95%) diff --git a/src/main/java/eirb/pg203/Main.java b/src/main/java/eirb/pg203/Main.java index 25b7d95..800cd5f 100644 --- a/src/main/java/eirb/pg203/Main.java +++ b/src/main/java/eirb/pg203/Main.java @@ -1,5 +1,11 @@ package eirb.pg203; +import eirb.pg203.weather.display.WeatherDisplay; +import eirb.pg203.weather.display.WeatherDisplayBasic; +import eirb.pg203.weather.data.api.OpenMeteo; +import eirb.pg203.weather.data.api.OpenWeatherMap; +import eirb.pg203.weather.data.api.WeatherAPI; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; diff --git a/src/main/java/eirb/pg203/exceptions/WeatherFetchingExceptionApi.java b/src/main/java/eirb/pg203/exceptions/WeatherFetchingExceptionApi.java deleted file mode 100644 index c6657e0..0000000 --- a/src/main/java/eirb/pg203/exceptions/WeatherFetchingExceptionApi.java +++ /dev/null @@ -1,7 +0,0 @@ -package eirb.pg203.exceptions; - -public class WeatherFetchingExceptionApi extends WeatherFetchingException{ - public WeatherFetchingExceptionApi() { - super("An error occurred during API fetching"); - } -} diff --git a/src/main/java/eirb/pg203/exceptions/WeatherFetchingExceptionCityCoords.java b/src/main/java/eirb/pg203/exceptions/WeatherFetchingExceptionCityCoords.java deleted file mode 100644 index 75f472c..0000000 --- a/src/main/java/eirb/pg203/exceptions/WeatherFetchingExceptionCityCoords.java +++ /dev/null @@ -1,7 +0,0 @@ -package eirb.pg203.exceptions; - -public class WeatherFetchingExceptionCityCoords extends WeatherFetchingException{ - public WeatherFetchingExceptionCityCoords() { - super("Impossible to get city coords"); - } -} diff --git a/src/main/java/eirb/pg203/WeatherData.java b/src/main/java/eirb/pg203/weather/data/WeatherData.java similarity index 96% rename from src/main/java/eirb/pg203/WeatherData.java rename to src/main/java/eirb/pg203/weather/data/WeatherData.java index 1a03b68..b728906 100644 --- a/src/main/java/eirb/pg203/WeatherData.java +++ b/src/main/java/eirb/pg203/weather/data/WeatherData.java @@ -1,8 +1,9 @@ -package eirb.pg203; +package eirb.pg203.weather.data; + +import eirb.pg203.weather.utils.City; import java.time.Instant; import java.util.Locale; -import java.util.concurrent.locks.Condition; /** * Weather Data representation @@ -131,7 +132,7 @@ public class WeatherData { } - WeatherData(City city, Instant date, float temp, float windSpeed, float windDirectionAngle, Condition condition) { + public WeatherData(City city, Instant date, float temp, float windSpeed, float windDirectionAngle, Condition condition) { this.city = city; this.date = date; this.temp = temp; diff --git a/src/main/java/eirb/pg203/OpenMeteo.java b/src/main/java/eirb/pg203/weather/data/api/OpenMeteo.java similarity index 91% rename from src/main/java/eirb/pg203/OpenMeteo.java rename to src/main/java/eirb/pg203/weather/data/api/OpenMeteo.java index 75fa51f..ad31c79 100644 --- a/src/main/java/eirb/pg203/OpenMeteo.java +++ b/src/main/java/eirb/pg203/weather/data/api/OpenMeteo.java @@ -1,10 +1,12 @@ -package eirb.pg203; +package eirb.pg203.weather.data.api; -import eirb.pg203.exceptions.WeatherFetchingException; -import eirb.pg203.exceptions.WeatherFetchingExceptionApi; +import eirb.pg203.weather.utils.City; +import eirb.pg203.weather.exceptions.WeatherFetchingException; +import eirb.pg203.weather.exceptions.WeatherFetchingExceptionApi; +import eirb.pg203.weather.data.WeatherData; import org.json.JSONObject; -import eirb.pg203.utils.JSONFetcher; +import eirb.pg203.weather.utils.JSONFetcher; import java.io.IOException; import java.net.URI; @@ -14,7 +16,7 @@ import java.time.Instant; import java.util.ArrayList; import java.util.Locale; -import eirb.pg203.WeatherData.Condition; +import eirb.pg203.weather.data.WeatherData.Condition; // https://api.open-meteo.com/v1/forecast?latitude=52.52&longitude=13.41&hourly=temperature_2m diff --git a/src/main/java/eirb/pg203/OpenWeatherMap.java b/src/main/java/eirb/pg203/weather/data/api/OpenWeatherMap.java similarity index 89% rename from src/main/java/eirb/pg203/OpenWeatherMap.java rename to src/main/java/eirb/pg203/weather/data/api/OpenWeatherMap.java index 2e4e424..ac3d088 100644 --- a/src/main/java/eirb/pg203/OpenWeatherMap.java +++ b/src/main/java/eirb/pg203/weather/data/api/OpenWeatherMap.java @@ -1,12 +1,14 @@ -package eirb.pg203; +package eirb.pg203.weather.data.api; -import eirb.pg203.exceptions.WeatherFetchingException; -import eirb.pg203.exceptions.WeatherFetchingExceptionApi; -import eirb.pg203.exceptions.WeatherFetchingExceptionCityCoords; +import eirb.pg203.weather.utils.City; +import eirb.pg203.weather.exceptions.WeatherFetchingException; +import eirb.pg203.weather.exceptions.WeatherFetchingExceptionApi; +import eirb.pg203.weather.exceptions.WeatherFetchingExceptionCityCoords; +import eirb.pg203.weather.data.WeatherData; import org.json.JSONObject; import org.json.JSONArray; -import eirb.pg203.utils.JSONFetcher; +import eirb.pg203.weather.utils.JSONFetcher; import java.io.IOException; import java.net.URI; @@ -18,7 +20,7 @@ import java.time.ZoneId; import java.util.ArrayList; import java.util.Locale; -import eirb.pg203.WeatherData.Condition; +import eirb.pg203.weather.data.WeatherData.Condition; /** * OpenWeatherMap api implementation @@ -29,7 +31,7 @@ public class OpenWeatherMap implements WeatherDataAPI { Clock clock = Clock.systemUTC(); JSONFetcher JSONFetcher = new JSONFetcher(); - OpenWeatherMap(String APIKey) { + public OpenWeatherMap(String APIKey) { this.APIKey = APIKey; } diff --git a/src/main/java/eirb/pg203/WeatherAPI.java b/src/main/java/eirb/pg203/weather/data/api/WeatherAPI.java similarity index 88% rename from src/main/java/eirb/pg203/WeatherAPI.java rename to src/main/java/eirb/pg203/weather/data/api/WeatherAPI.java index 570b169..02ba852 100644 --- a/src/main/java/eirb/pg203/WeatherAPI.java +++ b/src/main/java/eirb/pg203/weather/data/api/WeatherAPI.java @@ -1,12 +1,14 @@ -package eirb.pg203; +package eirb.pg203.weather.data.api; -import eirb.pg203.exceptions.WeatherFetchingException; -import eirb.pg203.exceptions.WeatherFetchingExceptionApi; +import eirb.pg203.weather.utils.City; +import eirb.pg203.weather.exceptions.WeatherFetchingException; +import eirb.pg203.weather.exceptions.WeatherFetchingExceptionApi; +import eirb.pg203.weather.data.WeatherData; import org.json.JSONArray; import org.json.JSONObject; -import eirb.pg203.WeatherData.Condition; -import eirb.pg203.utils.JSONFetcher; +import eirb.pg203.weather.data.WeatherData.Condition; +import eirb.pg203.weather.utils.JSONFetcher; import java.io.IOException; import java.net.MalformedURLException; @@ -24,7 +26,7 @@ public class WeatherAPI implements WeatherDataAPI{ JSONFetcher JSONFetcher = new JSONFetcher(); private static final String forecastBaseURL = "https://api.weatherapi.com/v1/forecast.json"; - WeatherAPI(String weatherAPIKey) { + public WeatherAPI(String weatherAPIKey) { this.weatherAPIKey = weatherAPIKey; } @@ -49,7 +51,7 @@ public class WeatherAPI implements WeatherDataAPI{ } } - private static WeatherData.Condition getConditionFromString(String str) { + private static Condition getConditionFromString(String str) { if (str.toLowerCase().contains("rain")) return Condition.RAINY; diff --git a/src/main/java/eirb/pg203/WeatherDataAPI.java b/src/main/java/eirb/pg203/weather/data/api/WeatherDataAPI.java similarity index 89% rename from src/main/java/eirb/pg203/WeatherDataAPI.java rename to src/main/java/eirb/pg203/weather/data/api/WeatherDataAPI.java index e9bc5c6..7c922ec 100644 --- a/src/main/java/eirb/pg203/WeatherDataAPI.java +++ b/src/main/java/eirb/pg203/weather/data/api/WeatherDataAPI.java @@ -1,8 +1,8 @@ -package eirb.pg203; +package eirb.pg203.weather.data.api; -import eirb.pg203.exceptions.WeatherFetchingException; +import eirb.pg203.weather.exceptions.WeatherFetchingException; +import eirb.pg203.weather.data.WeatherData; -import java.io.IOException; import java.util.ArrayList; /** diff --git a/src/main/java/eirb/pg203/WeatherDisplay.java b/src/main/java/eirb/pg203/weather/display/WeatherDisplay.java similarity index 84% rename from src/main/java/eirb/pg203/WeatherDisplay.java rename to src/main/java/eirb/pg203/weather/display/WeatherDisplay.java index d6adc3d..6aaf15c 100644 --- a/src/main/java/eirb/pg203/WeatherDisplay.java +++ b/src/main/java/eirb/pg203/weather/display/WeatherDisplay.java @@ -1,4 +1,6 @@ -package eirb.pg203; +package eirb.pg203.weather.display; + +import eirb.pg203.weather.data.api.WeatherDataAPI; /** * How to display weather information, make the API calls based on the collection of WheatherDataAPI diff --git a/src/main/java/eirb/pg203/WeatherDisplayBasic.java b/src/main/java/eirb/pg203/weather/display/WeatherDisplayBasic.java similarity index 95% rename from src/main/java/eirb/pg203/WeatherDisplayBasic.java rename to src/main/java/eirb/pg203/weather/display/WeatherDisplayBasic.java index 887a27f..408d4a8 100644 --- a/src/main/java/eirb/pg203/WeatherDisplayBasic.java +++ b/src/main/java/eirb/pg203/weather/display/WeatherDisplayBasic.java @@ -1,11 +1,13 @@ -package eirb.pg203; +package eirb.pg203.weather.display; -import eirb.pg203.exceptions.WeatherFetchingException; +import eirb.pg203.weather.exceptions.WeatherFetchingException; +import eirb.pg203.weather.data.WeatherData; +import eirb.pg203.weather.data.api.WeatherDataAPI; import java.util.ArrayList; import java.util.HashMap; -class WeatherDisplayBasic implements WeatherDisplay { +public class WeatherDisplayBasic implements eirb.pg203.weather.display.WeatherDisplay { /** * List of apis */ diff --git a/src/main/java/eirb/pg203/exceptions/WeatherFetchingException.java b/src/main/java/eirb/pg203/weather/exceptions/WeatherFetchingException.java similarity index 88% rename from src/main/java/eirb/pg203/exceptions/WeatherFetchingException.java rename to src/main/java/eirb/pg203/weather/exceptions/WeatherFetchingException.java index bf286f4..bd6ff28 100644 --- a/src/main/java/eirb/pg203/exceptions/WeatherFetchingException.java +++ b/src/main/java/eirb/pg203/weather/exceptions/WeatherFetchingException.java @@ -1,4 +1,4 @@ -package eirb.pg203.exceptions; +package eirb.pg203.weather.exceptions; /** * Exception when an error during the api call diff --git a/src/main/java/eirb/pg203/weather/exceptions/WeatherFetchingExceptionApi.java b/src/main/java/eirb/pg203/weather/exceptions/WeatherFetchingExceptionApi.java new file mode 100644 index 0000000..16bdf62 --- /dev/null +++ b/src/main/java/eirb/pg203/weather/exceptions/WeatherFetchingExceptionApi.java @@ -0,0 +1,7 @@ +package eirb.pg203.weather.exceptions; + +public class WeatherFetchingExceptionApi extends eirb.pg203.weather.exceptions.WeatherFetchingException { + public WeatherFetchingExceptionApi() { + super("An error occurred during API fetching"); + } +} diff --git a/src/main/java/eirb/pg203/weather/exceptions/WeatherFetchingExceptionCityCoords.java b/src/main/java/eirb/pg203/weather/exceptions/WeatherFetchingExceptionCityCoords.java new file mode 100644 index 0000000..0a71eda --- /dev/null +++ b/src/main/java/eirb/pg203/weather/exceptions/WeatherFetchingExceptionCityCoords.java @@ -0,0 +1,7 @@ +package eirb.pg203.weather.exceptions; + +public class WeatherFetchingExceptionCityCoords extends eirb.pg203.weather.exceptions.WeatherFetchingException { + public WeatherFetchingExceptionCityCoords() { + super("Impossible to get city coords"); + } +} diff --git a/src/main/java/eirb/pg203/City.java b/src/main/java/eirb/pg203/weather/utils/City.java similarity index 93% rename from src/main/java/eirb/pg203/City.java rename to src/main/java/eirb/pg203/weather/utils/City.java index 5084fae..7d76044 100644 --- a/src/main/java/eirb/pg203/City.java +++ b/src/main/java/eirb/pg203/weather/utils/City.java @@ -1,17 +1,16 @@ -package eirb.pg203; +package eirb.pg203.weather.utils; import java.io.IOException; import java.net.URI; import java.net.URL; import java.util.Locale; -import eirb.pg203.utils.JSONFetcher; +import eirb.pg203.weather.utils.Coords; +import eirb.pg203.weather.utils.JSONFetcher; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import eirb.pg203.utils.Coords; - /** * Representation of a city * Possibility to get city coordinates based on the name @@ -54,7 +53,7 @@ public class City { return new Coords(lat, lon); } - City (String cityName) { + public City (String cityName) { this.cityName = cityName; } diff --git a/src/main/java/eirb/pg203/utils/Coords.java b/src/main/java/eirb/pg203/weather/utils/Coords.java similarity index 95% rename from src/main/java/eirb/pg203/utils/Coords.java rename to src/main/java/eirb/pg203/weather/utils/Coords.java index 4308549..c78effc 100644 --- a/src/main/java/eirb/pg203/utils/Coords.java +++ b/src/main/java/eirb/pg203/weather/utils/Coords.java @@ -1,4 +1,4 @@ -package eirb.pg203.utils; +package eirb.pg203.weather.utils; /** * Coordinates representation diff --git a/src/main/java/eirb/pg203/utils/JSONFetcher.java b/src/main/java/eirb/pg203/weather/utils/JSONFetcher.java similarity index 80% rename from src/main/java/eirb/pg203/utils/JSONFetcher.java rename to src/main/java/eirb/pg203/weather/utils/JSONFetcher.java index 49d7799..be1b544 100644 --- a/src/main/java/eirb/pg203/utils/JSONFetcher.java +++ b/src/main/java/eirb/pg203/weather/utils/JSONFetcher.java @@ -1,4 +1,4 @@ -package eirb.pg203.utils; +package eirb.pg203.weather.utils; import java.io.BufferedReader; import java.io.IOException; @@ -49,16 +49,4 @@ public class JSONFetcher { return new JSONObject(result); } - - /** - * Fetch a Json array from an url - * @param url url - * @return JSON array - * @throws IOException when request failed - */ - public JSONArray fetchArray(URL url) throws IOException { - String result = fetchString(url); - - return new JSONArray(result); - } } diff --git a/src/test/java/eirb/pg203/WeatherDisplayBasicTest.java b/src/test/java/eirb/pg203/weather/WeatherDisplayBasicTest.java similarity index 90% rename from src/test/java/eirb/pg203/WeatherDisplayBasicTest.java rename to src/test/java/eirb/pg203/weather/WeatherDisplayBasicTest.java index 23656f2..69c2ea2 100644 --- a/src/test/java/eirb/pg203/WeatherDisplayBasicTest.java +++ b/src/test/java/eirb/pg203/weather/WeatherDisplayBasicTest.java @@ -1,17 +1,19 @@ -package eirb.pg203; +package eirb.pg203.weather; +import eirb.pg203.weather.display.WeatherDisplayBasic; +import eirb.pg203.weather.data.api.OpenMeteo; +import eirb.pg203.weather.data.api.OpenWeatherMap; +import eirb.pg203.weather.data.api.WeatherAPI; import org.junit.jupiter.api.*; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.MethodSource; -import org.junit.jupiter.params.provider.ValueSource; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.util.stream.Stream; -import static eirb.pg203.WeatherDataAPITest.*; +import static eirb.pg203.weather.data.api.WeatherDataAPITest.*; public class WeatherDisplayBasicTest { private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); diff --git a/src/test/java/eirb/pg203/WeatherDataTest.java b/src/test/java/eirb/pg203/weather/data/WeatherDataTest.java similarity index 97% rename from src/test/java/eirb/pg203/WeatherDataTest.java rename to src/test/java/eirb/pg203/weather/data/WeatherDataTest.java index 5dac2bc..bbf7a5c 100644 --- a/src/test/java/eirb/pg203/WeatherDataTest.java +++ b/src/test/java/eirb/pg203/weather/data/WeatherDataTest.java @@ -1,5 +1,6 @@ -package eirb.pg203; +package eirb.pg203.weather.data; +import eirb.pg203.weather.utils.City; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/src/test/java/eirb/pg203/WeatherAPITest.java b/src/test/java/eirb/pg203/weather/data/api/WeatherAPITest.java similarity index 62% rename from src/test/java/eirb/pg203/WeatherAPITest.java rename to src/test/java/eirb/pg203/weather/data/api/WeatherAPITest.java index 907ab10..3ecaeb0 100644 --- a/src/test/java/eirb/pg203/WeatherAPITest.java +++ b/src/test/java/eirb/pg203/weather/data/api/WeatherAPITest.java @@ -1,16 +1,9 @@ -package eirb.pg203; +package eirb.pg203.weather.data.api; -import eirb.pg203.fakeJSONFetcher.FakeJSONFetcherWeatherAPI; +import eirb.pg203.weather.fakeJSONFetcher.FakeJSONFetcherWeatherAPI; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.*; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.stream.Stream; public class WeatherAPITest { private static final String APIKey = "realKey"; diff --git a/src/test/java/eirb/pg203/WeatherDataAPITest.java b/src/test/java/eirb/pg203/weather/data/api/WeatherDataAPITest.java similarity index 92% rename from src/test/java/eirb/pg203/WeatherDataAPITest.java rename to src/test/java/eirb/pg203/weather/data/api/WeatherDataAPITest.java index 738b262..4d40335 100644 --- a/src/test/java/eirb/pg203/WeatherDataAPITest.java +++ b/src/test/java/eirb/pg203/weather/data/api/WeatherDataAPITest.java @@ -1,17 +1,15 @@ -package eirb.pg203; +package eirb.pg203.weather.data.api; -import eirb.pg203.exceptions.WeatherFetchingException; -import eirb.pg203.fakeJSONFetcher.FakeJSONFetcherOpenMeteo; -import eirb.pg203.fakeJSONFetcher.FakeJSONFetcherOpenWeatherMap; -import eirb.pg203.fakeJSONFetcher.FakeJSONFetcherWeatherAPI; +import eirb.pg203.weather.fakeJSONFetcher.FakeJSONFetcherOpenMeteo; +import eirb.pg203.weather.fakeJSONFetcher.FakeJSONFetcherOpenWeatherMap; +import eirb.pg203.weather.fakeJSONFetcher.FakeJSONFetcherWeatherAPI; +import eirb.pg203.weather.exceptions.WeatherFetchingException; +import eirb.pg203.weather.data.WeatherData; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import java.io.IOException; import java.time.Clock; import java.time.Instant; import java.time.ZoneId; @@ -22,13 +20,13 @@ public class WeatherDataAPITest { private static final float epsilon = 0.01F; private static final String APIKey = "realKey"; - static WeatherAPI weatherAPI(){ + public static WeatherAPI weatherAPI(){ WeatherAPI weatherAPI = new WeatherAPI(APIKey); weatherAPI.JSONFetcher = new FakeJSONFetcherWeatherAPI(); return weatherAPI; } - static OpenWeatherMap openWeatherMap(){ + public static OpenWeatherMap openWeatherMap(){ // Fix clock for testing String instantExpected = "2024-11-24T00:00:00.00Z"; Clock clock = Clock.fixed(Instant.parse(instantExpected), ZoneId.systemDefault()); @@ -39,7 +37,7 @@ public class WeatherDataAPITest { return openWeatherMap; } - static OpenMeteo openMeteo() { + public static OpenMeteo openMeteo() { // Fix clock for testing String instantExpected = "2024-11-24T00:00:00.00Z"; Clock clock = Clock.fixed(Instant.parse(instantExpected), ZoneId.systemDefault()); @@ -59,7 +57,7 @@ public class WeatherDataAPITest { return Stream.of( /* WeatherAPI */ - Arguments.arguments(weatherAPI(), 0, 8.1F,WeatherData.Condition.PARTIAL, 17.45F, 142.08F), + Arguments.arguments(weatherAPI(), 0, 8.1F, WeatherData.Condition.PARTIAL, 17.45F, 142.08F), Arguments.arguments(weatherAPI(), 1, 13F, WeatherData.Condition.SUNNY, 23.03F, 142.58F), Arguments.arguments(weatherAPI(), 2, 12.7F, WeatherData.Condition.RAINY, 13.19F, 222.92F), Arguments.arguments(weatherAPI(), 3, 8.1F,WeatherData.Condition.CLOUDY, 17.45F, 142.08F), diff --git a/src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherCity.java b/src/test/java/eirb/pg203/weather/fakeJSONFetcher/FakeJSONFetcherCity.java similarity index 60% rename from src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherCity.java rename to src/test/java/eirb/pg203/weather/fakeJSONFetcher/FakeJSONFetcherCity.java index 8634f12..f9eb3c2 100644 --- a/src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherCity.java +++ b/src/test/java/eirb/pg203/weather/fakeJSONFetcher/FakeJSONFetcherCity.java @@ -1,8 +1,8 @@ -package eirb.pg203.fakeJSONFetcher; +package eirb.pg203.weather.fakeJSONFetcher; -import eirb.pg203.utils.FileResourcesUtils; -import eirb.pg203.utils.JSONFetcher; -import eirb.pg203.utils.SplitQueryUrl; +import eirb.pg203.weather.utils.FileResourcesUtils; +import eirb.pg203.weather.utils.SplitQueryUrl; +import eirb.pg203.weather.utils.JSONFetcher; import org.json.JSONArray; import org.json.JSONObject; @@ -17,9 +17,9 @@ public class FakeJSONFetcherCity extends JSONFetcher{ private static HashMap cities(){ HashMap cities = new HashMap<>(); - cities.put("bordeaux", FileResourcesUtils.getFileFromResourceAsJson("City/bordeaux.json")); - cities.put("paris", FileResourcesUtils.getFileFromResourceAsJson("City/paris.json")); - cities.put("unknown", FileResourcesUtils.getFileFromResourceAsJson("City/fakeCity.json")); + cities.put("bordeaux", eirb.pg203.weather.utils.FileResourcesUtils.getFileFromResourceAsJson("City/bordeaux.json")); + cities.put("paris", eirb.pg203.weather.utils.FileResourcesUtils.getFileFromResourceAsJson("City/paris.json")); + cities.put("unknown", eirb.pg203.weather.utils.FileResourcesUtils.getFileFromResourceAsJson("City/fakeCity.json")); return cities; } diff --git a/src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherOpenMeteo.java b/src/test/java/eirb/pg203/weather/fakeJSONFetcher/FakeJSONFetcherOpenMeteo.java similarity index 79% rename from src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherOpenMeteo.java rename to src/test/java/eirb/pg203/weather/fakeJSONFetcher/FakeJSONFetcherOpenMeteo.java index 9630f7f..fd9e5e5 100644 --- a/src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherOpenMeteo.java +++ b/src/test/java/eirb/pg203/weather/fakeJSONFetcher/FakeJSONFetcherOpenMeteo.java @@ -1,7 +1,7 @@ -package eirb.pg203.fakeJSONFetcher; +package eirb.pg203.weather.fakeJSONFetcher; -import eirb.pg203.utils.FileResourcesUtils; -import eirb.pg203.utils.JSONFetcher; +import eirb.pg203.weather.utils.FileResourcesUtils; +import eirb.pg203.weather.utils.JSONFetcher; import org.json.JSONArray; import org.json.JSONObject; diff --git a/src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherOpenWeatherMap.java b/src/test/java/eirb/pg203/weather/fakeJSONFetcher/FakeJSONFetcherOpenWeatherMap.java similarity index 70% rename from src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherOpenWeatherMap.java rename to src/test/java/eirb/pg203/weather/fakeJSONFetcher/FakeJSONFetcherOpenWeatherMap.java index 2561930..5d50e75 100644 --- a/src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherOpenWeatherMap.java +++ b/src/test/java/eirb/pg203/weather/fakeJSONFetcher/FakeJSONFetcherOpenWeatherMap.java @@ -1,8 +1,8 @@ -package eirb.pg203.fakeJSONFetcher; +package eirb.pg203.weather.fakeJSONFetcher; -import eirb.pg203.utils.FileResourcesUtils; -import eirb.pg203.utils.JSONFetcher; -import eirb.pg203.utils.SplitQueryUrl; +import eirb.pg203.weather.utils.FileResourcesUtils; +import eirb.pg203.weather.utils.SplitQueryUrl; +import eirb.pg203.weather.utils.JSONFetcher; import org.json.JSONArray; import org.json.JSONObject; @@ -15,7 +15,7 @@ public class FakeJSONFetcherOpenWeatherMap extends JSONFetcher { private final JSONObject wrongKeyResponse = FileResourcesUtils.getFileFromResourceAsJson("OpenWeatherMap/wrong-apikey.json"); private JSONObject responseExample() { - return FileResourcesUtils.getFileFromResourceAsJson("OpenWeatherMap/Bordeaux-partial-cloudy-rain-sunny.json"); + return eirb.pg203.weather.utils.FileResourcesUtils.getFileFromResourceAsJson("OpenWeatherMap/Bordeaux-partial-cloudy-rain-sunny.json"); } @Override public JSONObject fetch(URL url) throws IOException { diff --git a/src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherWeatherAPI.java b/src/test/java/eirb/pg203/weather/fakeJSONFetcher/FakeJSONFetcherWeatherAPI.java similarity index 59% rename from src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherWeatherAPI.java rename to src/test/java/eirb/pg203/weather/fakeJSONFetcher/FakeJSONFetcherWeatherAPI.java index e5d4170..3f8ab6f 100644 --- a/src/test/java/eirb/pg203/fakeJSONFetcher/FakeJSONFetcherWeatherAPI.java +++ b/src/test/java/eirb/pg203/weather/fakeJSONFetcher/FakeJSONFetcherWeatherAPI.java @@ -1,8 +1,8 @@ -package eirb.pg203.fakeJSONFetcher; +package eirb.pg203.weather.fakeJSONFetcher; -import eirb.pg203.utils.FileResourcesUtils; -import eirb.pg203.utils.JSONFetcher; -import eirb.pg203.utils.SplitQueryUrl; +import eirb.pg203.weather.utils.FileResourcesUtils; +import eirb.pg203.weather.utils.SplitQueryUrl; +import eirb.pg203.weather.utils.JSONFetcher; import org.json.JSONArray; import org.json.JSONObject; @@ -17,10 +17,10 @@ public class FakeJSONFetcherWeatherAPI extends JSONFetcher { private static final JSONObject wrongKeyRequest = FileResourcesUtils.getFileFromResourceAsJson("WeatherAPI/wrong-apikey.json"); private static ArrayList bordeauxRequests() { ArrayList bordeauxRequest = new ArrayList<>(); - bordeauxRequest.add(FileResourcesUtils.getFileFromResourceAsJson("WeatherAPI/Bordeaux-1-partial.json")); - bordeauxRequest.add(FileResourcesUtils.getFileFromResourceAsJson("WeatherAPI/Bordeaux-2-partial-sunny.json")); - bordeauxRequest.add(FileResourcesUtils.getFileFromResourceAsJson("WeatherAPI/Bordeaux-3-partial-sunny-rain.json")); - bordeauxRequest.add(FileResourcesUtils.getFileFromResourceAsJson("WeatherAPI/Bordeaux-4-partial-sunny-rain-cloudy.json")); + bordeauxRequest.add(eirb.pg203.weather.utils.FileResourcesUtils.getFileFromResourceAsJson("WeatherAPI/Bordeaux-1-partial.json")); + bordeauxRequest.add(eirb.pg203.weather.utils.FileResourcesUtils.getFileFromResourceAsJson("WeatherAPI/Bordeaux-2-partial-sunny.json")); + bordeauxRequest.add(eirb.pg203.weather.utils.FileResourcesUtils.getFileFromResourceAsJson("WeatherAPI/Bordeaux-3-partial-sunny-rain.json")); + bordeauxRequest.add(eirb.pg203.weather.utils.FileResourcesUtils.getFileFromResourceAsJson("WeatherAPI/Bordeaux-4-partial-sunny-rain-cloudy.json")); return bordeauxRequest; } diff --git a/src/test/java/eirb/pg203/CityTest.java b/src/test/java/eirb/pg203/weather/utils/CityTest.java similarity index 94% rename from src/test/java/eirb/pg203/CityTest.java rename to src/test/java/eirb/pg203/weather/utils/CityTest.java index 2dad7b6..a0833ff 100644 --- a/src/test/java/eirb/pg203/CityTest.java +++ b/src/test/java/eirb/pg203/weather/utils/CityTest.java @@ -1,7 +1,6 @@ -package eirb.pg203; +package eirb.pg203.weather.utils; -import eirb.pg203.fakeJSONFetcher.FakeJSONFetcherCity; -import eirb.pg203.utils.Coords; +import eirb.pg203.weather.fakeJSONFetcher.FakeJSONFetcherCity; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; diff --git a/src/test/java/eirb/pg203/utils/CoordsTest.java b/src/test/java/eirb/pg203/weather/utils/CoordsTest.java similarity index 70% rename from src/test/java/eirb/pg203/utils/CoordsTest.java rename to src/test/java/eirb/pg203/weather/utils/CoordsTest.java index 374e731..c2a24e0 100644 --- a/src/test/java/eirb/pg203/utils/CoordsTest.java +++ b/src/test/java/eirb/pg203/weather/utils/CoordsTest.java @@ -1,6 +1,5 @@ -package eirb.pg203.utils; +package eirb.pg203.weather.utils; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; @@ -14,7 +13,7 @@ class CoordsTest { float lon = 2f; Coords coords = new Coords(lat, lon); - Assertions.assertEquals(lat, coords.getLat(), epsilon); + assertEquals(lat, coords.getLat(), epsilon); } @Test @@ -23,7 +22,7 @@ class CoordsTest { float lon = 2f; Coords coords = new Coords(lat, lon); - Assertions.assertEquals(lon, coords.getLon(), epsilon); + assertEquals(lon, coords.getLon(), epsilon); } @Test @@ -35,7 +34,7 @@ class CoordsTest { float sndLat = 3f; coords.setLat(sndLat); - Assertions.assertEquals(sndLat, coords.getLat(), epsilon); + assertEquals(sndLat, coords.getLat(), epsilon); } @@ -48,7 +47,7 @@ class CoordsTest { float sndLon = 4f; coords.setLon(sndLon); - Assertions.assertEquals(sndLon, coords.getLon(), epsilon); + assertEquals(sndLon, coords.getLon(), epsilon); } } \ No newline at end of file diff --git a/src/test/java/eirb/pg203/utils/FileResourcesUtils.java b/src/test/java/eirb/pg203/weather/utils/FileResourcesUtils.java similarity index 97% rename from src/test/java/eirb/pg203/utils/FileResourcesUtils.java rename to src/test/java/eirb/pg203/weather/utils/FileResourcesUtils.java index 8398e38..2e83ade 100644 --- a/src/test/java/eirb/pg203/utils/FileResourcesUtils.java +++ b/src/test/java/eirb/pg203/weather/utils/FileResourcesUtils.java @@ -1,4 +1,4 @@ -package eirb.pg203.utils; +package eirb.pg203.weather.utils; import org.json.JSONObject; diff --git a/src/test/java/eirb/pg203/utils/SplitQueryUrl.java b/src/test/java/eirb/pg203/weather/utils/SplitQueryUrl.java similarity index 95% rename from src/test/java/eirb/pg203/utils/SplitQueryUrl.java rename to src/test/java/eirb/pg203/weather/utils/SplitQueryUrl.java index 00819c6..881a9ab 100644 --- a/src/test/java/eirb/pg203/utils/SplitQueryUrl.java +++ b/src/test/java/eirb/pg203/weather/utils/SplitQueryUrl.java @@ -1,4 +1,4 @@ -package eirb.pg203.utils; +package eirb.pg203.weather.utils; import java.io.UnsupportedEncodingException; import java.net.URL; From 43425a742b3f98835187744f1eacb89eebb18c9c Mon Sep 17 00:00:00 2001 From: Martin Eyben Date: Mon, 2 Dec 2024 10:20:29 +0000 Subject: [PATCH 28/29] feat: remove JSONArray from fetcher --- .../pg203/weather/fakeJSONFetcher/FakeJSONFetcherCity.java | 5 ----- .../weather/fakeJSONFetcher/FakeJSONFetcherOpenMeteo.java | 4 ---- .../fakeJSONFetcher/FakeJSONFetcherOpenWeatherMap.java | 5 ----- .../weather/fakeJSONFetcher/FakeJSONFetcherWeatherAPI.java | 5 ----- 4 files changed, 19 deletions(-) diff --git a/src/test/java/eirb/pg203/weather/fakeJSONFetcher/FakeJSONFetcherCity.java b/src/test/java/eirb/pg203/weather/fakeJSONFetcher/FakeJSONFetcherCity.java index f9eb3c2..80d00b4 100644 --- a/src/test/java/eirb/pg203/weather/fakeJSONFetcher/FakeJSONFetcherCity.java +++ b/src/test/java/eirb/pg203/weather/fakeJSONFetcher/FakeJSONFetcherCity.java @@ -31,9 +31,4 @@ public class FakeJSONFetcherCity extends JSONFetcher{ String city = params.get("q").toLowerCase(Locale.ENGLISH); return cities().getOrDefault(city, unknownCity); } - - @Override - public JSONArray fetchArray(URL url) throws IOException { - return null; - } } \ No newline at end of file diff --git a/src/test/java/eirb/pg203/weather/fakeJSONFetcher/FakeJSONFetcherOpenMeteo.java b/src/test/java/eirb/pg203/weather/fakeJSONFetcher/FakeJSONFetcherOpenMeteo.java index fd9e5e5..0a3ae20 100644 --- a/src/test/java/eirb/pg203/weather/fakeJSONFetcher/FakeJSONFetcherOpenMeteo.java +++ b/src/test/java/eirb/pg203/weather/fakeJSONFetcher/FakeJSONFetcherOpenMeteo.java @@ -18,8 +18,4 @@ public class FakeJSONFetcherOpenMeteo extends JSONFetcher { return responseExample(); } - @Override - public JSONArray fetchArray(URL url) throws IOException { - return null; - } } diff --git a/src/test/java/eirb/pg203/weather/fakeJSONFetcher/FakeJSONFetcherOpenWeatherMap.java b/src/test/java/eirb/pg203/weather/fakeJSONFetcher/FakeJSONFetcherOpenWeatherMap.java index 5d50e75..c8270de 100644 --- a/src/test/java/eirb/pg203/weather/fakeJSONFetcher/FakeJSONFetcherOpenWeatherMap.java +++ b/src/test/java/eirb/pg203/weather/fakeJSONFetcher/FakeJSONFetcherOpenWeatherMap.java @@ -24,9 +24,4 @@ public class FakeJSONFetcherOpenWeatherMap extends JSONFetcher { return wrongKeyResponse; return responseExample(); } - - @Override - public JSONArray fetchArray(URL url) throws IOException { - return null; - } } diff --git a/src/test/java/eirb/pg203/weather/fakeJSONFetcher/FakeJSONFetcherWeatherAPI.java b/src/test/java/eirb/pg203/weather/fakeJSONFetcher/FakeJSONFetcherWeatherAPI.java index 3f8ab6f..79b93e9 100644 --- a/src/test/java/eirb/pg203/weather/fakeJSONFetcher/FakeJSONFetcherWeatherAPI.java +++ b/src/test/java/eirb/pg203/weather/fakeJSONFetcher/FakeJSONFetcherWeatherAPI.java @@ -34,9 +34,4 @@ public class FakeJSONFetcherWeatherAPI extends JSONFetcher { return bordeauxRequests().get(days - 1); } - - @Override - public JSONArray fetchArray(URL url) throws IOException { - return null; - } } From 138d65ae423fcc78aa71a45261db353e41ca035b Mon Sep 17 00:00:00 2001 From: Martin Eyben Date: Mon, 2 Dec 2024 11:27:17 +0000 Subject: [PATCH 29/29] feat: enhance tests --- .../pg203/weather/data/api/OpenMeteo.java | 3 +- .../weather/data/api/WeatherDataAPITest.java | 53 +++++++++++++++++++ .../FakeJSONFetcherOpenWeatherMap.java | 2 +- .../FakeJSONFetcherWeatherAPI.java | 2 +- 4 files changed, 57 insertions(+), 3 deletions(-) diff --git a/src/main/java/eirb/pg203/weather/data/api/OpenMeteo.java b/src/main/java/eirb/pg203/weather/data/api/OpenMeteo.java index ad31c79..3ef78fd 100644 --- a/src/main/java/eirb/pg203/weather/data/api/OpenMeteo.java +++ b/src/main/java/eirb/pg203/weather/data/api/OpenMeteo.java @@ -1,5 +1,6 @@ package eirb.pg203.weather.data.api; +import eirb.pg203.weather.exceptions.WeatherFetchingExceptionCityCoords; import eirb.pg203.weather.utils.City; import eirb.pg203.weather.exceptions.WeatherFetchingException; import eirb.pg203.weather.exceptions.WeatherFetchingExceptionApi; @@ -47,7 +48,7 @@ public class OpenMeteo implements WeatherDataAPI { ) ).toURL(); } catch (IOException e) { - throw new WeatherFetchingException("Impossible to get city coords"); + throw new WeatherFetchingExceptionCityCoords(); } try { diff --git a/src/test/java/eirb/pg203/weather/data/api/WeatherDataAPITest.java b/src/test/java/eirb/pg203/weather/data/api/WeatherDataAPITest.java index 4d40335..3c53967 100644 --- a/src/test/java/eirb/pg203/weather/data/api/WeatherDataAPITest.java +++ b/src/test/java/eirb/pg203/weather/data/api/WeatherDataAPITest.java @@ -1,5 +1,7 @@ package eirb.pg203.weather.data.api; +import eirb.pg203.weather.exceptions.WeatherFetchingExceptionApi; +import eirb.pg203.weather.exceptions.WeatherFetchingExceptionCityCoords; import eirb.pg203.weather.fakeJSONFetcher.FakeJSONFetcherOpenMeteo; import eirb.pg203.weather.fakeJSONFetcher.FakeJSONFetcherOpenWeatherMap; import eirb.pg203.weather.fakeJSONFetcher.FakeJSONFetcherWeatherAPI; @@ -19,6 +21,18 @@ import java.util.stream.Stream; public class WeatherDataAPITest { private static final float epsilon = 0.01F; private static final String APIKey = "realKey"; + private static final String wrongAPIKey = "wrongKey"; + + private static WeatherAPI wrongWeatherAPI() { + WeatherAPI weatherAPI = new WeatherAPI(wrongAPIKey); + weatherAPI.JSONFetcher = new FakeJSONFetcherWeatherAPI(); + return weatherAPI; + } + private static OpenWeatherMap wrongOpenWeatherMap() { + OpenWeatherMap weatherAPI = new OpenWeatherMap(wrongAPIKey); + weatherAPI.JSONFetcher = new FakeJSONFetcherOpenWeatherMap(); + return weatherAPI; + } public static WeatherAPI weatherAPI(){ WeatherAPI weatherAPI = new WeatherAPI(APIKey); @@ -155,6 +169,45 @@ public class WeatherDataAPITest { Assertions.assertAll( () -> weatherDataAPI.getTemperature(0,1, city) ); + } + + private static Stream testUnkowncity() { + return Stream.of( + Arguments.arguments(openWeatherMap()), + Arguments.arguments(openMeteo()) + ); + } + + @ParameterizedTest(name = "[{0}] Get temperature from missing city") + @MethodSource + public void testUnkowncity(WeatherDataAPI weatherDataAPI) { + String unknownCity = "jlkfreajflkj"; + Assertions.assertThrows(WeatherFetchingExceptionCityCoords.class, + () -> weatherDataAPI.getTemperature(3, unknownCity) + ); + Assertions.assertThrows(WeatherFetchingExceptionCityCoords.class, + () -> weatherDataAPI.getTemperatures(3, unknownCity) + ); + } + + private static Stream testWrongApiKey() { + return Stream.of( + Arguments.arguments(wrongWeatherAPI()), + Arguments.arguments(wrongOpenWeatherMap()) + ); + } + + @ParameterizedTest(name = "[{0}] Wrong API Key throws exception") + @MethodSource + public void testWrongApiKey(WeatherDataAPI weatherDataAPI) { + String city = "Bordeaux"; + Assertions.assertThrows(WeatherFetchingExceptionApi.class, + () -> weatherDataAPI.getTemperatures(3, city) + ); + + Assertions.assertThrows(WeatherFetchingExceptionApi.class, + () -> weatherDataAPI.getTemperature(3, city) + ); } } diff --git a/src/test/java/eirb/pg203/weather/fakeJSONFetcher/FakeJSONFetcherOpenWeatherMap.java b/src/test/java/eirb/pg203/weather/fakeJSONFetcher/FakeJSONFetcherOpenWeatherMap.java index c8270de..bcf5336 100644 --- a/src/test/java/eirb/pg203/weather/fakeJSONFetcher/FakeJSONFetcherOpenWeatherMap.java +++ b/src/test/java/eirb/pg203/weather/fakeJSONFetcher/FakeJSONFetcherOpenWeatherMap.java @@ -21,7 +21,7 @@ public class FakeJSONFetcherOpenWeatherMap extends JSONFetcher { public JSONObject fetch(URL url) throws IOException { Map params = SplitQueryUrl.splitQuery(url); if (!params.getOrDefault("appid", "").contentEquals(apiKey)) - return wrongKeyResponse; + throw new IOException(); return responseExample(); } } diff --git a/src/test/java/eirb/pg203/weather/fakeJSONFetcher/FakeJSONFetcherWeatherAPI.java b/src/test/java/eirb/pg203/weather/fakeJSONFetcher/FakeJSONFetcherWeatherAPI.java index 79b93e9..887452e 100644 --- a/src/test/java/eirb/pg203/weather/fakeJSONFetcher/FakeJSONFetcherWeatherAPI.java +++ b/src/test/java/eirb/pg203/weather/fakeJSONFetcher/FakeJSONFetcherWeatherAPI.java @@ -30,7 +30,7 @@ public class FakeJSONFetcherWeatherAPI extends JSONFetcher { int days = Integer.parseInt(params.get("days")); if (!params.get("key").contentEquals(apiKey)) - return wrongKeyRequest; + throw new IOException(); return bordeauxRequests().get(days - 1); }