Skip to content

Commit b4abfab

Browse files
committed
Adding integration test for InfluxDB and ICON weather model + improving the implementation
1 parent 96e8bf7 commit b4abfab

File tree

5 files changed

+201
-6
lines changed

5 files changed

+201
-6
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3131
- BREAKING: Moved methods `buildSafe{Coord,Point,LineString,LineStringBetweenCoords,LineStringBetweenPoints}`, `totalLengthOfLineString` from `GridAndGeoUtils` to `GeoUtils` in [_PowerSystemUtils_](https://github.com/ie3-institute/PowerSystemUtils)
3232
- BREAKING: Moved `CoordinateDistance` to [_PowerSystemUtils_](https://github.com/ie3-institute/PowerSystemUtils)
3333
- BREAKING: Weather source
34-
- Adapted data scheme (COSMO: `"coordinate"` to `"coordinate id"`, `"irradiation"` to `"irradiance"`, ICON: `"datum"` to `"coordinate"`)
34+
- Adapted data scheme (COSMO: `"coordinate"` to `"coordinate id"`, `"irradiation"` to `"irradiance"`, ICON: `"datum"` to `"time"`)
3535
- Harmonized the source of coordinate id column name across implementations of `WeatherSource`
3636
- Get field name in different casing (to actually get the column name in database, file, ...)
37+
- Force user to provide time stamp pattern to `CouchbaseWeatherSource` to ensure harmonized querying
38+
- Use naming convention dependent field names from factories within `InfluxDBWeatherSource`
3739

3840
### Fixed
3941
- InfluxDbConnector now keeps session instead of creating a new one each call

src/main/java/edu/ie3/datamodel/io/source/influxdb/InfluxDbWeatherSource.java

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import edu.ie3.datamodel.models.value.WeatherValue;
1616
import edu.ie3.util.StringUtils;
1717
import edu.ie3.util.interval.ClosedInterval;
18+
import edu.ie3.util.naming.NamingConvention;
1819
import java.time.ZonedDateTime;
1920
import java.util.*;
2021
import java.util.stream.Collectors;
@@ -30,26 +31,53 @@ public class InfluxDbWeatherSource implements WeatherSource {
3031
private static final String WHERE = " where ";
3132
private static final String MEASUREMENT_NAME_WEATHER = "weather";
3233
private static final int MILLI_TO_NANO_FACTOR = 1000000;
34+
private static final NamingConvention DEFAULT_NAMING_CONVENTION = NamingConvention.SNAKE;
3335

3436
private final String coordinateIdFieldName;
3537
private final InfluxDbConnector connector;
3638
private final IdCoordinateSource coordinateSource;
3739
private final TimeBasedWeatherValueFactory weatherValueFactory;
40+
private final NamingConvention namingConvention;
41+
42+
/**
43+
* Initializes a new InfluxDbWeatherSource using the default naming convention.
44+
*
45+
* @param connector needed for database connection
46+
* @param coordinateSource needed to map coordinates to ID as InfluxDB does not support spatial
47+
* types
48+
* @param weatherValueFactory instance of a time based weather value factory
49+
* @deprecated Use {@link InfluxDbWeatherSource#InfluxDbWeatherSource(InfluxDbConnector,
50+
* IdCoordinateSource, NamingConvention, TimeBasedWeatherValueFactory)}
51+
*/
52+
@Deprecated
53+
public InfluxDbWeatherSource(
54+
InfluxDbConnector connector,
55+
IdCoordinateSource coordinateSource,
56+
TimeBasedWeatherValueFactory weatherValueFactory) {
57+
this.connector = connector;
58+
this.coordinateSource = coordinateSource;
59+
this.namingConvention = DEFAULT_NAMING_CONVENTION;
60+
this.weatherValueFactory = weatherValueFactory;
61+
this.coordinateIdFieldName = weatherValueFactory.getCoordinateIdFieldString();
62+
}
3863

3964
/**
4065
* Initializes a new InfluxDbWeatherSource
4166
*
4267
* @param connector needed for database connection
4368
* @param coordinateSource needed to map coordinates to ID as InfluxDB does not support spatial
4469
* types
70+
* @param namingConvention the naming convention used for features
4571
* @param weatherValueFactory instance of a time based weather value factory
4672
*/
4773
public InfluxDbWeatherSource(
4874
InfluxDbConnector connector,
4975
IdCoordinateSource coordinateSource,
76+
NamingConvention namingConvention,
5077
TimeBasedWeatherValueFactory weatherValueFactory) {
5178
this.connector = connector;
5279
this.coordinateSource = coordinateSource;
80+
this.namingConvention = namingConvention;
5381
this.weatherValueFactory = weatherValueFactory;
5482
this.coordinateIdFieldName = weatherValueFactory.getCoordinateIdFieldString();
5583
}
@@ -196,18 +224,26 @@ private String createQueryStringForTimeInterval(ClosedInterval<ZonedDateTime> ti
196224
}
197225

198226
private String createTimeConstraint(ClosedInterval<ZonedDateTime> timeInterval) {
199-
return "time >= "
227+
return weatherValueFactory.getTimeFieldString()
228+
+ " >= "
200229
+ timeInterval.getLower().toInstant().toEpochMilli() * MILLI_TO_NANO_FACTOR
201-
+ " and time <= "
230+
+ " and "
231+
+ weatherValueFactory.getTimeFieldString()
232+
+ " <= "
202233
+ timeInterval.getUpper().toInstant().toEpochMilli() * MILLI_TO_NANO_FACTOR;
203234
}
204235

205236
private String createTimeConstraint(ZonedDateTime date) {
206-
return "time=" + date.toInstant().toEpochMilli() * MILLI_TO_NANO_FACTOR;
237+
return weatherValueFactory.getTimeFieldString()
238+
+ "="
239+
+ date.toInstant().toEpochMilli() * MILLI_TO_NANO_FACTOR;
207240
}
208241

209242
private String createCoordinateConstraintString(int coordinateId) {
210-
return "coordinate_id='" + coordinateId + "'";
243+
return weatherValueFactory.getCoordinateIdFieldString(namingConvention)
244+
+ " = '"
245+
+ coordinateId
246+
+ "'";
211247
}
212248

213249
/**

src/test/groovy/edu/ie3/datamodel/io/source/influxdb/InfluxDbWeatherSourceCosmoIT.groovy

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import edu.ie3.test.common.CosmoWeatherTestData
1414
import edu.ie3.test.helper.WeatherSourceTestHelper
1515
import edu.ie3.util.geo.GeoUtils
1616
import edu.ie3.util.interval.ClosedInterval
17+
import edu.ie3.util.naming.NamingConvention
1718
import org.locationtech.jts.geom.Point
1819
import org.testcontainers.containers.InfluxDBContainer
1920
import org.testcontainers.spock.Testcontainers
@@ -44,7 +45,7 @@ class InfluxDbWeatherSourceCosmoIT extends Specification implements WeatherSourc
4445

4546
def connector = new InfluxDbConnector(influxDbContainer.url,"test_weather", "test_scenario")
4647
def weatherFactory = new CosmoTimeBasedWeatherValueFactory()
47-
source = new InfluxDbWeatherSource(connector, CosmoWeatherTestData.coordinateSource, weatherFactory)
48+
source = new InfluxDbWeatherSource(connector, CosmoWeatherTestData.coordinateSource, NamingConvention.SNAKE, weatherFactory)
4849
}
4950

5051

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
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.source.influxdb
7+
8+
import edu.ie3.datamodel.io.connectors.InfluxDbConnector
9+
import edu.ie3.datamodel.io.factory.timeseries.CosmoTimeBasedWeatherValueFactory
10+
import edu.ie3.datamodel.io.factory.timeseries.IconTimeBasedWeatherValueFactory
11+
import edu.ie3.datamodel.models.timeseries.individual.IndividualTimeSeries
12+
import edu.ie3.datamodel.models.timeseries.individual.TimeBasedValue
13+
import edu.ie3.datamodel.models.value.WeatherValue
14+
import edu.ie3.test.common.IconWeatherTestData
15+
import edu.ie3.test.helper.WeatherSourceTestHelper
16+
import edu.ie3.util.geo.GeoUtils
17+
import edu.ie3.util.interval.ClosedInterval
18+
import edu.ie3.util.naming.NamingConvention
19+
import org.locationtech.jts.geom.Point
20+
import org.testcontainers.containers.InfluxDBContainer
21+
import org.testcontainers.spock.Testcontainers
22+
import org.testcontainers.utility.MountableFile
23+
import spock.lang.Shared
24+
import spock.lang.Specification
25+
26+
@Testcontainers
27+
class InfluxDbWeatherSourceIconIT extends Specification implements WeatherSourceTestHelper {
28+
29+
@Shared
30+
InfluxDBContainer influxDbContainer = new InfluxDBContainer("1.8.4")
31+
.withAuthEnabled(false)
32+
.withDatabase("test_weather")
33+
34+
@Shared
35+
InfluxDbWeatherSource source
36+
37+
def setupSpec() {
38+
// Copy import file into docker and then import it via influx CLI
39+
// more information on file format and usage here: https://docs.influxdata.com/influxdb/v1.7/tools/shell/#import-data-from-a-file-with-import
40+
MountableFile influxWeatherImportFile = MountableFile.forClasspathResource("/testcontainersFiles/influxDb/icon/weather.txt")
41+
influxDbContainer.copyFileToContainer(influxWeatherImportFile, "/home/weather.txt")
42+
def execResult = influxDbContainer.execInContainer("influx", "-import", "-path=/home/weather.txt", "-precision=ms")
43+
println "Command \"influx -import -path=/home/weather.txt -precision=ms\" returned:"
44+
if(!execResult.stderr.isEmpty()) println execResult.getStderr()
45+
if(!execResult.stdout.isEmpty()) println execResult.getStdout()
46+
47+
def connector = new InfluxDbConnector(influxDbContainer.url,"test_weather", "test_scenario")
48+
def weatherFactory = new IconTimeBasedWeatherValueFactory("yyyy-MM-dd'T'HH:mm:ss[.S[S][S]]'Z'")
49+
source = new InfluxDbWeatherSource(connector, IconWeatherTestData.coordinateSource, NamingConvention.SNAKE, weatherFactory)
50+
}
51+
52+
def "The test container can establish a valid connection"() {
53+
when:
54+
def connector = new InfluxDbConnector(influxDbContainer.url,"test_weather", "test_scenario")
55+
then:
56+
connector.connectionValid
57+
}
58+
59+
def "An InfluxDbWeatherSource can read and correctly parse a single value for a specific date and coordinate"() {
60+
given:
61+
def expectedTimeBasedValue = new TimeBasedValue(IconWeatherTestData.TIME_15H , IconWeatherTestData.WEATHER_VALUE_67775_15H)
62+
63+
when:
64+
def optTimeBasedValue = source.getWeather(IconWeatherTestData.TIME_15H , IconWeatherTestData.COORDINATE_67775)
65+
66+
then:
67+
optTimeBasedValue.present
68+
equalsIgnoreUUID(optTimeBasedValue.get(), expectedTimeBasedValue)
69+
}
70+
71+
def "An InfluxDbWeatherSource can read multiple time series values for multiple coordinates"() {
72+
given:
73+
def coordinates = [
74+
IconWeatherTestData.COORDINATE_67775,
75+
IconWeatherTestData.COORDINATE_67776
76+
]
77+
def timeInterval = new ClosedInterval(IconWeatherTestData.TIME_16H , IconWeatherTestData.TIME_17H)
78+
def timeseries67775 = new IndividualTimeSeries(null,
79+
[
80+
new TimeBasedValue(IconWeatherTestData.TIME_16H , IconWeatherTestData.WEATHER_VALUE_67775_16H),
81+
new TimeBasedValue(IconWeatherTestData.TIME_17H , IconWeatherTestData.WEATHER_VALUE_67775_17H)]
82+
as Set<TimeBasedValue>)
83+
def timeseries67776 = new IndividualTimeSeries(null,
84+
[
85+
new TimeBasedValue(IconWeatherTestData.TIME_16H , IconWeatherTestData.WEATHER_VALUE_67776_16H)] as Set<TimeBasedValue>)
86+
87+
when:
88+
def coordinateToTimeSeries = source.getWeather(timeInterval, coordinates)
89+
90+
then:
91+
coordinateToTimeSeries.keySet().size() == 2
92+
equalsIgnoreUUID(coordinateToTimeSeries.get(IconWeatherTestData.COORDINATE_67775), timeseries67775)
93+
equalsIgnoreUUID(coordinateToTimeSeries.get(IconWeatherTestData.COORDINATE_67776), timeseries67776)
94+
}
95+
96+
def "An InfluxDbWeatherSource can read all weather data in a given time interval"() {
97+
given:
98+
def timeInterval = new ClosedInterval(IconWeatherTestData.TIME_15H , IconWeatherTestData.TIME_17H)
99+
def timeseries_67775 = new IndividualTimeSeries(null,
100+
[
101+
new TimeBasedValue(IconWeatherTestData.TIME_15H ,IconWeatherTestData.WEATHER_VALUE_67775_15H),
102+
new TimeBasedValue(IconWeatherTestData.TIME_16H ,IconWeatherTestData.WEATHER_VALUE_67775_16H),
103+
new TimeBasedValue(IconWeatherTestData.TIME_17H ,IconWeatherTestData.WEATHER_VALUE_67775_17H)] as Set<TimeBasedValue>)
104+
def timeseries_67776 = new IndividualTimeSeries(null,
105+
[
106+
new TimeBasedValue(IconWeatherTestData.TIME_15H ,IconWeatherTestData.WEATHER_VALUE_67776_15H),
107+
new TimeBasedValue(IconWeatherTestData.TIME_16H ,IconWeatherTestData.WEATHER_VALUE_67776_16H)] as Set<TimeBasedValue>)
108+
109+
when:
110+
def coordinateToTimeSeries = source.getWeather(timeInterval)
111+
112+
then:
113+
coordinateToTimeSeries.keySet().size() == 2
114+
equalsIgnoreUUID(coordinateToTimeSeries.get(IconWeatherTestData.COORDINATE_67775).getEntries(), timeseries_67775.getEntries())
115+
equalsIgnoreUUID(coordinateToTimeSeries.get(IconWeatherTestData.COORDINATE_67776).getEntries(), timeseries_67776.getEntries())
116+
}
117+
118+
def "An InfluxDbWeatherSource will return an equivalent to 'empty' when being unable to map a coordinate to it's ID"() {
119+
def validCoordinate = IconWeatherTestData.COORDINATE_67775
120+
def invalidCoordinate = GeoUtils.xyToPoint(48d, 7d)
121+
def time = IconWeatherTestData.TIME_15H
122+
def timeInterval = new ClosedInterval(IconWeatherTestData.TIME_15H , IconWeatherTestData.TIME_17H)
123+
def emptyTimeSeries = new IndividualTimeSeries(UUID.randomUUID(), Collections.emptySet())
124+
def timeseries_67775 = new IndividualTimeSeries(null,
125+
[
126+
new TimeBasedValue(IconWeatherTestData.TIME_15H ,IconWeatherTestData.WEATHER_VALUE_67775_15H),
127+
new TimeBasedValue(IconWeatherTestData.TIME_16H ,IconWeatherTestData.WEATHER_VALUE_67775_16H),
128+
new TimeBasedValue(IconWeatherTestData.TIME_17H ,IconWeatherTestData.WEATHER_VALUE_67775_17H)] as Set<TimeBasedValue>)
129+
130+
when:
131+
def coordinateAtDate = source.getWeather(time, invalidCoordinate)
132+
def coordinateInInterval = source.getWeather(timeInterval, invalidCoordinate)
133+
def coordinatesToTimeSeries = source.getWeather(timeInterval, [
134+
validCoordinate,
135+
invalidCoordinate
136+
])
137+
138+
then:
139+
coordinateAtDate == Optional.empty()
140+
equalsIgnoreUUID(coordinateInInterval, emptyTimeSeries)
141+
coordinatesToTimeSeries.keySet() == [validCoordinate].toSet()
142+
equalsIgnoreUUID(coordinatesToTimeSeries.get(validCoordinate), timeseries_67775)
143+
}
144+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# DDL
2+
CREATE DATABASE test_weather
3+
4+
# DML
5+
# CONTEXT-DATABASE: test_weather
6+
7+
8+
weather,coordinate_id=67775 alb_rad=13.0152406690000007,asob_s=503.46974264373199,aswdifd_s=228.021339757130988,aswdifu_s=80.8246124780933997,aswdir_s=356.264885937500026,t_2m=297.624199265981986,t_g=300.663206566899987,u_10m=2.59460377536322007,u_131m=3.76589711568313001,u_20m=2.58124956131049998,u_216m=3.94152121323647009,u_65m=3.47402058173249983,v_10m=-0.0240786467212413986,v_131m=-0.0297608319165960991,v_20m=-0.0529678853045104994,v_216m=-0.00969812551875571041,v_65m=-0.0499661079932472024,w_131m=0.00409144377409564972,w_20m=0.00158090585046470004,w_216m=0.00595448465750138007,w_65m=0.00266634369620467014,z_0=0.955322166563199016,p_131m=42.0,p_20m=42.0,p_65m=42.0,sobsrad=42.0,t_131m=42.0 1564671600000
9+
weather,coordinate_id=67775 alb_rad=13.0152406690000007,asob_s=348.844393096137992,aswdifd_s=200.460490980380001,aswdifu_s=56.0043643110729974,aswdir_s=204.389633656249998,t_2m=297.320002347334992,t_g=298.844773762215993,u_10m=2.55725934195278981,u_131m=4.01651967392677012,u_20m=2.55434171324422987,u_216m=4.20461049739088022,u_65m=3.67091211581564014,v_10m=-0.38463576259530402,v_131m=-0.574806421919763055,v_20m=-0.400129700426714974,v_216m=-0.574231301551345052,v_65m=-0.548460101273112954,w_131m=0.00842078158830364062,w_20m=0.0040289199555488299,w_216m=0.0103738560877878003,w_65m=0.00642120845009563988,z_0=0.955323652611887009,p_131m=42.0,p_20m=42.0,p_65m=42.0,sobsrad=42.0,t_131m=42.0 1564675200000
10+
weather,coordinate_id=67775 alb_rad=13.0152406690000007,asob_s=306.571394509505012,aswdifd_s=180.734296104001999,aswdifu_s=49.1986036554934003,aswdir_s=175.039569078124998,t_2m=296.828740358407003,t_g=297.659601745757016,u_10m=2.25171266161903016,u_131m=3.65066950489564013,u_20m=2.24389620037050008,u_216m=3.79807360304292985,u_65m=3.33915291121141999,v_10m=-0.695089352961929974,v_131m=-1.10853234501432008,v_20m=-0.71224786535059903,v_216m=-1.12930575743681993,v_65m=-1.03523090092579007,w_131m=0.0124649216553269007,w_20m=0.00596557511757611018,w_216m=0.0152653602980476998,w_65m=0.00963211312941292079,z_0=0.955322760336951959,p_131m=42.0,p_20m=42.0,p_65m=42.0,sobsrad=42.0,t_131m=42.0 1564678800000
11+
weather,coordinate_id=67776 alb_rad=13.013334274,asob_s=498.219742300773987,aswdifd_s=245.24079037841301,aswdifu_s=80.0782271098216967,aswdir_s=333.054714062500011,t_2m=295.515335568403998,t_g=297.436843518737987,u_10m=2.69074813301160987,u_131m=4.00160121993897988,u_20m=2.68883329948458005,u_216m=4.14046943742340989,u_65m=3.71403226367276007,v_10m=1.20973866598336,v_131m=1.81482331766853999,v_20m=1.19637364179174011,v_216m=1.89445925143368998,v_65m=1.66706360898831996,w_131m=-0.0107351344598088008,w_20m=-0.00635049126331660007,w_216m=-0.0122340444408049996,w_65m=-0.00904490847631570991,z_0=0.955336762972383013,p_131m=42.0,p_20m=42.0,p_65m=42.0,sobsrad=42.0,t_131m=42.0 1564671600000
12+
weather,coordinate_id=67776 alb_rad=13.013334274,asob_s=287.163521177577024,aswdifd_s=241.641483540946012,aswdifu_s=46.1826228914205998,aswdir_s=91.7093913229697932,t_2m=293.455111314490978,t_g=294.987683227438993,u_10m=2.24292571281681008,u_131m=3.28865545990927988,u_20m=2.24693045663628999,u_216m=3.45619617779588006,u_65m=3.06350529841654984,v_10m=0.705285607539457016,v_131m=1.0173658432825099,v_20m=0.694030995652130001,v_216m=1.08316452397627994,v_65m=0.940270018404623986,w_131m=-0.00960905133118966984,w_20m=-0.00372074627807390985,w_216m=-0.0126429015220642007,w_65m=-0.00643946343215642987,z_0=0.955336572337003975,p_131m=42.0,p_20m=42.0,p_65m=42.0,sobsrad=42.0,t_131m=42.0 1564675200000

0 commit comments

Comments
 (0)