ffmerge
This commit is contained in:
commit
4fc4374583
48
DESIGN.md
48
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?
|
||||
Comment utilisez-vous les exceptions dans votre programme ?
|
||||
|
||||
Nous utilisons WeatherFetchingException qui est une Exception qui est envoyée lorsqu'
|
||||
|
@ -4,12 +4,26 @@ import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
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;
|
||||
import java.util.Scanner;
|
||||
|
||||
/**
|
||||
* Main class
|
||||
*/
|
||||
public class Main {
|
||||
|
||||
/**
|
||||
* Default constructor (private)
|
||||
*/
|
||||
private Main() {};
|
||||
|
||||
private static class Option {
|
||||
String flag, value;
|
||||
public Option(String flag, String value){
|
||||
@ -31,6 +45,11 @@ public class Main {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 IOException, IllegalArgumentException{
|
||||
HashMap<String, Option> options = new HashMap<>();
|
||||
List<String> argsList = new ArrayList<>();
|
||||
@ -69,8 +88,6 @@ public class Main {
|
||||
openWeatherMap.loadCacheFromFile("owm.cache.json");
|
||||
openMeteo.loadCacheFromFile("om.cache.json");
|
||||
|
||||
System.out.println(weatherAPI.toJSON().toString());
|
||||
|
||||
WeatherDisplay display = new WeatherDisplayBasic();
|
||||
|
||||
display.addAPI(weatherAPI);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
package eirb.pg203;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public interface WeatherDataAPI {
|
||||
|
||||
/**
|
||||
* Fetch the temperature for a specific day in a city
|
||||
* @param day Since D+0
|
||||
* @param city Localisation
|
||||
* @return Temperature of the day from the city
|
||||
* @throws IOException when request failed
|
||||
*/
|
||||
WeatherData getTemperature(int day, String city) throws IOException;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param day Since D+0
|
||||
* @param hour Since H+0
|
||||
* @param city Localisation
|
||||
* @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;
|
||||
|
||||
ArrayList<WeatherData> getTemperatures(int days, String city) throws IOException;
|
||||
|
||||
/***
|
||||
* Name of the API
|
||||
* @return Name of the API
|
||||
*/
|
||||
String getAPIName();
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package eirb.pg203.exceptions;
|
||||
|
||||
public class WeatherFetchingException extends Exception {
|
||||
public WeatherFetchingException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
package eirb.pg203.utils;
|
||||
|
||||
public class Coords {
|
||||
private float lat;
|
||||
private float lon;
|
||||
|
||||
public Coords(float lat, float lon) {
|
||||
this.lat = lat;
|
||||
this.lon = lon;
|
||||
}
|
||||
|
||||
public float getLat() {
|
||||
return lat;
|
||||
}
|
||||
|
||||
public float getLon() {
|
||||
return lon;
|
||||
}
|
||||
|
||||
public void setLat(float lat) {
|
||||
this.lat = lat;
|
||||
}
|
||||
|
||||
public void setLon(float lon) {
|
||||
this.lon = lon;
|
||||
}
|
||||
}
|
@ -1,14 +1,12 @@
|
||||
package eirb.pg203;
|
||||
package eirb.pg203.weather.data;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import com.sun.net.httpserver.Authenticator.Retry;
|
||||
import eirb.pg203.weather.utils.City;
|
||||
|
||||
class WeatherData {
|
||||
enum Condition {
|
||||
public class WeatherData {
|
||||
public enum Condition {
|
||||
SUNNY("☀️"),
|
||||
PARTIAL("🌤"),
|
||||
CLOUDY("☁️"),
|
||||
@ -115,7 +113,8 @@ class WeatherData {
|
||||
return WindDirection.ERROR;
|
||||
|
||||
}
|
||||
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;
|
263
src/main/java/eirb/pg203/weather/data/WeatherData.java.tmp
Normal file
263
src/main/java/eirb/pg203/weather/data/WeatherData.java.tmp
Normal file
@ -0,0 +1,263 @@
|
||||
package eirb.pg203.weather.data;
|
||||
|
||||
import eirb.pg203.weather.utils.City;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
Condition(String desc) { this.desc = desc; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.desc;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
WindDirection(String desc) {this.desc = desc;}
|
||||
|
||||
@Override
|
||||
public String toString(){ return this.desc;};
|
||||
}
|
||||
|
||||
|
||||
private City city;
|
||||
private Instant date;
|
||||
private float temp;
|
||||
private Condition condition; // cloudly, sunny ...
|
||||
private float windSpeed;
|
||||
private float windDirectionAngle;
|
||||
private 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 <= 360) || (windDirectionAngle >= 0 && windDirectionAngle <= 22.5))
|
||||
return WindDirection.N;
|
||||
if (windDirectionAngle > 22.5 && windDirectionAngle <= 67.5)
|
||||
return WindDirection.NE;
|
||||
if (windDirectionAngle > 67.5 && windDirectionAngle <= 112.5)
|
||||
return WindDirection.E;
|
||||
if (windDirectionAngle > 112.5 && windDirectionAngle <= 157.5)
|
||||
return WindDirection.SE;
|
||||
if (windDirectionAngle > 157.5 && windDirectionAngle <= 202.5)
|
||||
return WindDirection.S;
|
||||
if (windDirectionAngle > 202.5 && windDirectionAngle <= 247.5)
|
||||
return WindDirection.SW;
|
||||
if (windDirectionAngle > 247.5 && windDirectionAngle <= 292.5)
|
||||
return WindDirection.W;
|
||||
if (windDirectionAngle > 292.5 && windDirectionAngle <= 337.5)
|
||||
return WindDirection.NW;
|
||||
|
||||
return WindDirection.ERROR;
|
||||
|
||||
}
|
||||
|
||||
public WeatherData(City city, Instant date, float temp, float windSpeed, float windDirectionAngle, Condition condition) {
|
||||
this.city = city;
|
||||
this.date = date;
|
||||
this.temp = temp;
|
||||
this.condition = condition;
|
||||
this.windSpeed = windSpeed;
|
||||
this.windDirectionAngle = windDirectionAngle;
|
||||
this.windDirection = this.getWindDirection(windDirectionAngle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get city from where the weather data come from
|
||||
* @return city
|
||||
*/
|
||||
public City getCity() {
|
||||
return city;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get date of the weather data
|
||||
* @return date of the Weather data
|
||||
*/
|
||||
public Instant getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get weather condition representation
|
||||
* @return Weather Condition representation
|
||||
*/
|
||||
public Condition getCondition() {
|
||||
return condition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get temperature value
|
||||
* @return float temperature
|
||||
*/
|
||||
public float getTemp() {
|
||||
return temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get wind speed value
|
||||
* @return wind speed
|
||||
*/
|
||||
public float getWindSpeed() {
|
||||
return windSpeed;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set temperature value
|
||||
* @param temp Weather data temperature
|
||||
*/
|
||||
public void setTemp(float temp) {
|
||||
this.temp = temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set wind speed value
|
||||
* @param windSpeed Wind speed
|
||||
*/
|
||||
public void setWindSpeed(float windSpeed) {
|
||||
this.windSpeed = windSpeed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set wind direction angle value
|
||||
* @param windDirectionAngle wind direction angle
|
||||
*/
|
||||
public void setWindDirectionAngle(float windDirectionAngle) {
|
||||
this.windDirectionAngle = windDirectionAngle;
|
||||
this.windDirection = this.getWindDirection(windDirectionAngle);
|
||||
}
|
||||
|
||||
/**
|
||||
* WeatherData representation
|
||||
* 10,70° 🌧 25,80km/h 243,00° 🡯
|
||||
* @return String representation of the WeatherData
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(Locale.ENGLISH, "%05.2f° %s %05.2fkm/h %06.2f° %s",
|
||||
this.getTemp(),
|
||||
this.getCondition().toString(),
|
||||
this.getWindSpeed(),
|
||||
this.getWindDirectionAngle(),
|
||||
this.getWindDirection().toString()
|
||||
);
|
||||
}
|
||||
}
|
@ -1,45 +1,77 @@
|
||||
package eirb.pg203;
|
||||
package eirb.pg203.weather.data.api;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import eirb.pg203.weather.exceptions.WeatherFetchingExceptionCityCoords;
|
||||
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;
|
||||
import java.net.URL;
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import eirb.pg203.WeatherData.Condition;
|
||||
import java.util.Locale;
|
||||
|
||||
import eirb.pg203.weather.data.WeatherData.Condition;
|
||||
|
||||
// https://api.open-meteo.com/v1/forecast?latitude=52.52&longitude=13.41&hourly=temperature_2m
|
||||
|
||||
/**
|
||||
* OpenMeteo implementation
|
||||
*/
|
||||
public class OpenMeteo extends WeatherCachedAPI {
|
||||
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";
|
||||
JSONFetcher JSONFetcher = new JSONFetcher();
|
||||
Clock clock = Clock.systemUTC();
|
||||
|
||||
/**
|
||||
* 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(
|
||||
String.format(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 WeatherFetchingExceptionCityCoords();
|
||||
}
|
||||
|
||||
return JSONFetcher.fetch(url);
|
||||
try {
|
||||
return JSONFetcher.fetch(url);
|
||||
} catch (IOException e) {
|
||||
throw new WeatherFetchingExceptionApi();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* return condition based on the WMOCode
|
||||
* table can be found here : https://open-meteo.com/en/docs (at the end)
|
||||
* @param WMOCode id code
|
||||
* @return weather condition
|
||||
*/
|
||||
private static Condition getConditionFromCode(int WMOCode) {
|
||||
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;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -53,32 +85,32 @@ public class OpenMeteo extends WeatherCachedAPI {
|
||||
float windDirection = daily.getJSONArray("wind_direction_10m_dominant").getFloat(day);
|
||||
int conditionCode = daily.getJSONArray("weather_code").getInt(day);
|
||||
|
||||
return new WeatherData(
|
||||
new City(cityName),
|
||||
Instant.now(),
|
||||
temp_c,
|
||||
windSpeed,
|
||||
windDirection,
|
||||
getConditionFromCode(conditionCode)
|
||||
);
|
||||
return new WeatherData(
|
||||
new City(cityName),
|
||||
Instant.now(),
|
||||
temp_c,
|
||||
windSpeed,
|
||||
windDirection,
|
||||
getConditionFromCode(conditionCode)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param day Day, 0 ≤ day ≤ 14
|
||||
*/
|
||||
@Override
|
||||
public WeatherData getTemperature(int day, String cityName) throws IOException {
|
||||
public WeatherData getTemperature(int day, String cityName) throws WeatherFetchingException {
|
||||
JSONObject result = fetchWeather(day + 1, new City(cityName));
|
||||
|
||||
return getWeatherDataFromForecast(result, day, cityName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WeatherData getTemperature(int day, int hour, String cityName) throws IOException{
|
||||
public WeatherData getTemperature(int day, int hour, String cityName) throws WeatherFetchingException {
|
||||
return getTemperature(day, cityName);
|
||||
}
|
||||
|
||||
public ArrayList<WeatherData> fetchTemperatures(int days, String cityName) throws IOException {
|
||||
public ArrayList<WeatherData> fetchTemperatures(int days, String cityName) throws WeatherFetchingException {
|
||||
JSONObject result = fetchWeather(days, new City(cityName));
|
||||
ArrayList<WeatherData> weatherDatas = new ArrayList<>();
|
||||
|
||||
@ -95,4 +127,9 @@ public class OpenMeteo extends WeatherCachedAPI {
|
||||
public String getAPIName() {
|
||||
return "OpenMeteo";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getAPIName();
|
||||
}
|
||||
}
|
@ -1,46 +1,65 @@
|
||||
package eirb.pg203;
|
||||
package eirb.pg203.weather.data.api;
|
||||
|
||||
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;
|
||||
import java.net.URL;
|
||||
import java.time.Clock;
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import eirb.pg203.WeatherData.Condition;
|
||||
import java.util.Locale;
|
||||
|
||||
// https://api.open-meteo.com/v1/forecast?latitude=52.52&longitude=13.41&hourly=temperature_2m
|
||||
import eirb.pg203.weather.data.WeatherData.Condition;
|
||||
|
||||
/**
|
||||
* OpenWeatherMap api implementation
|
||||
*/
|
||||
public class OpenWeatherMap extends WeatherCachedAPI {
|
||||
private static final String forecastBaseURL = "https://api.openweathermap.org/data/2.5/forecast";
|
||||
private String APIKey;
|
||||
private static Clock clock = Clock.systemUTC();
|
||||
JSONFetcher JSONFetcher = new JSONFetcher();
|
||||
|
||||
OpenWeatherMap(String APIKey) {
|
||||
public OpenWeatherMap(String APIKey) {
|
||||
this.APIKey = APIKey;
|
||||
}
|
||||
|
||||
private JSONObject fetchWeather(int days, City city) throws IOException {
|
||||
URL url = URI.create(
|
||||
String.format(forecastBaseURL + "?appid=%s&lat=%.2f&lon=%.2f&units=metric",
|
||||
APIKey,
|
||||
city.getCityCoords().getLat(),
|
||||
city.getCityCoords().getLon(),
|
||||
days
|
||||
)
|
||||
).toURL();
|
||||
private JSONObject fetchWeather(int day, 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 static WeatherData getWeatherDataFromForecast(JSONObject response, int day, String cityName) {
|
||||
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;
|
||||
@ -84,7 +103,7 @@ public class OpenWeatherMap extends WeatherCachedAPI {
|
||||
condition = Condition.PARTIAL;
|
||||
|
||||
|
||||
return new WeatherData(
|
||||
return new WeatherData(
|
||||
new City(cityName),
|
||||
Instant.now().plusSeconds(day * 24 * 3600),
|
||||
temp_c,
|
||||
@ -98,18 +117,18 @@ public class OpenWeatherMap extends WeatherCachedAPI {
|
||||
* @param day Day, 0 ≤ day ≤ 14
|
||||
*/
|
||||
@Override
|
||||
public WeatherData getTemperature(int day, String cityName) throws IOException {
|
||||
public WeatherData getTemperature(int day, String cityName) throws WeatherFetchingException {
|
||||
JSONObject result = fetchWeather(day+1, new City(cityName));
|
||||
|
||||
return getWeatherDataFromForecast(result, day, cityName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WeatherData getTemperature(int day, int hour, String cityname) throws IOException{
|
||||
public WeatherData getTemperature(int day, int hour, String cityname) throws WeatherFetchingException {
|
||||
return getTemperature(day, cityname);
|
||||
}
|
||||
|
||||
public ArrayList<WeatherData> fetchTemperatures(int days, String cityName) throws IOException {
|
||||
public ArrayList<WeatherData> fetchTemperatures(int days, String cityName) throws WeatherFetchingException {
|
||||
JSONObject result = fetchWeather(days, new City(cityName));
|
||||
ArrayList<WeatherData> weatherDatas = new ArrayList<>();
|
||||
|
||||
@ -126,4 +145,9 @@ public class OpenWeatherMap extends WeatherCachedAPI {
|
||||
public String getAPIName() {
|
||||
return "OpenWeatherMap";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getAPIName();
|
||||
}
|
||||
}
|
@ -1,41 +1,57 @@
|
||||
package eirb.pg203;
|
||||
package eirb.pg203.weather.data.api;
|
||||
|
||||
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;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* WeatherAPI implementation
|
||||
*/
|
||||
public class WeatherAPI extends WeatherCachedAPI {
|
||||
private final String weatherAPIKey;
|
||||
private static final String forecastBaseURL = "https://api.weatherapi.com/v1/forecast.json";
|
||||
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;
|
||||
}
|
||||
|
||||
private JSONObject fetchWeather(int days, String city) throws IOException {
|
||||
URL url = URI.create(
|
||||
String.format(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) {
|
||||
private static Condition getConditionFromString(String str) {
|
||||
if (str.toLowerCase().contains("rain"))
|
||||
return Condition.RAINY;
|
||||
|
||||
@ -66,31 +82,31 @@ public class WeatherAPI extends WeatherCachedAPI {
|
||||
|
||||
String conditionStr = forecastDay.getJSONObject("day").getJSONObject("condition").getString("text");
|
||||
|
||||
return new WeatherData(
|
||||
new City(city),
|
||||
Instant.now(),
|
||||
temp_c,
|
||||
windSpeed,
|
||||
windDirection,
|
||||
getConditionFromString(conditionStr)
|
||||
);
|
||||
return new WeatherData(
|
||||
new City(city),
|
||||
Instant.now(),
|
||||
temp_c,
|
||||
windSpeed,
|
||||
windDirection,
|
||||
getConditionFromString(conditionStr)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param day Day, 0 ≤ day ≤ 14
|
||||
*/
|
||||
@Override
|
||||
public WeatherData getTemperature(int day, String cityName) throws IOException {
|
||||
public WeatherData getTemperature(int day, String cityName) throws WeatherFetchingException {
|
||||
JSONObject result = fetchWeather(day+1, cityName);
|
||||
return getWeatherDataFromForecast(result, day, cityName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WeatherData getTemperature(int day, int hour, String cityName) throws IOException{
|
||||
public WeatherData getTemperature(int day, int hour, String cityName) throws WeatherFetchingException {
|
||||
return getTemperature(day, cityName);
|
||||
}
|
||||
|
||||
public ArrayList<WeatherData> fetchTemperatures(int days, String cityName) throws IOException {
|
||||
public ArrayList<WeatherData> fetchTemperatures(int days, String cityName) throws WeatherFetchingException {
|
||||
JSONObject result = fetchWeather(days, cityName);
|
||||
ArrayList<WeatherData> weatherDatas = new ArrayList<>();
|
||||
|
||||
@ -107,4 +123,9 @@ public class WeatherAPI extends WeatherCachedAPI {
|
||||
public String getAPIName() {
|
||||
return "WeatherAPI";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getAPIName();
|
||||
}
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
package eirb.pg203;
|
||||
package eirb.pg203.weather.data.api;
|
||||
import eirb.pg203.weather.data.WeatherData;
|
||||
import eirb.pg203.weather.exceptions.WeatherFetchingException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
@ -11,16 +13,17 @@ import java.util.Scanner;
|
||||
|
||||
import org.json.JSONArray;
|
||||
|
||||
abstract class WeatherCachedAPI implements WeatherDataAPI {
|
||||
|
||||
public abstract class WeatherCachedAPI implements WeatherDataAPI {
|
||||
private final WeatherDataCache cache = new WeatherDataCache();
|
||||
|
||||
abstract ArrayList<WeatherData> fetchTemperatures(int days, String cityName) throws IOException;
|
||||
abstract ArrayList<WeatherData> fetchTemperatures(int days, String cityName) throws WeatherFetchingException;
|
||||
|
||||
public void loadCache(JSONArray data) {
|
||||
this.cache.fromJSON(data, this.getAPIName());
|
||||
}
|
||||
|
||||
private void updateCache(int days, String cityName) throws IOException {
|
||||
private void updateCache(int days, String cityName) throws WeatherFetchingException {
|
||||
ArrayList<WeatherData> data = fetchTemperatures(days, cityName);
|
||||
Instant timestamp = Instant.now();
|
||||
|
||||
@ -28,7 +31,7 @@ abstract class WeatherCachedAPI implements WeatherDataAPI {
|
||||
this.cache.set(cityName, i, data.get(i), timestamp);
|
||||
}
|
||||
|
||||
public ArrayList<WeatherData> getTemperatures(int days, String cityName) throws IOException
|
||||
public ArrayList<WeatherData> getTemperatures(int days, String cityName) throws WeatherFetchingException
|
||||
{
|
||||
ArrayList<WeatherData> result = new ArrayList<>();
|
||||
|
||||
@ -36,7 +39,9 @@ abstract class WeatherCachedAPI implements WeatherDataAPI {
|
||||
{
|
||||
if (this.cache.needsUpdate(cityName, i))
|
||||
{
|
||||
updateCache(days, cityName);
|
||||
try {
|
||||
updateCache(days, cityName);
|
||||
} catch(Exception e) {}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,46 @@
|
||||
package eirb.pg203.weather.data.api;
|
||||
|
||||
import eirb.pg203.weather.exceptions.WeatherFetchingException;
|
||||
import eirb.pg203.weather.data.WeatherData;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Interface to discuss with a weather API
|
||||
*/
|
||||
public interface WeatherDataAPI {
|
||||
|
||||
/**
|
||||
* Fetch the temperature for a specific day in a city
|
||||
* @param day Since D+0
|
||||
* @param city Localisation
|
||||
* @return Temperature of the day from the city
|
||||
* @throws WeatherFetchingException when request failed
|
||||
*/
|
||||
WeatherData getTemperature(int day, String city) throws WeatherFetchingException;
|
||||
|
||||
/**
|
||||
* Get WeatherData for a specific day
|
||||
* @param day Since D+0
|
||||
* @param hour Since H+0
|
||||
* @param city Localisation
|
||||
* @return Temperature of the day for a hour from the city
|
||||
* @throws WeatherFetchingException when request failed
|
||||
*/
|
||||
WeatherData getTemperature(int day, int hour, String city) throws WeatherFetchingException;
|
||||
|
||||
/**
|
||||
* 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 WeatherFetchingException when request failed
|
||||
*/
|
||||
ArrayList<WeatherData> getTemperatures(int days, String city) throws WeatherFetchingException;
|
||||
|
||||
/***
|
||||
* Name of the API
|
||||
* @return Name of the API
|
||||
*/
|
||||
String getAPIName();
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
package eirb.pg203;
|
||||
package eirb.pg203.weather.data.api;
|
||||
import eirb.pg203.weather.data.WeatherData;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
@ -8,6 +9,7 @@ import java.util.Locale;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
|
||||
public class WeatherDataCache {
|
||||
private static class CacheValue {
|
||||
private WeatherData value;
|
@ -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
|
@ -1,15 +1,25 @@
|
||||
package eirb.pg203;
|
||||
package eirb.pg203.weather.display;
|
||||
|
||||
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 {
|
||||
private ArrayList<WeatherDataAPI> apis;
|
||||
|
||||
WeatherDisplayBasic() {
|
||||
this.apis = new ArrayList<WeatherDataAPI>();
|
||||
}
|
||||
public class WeatherDisplayBasic implements eirb.pg203.weather.display.WeatherDisplay {
|
||||
/**
|
||||
* List of apis
|
||||
*/
|
||||
private final ArrayList<WeatherDataAPI> 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 +36,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<WeatherDataAPI, ArrayList<WeatherData>> weatherDataAPIArrayListHashMap) {
|
||||
double max = 0;
|
||||
for (WeatherDataAPI api: weatherDataAPIArrayListHashMap.keySet()) {
|
||||
@ -39,6 +56,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<WeatherData> weatherDatas, String startColumnString, double sourceColumnSize, double dayColumnSize) {
|
||||
StringBuilder line = new StringBuilder();
|
||||
String weatherDataString;
|
||||
@ -61,6 +87,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 +110,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<WeatherDataAPI, ArrayList<WeatherData>> weatherDataAPIArrayListHashMap, int days) {
|
||||
double dayColumnSize = this.getColumnSize(weatherDataAPIArrayListHashMap);
|
||||
|
||||
@ -105,7 +152,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);
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package eirb.pg203.weather.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);
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
@ -1,52 +1,51 @@
|
||||
package eirb.pg203;
|
||||
package eirb.pg203.weather.utils;
|
||||
|
||||
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.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;
|
||||
|
||||
class City {
|
||||
/**
|
||||
* Representation of a city
|
||||
* Possibility to get city coordinates based on the name
|
||||
*/
|
||||
public class City {
|
||||
private String cityName;
|
||||
private Coords cityCoords;
|
||||
JSONFetcher 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("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();
|
||||
|
||||
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 {
|
||||
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);
|
||||
@ -54,10 +53,14 @@ class City {
|
||||
return new Coords(lat, lon);
|
||||
}
|
||||
|
||||
City (String cityName) {
|
||||
public City (String cityName) {
|
||||
this.cityName = cityName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get city name
|
||||
* @return city name string
|
||||
*/
|
||||
public String getCityName() {
|
||||
return cityName;
|
||||
}
|
||||
@ -80,7 +83,7 @@ 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(),
|
||||
@ -88,7 +91,7 @@ class City {
|
||||
);
|
||||
}
|
||||
catch (IOException e) {
|
||||
return String.format(
|
||||
return String.format(Locale.ENGLISH,
|
||||
"City(%s, lat: Request failed, lon: Request Failed)",
|
||||
this.cityName
|
||||
);
|
51
src/main/java/eirb/pg203/weather/utils/Coords.java
Normal file
51
src/main/java/eirb/pg203/weather/utils/Coords.java
Normal file
@ -0,0 +1,51 @@
|
||||
package eirb.pg203.weather.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;
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package eirb.pg203.utils;
|
||||
package eirb.pg203.weather.utils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
@ -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
|
||||
*/
|
||||
public 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{
|
||||
System.err.println("Requesting " + url);
|
||||
StringBuilder result = new StringBuilder();
|
||||
@ -22,20 +35,19 @@ public class JSONFetcher {
|
||||
result.append(line);
|
||||
}
|
||||
}
|
||||
System.out.println(url);
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
public static JSONObject fetch(URL url) throws IOException {
|
||||
|
||||
/**
|
||||
* Fetch an url
|
||||
* @param url url
|
||||
* @return Json object of the response
|
||||
* @throws IOException if the request failed
|
||||
*/
|
||||
public JSONObject fetch(URL url) throws IOException {
|
||||
String result = fetchString(url);
|
||||
|
||||
return new JSONObject(result);
|
||||
}
|
||||
|
||||
public static JSONArray fetchArray(URL url) throws IOException {
|
||||
String result = fetchString(url);
|
||||
|
||||
return new JSONArray(result);
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
||||
}
|
@ -1,71 +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 WeatherAPITest {
|
||||
private static String APIKey = "cef8e1b6ea364994b5072423240111";
|
||||
|
||||
@Test
|
||||
public void testRightAPIKey() {
|
||||
WeatherAPI weatherAPI = new WeatherAPI(WeatherAPITest.APIKey);
|
||||
int day = 0;
|
||||
// int hour = 10;
|
||||
int days = 7;
|
||||
String city = "Bordeaux";
|
||||
|
||||
Assertions.assertAll(
|
||||
() -> weatherAPI.getTemperature(day, city),
|
||||
// () -> weatherAPI.getTemperature(day, hour, city),
|
||||
() -> weatherAPI.getTemperatures(days, city)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWrongAPIKey() {
|
||||
WeatherAPI weatherAPI = new WeatherAPI("");
|
||||
int day = 0;
|
||||
// int hour = 10;
|
||||
int days = 7;
|
||||
String city = "Bordeaux";
|
||||
|
||||
Assertions.assertThrows(IOException.class, () -> weatherAPI.getTemperature(day, city));
|
||||
Assertions.assertThrows(IOException.class, () -> weatherAPI.getTemperatures(days, city));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWrongDay() {
|
||||
WeatherAPI weatherAPI = new WeatherAPI(WeatherAPITest.APIKey);
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAPIName() {
|
||||
WeatherAPI weatherAPI = new WeatherAPI(WeatherAPITest.APIKey);
|
||||
Assertions.assertTrue(weatherAPI.getAPIName().equals("WeatherAPI"));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
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.MethodSource;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static eirb.pg203.weather.data.api.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<Arguments> 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() {
|
||||
|
||||
}
|
||||
}
|
109
src/test/java/eirb/pg203/weather/data/WeatherDataTest.java
Normal file
109
src/test/java/eirb/pg203/weather/data/WeatherDataTest.java
Normal file
@ -0,0 +1,109 @@
|
||||
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;
|
||||
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<Arguments> 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());
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package eirb.pg203.weather.data.api;
|
||||
|
||||
import eirb.pg203.weather.fakeJSONFetcher.FakeJSONFetcherWeatherAPI;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class WeatherAPITest {
|
||||
private static final String APIKey = "realKey";
|
||||
private WeatherAPI weatherAPI;
|
||||
|
||||
@BeforeEach
|
||||
public void setupWeatherApi() {
|
||||
this.weatherAPI = new WeatherAPI(WeatherAPITest.APIKey);
|
||||
this.weatherAPI.JSONFetcher = new FakeJSONFetcherWeatherAPI();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAPIName() {
|
||||
Assertions.assertEquals("WeatherAPI", weatherAPI.getAPIName());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,213 @@
|
||||
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;
|
||||
import eirb.pg203.weather.exceptions.WeatherFetchingException;
|
||||
import eirb.pg203.weather.data.WeatherData;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
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 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);
|
||||
weatherAPI.JSONFetcher = new FakeJSONFetcherWeatherAPI();
|
||||
return weatherAPI;
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
OpenWeatherMap openWeatherMap= new OpenWeatherMap(APIKey);
|
||||
openWeatherMap.JSONFetcher = new FakeJSONFetcherOpenWeatherMap();
|
||||
openWeatherMap.clock = clock;
|
||||
return openWeatherMap;
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
OpenMeteo openMeteo = new OpenMeteo();
|
||||
openMeteo.clock = clock;
|
||||
openMeteo.JSONFetcher = new FakeJSONFetcherOpenMeteo();
|
||||
return openMeteo;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* List of args for Temperature testing
|
||||
* @return Args for testing
|
||||
*/
|
||||
private static Stream<Arguments> 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),
|
||||
/* 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)
|
||||
);
|
||||
}
|
||||
|
||||
@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 WeatherFetchingException {
|
||||
String city = "Bordeaux";
|
||||
WeatherData weatherData;
|
||||
weatherData = weatherDataAPI.getTemperature(day, city);
|
||||
|
||||
/* Temperatures */
|
||||
Assertions.assertEquals(expectedTemp, weatherData.getTemp(), epsilon);
|
||||
/* Condition */
|
||||
Assertions.assertEquals(expectedCond, weatherData.getCondition());
|
||||
/* Wind */
|
||||
Assertions.assertEquals(expectedWindSpeed, weatherData.getWindSpeed(), epsilon);
|
||||
Assertions.assertEquals(expectedWindAngle, weatherData.getWindDirectionAngle(),epsilon);
|
||||
}
|
||||
|
||||
private static Stream<Arguments> 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};
|
||||
|
||||
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};
|
||||
|
||||
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 = {151.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(openMeteo(), 4, openMeteoExpectedTemperatures, openMeteoExpectedConditions, openMeteoExpectedWindSpeed, openMeteoExpectedWindDirection)
|
||||
);
|
||||
|
||||
}
|
||||
@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 WeatherFetchingException{
|
||||
String city = "Bordeaux";
|
||||
|
||||
ArrayList<WeatherData> 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(), epsilon);
|
||||
/* Weather condition */
|
||||
Assertions.assertEquals(expectedConditions[index], weatherData.getCondition());
|
||||
/* Wind */
|
||||
Assertions.assertEquals(expectedWindSpeed[index],weatherData.getWindSpeed(), epsilon);
|
||||
Assertions.assertEquals(expectedWindDirection[index], weatherData.getWindDirectionAngle(), epsilon);
|
||||
}
|
||||
}
|
||||
|
||||
private static Stream<Arguments> testGetTemperatureByHours() {
|
||||
|
||||
return Stream.of(
|
||||
Arguments.arguments(weatherAPI()),
|
||||
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) {
|
||||
String city = "Bordeaux";
|
||||
Assertions.assertAll(
|
||||
() -> weatherDataAPI.getTemperature(0,1, city)
|
||||
);
|
||||
}
|
||||
|
||||
private static Stream<Arguments> 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<Arguments> 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)
|
||||
);
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package eirb.pg203.weather.fakeJSONFetcher;
|
||||
|
||||
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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
public class FakeJSONFetcherCity extends JSONFetcher{
|
||||
JSONObject unknownCity = FileResourcesUtils.getFileFromResourceAsJson("City/fakeCity.json");
|
||||
|
||||
private static HashMap<String, JSONObject> cities(){
|
||||
HashMap<String, JSONObject> cities = new HashMap<>();
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public JSONObject fetch(URL url) throws IOException {
|
||||
Map<String, String> params = SplitQueryUrl.splitQuery(url);
|
||||
|
||||
String city = params.get("q").toLowerCase(Locale.ENGLISH);
|
||||
return cities().getOrDefault(city, unknownCity);
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package eirb.pg203.weather.fakeJSONFetcher;
|
||||
|
||||
import eirb.pg203.weather.utils.FileResourcesUtils;
|
||||
import eirb.pg203.weather.utils.JSONFetcher;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
|
||||
public class FakeJSONFetcherOpenMeteo extends JSONFetcher {
|
||||
private JSONObject responseExample() {
|
||||
return FileResourcesUtils.getFileFromResourceAsJson("OpenMeteo/Bordeaux-partial-cloudy-rain-sunny.json");
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject fetch(URL url) throws IOException {
|
||||
return responseExample();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package eirb.pg203.weather.fakeJSONFetcher;
|
||||
|
||||
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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Map;
|
||||
|
||||
public class FakeJSONFetcherOpenWeatherMap extends JSONFetcher {
|
||||
private final String apiKey = "realKey";
|
||||
private final JSONObject wrongKeyResponse = FileResourcesUtils.getFileFromResourceAsJson("OpenWeatherMap/wrong-apikey.json");
|
||||
|
||||
private JSONObject responseExample() {
|
||||
return eirb.pg203.weather.utils.FileResourcesUtils.getFileFromResourceAsJson("OpenWeatherMap/Bordeaux-partial-cloudy-rain-sunny.json");
|
||||
}
|
||||
@Override
|
||||
public JSONObject fetch(URL url) throws IOException {
|
||||
Map<String, String> params = SplitQueryUrl.splitQuery(url);
|
||||
if (!params.getOrDefault("appid", "").contentEquals(apiKey))
|
||||
throw new IOException();
|
||||
return responseExample();
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package eirb.pg203.weather.fakeJSONFetcher;
|
||||
|
||||
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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
|
||||
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");
|
||||
private static ArrayList<JSONObject> bordeauxRequests() {
|
||||
ArrayList<JSONObject> bordeauxRequest = new ArrayList<>();
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject fetch(URL url) throws IOException {
|
||||
Map<String, String> params = SplitQueryUrl.splitQuery(url);
|
||||
int days = Integer.parseInt(params.get("days"));
|
||||
|
||||
if (!params.get("key").contentEquals(apiKey))
|
||||
throw new IOException();
|
||||
|
||||
return bordeauxRequests().get(days - 1);
|
||||
}
|
||||
}
|
50
src/test/java/eirb/pg203/weather/utils/CityTest.java
Normal file
50
src/test/java/eirb/pg203/weather/utils/CityTest.java
Normal file
@ -0,0 +1,50 @@
|
||||
package eirb.pg203.weather.utils;
|
||||
|
||||
import eirb.pg203.weather.fakeJSONFetcher.FakeJSONFetcherCity;
|
||||
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();
|
||||
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
}
|
53
src/test/java/eirb/pg203/weather/utils/CoordsTest.java
Normal file
53
src/test/java/eirb/pg203/weather/utils/CoordsTest.java
Normal file
@ -0,0 +1,53 @@
|
||||
package eirb.pg203.weather.utils;
|
||||
|
||||
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);
|
||||
|
||||
assertEquals(lat, coords.getLat(), epsilon);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getLon() {
|
||||
float lat = 1f;
|
||||
float lon = 2f;
|
||||
Coords coords = new Coords(lat, lon);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
assertEquals(sndLon, coords.getLon(), epsilon);
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package eirb.pg203.weather.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());
|
||||
}
|
||||
}
|
20
src/test/java/eirb/pg203/weather/utils/SplitQueryUrl.java
Normal file
20
src/test/java/eirb/pg203/weather/utils/SplitQueryUrl.java
Normal file
@ -0,0 +1,20 @@
|
||||
package eirb.pg203.weather.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<String, String> splitQuery(URL url) throws UnsupportedEncodingException {
|
||||
Map<String, String> query_pairs = new LinkedHashMap<String, String>();
|
||||
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;
|
||||
}
|
||||
}
|
37
src/test/resources/City/bordeaux.json
Normal file
37
src/test/resources/City/bordeaux.json
Normal file
@ -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
|
||||
}
|
9
src/test/resources/City/fakeCity.json
Normal file
9
src/test/resources/City/fakeCity.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "FeatureCollection",
|
||||
"version": "draft",
|
||||
"features": [],
|
||||
"attribution": "BAN",
|
||||
"licence": "ETALAB-2.0",
|
||||
"query": "farmjfakj",
|
||||
"limit": 1
|
||||
}
|
36
src/test/resources/City/paris.json
Normal file
36
src/test/resources/City/paris.json
Normal file
@ -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
|
||||
}
|
@ -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
|
||||
]
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
4
src/test/resources/OpenWeatherMap/wrong-apikey.json
Normal file
4
src/test/resources/OpenWeatherMap/wrong-apikey.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"cod": 401,
|
||||
"message": "Invalid API key. Please see https://openweathermap.org/faq#error401 for more info."
|
||||
}
|
3057
src/test/resources/WeatherAPI/Bordeaux-1-partial.json
Normal file
3057
src/test/resources/WeatherAPI/Bordeaux-1-partial.json
Normal file
File diff suppressed because it is too large
Load Diff
3057
src/test/resources/WeatherAPI/Bordeaux-2-partial-sunny.json
Normal file
3057
src/test/resources/WeatherAPI/Bordeaux-2-partial-sunny.json
Normal file
File diff suppressed because it is too large
Load Diff
3057
src/test/resources/WeatherAPI/Bordeaux-3-partial-sunny-rain.json
Normal file
3057
src/test/resources/WeatherAPI/Bordeaux-3-partial-sunny-rain.json
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
6
src/test/resources/WeatherAPI/wrong-apikey.json
Normal file
6
src/test/resources/WeatherAPI/wrong-apikey.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"error": {
|
||||
"code": 2008,
|
||||
"message": "API key has been disabled."
|
||||
}
|
||||
}
|
BIN
weather-app.png
Normal file
BIN
weather-app.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 159 KiB |
Loading…
x
Reference in New Issue
Block a user