diff --git a/src/main/java/edu/ie3/datamodel/io/factory/timeseries/ValueWithWeight.java b/src/main/java/edu/ie3/datamodel/io/factory/timeseries/ValueWithWeight.java new file mode 100644 index 000000000..faa8c59e2 --- /dev/null +++ b/src/main/java/edu/ie3/datamodel/io/factory/timeseries/ValueWithWeight.java @@ -0,0 +1,16 @@ +/* + * © 2021. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation +*/ +package edu.ie3.datamodel.io.factory.timeseries; + +import javax.measure.Quantity; + +public record ValueWithWeight>(Quantity value, long weight) { + + @Override + public String toString() { + return "ValueWithWeight{" + "value=" + value + ", weight=" + weight + '}'; + } +} diff --git a/src/main/java/edu/ie3/datamodel/io/factory/timeseries/WeatherValueTimeseriesInterpolation.java b/src/main/java/edu/ie3/datamodel/io/factory/timeseries/WeatherValueTimeseriesInterpolation.java new file mode 100644 index 000000000..d03de8781 --- /dev/null +++ b/src/main/java/edu/ie3/datamodel/io/factory/timeseries/WeatherValueTimeseriesInterpolation.java @@ -0,0 +1,132 @@ +/* + * © 2021. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation +*/ +package edu.ie3.datamodel.io.factory.timeseries; + +import edu.ie3.datamodel.models.timeseries.individual.IndividualTimeSeries; +import edu.ie3.datamodel.models.value.WeatherValue; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.*; +import java.util.stream.Collectors; +import javax.measure.Quantity; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class WeatherValueTimeseriesInterpolation { + + private static final Logger logger = + LoggerFactory.getLogger(WeatherValueTimeseriesInterpolation.class); + + public static > Quantity interpolate( + IndividualTimeSeries timeSeries, + ZonedDateTime dateTime, + String typeString, + Quantity defaultValue) { + Optional> valueSet = getValueOptions(timeSeries, dateTime, typeString); + + if (valueSet.isPresent()) { + ValueSet vs = valueSet.get(); + long interval = vs.weight1 + vs.weight2; + + Quantity weighted1 = vs.value1.multiply(vs.weight1); + Quantity weighted2 = vs.value2.multiply(vs.weight2); + + return weighted1.add(weighted2).divide(interval); + } else { + logger.warn( + "Interpolating value for timestamp {} failed. Using default: {}", dateTime, defaultValue); + return defaultValue; + } + } + + private static > Optional> getValueOptions( + IndividualTimeSeries timeSeries, ZonedDateTime dateTime, String typeString) { + if (timeSeries.getEntries().size() < 3) { + logger.info( + "Not enough entries to interpolate. Need at least 3, got {}", + timeSeries.getEntries().size()); + return Optional.empty(); + } + + ZonedDateTime intervalStart = dateTime.minusHours(2); + ZonedDateTime intervalEnd = dateTime.plusHours(2); + + Optional> previous = + getValue(timeSeries, dateTime, intervalStart, dateTime, typeString); + Optional> next = + getValue(timeSeries, dateTime, dateTime, intervalEnd, typeString); + + if (previous.isPresent() && next.isPresent()) { + return Optional.of( + new ValueSet<>( + previous.get().value(), + previous.get().weight(), + next.get().value(), + next.get().weight())); + } else { + return Optional.empty(); + } + } + + private static > Optional> getValue( + IndividualTimeSeries timeSeries, + ZonedDateTime timestamp, + ZonedDateTime intervalStart, + ZonedDateTime intervalEnd, + String typeString) { + List> values = + (List>) + timeSeries.getEntries().stream() + .map( + entry -> { + ZonedDateTime time = entry.getTime(); + long weight = Math.abs(ChronoUnit.SECONDS.between(time, timestamp)); + if (time.isAfter(intervalStart) && time.isBefore(intervalEnd)) { + return getTypedValue(entry.getValue(), typeString) + .map(v -> new ValueWithWeight<>(v, weight)); + } + return Optional.>empty(); + }) + .filter(Optional::isPresent) + .map(Optional::get) + .sorted(Comparator.comparingLong(ValueWithWeight::weight)) + .collect(Collectors.toList()); + + return values.stream().findFirst(); + } + + private static > Optional> getTypedValue( + WeatherValue weatherValue, String typeString) { + switch (typeString) { + case "diffIrr": + return (Optional>) + (Optional) weatherValue.getSolarIrradiance().getDiffuseIrradiance(); + case "dirIrr": + return (Optional>) + (Optional) weatherValue.getSolarIrradiance().getDirectIrradiance(); + case "temp": + return (Optional>) (Optional) weatherValue.getTemperature().getTemperature(); + case "windVel": + return (Optional>) (Optional) weatherValue.getWind().getVelocity(); + default: + return Optional.empty(); + } + } + + private static class ValueSet> { + final Quantity value1; + final long weight1; + final Quantity value2; + final long weight2; + + public ValueSet(Quantity value1, long weight1, Quantity value2, long weight2) { + this.value1 = value1; + this.weight1 = weight1; + this.value2 = value2; + this.weight2 = weight2; + } + } +} diff --git a/src/test/groovy/edu/ie3/datamodel/models/timeseries/WeatherValueTimeseriesInterpolationTest.groovy b/src/test/groovy/edu/ie3/datamodel/models/timeseries/WeatherValueTimeseriesInterpolationTest.groovy new file mode 100644 index 000000000..3aa55377b --- /dev/null +++ b/src/test/groovy/edu/ie3/datamodel/models/timeseries/WeatherValueTimeseriesInterpolationTest.groovy @@ -0,0 +1,11 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ +package edu.ie3.datamodel.models.timeseries + +import edu.ie3.datamodel.io.factory.timeseries.WeatherValueTimeseriesInterpolation + +class WeatherValueTimeseriesInterpolationTest { +}