Partial update, not working
This commit is contained in:
parent
d6d41fccd3
commit
dafb1d770a
29 changed files with 415 additions and 262 deletions
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
17
src/main/java/eu/m724/wtapi/object/GeoLocation.java
Normal file
17
src/main/java/eu/m724/wtapi/object/GeoLocation.java
Normal file
|
@ -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
|
||||
) {
|
||||
}
|
24
src/main/java/eu/m724/wtapi/object/Quota.java
Normal file
24
src/main/java/eu/m724/wtapi/object/Quota.java
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
package eu.m724.wtapi.object;
|
||||
|
||||
public enum Severity {
|
||||
LIGHT, MODERATE, HEAVY
|
||||
}
|
|
@ -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
|
||||
) {
|
||||
|
||||
}
|
||||
|
|
|
@ -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.<br>
|
||||
* Currently: <code>blitzortung</code>
|
||||
*
|
||||
* @param name The provider name
|
||||
* @param apiKey The API key, or <code>null</code> 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.<br>
|
||||
* Currently: <code>openmeteo</code>
|
||||
*
|
||||
* @param name The provider name
|
||||
* @param apiKey The API key, or <code>null</code> 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.<br>
|
||||
* Currently: <code>approximate</code>
|
||||
*
|
||||
* @param name The provider name
|
||||
* @param apiKey The API key, or <code>null</code> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package eu.m724.wtapi.provider.exception;
|
||||
|
||||
public class ProviderAuthorizationException extends ProviderException {
|
||||
public ProviderAuthorizationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 <em>suggestion</em> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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<br>
|
||||
* Twilight refers to sunset and sunrise timestamp<br>
|
||||
* 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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Weather> 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<Weather[]> 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<Weather[]> getWeather(Coordinates... coordinates);
|
||||
|
||||
public abstract Quota getQuota();
|
||||
}
|
||||
|
|
|
@ -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<Weather[]> 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<HttpResponse<String>> 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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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<Weather> weatherFuture =
|
||||
getWeather(new Coordinates(0, 0));
|
||||
|
||||
try {
|
||||
weatherFuture.join();
|
||||
} catch (CompletionException e) {
|
||||
throw (ProviderException) e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Weather> getWeather(Coordinates coordinates) {
|
||||
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);
|
||||
|
||||
.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 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<Weather[]> getWeatherBulk(Coordinates[] coordinateses) {
|
||||
public CompletableFuture<Weather[]> getWeather(Coordinates... coordinates) {
|
||||
ArrayList<CompletableFuture<Weather>> 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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Weather> 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<Weather[]> getWeatherBulk(Coordinates[] coordinateses) {
|
||||
int len = coordinateses.length;
|
||||
public CompletableFuture<Weather[]> 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<Weather[]> completableFuture =
|
||||
new CompletableFuture<>();
|
||||
|
@ -55,14 +55,14 @@ public class MockWeatherProvider extends WeatherProvider {
|
|||
Weather[] weathers = new Weather[len];
|
||||
|
||||
for (int i=0; i<len; i++) {
|
||||
if (coordinateses[i] == null)
|
||||
throw new IllegalArgumentException("a coordinate is null");
|
||||
if (coordinatesArray[i] == null)
|
||||
throw new IllegalArgumentException("A coordinate is null");
|
||||
|
||||
weathers[i] = new Weather();
|
||||
}
|
||||
|
||||
if (faulty)
|
||||
completableFuture.completeExceptionally(new QuotaExceededException("im too lazy lmao", 60));
|
||||
completableFuture.completeExceptionally(new QuotaExceededException("Quota exceeded", 60));
|
||||
else
|
||||
completableFuture.complete(weathers);
|
||||
|
||||
|
@ -70,7 +70,7 @@ public class MockWeatherProvider extends WeatherProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int getQuotaHourly() {
|
||||
public int getQuota() {
|
||||
return 5;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,28 +20,18 @@ public class OpenWeatherMapTest {
|
|||
WeatherProvider provider = new OpenWeatherMapProvider(apiKey);
|
||||
|
||||
provider.init();
|
||||
|
||||
CompletableFuture<Weather> 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<Weather[]> weatherBulkFuture =
|
||||
provider.getWeatherBulk(
|
||||
new Coordinates[] {
|
||||
new Coordinates(54.6606714, -3.3827237),
|
||||
new Coordinates(47.5705952, -53.5556464),
|
||||
new Coordinates(34.2073721, -84.1402857),
|
||||
});
|
||||
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 = 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Weather> weatherFuture =
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue