diff --git a/src/main/java/eu/m724/wtapi/object/Coordinates.java b/src/main/java/eu/m724/wtapi/object/Coordinates.java
index f38f6ce..ddbab63 100644
--- a/src/main/java/eu/m724/wtapi/object/Coordinates.java
+++ b/src/main/java/eu/m724/wtapi/object/Coordinates.java
@@ -1,21 +1,14 @@
package eu.m724.wtapi.object;
/**
- * represents geographic coordinates
- * contains fields latitude and longitude
+ * Represents coordinates
+ *
+ * @param latitude The latitude (-90-90)
+ * @param longitude The longitude (-180-180)
*/
-public class Coordinates {
- public final double latitude, longitude;
- public String country, city; // TODO should it stay here?
-
+public record Coordinates(double latitude, double longitude) {
public Coordinates(double latitude, double longitude) {
this.latitude = (((latitude + 90) % 180) + 180) % 180 - 90;
this.longitude = (((longitude + 180) % 360) + 360) % 360 - 180;
}
-
- public Coordinates setAddress(String country, String city) {
- this.country = country;
- this.city = city;
- return this;
- }
}
diff --git a/src/main/java/eu/m724/wtapi/object/GeoLocation.java b/src/main/java/eu/m724/wtapi/object/GeoLocation.java
new file mode 100644
index 0000000..3ba4e4e
--- /dev/null
+++ b/src/main/java/eu/m724/wtapi/object/GeoLocation.java
@@ -0,0 +1,17 @@
+package eu.m724.wtapi.object;
+
+/**
+ * Represents a geolocation
+ *
+ * @param coordinates The coordinates
+ * @param city the city
+ * @param country The country name
+ * @param countryCode The ISO 3166-1 alpha-2 country code
+ */
+public record GeoLocation(
+ Coordinates coordinates,
+ String city,
+ String country,
+ String countryCode
+) {
+}
diff --git a/src/main/java/eu/m724/wtapi/object/Quota.java b/src/main/java/eu/m724/wtapi/object/Quota.java
new file mode 100644
index 0000000..0b1f7e1
--- /dev/null
+++ b/src/main/java/eu/m724/wtapi/object/Quota.java
@@ -0,0 +1,24 @@
+package eu.m724.wtapi.object;
+
+import java.util.concurrent.TimeUnit;
+
+public record Quota(
+ int maxRequests,
+ TimeUnit timeUnit
+) {
+ public static Quota hourly(int maxRequestsHourly) {
+ return new Quota(maxRequestsHourly, TimeUnit.HOURS);
+ }
+
+ public static Quota daily(int maxRequestsDaily) {
+ return new Quota(maxRequestsDaily, TimeUnit.DAYS);
+ }
+
+ public int getMinuteQuota() {
+ return maxRequests / (int) timeUnit.toMicros(1);
+ }
+
+ public int getHourlyQuota() {
+ return maxRequests / (int) timeUnit.toHours(1);
+ }
+}
diff --git a/src/main/java/eu/m724/wtapi/object/Severity.java b/src/main/java/eu/m724/wtapi/object/Severity.java
deleted file mode 100644
index e6ad4fd..0000000
--- a/src/main/java/eu/m724/wtapi/object/Severity.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package eu.m724.wtapi.object;
-
-public enum Severity {
- LIGHT, MODERATE, HEAVY
-}
diff --git a/src/main/java/eu/m724/wtapi/object/Weather.java b/src/main/java/eu/m724/wtapi/object/Weather.java
index 76400b9..b4ac0f5 100644
--- a/src/main/java/eu/m724/wtapi/object/Weather.java
+++ b/src/main/java/eu/m724/wtapi/object/Weather.java
@@ -1,53 +1,33 @@
package eu.m724.wtapi.object;
+import java.time.LocalDateTime;
+
/**
- * contains weather conditions
+ * Represents current weather
+ *
+ * @param coordinates The coordinates
+ * @param timestamp The timestamp of this data
+ * @param raining Whether it's raining
+ * @param thundering Whether it's a thunderstorm
+ * @param snowing Whether it's snowing
+ * @param temperatureCelsius The temperature in Celsius
+ * @param temperatureApparentCelsius The apparent temperature (feels like) in Celsius
+ * @param relativeHumidityPercentage Percentage of relative humidity, 0.0-1.0
+ * @param cloudCoverPercentage Percentage of cloud cover (cloudiness), 0.0-1.0
*/
-public class Weather {
- public Coordinates coordinates;
-
- public Severity rainSeverity, thunderstormSeverity, snowSeverity;
-
- // secondary conditions
- public Severity sleetSeverity, drizzleSeverity;
-
- public boolean shower;
-
- /**
- * in kelvin
- */
- public float temperature, temperatureApparent;
-
- /**
- * in meters per second
- */
- public float windSpeed, windGust;
-
- /**
- * 0.0 - 1.0
- */
- public float humidity, cloudiness;
-
- /**
- * as unix timestamp
- */
- public long sunrise, sunset;
-
- /**
- * short name of weather
- */
- public String description;
-
-
- public boolean isRaining() {
- return rainSeverity != null;
- }
-
- public boolean isThundering() {
- return thunderstormSeverity != null;
- }
-
- public boolean isSnowing() {
- return snowSeverity != null;
- }
+public record Weather(
+ Coordinates coordinates,
+ LocalDateTime timestamp,
+
+ boolean raining,
+ boolean thundering,
+ boolean snowing,
+
+ float temperatureCelsius,
+ float temperatureApparentCelsius,
+
+ float relativeHumidityPercentage,
+ float cloudCoverPercentage
+) {
+
}
diff --git a/src/main/java/eu/m724/wtapi/provider/Providers.java b/src/main/java/eu/m724/wtapi/provider/Providers.java
index 0dfe691..9aea931 100644
--- a/src/main/java/eu/m724/wtapi/provider/Providers.java
+++ b/src/main/java/eu/m724/wtapi/provider/Providers.java
@@ -1,40 +1,68 @@
package eu.m724.wtapi.provider;
-import eu.m724.wtapi.provider.exception.NoSuchProviderException;
import eu.m724.wtapi.provider.thunder.ThunderProvider;
import eu.m724.wtapi.provider.thunder.impl.blitzortung.BlitzortungProvider;
import eu.m724.wtapi.provider.twilight.impl.approximate.ApproximateTwilightTimeProvider;
import eu.m724.wtapi.provider.twilight.TwilightTimeProvider;
import eu.m724.wtapi.provider.weather.WeatherProvider;
-import eu.m724.wtapi.provider.weather.impl.openweathermap.OpenWeatherMapProvider;
+import eu.m724.wtapi.provider.weather.impl.openmeteo.OpenMeteoProvider;
public class Providers {
-
- public static ThunderProvider getThunderProvider(String name, String apiKey) throws NoSuchProviderException {
+
+ /**
+ * Gets a thunder provider by name.
+ * Currently: blitzortung
+ *
+ * @param name The provider name
+ * @param apiKey The API key, or null
if not needed
+ * @return The thunder provider
+ * @throws NoSuchProviderException If invalid provider name
+ */
+ public static ThunderProvider getThunderProvider(String name, String apiKey) {
switch (name.toLowerCase()) {
- case "blitzortung":
- return new BlitzortungProvider();
+ case "blitzortung" -> new BlitzortungProvider();
}
- throw new NoSuchProviderException();
+ throw new NoSuchProviderException(name);
}
-
- public static WeatherProvider getWeatherProvider(String name, String apiKey) throws NoSuchProviderException {
+
+ /**
+ * Gets a weather provider by name.
+ * Currently: openmeteo
+ *
+ * @param name The provider name
+ * @param apiKey The API key, or null
if not needed
+ * @return The weather provider
+ * @throws NoSuchProviderException If invalid provider name
+ */
+ public static WeatherProvider getWeatherProvider(String name, String apiKey) {
switch (name.toLowerCase()) {
- case "openweathermap":
- return new OpenWeatherMapProvider(apiKey);
+ case "openmeteo" -> new OpenMeteoProvider();
}
- throw new NoSuchProviderException();
+ throw new NoSuchProviderException(name);
}
- public static TwilightTimeProvider getTwilightTimeProvider(String name, String apiKey) throws NoSuchProviderException {
+ /**
+ * Gets a twilight time provider by name.
+ * Currently: approximate
+ *
+ * @param name The provider name
+ * @param apiKey The API key, or null
if not needed
+ * @return The twilight time provider
+ * @throws NoSuchProviderException If invalid provider name
+ */
+ public static TwilightTimeProvider getTwilightTimeProvider(String name, String apiKey) {
switch (name.toLowerCase()) {
- case "approximate":
- return new ApproximateTwilightTimeProvider();
+ case "approximate" -> new ApproximateTwilightTimeProvider();
}
- throw new NoSuchProviderException();
+ throw new NoSuchProviderException(name);
+ }
+
+ public static class NoSuchProviderException extends RuntimeException {
+ public NoSuchProviderException(String provider) {
+ super(provider);
+ }
}
-
}
diff --git a/src/main/java/eu/m724/wtapi/provider/exception/AuthorizationException.java b/src/main/java/eu/m724/wtapi/provider/exception/AuthorizationException.java
deleted file mode 100644
index 18d56b2..0000000
--- a/src/main/java/eu/m724/wtapi/provider/exception/AuthorizationException.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package eu.m724.wtapi.provider.exception;
-
-/**
- * when you specified an incorrect api key
- */
-public class AuthorizationException extends ProviderException {
-
- private static final long serialVersionUID = -2258293509429607176L;
-
- public AuthorizationException(String message) {
- super(message);
- // TODO Auto-generated constructor stub
- }
-
-}
diff --git a/src/main/java/eu/m724/wtapi/provider/exception/NoSuchProviderException.java b/src/main/java/eu/m724/wtapi/provider/exception/NoSuchProviderException.java
deleted file mode 100644
index 621a4cb..0000000
--- a/src/main/java/eu/m724/wtapi/provider/exception/NoSuchProviderException.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package eu.m724.wtapi.provider.exception;
-
-/**
- * thrown when there's no known provider with that name
- */
-public class NoSuchProviderException extends Exception {
-
- private static final long serialVersionUID = -2740598348303023762L;
-
-}
diff --git a/src/main/java/eu/m724/wtapi/provider/exception/ProviderAuthorizationException.java b/src/main/java/eu/m724/wtapi/provider/exception/ProviderAuthorizationException.java
new file mode 100644
index 0000000..8d630c6
--- /dev/null
+++ b/src/main/java/eu/m724/wtapi/provider/exception/ProviderAuthorizationException.java
@@ -0,0 +1,7 @@
+package eu.m724.wtapi.provider.exception;
+
+public class ProviderAuthorizationException extends ProviderException {
+ public ProviderAuthorizationException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/eu/m724/wtapi/provider/exception/ProviderException.java b/src/main/java/eu/m724/wtapi/provider/exception/ProviderException.java
index ee5e7f7..015984c 100644
--- a/src/main/java/eu/m724/wtapi/provider/exception/ProviderException.java
+++ b/src/main/java/eu/m724/wtapi/provider/exception/ProviderException.java
@@ -3,11 +3,12 @@ package eu.m724.wtapi.provider.exception;
import java.util.concurrent.CompletionException;
public class ProviderException extends CompletionException {
-
- private static final long serialVersionUID = -841882181122537157L;
-
public ProviderException(String message) {
super(message);
}
+ public ProviderException(Throwable cause) {
+ super(cause);
+ }
+
}
diff --git a/src/main/java/eu/m724/wtapi/provider/exception/ProviderRequestException.java b/src/main/java/eu/m724/wtapi/provider/exception/ProviderRequestException.java
new file mode 100644
index 0000000..1da2f7e
--- /dev/null
+++ b/src/main/java/eu/m724/wtapi/provider/exception/ProviderRequestException.java
@@ -0,0 +1,14 @@
+package eu.m724.wtapi.provider.exception;
+
+/**
+ * When unable to connect to the provider.
+ */
+public class ProviderRequestException extends ProviderException {
+ public ProviderRequestException(String message) {
+ super(message);
+ }
+
+ public ProviderRequestException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/src/main/java/eu/m724/wtapi/provider/exception/QuotaExceededException.java b/src/main/java/eu/m724/wtapi/provider/exception/QuotaExceededException.java
index 9f26b0f..10c8897 100644
--- a/src/main/java/eu/m724/wtapi/provider/exception/QuotaExceededException.java
+++ b/src/main/java/eu/m724/wtapi/provider/exception/QuotaExceededException.java
@@ -1,22 +1,16 @@
package eu.m724.wtapi.provider.exception;
-/**
- * too many api requests, or ratelimited
- */
public class QuotaExceededException extends ProviderException {
-
- private static final long serialVersionUID = 7550042052176034614L;
-
- private int retryMs;
+ private final int retryInSeconds;
/**
- *
- * @param message
- * @param retryMs SUGGESTION after how many ms to retry. it can be null
+ * Instantiates a new QuotaExceededException.
+ *
+ * @param retryInSeconds A suggestion after how many seconds to retry.
*/
- public QuotaExceededException(String message, int retryMs) {
- super(message);
- this.retryMs = retryMs;
+ public QuotaExceededException(int retryInSeconds) {
+ super("Rate limited. Try again in " + retryInSeconds + "s");
+ this.retryInSeconds = retryInSeconds;
}
/**
@@ -24,7 +18,7 @@ public class QuotaExceededException extends ProviderException {
* @return SUGGESTION after how many ms to retry. it can be null
*/
public int getRetryIn() {
- return this.retryMs;
+ return this.retryInSeconds;
}
}
diff --git a/src/main/java/eu/m724/wtapi/provider/exception/ServerProviderException.java b/src/main/java/eu/m724/wtapi/provider/exception/ServerProviderException.java
deleted file mode 100644
index 4aaa9fe..0000000
--- a/src/main/java/eu/m724/wtapi/provider/exception/ServerProviderException.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package eu.m724.wtapi.provider.exception;
-
-/**
- * when the provider is at fault
- */
-public class ServerProviderException extends ProviderException {
-
- private static final long serialVersionUID = 4461164912391786673L;
-
- public ServerProviderException(String message) {
- super(message);
- // TODO Auto-generated constructor stub
- }
-
-
-}
diff --git a/src/main/java/eu/m724/wtapi/provider/thunder/impl/blitzortung/BlitzortungWebsocketClient.java b/src/main/java/eu/m724/wtapi/provider/thunder/impl/blitzortung/BlitzortungWebsocketClient.java
index bdb7bef..4a06e21 100644
--- a/src/main/java/eu/m724/wtapi/provider/thunder/impl/blitzortung/BlitzortungWebsocketClient.java
+++ b/src/main/java/eu/m724/wtapi/provider/thunder/impl/blitzortung/BlitzortungWebsocketClient.java
@@ -62,7 +62,7 @@ class BlitzortungWebsocketClient extends WebSocketClient {
JsonParser.parseString(decode(message))
.getAsJsonObject();
- long time = json.getAsJsonPrimitive("time").getAsLong() / 1000000;
+ long time = json.getAsJsonPrimitive("timestamp").getAsLong() / 1000000;
double lat = json.getAsJsonPrimitive("lat").getAsDouble();
double lon = json.getAsJsonPrimitive("lon").getAsDouble();
diff --git a/src/main/java/eu/m724/wtapi/provider/twilight/TwilightTimeProvider.java b/src/main/java/eu/m724/wtapi/provider/twilight/TwilightTimeProvider.java
index 92fd24d..5805652 100644
--- a/src/main/java/eu/m724/wtapi/provider/twilight/TwilightTimeProvider.java
+++ b/src/main/java/eu/m724/wtapi/provider/twilight/TwilightTimeProvider.java
@@ -4,10 +4,11 @@ import eu.m724.wtapi.object.Coordinates;
import eu.m724.wtapi.object.Twilight;
import java.time.LocalDate;
+import java.time.LocalDateTime;
// TODO make this an interface? also consider new name
/**
- * Twilight refers to sunset and sunrise time
+ * Twilight refers to sunset and sunrise timestamp
* Is it the correct term? I don't know
*/
public abstract class TwilightTimeProvider {
@@ -19,4 +20,6 @@ public abstract class TwilightTimeProvider {
* @return {@link Twilight}
*/
public abstract Twilight calculateTwilightTime(LocalDate date, Coordinates coordinates);
+
+ public abstract double calculateSolarAltitude(LocalDateTime time, Coordinates coordinates);
}
diff --git a/src/main/java/eu/m724/wtapi/provider/twilight/impl/approximate/ApproximateTwilightTimeProvider.java b/src/main/java/eu/m724/wtapi/provider/twilight/impl/approximate/ApproximateTwilightTimeProvider.java
index db1ef2b..44e5070 100644
--- a/src/main/java/eu/m724/wtapi/provider/twilight/impl/approximate/ApproximateTwilightTimeProvider.java
+++ b/src/main/java/eu/m724/wtapi/provider/twilight/impl/approximate/ApproximateTwilightTimeProvider.java
@@ -21,8 +21,8 @@ public class ApproximateTwilightTimeProvider extends CacheableTwilightTimeProvid
double declination = cache.declination();
double equationOfTime = cache.equationOfTime();
- double latRad = toRadians(coordinates.latitude);
- double solarNoon = 720 - 4 * coordinates.longitude - equationOfTime;
+ double latRad = toRadians(coordinates.latitude());
+ double solarNoon = 720 - 4 * coordinates.longitude() - equationOfTime;
LocalDateTime solarNoonDateTime = cache.date().atStartOfDay().plusSeconds((long) (solarNoon * 60));
// 90.833 deg = 1.5853349194640094 rad
@@ -94,15 +94,9 @@ public class ApproximateTwilightTimeProvider extends CacheableTwilightTimeProvid
public ApproximateTwilightTimeCache initializeCache(LocalDate date) {
double fractionalYear = getFractionalYear(date);
- double equationOfTime = 229.18 * (0.000075
- + 0.001868 * cos(fractionalYear)
- - 0.032077 * sin(fractionalYear)
- - 0.014615 * cos(2 * fractionalYear)
- - 0.040849 * sin(2 * fractionalYear));
-
return new ApproximateTwilightTimeCache(
date,
- equationOfTime,
+ getEquationOfTime(fractionalYear),
getDeclination(fractionalYear)
);
}
@@ -115,6 +109,14 @@ public class ApproximateTwilightTimeProvider extends CacheableTwilightTimeProvid
return 0.01721420632103996 * dayOfYear;
}
+ private double getEquationOfTime(double fractionalYear) {
+ return 229.18 * (0.000075
+ + 0.001868 * cos(fractionalYear)
+ - 0.032077 * sin(fractionalYear)
+ - 0.014615 * cos(2 * fractionalYear)
+ - 0.040849 * sin(2 * fractionalYear));
+ }
+
private double getDeclination(double fractionalYear) {
return 0.006918
- 0.399912 * cos(fractionalYear)
@@ -124,4 +126,24 @@ public class ApproximateTwilightTimeProvider extends CacheableTwilightTimeProvid
- 0.002697 * cos(3 * fractionalYear)
+ 0.00148 * sin(3 * fractionalYear);
}
+
+ @Override
+ public double calculateSolarAltitude(LocalDateTime time, Coordinates coordinates) {
+ double latRad = toRadians(coordinates.latitude());
+ double declination = getDeclination(getFractionalYear(time.toLocalDate()));
+
+ double hourAngle = toRadians(15 * (lst(time, coordinates.longitude()) - 12));
+ return asin(sin(declination) * sin(latRad) + cos(declination) * cos(latRad) * cos(hourAngle));
+ }
+
+ private double lst(LocalDateTime dateTime, double longitude) {
+ double hours = dateTime.toLocalTime().toSecondOfDay() / 3600.0;
+ return hours + longitude + longitude / 15 + getEquationOfTime(getFractionalYear(dateTime.toLocalDate()));
+ }
+
+ private double G_year(int year) {
+ double G_ref = 6.6768410277777778;
+ int year_ref = 2024;
+ return (G_ref + 0.0657098244 * (year - year_ref)) % 24;
+ }
}
diff --git a/src/main/java/eu/m724/wtapi/provider/weather/WeatherProvider.java b/src/main/java/eu/m724/wtapi/provider/weather/WeatherProvider.java
index 24b8eda..4b2e56b 100644
--- a/src/main/java/eu/m724/wtapi/provider/weather/WeatherProvider.java
+++ b/src/main/java/eu/m724/wtapi/provider/weather/WeatherProvider.java
@@ -1,8 +1,10 @@
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;
@@ -15,40 +17,12 @@ public abstract class WeatherProvider {
public abstract void init() throws ProviderException;
/**
- * get weather for a single point
- * @param coordinates
- * @throws NullPointerException if coordinates is null
- * @return a future that CAN throw {@link ProviderException}
+ * Get the current weather of a single or multiple points
+ *
+ * @param coordinates The coordinates
+ * @return A future that CAN throw {@link ProviderException}
*/
- public abstract CompletableFuture getWeather(Coordinates coordinates);
-
- /**
- * get weather for multiple points at bulk
- * it can be called one at a time if the api doesn't support this or if there's too many
- * @param coordinateses array of coordinates
- * @throws IllegalArgumentException if array is empty
- * @return a future that CAN throw {@link ProviderException}
- */
- public abstract CompletableFuture getWeatherBulk(Coordinates[] coordinateses);
-
- /**
- * get hourly quota that is max requests per hour
- * @return hourly quota
- */
- public abstract int getQuotaHourly();
-
- /**
- * how many coordinates in one bulk request
- * this is because some apis don't support bulk or limit it
- * @return amount of coordinates per bulk request
- */
- public abstract int getBulkLimit();
-
- /**
- * estimates minimum delay between calls given last request
- * @return milliseconds
- */
- public int estimateDelay() {
- return (int) Math.ceil(this.getQuotaHourly() / 2.0 / 60 / 60 / 1000);
- }
+ public abstract CompletableFuture getWeather(Coordinates... coordinates);
+
+ public abstract Quota getQuota();
}
diff --git a/src/main/java/eu/m724/wtapi/provider/weather/impl/openmeteo/OpenMeteoProvider.java b/src/main/java/eu/m724/wtapi/provider/weather/impl/openmeteo/OpenMeteoProvider.java
new file mode 100644
index 0000000..b27d667
--- /dev/null
+++ b/src/main/java/eu/m724/wtapi/provider/weather/impl/openmeteo/OpenMeteoProvider.java
@@ -0,0 +1,89 @@
+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 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 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.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+
+public class OpenMeteoProvider extends WeatherProvider {
+ @Override
+ public void init() throws ProviderException {
+
+ }
+
+ @Override
+ public CompletableFuture getWeather(Coordinates... coordinates) {
+ String latitudes = Arrays.stream(coordinates)
+ .map(Coordinates::latitude)
+ .map(Number::toString)
+ .collect(Collectors.joining(","));
+
+ String longitudes = Arrays.stream(coordinates)
+ .map(Coordinates::longitude)
+ .map(Number::toString)
+ .collect(Collectors.joining(","));
+
+ 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();
+
+ CompletableFuture> responseFuture =
+ HttpClient.newBuilder().build()
+ .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);
+ });
+ }
+
+ @Override
+ public Quota getQuota() {
+ return Quota.daily(10000);
+ }
+}
diff --git a/src/main/java/eu/m724/wtapi/provider/weather/impl/openmeteo/OpenMeteoResponseConverter.java b/src/main/java/eu/m724/wtapi/provider/weather/impl/openmeteo/OpenMeteoResponseConverter.java
new file mode 100644
index 0000000..d32f7a7
--- /dev/null
+++ b/src/main/java/eu/m724/wtapi/provider/weather/impl/openmeteo/OpenMeteoResponseConverter.java
@@ -0,0 +1,53 @@
+package eu.m724.wtapi.provider.weather.impl.openmeteo;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import eu.m724.wtapi.object.Coordinates;
+import eu.m724.wtapi.object.Weather;
+
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+
+public class OpenMeteoResponseConverter {
+ static Weather[] convert(JsonElement jsonElement) {
+ Weather[] weathers;
+
+ if (jsonElement.isJsonArray()) {
+ weathers = jsonElement.getAsJsonArray().asList().stream()
+ .map(JsonElement::getAsJsonObject)
+ .map(OpenMeteoResponseConverter::convertSingle)
+ .toArray(Weather[]::new);
+ } else {
+ weathers = new Weather[] {
+ convertSingle(jsonElement.getAsJsonObject())
+ };
+ }
+
+ return weathers;
+ }
+
+ private static Weather convertSingle(JsonObject jsonObject) {
+ JsonObject current = jsonObject.getAsJsonObject("current");
+
+ Coordinates coordinates = new Coordinates(
+ jsonObject.get("latitude").getAsDouble(),
+ jsonObject.get("longitude").getAsDouble()
+ );
+
+ LocalDateTime timestamp = LocalDateTime.ofEpochSecond(current.get("time").getAsLong(), 0, ZoneOffset.UTC);
+
+ int weatherCode = current.get("weather_code").getAsInt();
+
+ boolean raining = current.get("rain").getAsDouble() > 0;
+ boolean thundering = weatherCode >= 90 && weatherCode < 100;
+ boolean snowing = current.get("snowfall").getAsDouble() > 0;
+
+ float temperatureCelsius = current.get("temperature").getAsFloat();
+ float temperatureApparentCelsius = current.get("apparent_temperature").getAsFloat();
+
+ float relativeHumidityPercentage = current.get("relative_humidity_2m").getAsInt() / 100f;
+ float cloudCoverPercentage = current.get("cloud_cover").getAsInt() / 100f;
+
+ return new Weather(coordinates, timestamp, raining, thundering, snowing, temperatureCelsius, temperatureApparentCelsius, relativeHumidityPercentage, cloudCoverPercentage);
+ }
+}
diff --git a/src/main/java/eu/m724/wtapi/provider/weather/impl/openweathermap/OWMResponseConverter.java b/src/main/java/eu/m724/wtapi/provider/weather/impl/openweathermap/OWMResponseConverter.java
index ea6649c..c464e62 100644
--- a/src/main/java/eu/m724/wtapi/provider/weather/impl/openweathermap/OWMResponseConverter.java
+++ b/src/main/java/eu/m724/wtapi/provider/weather/impl/openweathermap/OWMResponseConverter.java
@@ -9,6 +9,10 @@ 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")
@@ -178,12 +182,12 @@ public class OWMResponseConverter {
json.getAsJsonObject("coord").getAsJsonPrimitive("lat").getAsDouble()
).setAddress(country, city);
- weather.temperature = json
+ weather.temperatureCelsius = json
.getAsJsonObject("main")
.getAsJsonPrimitive("temp")
.getAsFloat();
- weather.temperatureApparent = json
+ weather.temperatureApparentCelsius = json
.getAsJsonObject("main")
.getAsJsonPrimitive("feels_like")
.getAsFloat();
@@ -200,12 +204,12 @@ public class OWMResponseConverter {
if (pri != null)
weather.windSpeed = pri.getAsFloat();
- weather.humidity = json
+ weather.relativeHumidity = json
.getAsJsonObject("main")
- .getAsJsonPrimitive("humidity")
+ .getAsJsonPrimitive("relativeHumidityPercentage")
.getAsInt() / 100f;
- weather.cloudiness = json
+ weather.cloudCover = json
.getAsJsonObject("clouds")
.getAsJsonPrimitive("all")
.getAsInt() / 100f;
diff --git a/src/main/java/eu/m724/wtapi/provider/weather/impl/openweathermap/OpenWeatherMapProvider.java b/src/main/java/eu/m724/wtapi/provider/weather/impl/openweathermap/OpenWeatherMapProvider.java
index eabf18a..eff20ed 100644
--- a/src/main/java/eu/m724/wtapi/provider/weather/impl/openweathermap/OpenWeatherMapProvider.java
+++ b/src/main/java/eu/m724/wtapi/provider/weather/impl/openweathermap/OpenWeatherMapProvider.java
@@ -6,21 +6,27 @@ 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 java.util.concurrent.CompletionException;
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.AuthorizationException;
+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.ServerProviderException;
+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;
@@ -32,56 +38,49 @@ public class OpenWeatherMapProvider extends WeatherProvider {
public void init() throws ProviderException {
CompletableFuture weatherFuture =
getWeather(new Coordinates(0, 0));
-
- try {
- weatherFuture.join();
- } catch (CompletionException e) {
- throw (ProviderException) e;
- }
- }
- @Override
- public CompletableFuture getWeather(Coordinates coordinates) {
+ weatherFuture.join();
+ }
+
+ private CompletableFuture 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);
-
+ .formatted(coordinates.latitude(), coordinates.longitude(), apiKey);
+
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");
+ throw new ProviderAuthorizationException("Invalid api key");
if (code == 429)
- throw new QuotaExceededException("ratelimited", 1000);
+ throw new QuotaExceededException("Ratelimited", 1000);
if (code != 200)
- throw new ServerProviderException(String.format("status code %d", code));
-
+ throw new ProviderRequestException("Status code: " + code);
+
JsonObject jsonResponse =
JsonParser.parseString(response.body())
.getAsJsonObject();
-
- Weather weather = OWMResponseConverter.convert(jsonResponse);
-
- return weather;
+
+ return OWMResponseConverter.convert(jsonResponse);
});
-
+
return weatherFuture;
}
@Override
- public CompletableFuture getWeatherBulk(Coordinates[] coordinateses) {
+ public CompletableFuture getWeather(Coordinates... coordinates) {
ArrayList> weatherFutures = new ArrayList<>();
- for (Coordinates coordinates : coordinateses) {
+ for (Coordinates coordinates : coordinatesArray) {
weatherFutures.add(getWeather(coordinates));
}
@@ -97,8 +96,8 @@ public class OpenWeatherMapProvider extends WeatherProvider {
}
@Override
- public int getQuotaHourly() {
- return 1370;
+ public int getQuota() {
+ return Duration.ofHours(1370);
}
@Override
diff --git a/src/test/java/eu/m724/wtapi/object/CoordinateTest.java b/src/test/java/eu/m724/wtapi/object/CoordinateTest.java
index 4e2388a..4c87bfe 100644
--- a/src/test/java/eu/m724/wtapi/object/CoordinateTest.java
+++ b/src/test/java/eu/m724/wtapi/object/CoordinateTest.java
@@ -9,14 +9,14 @@ public class CoordinateTest {
public void testCoordinates() {
Coordinates coordinates = new Coordinates(-91.1, 180.01);
- System.out.println(coordinates.longitude);
+ System.out.println(coordinates.longitude());
- assert coordinates.latitude == 88.9;
- assert coordinates.longitude == -179.99;
+ assert coordinates.latitude() == 88.9;
+ assert coordinates.longitude() == -179.99;
coordinates = new Coordinates(-91.1, 180.1);
- System.out.printf("Precision loss expected: %f\n", coordinates.longitude);
- assert coordinates.longitude != -179.9; // TODO fix precision loss
+ 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);
diff --git a/src/test/java/eu/m724/wtapi/thunder/BlitzortungTest.java b/src/test/java/eu/m724/wtapi/thunder/BlitzortungTest.java
index 8015522..51ce79e 100644
--- a/src/test/java/eu/m724/wtapi/thunder/BlitzortungTest.java
+++ b/src/test/java/eu/m724/wtapi/thunder/BlitzortungTest.java
@@ -25,7 +25,7 @@ public class BlitzortungTest {
provider.tick();
int size = coordinatesList.size();
if (size > 0)
- System.out.printf("Last from tick: %f %f (total %d)\n", coordinatesList.get(size-1).latitude, coordinatesList.get(size-1).longitude, size);
+ System.out.printf("Last from tick: %f %f (total %d)\n", coordinatesList.get(size - 1).latitude(), coordinatesList.get(size - 1).longitude(), size);
Thread.sleep(25);
}
diff --git a/src/test/java/eu/m724/wtapi/thunder/ThunderProviderTest.java b/src/test/java/eu/m724/wtapi/thunder/ThunderProviderTest.java
index 5c6b299..6778d7d 100644
--- a/src/test/java/eu/m724/wtapi/thunder/ThunderProviderTest.java
+++ b/src/test/java/eu/m724/wtapi/thunder/ThunderProviderTest.java
@@ -24,7 +24,7 @@ public class ThunderProviderTest {
provider.tick();
int size = coordinatesList.size();
if (size > 0)
- System.out.printf("Last from tick: %f %f (total %d)\n", coordinatesList.get(size-1).latitude, coordinatesList.get(size-1).longitude, size);
+ System.out.printf("Last from tick: %f %f (total %d)\n", coordinatesList.get(size - 1).latitude(), coordinatesList.get(size - 1).longitude(), size);
Thread.sleep(20);
}
@@ -33,6 +33,6 @@ public class ThunderProviderTest {
System.out.printf("Strikes in the last 1s: %d\n", coordinatesList.size());
System.out.printf("Latency: %dms\n", provider.getLatency());
- assert coordinatesList.size() == 20; // TODO this is time sensitive and fails under loaded system. Also, the entire test is suboptimal
+ assert coordinatesList.size() == 20; // TODO this is timestamp sensitive and fails under loaded system. Also, the entire test is suboptimal
}
}
diff --git a/src/test/java/eu/m724/wtapi/twilight/ApproximateTwilightTimeTest.java b/src/test/java/eu/m724/wtapi/twilight/ApproximateTwilightTimeTest.java
index 7845ec8..e964c50 100644
--- a/src/test/java/eu/m724/wtapi/twilight/ApproximateTwilightTimeTest.java
+++ b/src/test/java/eu/m724/wtapi/twilight/ApproximateTwilightTimeTest.java
@@ -22,6 +22,10 @@ public class ApproximateTwilightTimeTest {
public void approximateTest() {
TwilightTimeProvider provider = new ApproximateTwilightTimeProvider();
+ 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);
testLocation(provider, 13, 11, 2040, 45.432427, -122.3899276, LocalDateTime.of(2040, 11, 13, 15, 7), LocalDateTime.of(2040, 11, 14, 0, 41), 0);
@@ -39,7 +43,7 @@ public class ApproximateTwilightTimeTest {
Twilight twilight = provider.calculateTwilightTime(date, coordinates);
System.out.println(date);
- System.out.println(coordinates.latitude + " " + coordinates.longitude);
+ System.out.println(coordinates.latitude() + " " + coordinates.longitude());
System.out.println("Solar noon: " + twilight.solarNoon());
System.out.println("Calculated sunrise: " + twilight.sunrise());
diff --git a/src/test/java/eu/m724/wtapi/twilight/MockTwilightTimeProvider.java b/src/test/java/eu/m724/wtapi/twilight/MockTwilightTimeProvider.java
index c3ca302..bc32656 100644
--- a/src/test/java/eu/m724/wtapi/twilight/MockTwilightTimeProvider.java
+++ b/src/test/java/eu/m724/wtapi/twilight/MockTwilightTimeProvider.java
@@ -4,15 +4,13 @@ import eu.m724.wtapi.object.Coordinates;
import eu.m724.wtapi.object.Twilight;
import eu.m724.wtapi.provider.twilight.TwilightTimeProvider;
-import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
-import java.time.LocalTime;
public class MockTwilightTimeProvider extends TwilightTimeProvider {
@Override
public Twilight calculateTwilightTime(LocalDate date, Coordinates coordinates) {
- int time = (int) (coordinates.latitude + coordinates.longitude);
+ int time = (int) (coordinates.latitude() + coordinates.longitude());
return new Twilight(
date,
date.atStartOfDay().plusSeconds(-time),
@@ -21,4 +19,9 @@ public class MockTwilightTimeProvider extends TwilightTimeProvider {
false
);
}
+
+ @Override
+ public double calculateSolarAltitude(LocalDateTime time, Coordinates coordinates) {
+ return coordinates.latitude();
+ }
}
diff --git a/src/test/java/eu/m724/wtapi/weather/MockWeatherProvider.java b/src/test/java/eu/m724/wtapi/weather/MockWeatherProvider.java
index 3e74bd0..ef8fe3c 100644
--- a/src/test/java/eu/m724/wtapi/weather/MockWeatherProvider.java
+++ b/src/test/java/eu/m724/wtapi/weather/MockWeatherProvider.java
@@ -5,7 +5,7 @@ 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.exception.ServerProviderException;
+import eu.m724.wtapi.provider.exception.ProviderRequestException;
import eu.m724.wtapi.provider.weather.WeatherProvider;
public class MockWeatherProvider extends WeatherProvider {
@@ -26,13 +26,13 @@ public class MockWeatherProvider extends WeatherProvider {
throw new NullPointerException("no coordinates passed");
System.out.printf("mock getting weather for %f, %f\n",
- coordinates.latitude, coordinates.longitude);
+ coordinates.latitude(), coordinates.longitude());
CompletableFuture completableFuture =
new CompletableFuture<>();
if (faulty)
- completableFuture.completeExceptionally(new ServerProviderException("server is on vacation rn"));
+ completableFuture.completeExceptionally(new ProviderRequestException("Imagined server is down"));
else
completableFuture.complete(new Weather());
@@ -40,13 +40,13 @@ public class MockWeatherProvider extends WeatherProvider {
}
@Override
- public CompletableFuture getWeatherBulk(Coordinates[] coordinateses) {
- int len = coordinateses.length;
+ public CompletableFuture getWeatherBulk(Coordinates[] coordinatesArray) {
+ int len = coordinatesArray.length;
if (len == 0)
throw new IllegalArgumentException("no coordinates passed");
- System.out.printf("mock getting weather for multiple coords");
+ System.out.println("mock getting weather for multiple coords");
CompletableFuture completableFuture =
new CompletableFuture<>();
@@ -55,14 +55,14 @@ public class MockWeatherProvider extends WeatherProvider {
Weather[] weathers = new Weather[len];
for (int i=0; i weatherFuture =
- provider.getWeather(new Coordinates(53.2232, -4.2008));
-
- Weather weather = weatherFuture.get();
- assertNotNull(weather);
-
- System.out.printf("current weather in %s, %s: %s\n", weather.coordinates.city, weather.coordinates.country, 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),
- });
+ CompletableFuture weathersFuture =
+ provider.getWeather(
+ new Coordinates(54.6606714, -3.3827237),
+ new Coordinates(47.5705952, -53.5556464),
+ new Coordinates(34.2073721, -84.1402857));
- Weather[] weathers = weatherBulkFuture.get();
+ Weather[] weathers = weathersFuture.get();
assert weathers.length == 3;
- for (Weather weather1 : weathers) {
- System.out.printf("current weather in %s, %s: %s\n", weather.coordinates.city, weather.coordinates.country, weather1.description);
+ for (Weather weather : weathers) {
+ System.out.printf("Current weather in %s, %s: %s\n", weather.coordinates().city, weather.coordinates().country, weather.description);
}
}
}
diff --git a/src/test/java/eu/m724/wtapi/weather/ProviderTest.java b/src/test/java/eu/m724/wtapi/weather/ProviderTest.java
index cee26a7..76b5d66 100644
--- a/src/test/java/eu/m724/wtapi/weather/ProviderTest.java
+++ b/src/test/java/eu/m724/wtapi/weather/ProviderTest.java
@@ -22,7 +22,7 @@ public class ProviderTest {
WeatherProvider provider = new MockWeatherProvider(false);
provider.init();
- assert provider.getQuotaHourly() == 5;
+ assert provider.getQuota() == 5;
assert provider.getBulkLimit() == 1;
CompletableFuture weatherFuture =