Skip to content

Commit 0ca4bda

Browse files
Interpolate functionality and InterpolationTest.
1 parent e03cad8 commit 0ca4bda

File tree

3 files changed

+163
-0
lines changed

3 files changed

+163
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616
### Fixed
1717
- Fixed SonarQube junit path issue in GitHub Actions [#1284](https://github.com/ie3-institute/PowerSystemDataModel/issues/1284)
1818
- Fixed no errors thrown in `getMapping()` in `TimeSeriesMappingSource` [#1287](https://github.com/ie3-institute/PowerSystemDataModel/issues/1287)
19+
- Consider None-equivalent value for missing data points in weather [#1304](https://github.com/ie3-institute/PowerSystemDataModel/issues/1304)
1920

2021
### Changed
2122
- Replaced `return this` with `return thisInstance` in CopyBuilders [#1250](https://github.com/ie3-institute/PowerSystemDataModel/issues/1250)

src/main/java/edu/ie3/datamodel/io/source/WeatherSource.java

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,17 @@
1414
import edu.ie3.datamodel.models.value.WeatherValue;
1515
import edu.ie3.datamodel.utils.Try;
1616
import edu.ie3.util.interval.ClosedInterval;
17+
import java.time.Duration;
1718
import java.time.ZonedDateTime;
1819
import java.util.*;
1920
import java.util.stream.Collectors;
2021
import java.util.stream.Stream;
22+
import javax.measure.Quantity;
2123
import org.apache.commons.lang3.tuple.Pair;
2224
import org.locationtech.jts.geom.Point;
2325
import org.slf4j.Logger;
2426
import org.slf4j.LoggerFactory;
27+
import tech.units.indriya.ComparableQuantity;
2528

2629
/** Abstract class for WeatherSource by Csv and Sql Data */
2730
public abstract class WeatherSource extends EntitySource {
@@ -52,6 +55,90 @@ public void validate() throws ValidationException {
5255
validate(WeatherValue.class, this::getSourceFields, weatherFactory);
5356
}
5457

58+
/**
59+
* Method for interpolating weather values.
60+
*
61+
* @return a new quantity
62+
*/
63+
protected List<TimeBasedValue<WeatherValue>> interpolateMissingValues(
64+
List<TimeBasedValue<WeatherValue>> timeSeries) {
65+
66+
List<TimeBasedValue<WeatherValue>> result = new ArrayList<>();
67+
int i = 0;
68+
while (i < timeSeries.size()) {
69+
TimeBasedValue<WeatherValue> current = timeSeries.get(i);
70+
71+
if (current.getValue() != null) {
72+
result.add(current);
73+
i++;
74+
continue;
75+
}
76+
int prevIdx = i - 1;
77+
int nextIdx = i + 1;
78+
while (nextIdx < timeSeries.size() && timeSeries.get(nextIdx).getValue() == null) {
79+
nextIdx++;
80+
}
81+
82+
if (prevIdx >= 0 && nextIdx < timeSeries.size()) {
83+
TimeBasedValue<WeatherValue> prev = timeSeries.get(prevIdx);
84+
TimeBasedValue<WeatherValue> next = timeSeries.get(nextIdx);
85+
Duration total = Duration.between(prev.getTime(), next.getTime());
86+
for (int j = i; j < nextIdx; j++) {
87+
TimeBasedValue<WeatherValue> missing = timeSeries.get(j);
88+
Duration fromPrev = Duration.between(prev.getTime(), missing.getTime());
89+
double ratio = (double) fromPrev.toSeconds() / total.toSeconds();
90+
WeatherValue interpolated =
91+
interpolateWeatherValue(prev.getValue(), next.getValue(), ratio);
92+
result.add(new TimeBasedValue<>(missing.getTime(), interpolated));
93+
}
94+
i = nextIdx;
95+
} else {
96+
result.add(current);
97+
i++;
98+
}
99+
}
100+
101+
return result;
102+
}
103+
104+
private WeatherValue interpolateWeatherValue(WeatherValue start, WeatherValue end, double ratio) {
105+
var startSolar = start.getSolarIrradiance();
106+
var endSolar = end.getSolarIrradiance();
107+
108+
var direct =
109+
interpolateOptional(
110+
startSolar.getDirectIrradiance(), endSolar.getDirectIrradiance(), ratio);
111+
var diffuse =
112+
interpolateOptional(
113+
startSolar.getDiffuseIrradiance(), endSolar.getDiffuseIrradiance(), ratio);
114+
115+
var temp = interpolateDirect(start.getTemperature(), end.getTemperature(), ratio);
116+
var dir =
117+
interpolateDirect(start.getWind().getDirection(), end.getWind().getDirection(), ratio);
118+
var vel = interpolateDirect(start.getWind().getVelocity(), end.getWind().getVelocity(), ratio);
119+
120+
return new WeatherValue(start.getCoordinate(), direct, diffuse, temp, dir, vel);
121+
}
122+
123+
private <Q extends Quantity<Q>> ComparableQuantity<Q> interpolateOptional(
124+
Optional<ComparableQuantity<Q>> startOpt,
125+
Optional<ComparableQuantity<Q>> endOpt,
126+
double ratio) {
127+
return startOpt
128+
.flatMap(startVal -> endOpt.map(endVal -> interpolateQuantity(startVal, endVal, ratio)))
129+
.orElse(null);
130+
}
131+
132+
private <Q extends Quantity<Q>> ComparableQuantity<Q> interpolateDirect(
133+
ComparableQuantity<Q> start, ComparableQuantity<Q> end, double ratio) {
134+
return interpolateQuantity(start, end, ratio);
135+
}
136+
137+
private <Q extends Quantity<Q>> ComparableQuantity<Q> interpolateQuantity(
138+
ComparableQuantity<Q> a, ComparableQuantity<Q> b, double ratio) {
139+
return a.add(b.subtract(a).multiply(ratio));
140+
}
141+
55142
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
56143

57144
public abstract Map<Point, IndividualTimeSeries<WeatherValue>> getWeather(
@@ -72,6 +159,19 @@ public List<ZonedDateTime> getTimeKeysAfter(ZonedDateTime time, Point coordinate
72159
return getTimeKeysAfter(time).getOrDefault(coordinate, Collections.emptyList());
73160
}
74161

162+
protected Map<Point, IndividualTimeSeries<WeatherValue>> interpolateWeatherData(
163+
Map<Point, IndividualTimeSeries<WeatherValue>> rawData) {
164+
return rawData.entrySet().stream()
165+
.collect(
166+
Collectors.toMap(
167+
Map.Entry::getKey,
168+
entry ->
169+
new IndividualTimeSeries<>(
170+
new LinkedHashSet<>(
171+
interpolateMissingValues(
172+
new ArrayList<>(entry.getValue().getEntries()))))));
173+
}
174+
75175
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
76176

77177
/**
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* © 2025. 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.source
7+
8+
import edu.ie3.datamodel.models.timeseries.individual.TimeBasedValue
9+
import edu.ie3.datamodel.models.value.WeatherValue
10+
import edu.ie3.util.TimeUtil
11+
import org.locationtech.jts.geom.Coordinate
12+
import org.locationtech.jts.geom.GeometryFactory
13+
import org.locationtech.jts.geom.Point
14+
import spock.lang.Shared
15+
import spock.lang.Specification
16+
17+
import java.time.ZonedDateTime
18+
19+
class WeatherSourceInterpolationTest extends Specification {
20+
21+
@Shared
22+
def geometryFactory = new GeometryFactory()
23+
24+
def "interpolateMissingValues fills gaps between known weather values"() {
25+
given:
26+
def coordinate = geometryFactory.createPoint(new Coordinate(7.0, 51.0))
27+
def t1 = TimeUtil.withDefaults.toZonedDateTime("2020-01-01T00:00:00Z")
28+
def t2 = TimeUtil.withDefaults.toZonedDateTime("2020-01-01T01:00:00Z")
29+
def t3 = TimeUtil.withDefaults.toZonedDateTime("2020-01-01T02:00:00Z")
30+
def t4 = TimeUtil.withDefaults.toZonedDateTime("2020-01-01T03:00:00Z")
31+
def value1 = new WeatherValue(coordinate, 100, 200, 10, 5, 180)
32+
def value3 = new WeatherValue(coordinate, 300 , 400, 20, 10, 270)
33+
34+
def series = [
35+
new TimeBasedValue(t1, value1),
36+
new TimeBasedValue(t3, value3),
37+
new TimeBasedValue(t4, value3)
38+
] as Set<TimeBasedValue>
39+
40+
def mockSource = new InterpolatingWeatherSource()
41+
42+
when:
43+
def interpolatedSeries = mockSource.interpolateMissingValues(series)
44+
45+
then:
46+
interpolatedSeries.size() == 4
47+
def interpolated = interpolatedSeries.find { it.time == t2 }
48+
interpolated != null
49+
with(interpolated.value as WeatherValue) {
50+
getTemperature().value.doubleValue() == 15.0
51+
getWindVelocity().value.doubleValue() == 7.5
52+
getWindDirection().value.doubleValue() == 225.0
53+
}
54+
}
55+
56+
private static class InterpolatingWeatherSource extends WeatherSource {
57+
@Override
58+
Optional<TimeBasedValue<WeatherValue>> getWeather(ZonedDateTime dateTime, Point coordinate) {
59+
return Optional.empty()
60+
}
61+
}
62+
}

0 commit comments

Comments
 (0)