diff --git a/CHANGELOG.md b/CHANGELOG.md index a7593a97d..aa59cb90a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fixed handling of `CongestionResult.InputModelType` in `EntityProcessor` [#1325](https://github.com/ie3-institute/PowerSystemDataModel/issues/1325) - -Fixed em fields in input models [#1331](https://github.com/ie3-institute/PowerSystemDataModel/issues/1331) +- Add interpolation functionality to estimate missing weather values [#1304](https://github.com/ie3-institute/PowerSystemDataModel/issues/1304) ### Changed - Updated dependabot workflow and added CODEOWNERS [#1328](https://github.com/ie3-institute/PowerSystemDataModel/issues/1328) diff --git a/src/main/java/edu/ie3/datamodel/io/source/WeatherSource.java b/src/main/java/edu/ie3/datamodel/io/source/WeatherSource.java index a7a056d85..5771fd202 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/WeatherSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/WeatherSource.java @@ -14,14 +14,17 @@ import edu.ie3.datamodel.models.value.WeatherValue; import edu.ie3.datamodel.utils.Try; import edu.ie3.util.interval.ClosedInterval; +import java.time.Duration; import java.time.ZonedDateTime; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; +import javax.measure.Quantity; import org.apache.commons.lang3.tuple.Pair; import org.locationtech.jts.geom.Point; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import tech.units.indriya.ComparableQuantity; /** Abstract class for WeatherSource by Csv and Sql Data */ public abstract class WeatherSource extends EntitySource { @@ -52,6 +55,32 @@ public void validate() throws ValidationException { validate(WeatherValue.class, this::getSourceFields, weatherFactory); } + private WeatherValue interpolateWeatherValue(WeatherValue start, WeatherValue end, double ratio) { + var direct = interpolateOptional(start.getDirectIrradiance(), end.getDirectIrradiance(), ratio); + var diffuse = + interpolateOptional(start.getDiffuseIrradiance(), end.getDiffuseIrradiance(), ratio); + + var temp = interpolateOptional(start.getTemperatureValue(), end.getTemperatureValue(), ratio); + var dir = interpolateOptional(start.getWindDirection(), end.getWindDirection(), ratio); + var vel = interpolateOptional(start.getWindVelocity(), end.getWindVelocity(), ratio); + + return new WeatherValue(start.getCoordinate(), direct, diffuse, temp, dir, vel); + } + + private > ComparableQuantity interpolateOptional( + Optional> startOpt, + Optional> endOpt, + double ratio) { + return startOpt + .flatMap(startVal -> endOpt.map(endVal -> interpolateQuantity(startVal, endVal, ratio))) + .orElse(null); + } + + private > ComparableQuantity interpolateQuantity( + ComparableQuantity a, ComparableQuantity b, double ratio) { + return a.add(b.subtract(a).multiply(ratio)); + } + // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- public abstract Map> getWeather( @@ -64,6 +93,51 @@ public abstract Map> getWeather( public abstract Optional> getWeather( ZonedDateTime date, Point coordinate) throws SourceException; + public Optional getWeatherInterpolated( + ZonedDateTime date, Point coordinate, int plus, int minus) throws SourceException { + + ClosedInterval interpolationInterval = + new ClosedInterval<>(date.minusHours(minus), date.plusHours(plus)); + IndividualTimeSeries ts = + getWeather(interpolationInterval, List.of(coordinate)).get(coordinate); + + if (ts == null) { + log.warn("No time series available for coordinate {}", coordinate); + return Optional.empty(); + } + + Optional value = ts.getValue(date); + + if (value.isPresent() && value.get().hasPartialValues()) { + return value; + } + + Optional> prevValue = ts.getPreviousTimeBasedValue(date); + Optional> nextValue = ts.getNextTimeBasedValue(date); + + if (prevValue.isEmpty() || nextValue.isEmpty()) { + log.warn( + "Not enough data to interpolate weather value at {} for coordinate {}", date, coordinate); + return Optional.empty(); + } + + TimeBasedValue prev = prevValue.get(); + TimeBasedValue next = nextValue.get(); + + Duration totalDuration = Duration.between(prev.getTime(), next.getTime()); + Duration partialDuration = Duration.between(prev.getTime(), date); + + if (totalDuration.isZero()) { + return Optional.of(prev.getValue()); + } + + double ratio = (double) partialDuration.toSeconds() / totalDuration.toSeconds(); + + WeatherValue interpolated = interpolateWeatherValue(prev.getValue(), next.getValue(), ratio); + + return Optional.of(interpolated); + } + public abstract Map> getTimeKeysAfter(ZonedDateTime time) throws SourceException; diff --git a/src/main/java/edu/ie3/datamodel/models/value/WeatherValue.java b/src/main/java/edu/ie3/datamodel/models/value/WeatherValue.java index 09ec1787f..92f9368e8 100644 --- a/src/main/java/edu/ie3/datamodel/models/value/WeatherValue.java +++ b/src/main/java/edu/ie3/datamodel/models/value/WeatherValue.java @@ -7,6 +7,7 @@ import edu.ie3.util.quantities.interfaces.Irradiance; import java.util.Objects; +import java.util.Optional; import javax.measure.quantity.Angle; import javax.measure.quantity.Speed; import javax.measure.quantity.Temperature; @@ -81,6 +82,35 @@ public WindValue getWind() { return wind; } + public Optional> getDirectIrradiance() { + return solarIrradiance.getDirectIrradiance(); + } + + public Optional> getDiffuseIrradiance() { + return solarIrradiance.getDiffuseIrradiance(); + } + + public Optional> getTemperatureValue() { + return temperature.getTemperature(); + } + + public Optional> getWindDirection() { + return wind.getDirection(); + } + + public Optional> getWindVelocity() { + return wind.getVelocity(); + } + + /** + * Checks if all mandatory values are present. + * + * @return true if all values are present, false otherwise + */ + public boolean hasPartialValues() { + return solarIrradiance != null || temperature != null || wind != null; + } + @Override public boolean equals(Object o) { if (this == o) return true;