diff --git a/pom.xml b/pom.xml
index d35d3e3..03d8801 100644
--- a/pom.xml
+++ b/pom.xml
@@ -12,6 +12,13 @@
17
17
+
+
+
+ 724rocks
+ https://git.724.rocks/api/packages/Minecon724/maven
+
+
@@ -26,5 +33,30 @@
test
+
+
+
+
+ org.apache.maven.plugins
+ maven-release-plugin
+ 3.0.1
+
+
+
+
+
+
+ 724rocks
+ https://git.724.rocks/api/packages/Minecon724/maven
+
+
+ 724rocks
+ https://git.724.rocks/api/packages/Minecon724/maven
+
+
+
+
+ scm:git:https://git.724.rocks/Minecon724/wtapi.git
+
\ No newline at end of file
diff --git a/release.properties b/release.properties
new file mode 100644
index 0000000..5e6aa36
--- /dev/null
+++ b/release.properties
@@ -0,0 +1,21 @@
+#release configuration
+#Thu May 30 13:53:21 CEST 2024
+completedPhase=check-poms
+exec.pomFileName=pom.xml
+exec.snapshotReleasePluginAllowed=false
+pinExternals=false
+preparationGoals=clean verify
+project.scm.eu.m724\:wtapi.developerConnection=scm\:git\:https\://git.724.rocks/Minecon724/wtapi.git
+project.scm.eu.m724\:wtapi.tag=HEAD
+projectVersionPolicyConfig=${projectVersionPolicyConfig}\n
+projectVersionPolicyId=default
+pushChanges=true
+releaseStrategyId=default
+remoteTagging=true
+scm.branchCommitComment=@{prefix} prepare branch @{releaseLabel}
+scm.commentPrefix=[maven-release-plugin]
+scm.developmentCommitComment=@{prefix} prepare for next development iteration
+scm.releaseCommitComment=@{prefix} prepare release @{releaseLabel}
+scm.rollbackCommitComment=@{prefix} rollback the release of @{releaseLabel}
+scm.tagNameFormat=@{project.artifactId}-@{project.version}
+scm.url=scm\:git\:https\://git.724.rocks/Minecon724/wtapi.git
diff --git a/src/main/java/eu/m724/wtapi/object/Severity.java b/src/main/java/eu/m724/wtapi/object/Severity.java
new file mode 100644
index 0000000..e6ad4fd
--- /dev/null
+++ b/src/main/java/eu/m724/wtapi/object/Severity.java
@@ -0,0 +1,5 @@
+package eu.m724.wtapi.object;
+
+public enum Severity {
+ LIGHT, MODERATE, HEAVY
+}
diff --git a/src/main/java/eu/m724/wtapi/object/WeatherState.java b/src/main/java/eu/m724/wtapi/object/WeatherState.java
deleted file mode 100644
index 066f372..0000000
--- a/src/main/java/eu/m724/wtapi/object/WeatherState.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package eu.m724.wtapi.object;
-
-public enum WeatherState {
- CLEAR, RAIN, THUNDER
-}
diff --git a/src/main/java/eu/m724/wtapi/provider/impl/openweathermap/OWMResponseConverter.java b/src/main/java/eu/m724/wtapi/provider/impl/openweathermap/OWMResponseConverter.java
new file mode 100644
index 0000000..5426a7e
--- /dev/null
+++ b/src/main/java/eu/m724/wtapi/provider/impl/openweathermap/OWMResponseConverter.java
@@ -0,0 +1,230 @@
+package eu.m724.wtapi.provider.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) {
+ 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;
+ }
+
+ weather.coordinates =
+ new Coordinates(
+ json.getAsJsonObject("coord").getAsJsonPrimitive("lon").getAsDouble(),
+ json.getAsJsonObject("coord").getAsJsonPrimitive("lat").getAsDouble()
+ );
+
+ weather.temperature = json
+ .getAsJsonObject("main")
+ .getAsJsonPrimitive("temp")
+ .getAsFloat();
+
+ weather.temperatureApparent = 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.humidity = json
+ .getAsJsonObject("main")
+ .getAsJsonPrimitive("humidity")
+ .getAsInt() / 100f;
+
+ weather.cloudiness = json
+ .getAsJsonObject("clouds")
+ .getAsJsonPrimitive("all")
+ .getAsInt() / 100f;
+
+ weather.sunrise = json
+ .getAsJsonObject("sys")
+ .getAsJsonPrimitive("sunrise")
+ .getAsLong();
+
+ weather.sunset = json
+ .getAsJsonObject("sys")
+ .getAsJsonPrimitive("sunrise")
+ .getAsLong();
+
+ weather.city = json
+ .getAsJsonPrimitive("name")
+ .getAsString();
+
+ weather.description = json
+ .getAsJsonArray("weather")
+ .get(0).getAsJsonObject()
+ .getAsJsonPrimitive("description")
+ .getAsString();
+
+ return weather;
+ }
+}
diff --git a/src/main/java/eu/m724/wtapi/provider/impl/openweathermap/OpenWeatherMapProvider.java b/src/main/java/eu/m724/wtapi/provider/impl/openweathermap/OpenWeatherMapProvider.java
new file mode 100644
index 0000000..c91a9a3
--- /dev/null
+++ b/src/main/java/eu/m724/wtapi/provider/impl/openweathermap/OpenWeatherMapProvider.java
@@ -0,0 +1,113 @@
+package eu.m724.wtapi.provider.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.util.ArrayList;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+
+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.WeatherProvider;
+import eu.m724.wtapi.provider.exception.AuthorizationException;
+import eu.m724.wtapi.provider.exception.ProviderException;
+import eu.m724.wtapi.provider.exception.QuotaExceededException;
+import eu.m724.wtapi.provider.exception.ServerProviderException;
+
+public class OpenWeatherMapProvider extends WeatherProvider {
+ private String requestUrlFormat;
+ private String apiKey;
+
+ public OpenWeatherMapProvider(String apiKey) {
+ this.apiKey = apiKey;
+ this.requestUrlFormat = "https://api.openweathermap.org/data/2.5/weather?lat=%f&lon=%f&appid=" + apiKey;
+ }
+
+ @Override
+ public void init() throws ProviderException {
+ CompletableFuture weatherFuture =
+ getWeather(new Coordinates(0, 0));
+
+ try {
+ weatherFuture.get();
+ } catch (InterruptedException e) {
+ throw new ProviderException("unexpected interruptedexception");
+ } catch (ExecutionException e) {
+ throw (ProviderException) e.getSuppressed()[0]; // TODO
+ }
+ }
+
+ @Override
+ public CompletableFuture getWeather(Coordinates coordinates) {
+ String url = String.format(
+ requestUrlFormat, coordinates.latitude, coordinates.longitude);
+
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create(url))
+ .build();
+
+ CompletableFuture> responseFuture =
+ HttpClient.newBuilder()
+ .proxy(ProxySelector.getDefault()).build()
+ .sendAsync(request, BodyHandlers.ofString());
+
+ CompletableFuture weatherFuture =
+ responseFuture.thenApply((response) -> {
+ int code = response.statusCode();
+
+ if (code == 401)
+ throw new AuthorizationException("invalid api key");
+ if (code == 429)
+ throw new QuotaExceededException("ratelimited", 1000);
+ if (code != 200)
+ throw new ServerProviderException(String.format("status code %d", code));
+
+ JsonObject jsonResponse =
+ JsonParser.parseString(response.body())
+ .getAsJsonObject();
+
+ Weather weather = OWMResponseConverter.convert(jsonResponse);
+
+ return weather;
+ });
+
+ return weatherFuture;
+ }
+
+ @Override
+ public CompletableFuture getWeatherBulk(Coordinates[] coordinateses) {
+ ArrayList> weatherFutures = new ArrayList<>();
+
+ for (Coordinates coordinates : coordinateses) {
+ weatherFutures.add(getWeather(coordinates));
+ }
+
+ CompletableFuture weathersFuture =
+ CompletableFuture.allOf(
+ weatherFutures.toArray(new CompletableFuture[0]))
+ .thenApply(v ->
+ weatherFutures.stream()
+ .map(CompletableFuture::join)
+ .toArray(Weather[]::new));
+
+ return weathersFuture;
+ }
+
+ @Override
+ public int getQuotaHourly() {
+ return 1370;
+ }
+
+ @Override
+ public int getBulkLimit() {
+ return 1;
+ }
+
+}
diff --git a/src/test/java/eu/m724/wtapi/CoordinateTest.java b/src/test/java/eu/m724/wtapi/CoordinateTest.java
new file mode 100644
index 0000000..5869512
--- /dev/null
+++ b/src/test/java/eu/m724/wtapi/CoordinateTest.java
@@ -0,0 +1,20 @@
+package eu.m724.wtapi;
+
+import org.junit.Test;
+
+import eu.m724.wtapi.object.Coordinates;
+
+public class CoordinateTest {
+ @Test
+ public void testCoordinates() {
+ Coordinates coordinates = new Coordinates(-91.1, 180.01);
+
+ System.out.println(coordinates.longitude);
+
+ assert coordinates.latitude == 88.9;
+ assert coordinates.longitude == -179.99;
+
+ coordinates = new Coordinates(-91.1, 180.1);
+ assert coordinates.longitude != -179.9; // TODO
+ }
+}
diff --git a/src/test/java/eu/m724/wtapi/TestOWM.java b/src/test/java/eu/m724/wtapi/TestOWM.java
new file mode 100644
index 0000000..2bffb8f
--- /dev/null
+++ b/src/test/java/eu/m724/wtapi/TestOWM.java
@@ -0,0 +1,47 @@
+package eu.m724.wtapi;
+
+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.WeatherProvider;
+import eu.m724.wtapi.provider.impl.openweathermap.OpenWeatherMapProvider;
+
+public class TestOWM {
+ @Test
+ public void openWeatherMapTest() throws InterruptedException, ExecutionException {
+ String apiKey = "f3198d2a4ae3076e0aa47c821a253447";
+
+ WeatherProvider provider = new OpenWeatherMapProvider(apiKey);
+
+ provider.init();
+
+ CompletableFuture weatherFuture =
+ provider.getWeather(new Coordinates(53.2232, -4.2008));
+
+ Weather weather = weatherFuture.get();
+ assertNotNull(weather);
+
+ System.out.printf("current weather in %s: %s\n", weather.city, weather.description);
+
+ CompletableFuture weatherBulkFuture =
+ provider.getWeatherBulk(
+ new Coordinates[] {
+ new Coordinates(54.6606714, -3.3827237),
+ new Coordinates(47.5705952, -53.5556464),
+ new Coordinates(34.2073721, -84.1402857),
+ });
+
+ Weather[] weathers = weatherBulkFuture.get();
+ assert weathers.length == 3;
+
+ for (Weather weather1 : weathers) {
+ System.out.printf("current weather in %s: %s\n", weather1.city, weather1.description);
+ }
+ }
+}