Finish update, working
This commit is contained in:
parent
dafb1d770a
commit
1895673c50
13 changed files with 289 additions and 592 deletions
|
@ -1,11 +1,9 @@
|
|||
package eu.m724.wtapi.provider.weather;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import eu.m724.wtapi.object.Coordinates;
|
||||
import eu.m724.wtapi.object.Quota;
|
||||
import eu.m724.wtapi.object.Weather;
|
||||
import eu.m724.wtapi.provider.exception.ProviderException;
|
||||
|
||||
public abstract class WeatherProvider {
|
||||
|
@ -20,9 +18,9 @@ public abstract class WeatherProvider {
|
|||
* Get the current weather of a single or multiple points
|
||||
*
|
||||
* @param coordinates The coordinates
|
||||
* @return A future that CAN throw {@link ProviderException}
|
||||
* @return A {@link WeatherQueryResult}
|
||||
*/
|
||||
public abstract CompletableFuture<Weather[]> getWeather(Coordinates... coordinates);
|
||||
public abstract CompletableFuture<WeatherQueryResult> getWeather(Coordinates... coordinates);
|
||||
|
||||
public abstract Quota getQuota();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package eu.m724.wtapi.provider.weather;
|
||||
|
||||
import eu.m724.wtapi.object.Weather;
|
||||
|
||||
/**
|
||||
* A result from a weather query.
|
||||
*
|
||||
* @param weathers The weathers, null if exception
|
||||
* @param exception The exception, null if no exception
|
||||
*/
|
||||
public record WeatherQueryResult(
|
||||
Weather[] weathers,
|
||||
Throwable exception
|
||||
) {
|
||||
}
|
|
@ -1,9 +1,6 @@
|
|||
package eu.m724.wtapi.provider.weather.impl.openmeteo;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import com.google.gson.*;
|
||||
import eu.m724.wtapi.object.Coordinates;
|
||||
import eu.m724.wtapi.object.Quota;
|
||||
import eu.m724.wtapi.object.Weather;
|
||||
|
@ -11,24 +8,39 @@ import eu.m724.wtapi.provider.exception.ProviderException;
|
|||
import eu.m724.wtapi.provider.exception.QuotaExceededException;
|
||||
import eu.m724.wtapi.provider.exception.ProviderRequestException;
|
||||
import eu.m724.wtapi.provider.weather.WeatherProvider;
|
||||
import eu.m724.wtapi.provider.weather.WeatherQueryResult;
|
||||
|
||||
import java.net.ProxySelector;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class OpenMeteoProvider extends WeatherProvider {
|
||||
private final HttpClient httpClient = HttpClient.newBuilder().build();
|
||||
|
||||
@Override
|
||||
public void init() throws ProviderException {
|
||||
|
||||
// TODO connectivity check
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Weather[]> getWeather(Coordinates... coordinates) {
|
||||
public CompletableFuture<WeatherQueryResult> getWeather(Coordinates... coordinates) {
|
||||
if (coordinates == null) {
|
||||
throw new NullPointerException("Coordinates are null");
|
||||
}
|
||||
|
||||
if (coordinates.length == 0) {
|
||||
throw new IllegalArgumentException("coordinates is empty");
|
||||
}
|
||||
|
||||
if (Arrays.stream(coordinates).anyMatch(Objects::isNull)) {
|
||||
throw new IllegalArgumentException("One or more coordinates are null");
|
||||
}
|
||||
|
||||
String latitudes = Arrays.stream(coordinates)
|
||||
.map(Coordinates::latitude)
|
||||
.map(Number::toString)
|
||||
|
@ -42,48 +54,59 @@ public class OpenMeteoProvider extends WeatherProvider {
|
|||
String url = "https://api.open-meteo.com/v1/forecast?latitude=%s&longitude=%s¤t=temperature_2m,relative_humidity_2m,rain,snowfall,apparent_temperature,cloud_cover,weather_code&timeformat=unixtime&wind_speed_unit=ms"
|
||||
.formatted(latitudes, longitudes);
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(url))
|
||||
.build();
|
||||
HttpRequest request =
|
||||
HttpRequest.newBuilder(URI.create(url)).build();
|
||||
|
||||
CompletableFuture<HttpResponse<String>> responseFuture =
|
||||
HttpClient.newBuilder().build()
|
||||
.sendAsync(request, HttpResponse.BodyHandlers.ofString());
|
||||
httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
return responseFuture.thenApply((response) -> {
|
||||
int code = response.statusCode();
|
||||
|
||||
JsonObject jsonResponse;
|
||||
try {
|
||||
jsonResponse = JsonParser.parseString(response.body()).getAsJsonObject();
|
||||
} catch (JsonParseException e) {
|
||||
throw new ProviderRequestException(e);
|
||||
}
|
||||
|
||||
if (code == 429) {
|
||||
long now = System.currentTimeMillis();
|
||||
int retryIn = -1;
|
||||
|
||||
String reason = jsonResponse.get("reason").getAsString();
|
||||
|
||||
if (reason.contains("Minutely")) {
|
||||
retryIn = (int) (60 - (now % 60));
|
||||
} else if (reason.contains("Daily")) {
|
||||
retryIn = (int) (3600 - (now % 3600));
|
||||
}
|
||||
|
||||
throw new QuotaExceededException(1000);
|
||||
}
|
||||
|
||||
if (code != 200)
|
||||
throw new ProviderRequestException("Status code: " + code);
|
||||
|
||||
return OpenMeteoResponseConverter.convert(jsonResponse);
|
||||
});
|
||||
return responseFuture
|
||||
.thenApply(this::processResponse)
|
||||
.handle(WeatherQueryResult::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Quota getQuota() {
|
||||
return Quota.daily(10000);
|
||||
}
|
||||
|
||||
private Weather[] processResponse(HttpResponse<String> response) throws ProviderException {
|
||||
int code = response.statusCode();
|
||||
|
||||
JsonElement jsonResponse;
|
||||
try {
|
||||
jsonResponse = JsonParser.parseString(response.body());
|
||||
} catch (JsonParseException e) {
|
||||
throw new ProviderRequestException(e);
|
||||
}
|
||||
|
||||
if (code == 429) {
|
||||
long now = System.currentTimeMillis();
|
||||
int retryIn = -1;
|
||||
|
||||
if (jsonResponse.isJsonObject() && jsonResponse.getAsJsonObject().has("reason")) {
|
||||
String reason = jsonResponse.getAsJsonObject().get("reason").getAsString();
|
||||
|
||||
if (reason.contains("Minutely")) {
|
||||
retryIn = (int) (60 - (now % 60));
|
||||
} else if (reason.contains("Daily")) {
|
||||
retryIn = (int) (3600 - (now % 3600));
|
||||
}
|
||||
}
|
||||
|
||||
throw new QuotaExceededException(retryIn);
|
||||
}
|
||||
|
||||
if (code != 200) {
|
||||
if (jsonResponse.isJsonObject() && jsonResponse.getAsJsonObject().has("reason")) {
|
||||
String reason = jsonResponse.getAsJsonObject().get("reason").getAsString();
|
||||
|
||||
throw new ProviderRequestException("Error reason: " + reason);
|
||||
}
|
||||
|
||||
throw new ProviderRequestException("Status code: " + code);
|
||||
}
|
||||
|
||||
return OpenMeteoResponseConverter.convert(jsonResponse);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ public class OpenMeteoResponseConverter {
|
|||
boolean thundering = weatherCode >= 90 && weatherCode < 100;
|
||||
boolean snowing = current.get("snowfall").getAsDouble() > 0;
|
||||
|
||||
float temperatureCelsius = current.get("temperature").getAsFloat();
|
||||
float temperatureCelsius = current.get("temperature_2m").getAsFloat();
|
||||
float temperatureApparentCelsius = current.get("apparent_temperature").getAsFloat();
|
||||
|
||||
float relativeHumidityPercentage = current.get("relative_humidity_2m").getAsInt() / 100f;
|
||||
|
|
|
@ -1,235 +0,0 @@
|
|||
package eu.m724.wtapi.provider.weather.impl.openweathermap;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
|
||||
import eu.m724.wtapi.object.Coordinates;
|
||||
import eu.m724.wtapi.object.Severity;
|
||||
import eu.m724.wtapi.object.Weather;
|
||||
|
||||
public class OWMResponseConverter {
|
||||
static Weather convert(JsonObject json) {
|
||||
Coordinates coordinates = new Coordinates(
|
||||
json.getAsJsonObject("coord").getAsJsonPrimitive("lon").getAsDouble(),
|
||||
json.getAsJsonObject("coord").getAsJsonPrimitive("lat").getAsDouble()
|
||||
);
|
||||
Weather weather = new Weather();
|
||||
|
||||
int conditionCode = json.getAsJsonArray("weather")
|
||||
.get(0).getAsJsonObject()
|
||||
.getAsJsonPrimitive("id").getAsInt();
|
||||
|
||||
switch (conditionCode) {
|
||||
// Group 2xx: Thunderstorm
|
||||
case 200: // thunderstorm with light rain
|
||||
weather.thunderstormSeverity = Severity.LIGHT;
|
||||
weather.rainSeverity = Severity.LIGHT;
|
||||
break;
|
||||
case 201: // thunderstorm with rain
|
||||
weather.thunderstormSeverity = Severity.MODERATE;
|
||||
weather.rainSeverity = Severity.MODERATE;
|
||||
break;
|
||||
case 202: // thunderstorm with heavy rain
|
||||
weather.thunderstormSeverity = Severity.HEAVY;
|
||||
weather.rainSeverity = Severity.HEAVY;
|
||||
break;
|
||||
case 210: // light thunderstorm
|
||||
weather.thunderstormSeverity = Severity.LIGHT;
|
||||
break;
|
||||
case 211: // thunderstorm
|
||||
weather.thunderstormSeverity = Severity.MODERATE;
|
||||
break;
|
||||
case 212: // heavy thunderstorm
|
||||
weather.thunderstormSeverity = Severity.HEAVY;
|
||||
break;
|
||||
case 221: // ragged thunderstorm
|
||||
weather.thunderstormSeverity = Severity.MODERATE;
|
||||
break;
|
||||
case 230: // thunderstorm with light drizzle
|
||||
weather.thunderstormSeverity = Severity.LIGHT;
|
||||
weather.drizzleSeverity = Severity.LIGHT;
|
||||
break;
|
||||
case 231: // thunderstorm with drizzle
|
||||
weather.thunderstormSeverity = Severity.MODERATE;
|
||||
weather.drizzleSeverity = Severity.MODERATE;
|
||||
break;
|
||||
case 232: // thunderstorm with heavy drizzle
|
||||
weather.thunderstormSeverity = Severity.HEAVY;
|
||||
weather.drizzleSeverity = Severity.HEAVY;
|
||||
break;
|
||||
|
||||
// Group 3xx: Drizzle
|
||||
case 300: // light intensity drizzle
|
||||
weather.drizzleSeverity = Severity.LIGHT;
|
||||
break;
|
||||
case 301: // drizzle
|
||||
weather.drizzleSeverity = Severity.MODERATE;
|
||||
break;
|
||||
case 302: // heavy intensity drizzle
|
||||
weather.drizzleSeverity = Severity.HEAVY;
|
||||
break;
|
||||
case 310: // light intensity drizzle rain
|
||||
weather.drizzleSeverity = Severity.LIGHT;
|
||||
break;
|
||||
case 311: // drizzle rain
|
||||
weather.drizzleSeverity = Severity.MODERATE;
|
||||
break;
|
||||
case 312: // heavy intensity drizzle rain
|
||||
weather.drizzleSeverity = Severity.HEAVY;
|
||||
break;
|
||||
case 313: // shower rain and drizzle
|
||||
weather.rainSeverity = Severity.MODERATE;
|
||||
weather.drizzleSeverity = Severity.MODERATE;
|
||||
weather.shower = true;
|
||||
break;
|
||||
case 314: // heavy shower rain and drizzle
|
||||
weather.rainSeverity = Severity.HEAVY;
|
||||
weather.drizzleSeverity = Severity.HEAVY;
|
||||
weather.shower = true;
|
||||
break;
|
||||
case 321: // shower drizzle
|
||||
weather.drizzleSeverity = Severity.MODERATE;
|
||||
weather.shower = true;
|
||||
break;
|
||||
|
||||
// Group 5xx: Rain
|
||||
case 500: // light rain
|
||||
weather.rainSeverity = Severity.LIGHT;
|
||||
break;
|
||||
case 501: // moderate rain
|
||||
weather.rainSeverity = Severity.MODERATE;
|
||||
break;
|
||||
case 502: // heavy intensity rain
|
||||
weather.rainSeverity = Severity.HEAVY;
|
||||
break;
|
||||
case 503: // very heavy rain
|
||||
weather.rainSeverity = Severity.HEAVY;
|
||||
break;
|
||||
case 504: // extreme rain
|
||||
weather.rainSeverity = Severity.HEAVY;
|
||||
break;
|
||||
case 511: // freezing rain
|
||||
weather.rainSeverity = Severity.MODERATE;
|
||||
break;
|
||||
case 520: // light intensity shower rain
|
||||
weather.rainSeverity = Severity.LIGHT;
|
||||
weather.shower = true;
|
||||
break;
|
||||
case 521: // shower rain
|
||||
weather.rainSeverity = Severity.MODERATE;
|
||||
weather.shower = true;
|
||||
break;
|
||||
case 522: // heavy intensity shower rain
|
||||
weather.rainSeverity = Severity.HEAVY;
|
||||
weather.shower = true;
|
||||
break;
|
||||
case 531: // ragged shower rain
|
||||
weather.rainSeverity = Severity.MODERATE;
|
||||
weather.shower = true;
|
||||
break;
|
||||
|
||||
// Group 6xx: Snow
|
||||
case 600: // light snow
|
||||
weather.snowSeverity = Severity.LIGHT;
|
||||
break;
|
||||
case 601: // snow
|
||||
weather.snowSeverity = Severity.MODERATE;
|
||||
break;
|
||||
case 602: // heavy snow
|
||||
weather.snowSeverity = Severity.HEAVY;
|
||||
break;
|
||||
case 611: // sleet
|
||||
weather.sleetSeverity = Severity.MODERATE;
|
||||
break;
|
||||
case 612: // light shower sleet
|
||||
weather.sleetSeverity = Severity.LIGHT;
|
||||
weather.shower = true;
|
||||
break;
|
||||
case 613: // shower sleet
|
||||
weather.sleetSeverity = Severity.MODERATE;
|
||||
weather.shower = true;
|
||||
break;
|
||||
case 615: // light rain and snow
|
||||
weather.rainSeverity = Severity.LIGHT;
|
||||
weather.snowSeverity = Severity.LIGHT;
|
||||
break;
|
||||
case 616: // rain and snow
|
||||
weather.rainSeverity = Severity.MODERATE;
|
||||
weather.snowSeverity = Severity.MODERATE;
|
||||
break;
|
||||
case 620: // light shower snow
|
||||
weather.snowSeverity = Severity.LIGHT;
|
||||
weather.shower = true;
|
||||
break;
|
||||
case 621: // shower snow
|
||||
weather.snowSeverity = Severity.MODERATE;
|
||||
weather.shower = true;
|
||||
break;
|
||||
case 622: // heavy shower snow
|
||||
weather.snowSeverity = Severity.HEAVY;
|
||||
weather.shower = true;
|
||||
break;
|
||||
}
|
||||
|
||||
String city = json.getAsJsonPrimitive("name").getAsString();
|
||||
if (city.equals("Globe") || city.equals("")) city = null;
|
||||
JsonPrimitive countryJson = json.getAsJsonObject("sys").getAsJsonPrimitive("country");
|
||||
String country = countryJson != null ? countryJson.getAsString() : null;
|
||||
|
||||
weather.coordinates =
|
||||
new Coordinates(
|
||||
json.getAsJsonObject("coord").getAsJsonPrimitive("lon").getAsDouble(),
|
||||
json.getAsJsonObject("coord").getAsJsonPrimitive("lat").getAsDouble()
|
||||
).setAddress(country, city);
|
||||
|
||||
weather.temperatureCelsius = json
|
||||
.getAsJsonObject("main")
|
||||
.getAsJsonPrimitive("temp")
|
||||
.getAsFloat();
|
||||
|
||||
weather.temperatureApparentCelsius = json
|
||||
.getAsJsonObject("main")
|
||||
.getAsJsonPrimitive("feels_like")
|
||||
.getAsFloat();
|
||||
|
||||
weather.windSpeed = json
|
||||
.getAsJsonObject("wind")
|
||||
.getAsJsonPrimitive("speed")
|
||||
.getAsFloat();
|
||||
|
||||
JsonPrimitive pri = json
|
||||
.getAsJsonObject("wind")
|
||||
.getAsJsonPrimitive("gust");
|
||||
|
||||
if (pri != null)
|
||||
weather.windSpeed = pri.getAsFloat();
|
||||
|
||||
weather.relativeHumidity = json
|
||||
.getAsJsonObject("main")
|
||||
.getAsJsonPrimitive("relativeHumidityPercentage")
|
||||
.getAsInt() / 100f;
|
||||
|
||||
weather.cloudCover = json
|
||||
.getAsJsonObject("clouds")
|
||||
.getAsJsonPrimitive("all")
|
||||
.getAsInt() / 100f;
|
||||
|
||||
weather.sunrise = json
|
||||
.getAsJsonObject("sys")
|
||||
.getAsJsonPrimitive("sunrise")
|
||||
.getAsLong();
|
||||
|
||||
weather.sunset = json
|
||||
.getAsJsonObject("sys")
|
||||
.getAsJsonPrimitive("sunrise")
|
||||
.getAsLong();
|
||||
|
||||
weather.description = json
|
||||
.getAsJsonArray("weather")
|
||||
.get(0).getAsJsonObject()
|
||||
.getAsJsonPrimitive("description")
|
||||
.getAsString();
|
||||
|
||||
return weather;
|
||||
}
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
package eu.m724.wtapi.provider.weather.impl.openweathermap;
|
||||
|
||||
import java.net.ProxySelector;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.net.http.HttpResponse.BodyHandlers;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
import eu.m724.wtapi.object.Coordinates;
|
||||
import eu.m724.wtapi.object.Weather;
|
||||
import eu.m724.wtapi.provider.exception.ProviderAuthorizationException;
|
||||
import eu.m724.wtapi.provider.exception.ProviderException;
|
||||
import eu.m724.wtapi.provider.exception.QuotaExceededException;
|
||||
import eu.m724.wtapi.provider.exception.ProviderRequestException;
|
||||
import eu.m724.wtapi.provider.weather.WeatherProvider;
|
||||
import eu.m724.wtapi.provider.weather.impl.openmeteo.OpenMeteoProvider;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* Development will focus on {@link OpenMeteoProvider} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public class OpenWeatherMapProvider extends WeatherProvider {
|
||||
private String apiKey;
|
||||
|
||||
public OpenWeatherMapProvider(String apiKey) {
|
||||
this.apiKey = apiKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() throws ProviderException {
|
||||
CompletableFuture<Weather> weatherFuture =
|
||||
getWeather(new Coordinates(0, 0));
|
||||
|
||||
weatherFuture.join();
|
||||
}
|
||||
|
||||
private CompletableFuture<Weather> getWeather(Coordinates coordinates) {
|
||||
String url = "https://api.openweathermap.org/data/2.5/weather?lat=%f&lon=%f&appid=%s"
|
||||
.formatted(coordinates.latitude(), coordinates.longitude(), apiKey);
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(url))
|
||||
.build();
|
||||
|
||||
CompletableFuture<HttpResponse<String>> responseFuture =
|
||||
HttpClient.newBuilder()
|
||||
.proxy(ProxySelector.getDefault()).build()
|
||||
.sendAsync(request, BodyHandlers.ofString());
|
||||
|
||||
CompletableFuture<Weather> weatherFuture =
|
||||
responseFuture.thenApply((response) -> {
|
||||
int code = response.statusCode();
|
||||
|
||||
if (code == 401)
|
||||
throw new ProviderAuthorizationException("Invalid api key");
|
||||
if (code == 429)
|
||||
throw new QuotaExceededException("Ratelimited", 1000);
|
||||
if (code != 200)
|
||||
throw new ProviderRequestException("Status code: " + code);
|
||||
|
||||
JsonObject jsonResponse =
|
||||
JsonParser.parseString(response.body())
|
||||
.getAsJsonObject();
|
||||
|
||||
return OWMResponseConverter.convert(jsonResponse);
|
||||
});
|
||||
|
||||
return weatherFuture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Weather[]> getWeather(Coordinates... coordinates) {
|
||||
ArrayList<CompletableFuture<Weather>> weatherFutures = new ArrayList<>();
|
||||
|
||||
for (Coordinates coordinates : coordinatesArray) {
|
||||
weatherFutures.add(getWeather(coordinates));
|
||||
}
|
||||
|
||||
CompletableFuture<Weather[]> weathersFuture =
|
||||
CompletableFuture.allOf(
|
||||
weatherFutures.toArray(new CompletableFuture[0]))
|
||||
.thenApply(v ->
|
||||
weatherFutures.stream()
|
||||
.map(CompletableFuture::join)
|
||||
.toArray(Weather[]::new));
|
||||
|
||||
return weathersFuture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getQuota() {
|
||||
return Duration.ofHours(1370);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBulkLimit() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,7 +1,5 @@
|
|||
package eu.m724.wtapi.object;
|
||||
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class CoordinateTest {
|
||||
|
@ -15,15 +13,7 @@ public class CoordinateTest {
|
|||
assert coordinates.longitude() == -179.99;
|
||||
|
||||
coordinates = new Coordinates(-91.1, 180.1);
|
||||
System.out.printf("Precision loss expected: %f\n", coordinates.longitude());
|
||||
System.out.printf("Precision loss expected: %f%n", coordinates.longitude());
|
||||
assert coordinates.longitude() != -179.9; // TODO fix precision loss
|
||||
|
||||
assertNull(coordinates.city);
|
||||
assertNull(coordinates.country);
|
||||
|
||||
coordinates.setAddress("SQ", "San Escobar");
|
||||
|
||||
assert coordinates.city.equals("San Escobar");
|
||||
assert coordinates.country.equals("SQ");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@ public class ApproximateTwilightTimeTest {
|
|||
|
||||
double solarAltitude = provider.calculateSolarAltitude(LocalDateTime.now(), new Coordinates(51, 0));
|
||||
System.out.println(Math.toDegrees(solarAltitude));
|
||||
assert false;
|
||||
|
||||
// used https://gml.noaa.gov/grad/solcalc/index.html for reference values
|
||||
testLocation(provider, 26, 6, 2023, 53.123394, 23.0864867, LocalDateTime.of(2023, 6, 26, 2, 2), LocalDateTime.of(2023, 6, 26, 18, 59), 0);
|
||||
|
|
|
@ -1,15 +1,20 @@
|
|||
package eu.m724.wtapi.weather;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import eu.m724.wtapi.object.Coordinates;
|
||||
import eu.m724.wtapi.object.Quota;
|
||||
import eu.m724.wtapi.object.Weather;
|
||||
import eu.m724.wtapi.provider.exception.ProviderException;
|
||||
import eu.m724.wtapi.provider.exception.QuotaExceededException;
|
||||
import eu.m724.wtapi.provider.exception.ProviderRequestException;
|
||||
import eu.m724.wtapi.provider.weather.WeatherProvider;
|
||||
import eu.m724.wtapi.provider.weather.WeatherQueryResult;
|
||||
|
||||
public class MockWeatherProvider extends WeatherProvider {
|
||||
private boolean faulty;
|
||||
private final boolean faulty;
|
||||
|
||||
public MockWeatherProvider(boolean faulty) {
|
||||
this.faulty = faulty;
|
||||
|
@ -17,66 +22,51 @@ public class MockWeatherProvider extends WeatherProvider {
|
|||
|
||||
@Override
|
||||
public void init() throws ProviderException {
|
||||
System.out.println("hello from mock provider");
|
||||
System.out.println("Mock provider 'initialized'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Weather> getWeather(Coordinates coordinates) {
|
||||
if (coordinates == null)
|
||||
throw new NullPointerException("no coordinates passed");
|
||||
|
||||
System.out.printf("mock getting weather for %f, %f\n",
|
||||
coordinates.latitude(), coordinates.longitude());
|
||||
|
||||
CompletableFuture<Weather> completableFuture =
|
||||
new CompletableFuture<>();
|
||||
|
||||
if (faulty)
|
||||
completableFuture.completeExceptionally(new ProviderRequestException("Imagined server is down"));
|
||||
else
|
||||
completableFuture.complete(new Weather());
|
||||
|
||||
return completableFuture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Weather[]> getWeatherBulk(Coordinates[] coordinatesArray) {
|
||||
int len = coordinatesArray.length;
|
||||
|
||||
if (len == 0)
|
||||
throw new IllegalArgumentException("no coordinates passed");
|
||||
|
||||
System.out.println("mock getting weather for multiple coords");
|
||||
|
||||
CompletableFuture<Weather[]> completableFuture =
|
||||
new CompletableFuture<>();
|
||||
|
||||
|
||||
Weather[] weathers = new Weather[len];
|
||||
|
||||
for (int i=0; i<len; i++) {
|
||||
if (coordinatesArray[i] == null)
|
||||
throw new IllegalArgumentException("A coordinate is null");
|
||||
|
||||
weathers[i] = new Weather();
|
||||
public CompletableFuture<WeatherQueryResult> getWeather(Coordinates... coordinates) {
|
||||
if (coordinates == null) {
|
||||
throw new NullPointerException("Coordinates are null");
|
||||
}
|
||||
|
||||
if (faulty)
|
||||
completableFuture.completeExceptionally(new QuotaExceededException("Quota exceeded", 60));
|
||||
else
|
||||
completableFuture.complete(weathers);
|
||||
|
||||
return completableFuture;
|
||||
|
||||
if (coordinates.length == 0) {
|
||||
throw new IllegalArgumentException("coordinates is empty");
|
||||
}
|
||||
|
||||
if (Arrays.stream(coordinates).anyMatch(Objects::isNull)) {
|
||||
throw new IllegalArgumentException("One or more coordinates are null");
|
||||
}
|
||||
|
||||
System.out.printf("Mock getting weather%n");
|
||||
|
||||
CompletableFuture<Weather[]> completableFuture =
|
||||
CompletableFuture.supplyAsync(() -> {
|
||||
if (faulty) {
|
||||
throw new ProviderRequestException("Imagined server is down");
|
||||
}
|
||||
|
||||
return Arrays.stream(coordinates).map(
|
||||
c -> new Weather(
|
||||
c,
|
||||
LocalDateTime.now(),
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
20f,
|
||||
100f,
|
||||
0f,
|
||||
0f
|
||||
)
|
||||
).toArray(Weather[]::new);
|
||||
});
|
||||
|
||||
return completableFuture.handle(WeatherQueryResult::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getQuota() {
|
||||
return 5;
|
||||
public Quota getQuota() {
|
||||
return Quota.daily(120);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBulkLimit() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
package eu.m724.wtapi.weather;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import eu.m724.wtapi.provider.weather.WeatherQueryResult;
|
||||
import eu.m724.wtapi.provider.weather.impl.openmeteo.OpenMeteoProvider;
|
||||
import org.junit.Test;
|
||||
|
||||
import eu.m724.wtapi.object.Coordinates;
|
||||
import eu.m724.wtapi.object.Weather;
|
||||
import eu.m724.wtapi.provider.weather.WeatherProvider;
|
||||
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
public class OpenMeteoProviderTest {
|
||||
@Test
|
||||
public void openWeatherMapTest() throws InterruptedException, ExecutionException {
|
||||
WeatherProvider provider = new OpenMeteoProvider();
|
||||
|
||||
provider.init();
|
||||
|
||||
testInputErrors(provider);
|
||||
|
||||
CompletableFuture<WeatherQueryResult> weathersFuture =
|
||||
provider.getWeather(
|
||||
new Coordinates(54.6606714, -3.3827237),
|
||||
new Coordinates(47.5705952, -53.5556464),
|
||||
new Coordinates(34.2073721, -84.1402857)
|
||||
);
|
||||
|
||||
WeatherQueryResult result = weathersFuture.get();
|
||||
|
||||
if (result.exception() != null) {
|
||||
throw new ExecutionException("Query errored", result.exception());
|
||||
}
|
||||
|
||||
Weather[] weathers = result.weathers();
|
||||
assert weathers.length == 3;
|
||||
|
||||
for (Weather weather : weathers) {
|
||||
printWeather(weather);
|
||||
}
|
||||
}
|
||||
|
||||
private void printWeather(Weather weather) {
|
||||
System.out.printf("Weather in %f,%f as of %s:%n", weather.coordinates().latitude(), weather.coordinates().longitude(), weather.timestamp().toString());
|
||||
System.out.printf(" Rain: %b | Snow: %b | Thunder: %b%n", weather.raining(), weather.snowing(), weather.thundering());
|
||||
System.out.printf(" Temperature: %.1f°C (feels like %.1f°C)%n", weather.temperatureCelsius(), weather.temperatureApparentCelsius());
|
||||
System.out.printf(" Relative humidity: %.0f%%%n", weather.relativeHumidityPercentage() * 100);
|
||||
System.out.printf(" Cloudiness: %.0f%%%n", weather.cloudCoverPercentage() * 100);
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
public void testInputErrors(WeatherProvider provider) {
|
||||
assertThrows(NullPointerException.class, () -> provider.getWeather(null));
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> provider.getWeather(new Coordinates[0]));
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
provider.getWeather(new Coordinates[] { new Coordinates(0, 0), null }));
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
package eu.m724.wtapi.weather;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import eu.m724.wtapi.object.Coordinates;
|
||||
import eu.m724.wtapi.object.Weather;
|
||||
import eu.m724.wtapi.provider.weather.WeatherProvider;
|
||||
import eu.m724.wtapi.provider.weather.impl.openweathermap.OpenWeatherMapProvider;
|
||||
|
||||
public class OpenWeatherMapTest {
|
||||
@Test
|
||||
public void openWeatherMapTest() throws InterruptedException, ExecutionException {
|
||||
String apiKey = "48ac630acaa5c003316aea1dd7327d05";
|
||||
|
||||
WeatherProvider provider = new OpenWeatherMapProvider(apiKey);
|
||||
|
||||
provider.init();
|
||||
|
||||
CompletableFuture<Weather[]> weathersFuture =
|
||||
provider.getWeather(
|
||||
new Coordinates(54.6606714, -3.3827237),
|
||||
new Coordinates(47.5705952, -53.5556464),
|
||||
new Coordinates(34.2073721, -84.1402857));
|
||||
|
||||
Weather[] weathers = weathersFuture.get();
|
||||
assert weathers.length == 3;
|
||||
|
||||
for (Weather weather : weathers) {
|
||||
System.out.printf("Current weather in %s, %s: %s\n", weather.coordinates().city, weather.coordinates().country, weather.description);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
package eu.m724.wtapi.weather;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import eu.m724.wtapi.object.Coordinates;
|
||||
import eu.m724.wtapi.object.Weather;
|
||||
import eu.m724.wtapi.provider.exception.ProviderException;
|
||||
import eu.m724.wtapi.provider.exception.QuotaExceededException;
|
||||
import eu.m724.wtapi.provider.weather.WeatherProvider;
|
||||
|
||||
public class ProviderTest {
|
||||
@Test
|
||||
public void testProvider() throws InterruptedException, ExecutionException {
|
||||
WeatherProvider provider = new MockWeatherProvider(false);
|
||||
provider.init();
|
||||
|
||||
assert provider.getQuota() == 5;
|
||||
assert provider.getBulkLimit() == 1;
|
||||
|
||||
CompletableFuture<Weather> weatherFuture =
|
||||
provider.getWeather(new Coordinates(0, 0));
|
||||
|
||||
Weather weather = weatherFuture.get();
|
||||
assertNotNull(weather);
|
||||
|
||||
CompletableFuture<Weather[]> weathersFuture =
|
||||
provider.getWeatherBulk(new Coordinates[] {
|
||||
new Coordinates(0, 0),
|
||||
new Coordinates(0, 100)
|
||||
});
|
||||
|
||||
Weather[] weathers = weathersFuture.get();
|
||||
assertNotNull(weathers);
|
||||
assert weathers.length == 2;
|
||||
assertNotNull(weathers[0]);
|
||||
assertNotNull(weathers[1]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFaultyProvider() throws InterruptedException {
|
||||
WeatherProvider provider = new MockWeatherProvider(true);
|
||||
|
||||
CompletableFuture<Weather> weatherFuture =
|
||||
provider.getWeather(new Coordinates(0, 0));
|
||||
|
||||
Weather weather = null;
|
||||
|
||||
try {
|
||||
weather = weatherFuture.get();
|
||||
fail("Shouldn't pass");
|
||||
} catch (ExecutionException e) {
|
||||
assert e.getCause() instanceof ProviderException;
|
||||
}
|
||||
|
||||
assertNull(weather);
|
||||
|
||||
Weather[] weathers = null;
|
||||
|
||||
CompletableFuture<Weather[]> weathersFuture =
|
||||
provider.getWeatherBulk(new Coordinates[] {
|
||||
new Coordinates(0, 0),
|
||||
new Coordinates(0, 100)
|
||||
});
|
||||
|
||||
try {
|
||||
weathers = weathersFuture.get();
|
||||
fail("Shouldn't pass");
|
||||
} catch (ExecutionException e) {
|
||||
QuotaExceededException qee = (QuotaExceededException) e.getCause();
|
||||
assert qee.getRetryIn() == 60;
|
||||
}
|
||||
|
||||
assertNull(weathers);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInputErrors() {
|
||||
WeatherProvider provider = new MockWeatherProvider(false);
|
||||
|
||||
assertThrows(NullPointerException.class, () -> provider.getWeather(null));
|
||||
assertThrows(NullPointerException.class, () -> provider.getWeatherBulk(null));
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> provider.getWeatherBulk(new Coordinates[0]));
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
provider.getWeatherBulk(new Coordinates[] { new Coordinates(0, 0), null }));
|
||||
}
|
||||
}
|
96
src/test/java/eu/m724/wtapi/weather/WeatherProviderTest.java
Normal file
96
src/test/java/eu/m724/wtapi/weather/WeatherProviderTest.java
Normal file
|
@ -0,0 +1,96 @@
|
|||
package eu.m724.wtapi.weather;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import eu.m724.wtapi.provider.weather.WeatherQueryResult;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import eu.m724.wtapi.object.Coordinates;
|
||||
import eu.m724.wtapi.object.Weather;
|
||||
import eu.m724.wtapi.provider.exception.ProviderException;
|
||||
import eu.m724.wtapi.provider.weather.WeatherProvider;
|
||||
|
||||
public class WeatherProviderTest {
|
||||
WeatherProvider provider;
|
||||
|
||||
@Before
|
||||
public void initProvider() {
|
||||
this.provider = new MockWeatherProvider(false);
|
||||
provider.init();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProvider() throws InterruptedException, ExecutionException {
|
||||
assert provider.getQuota().getHourlyQuota() == 5;
|
||||
|
||||
CompletableFuture<WeatherQueryResult> weatherFuture =
|
||||
provider.getWeather(new Coordinates(0, 0));
|
||||
|
||||
WeatherQueryResult result = weatherFuture.get();
|
||||
|
||||
if (result.exception() != null) {
|
||||
throw new ExecutionException("Query errored", result.exception());
|
||||
}
|
||||
|
||||
Weather[] weathers = result.weathers();
|
||||
assert weathers.length == 1;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultiple() throws ExecutionException, InterruptedException {
|
||||
CompletableFuture<WeatherQueryResult> weatherFuture =
|
||||
provider.getWeather(
|
||||
new Coordinates(0, 0),
|
||||
new Coordinates(0, 100)
|
||||
);
|
||||
|
||||
WeatherQueryResult result = weatherFuture.get();
|
||||
|
||||
if (result.exception() != null) {
|
||||
throw new ExecutionException("Query errored", result.exception());
|
||||
}
|
||||
|
||||
Weather[] weathers = result.weathers();
|
||||
assert weathers.length == 2;
|
||||
|
||||
assertNotNull(weathers[0]);
|
||||
assertNotNull(weathers[1]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFaultyProvider() throws InterruptedException, ExecutionException {
|
||||
WeatherProvider provider = new MockWeatherProvider(true);
|
||||
|
||||
CompletableFuture<WeatherQueryResult> weatherFuture =
|
||||
provider.getWeather(new Coordinates(0, 0));
|
||||
|
||||
WeatherQueryResult result = weatherFuture.get();
|
||||
|
||||
assertNull(result.weathers());
|
||||
|
||||
if (result.exception() == null) {
|
||||
fail("Faulty provider didn't throw");
|
||||
}
|
||||
|
||||
assert result.exception() instanceof ProviderException;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInputErrors() {
|
||||
WeatherProvider provider = new MockWeatherProvider(false);
|
||||
|
||||
assertThrows(NullPointerException.class, () -> provider.getWeather(null));
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> provider.getWeather(new Coordinates[0]));
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
provider.getWeather(new Coordinates[] { new Coordinates(0, 0), null }));
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue