|
| 1 | +/* |
| 2 | + * © 2021. TU Dortmund University, |
| 3 | + * Institute of Energy Systems, Energy Efficiency and Energy Economics, |
| 4 | + * Research group Distribution grid planning and operation |
| 5 | +*/ |
| 6 | +package edu.ie3.datamodel.io.factory.timeseries; |
| 7 | + |
| 8 | +import edu.ie3.datamodel.models.timeseries.individual.IndividualTimeSeries; |
| 9 | +import edu.ie3.datamodel.models.value.WeatherValue; |
| 10 | +import java.time.ZonedDateTime; |
| 11 | +import java.time.temporal.ChronoUnit; |
| 12 | +import java.util.*; |
| 13 | +import java.util.stream.Collectors; |
| 14 | +import javax.measure.Quantity; |
| 15 | +import org.slf4j.Logger; |
| 16 | +import org.slf4j.LoggerFactory; |
| 17 | + |
| 18 | +public class WeatherValueTimeseriesInterpolation { |
| 19 | + |
| 20 | + private static final Logger logger = |
| 21 | + LoggerFactory.getLogger(WeatherValueTimeseriesInterpolation.class); |
| 22 | + |
| 23 | + public static <V extends Quantity<V>> Quantity<V> interpolate( |
| 24 | + IndividualTimeSeries<WeatherValue> timeSeries, |
| 25 | + ZonedDateTime dateTime, |
| 26 | + String typeString, |
| 27 | + Quantity<V> defaultValue) { |
| 28 | + Optional<ValueSet<V>> valueSet = getValueOptions(timeSeries, dateTime, typeString); |
| 29 | + |
| 30 | + if (valueSet.isPresent()) { |
| 31 | + ValueSet<V> vs = valueSet.get(); |
| 32 | + long interval = vs.weight1 + vs.weight2; |
| 33 | + |
| 34 | + Quantity<V> weighted1 = vs.value1.multiply(vs.weight1); |
| 35 | + Quantity<V> weighted2 = vs.value2.multiply(vs.weight2); |
| 36 | + |
| 37 | + return weighted1.add(weighted2).divide(interval); |
| 38 | + } else { |
| 39 | + logger.warn( |
| 40 | + "Interpolating value for timestamp {} failed. Using default: {}", dateTime, defaultValue); |
| 41 | + return defaultValue; |
| 42 | + } |
| 43 | + } |
| 44 | + |
| 45 | + private static <V extends Quantity<V>> Optional<ValueSet<V>> getValueOptions( |
| 46 | + IndividualTimeSeries<WeatherValue> timeSeries, ZonedDateTime dateTime, String typeString) { |
| 47 | + if (timeSeries.getEntries().size() < 3) { |
| 48 | + logger.info( |
| 49 | + "Not enough entries to interpolate. Need at least 3, got {}", |
| 50 | + timeSeries.getEntries().size()); |
| 51 | + return Optional.empty(); |
| 52 | + } |
| 53 | + |
| 54 | + ZonedDateTime intervalStart = dateTime.minusHours(2); |
| 55 | + ZonedDateTime intervalEnd = dateTime.plusHours(2); |
| 56 | + |
| 57 | + Optional<ValueWithWeight<V>> previous = |
| 58 | + getValue(timeSeries, dateTime, intervalStart, dateTime, typeString); |
| 59 | + Optional<ValueWithWeight<V>> next = |
| 60 | + getValue(timeSeries, dateTime, dateTime, intervalEnd, typeString); |
| 61 | + |
| 62 | + if (previous.isPresent() && next.isPresent()) { |
| 63 | + return Optional.of( |
| 64 | + new ValueSet<>( |
| 65 | + previous.get().value(), |
| 66 | + previous.get().weight(), |
| 67 | + next.get().value(), |
| 68 | + next.get().weight())); |
| 69 | + } else { |
| 70 | + return Optional.empty(); |
| 71 | + } |
| 72 | + } |
| 73 | + |
| 74 | + private static <V extends Quantity<V>> Optional<ValueWithWeight<V>> getValue( |
| 75 | + IndividualTimeSeries<WeatherValue> timeSeries, |
| 76 | + ZonedDateTime timestamp, |
| 77 | + ZonedDateTime intervalStart, |
| 78 | + ZonedDateTime intervalEnd, |
| 79 | + String typeString) { |
| 80 | + List<ValueWithWeight<V>> values = |
| 81 | + (List<ValueWithWeight<V>>) |
| 82 | + timeSeries.getEntries().stream() |
| 83 | + .map( |
| 84 | + entry -> { |
| 85 | + ZonedDateTime time = entry.getTime(); |
| 86 | + long weight = Math.abs(ChronoUnit.SECONDS.between(time, timestamp)); |
| 87 | + if (time.isAfter(intervalStart) && time.isBefore(intervalEnd)) { |
| 88 | + return getTypedValue(entry.getValue(), typeString) |
| 89 | + .map(v -> new ValueWithWeight<>(v, weight)); |
| 90 | + } |
| 91 | + return Optional.<ValueWithWeight<V>>empty(); |
| 92 | + }) |
| 93 | + .filter(Optional::isPresent) |
| 94 | + .map(Optional::get) |
| 95 | + .sorted(Comparator.comparingLong(ValueWithWeight::weight)) |
| 96 | + .collect(Collectors.toList()); |
| 97 | + |
| 98 | + return values.stream().findFirst(); |
| 99 | + } |
| 100 | + |
| 101 | + private static <V extends Quantity<V>> Optional<Quantity<V>> getTypedValue( |
| 102 | + WeatherValue weatherValue, String typeString) { |
| 103 | + switch (typeString) { |
| 104 | + case "diffIrr": |
| 105 | + return (Optional<Quantity<V>>) |
| 106 | + (Optional<?>) weatherValue.getSolarIrradiance().getDiffuseIrradiance(); |
| 107 | + case "dirIrr": |
| 108 | + return (Optional<Quantity<V>>) |
| 109 | + (Optional<?>) weatherValue.getSolarIrradiance().getDirectIrradiance(); |
| 110 | + case "temp": |
| 111 | + return (Optional<Quantity<V>>) (Optional<?>) weatherValue.getTemperature().getTemperature(); |
| 112 | + case "windVel": |
| 113 | + return (Optional<Quantity<V>>) (Optional<?>) weatherValue.getWind().getVelocity(); |
| 114 | + default: |
| 115 | + return Optional.empty(); |
| 116 | + } |
| 117 | + } |
| 118 | + |
| 119 | + private static class ValueSet<V extends Quantity<V>> { |
| 120 | + final Quantity<V> value1; |
| 121 | + final long weight1; |
| 122 | + final Quantity<V> value2; |
| 123 | + final long weight2; |
| 124 | + |
| 125 | + public ValueSet(Quantity<V> value1, long weight1, Quantity<V> value2, long weight2) { |
| 126 | + this.value1 = value1; |
| 127 | + this.weight1 = weight1; |
| 128 | + this.value2 = value2; |
| 129 | + this.weight2 = weight2; |
| 130 | + } |
| 131 | + } |
| 132 | +} |
0 commit comments