Table of Contents |
---|
How to add a custom transformation to my FEWS system?
...
The code needed for such a transformation would be quite basic and is given below:
Code Block |
---|
package example;
import nl.wldelft.fews.openapi.transformationmodule.Calculation;
import nl.wldelft.fews.openapi.transformationmodule.Input;
import nl.wldelft.fews.openapi.transformationmodule.Output;
import nl.wldelft.util.timeseries.Variable;
import org.apache.log4j.Logger;
public class Example1 implements Calculation {
private static final Logger log = Logger.getLogger(Example1.class.getName());
@Input
float option1;
@Input
float option2;
@Input
Variable input1 = null;
@Input
Variable input2 = null;
@Output
Variable output = null;
@Override
public void calculate() throws Exception {
if (Float.isNaN(input1.value) || Float.isNaN(input2.value)) return;
log.info("Input is " + input1.value + " and " + input2.value);
output.value = (input1.value * option1 + input2.value * option2) / 2;
log.info("Output is " + output.value);
}
}
|
...
The interface Calculation has one method void calculate().
Code Block |
---|
package nl.wldelft.fews.openapi.transformationmodule;
import nl.wldelft.util.Initializable;
import nl.wldelft.util.TimeZeroConsumer;
public interface Calculation extends Function {
/**
* This method is called by the framework to do the actual calculations.
* The implementing class should get the input from its InputVariable
* fields and put the calculation output in its OutputVariable fields.
* The framework will initialize the InputVariable and OutputVariable
* fields and set the input values in the InputVariables before calling
* this method and get the output values out of the OutputVariables
* afterwards.
*
* @throws Exception
*/
void calculate() throws Exception;
} |
...
They are both defined as fields in the java-class.
Code Block |
---|
@Input
float option1;
@Input
float option2;
@Input
Variable input1 = null;
@Input
Variable input2 = null;
|
...
In our example the output (just as the input) is defined as a Variable.
Code Block |
---|
@Output
Variable output = null;
|
...
The example code is given below.
Code Block |
---|
package example;
import nl.wldelft.fews.openapi.transformationmodule.Calculation;
import nl.wldelft.fews.openapi.transformationmodule.Input;
import nl.wldelft.fews.openapi.transformationmodule.Output;
import nl.wldelft.util.timeseries.TimeSeriesArray;
import org.apache.log4j.Logger;
public class Example2 implements Calculation {
private static final Logger log = Logger.getLogger(Example1.class.getName());
@Input
private float option1;
@Input
private float option2;
@Input
TimeSeriesArray input1 = null;
@Input
TimeSeriesArray input2 = null;
@Output
TimeSeriesArray output = null;
@Override
public void calculate() throws Exception {
for (int i = 0; i < output.size(); i++) {
long time = output.getTime(i);
int index1 = input1.indexOfTime(time);
if (index1 == -1) continue;
float value1 = input1.getValue(index1);
int index2 = input2.indexOfTime(time);
if (index2 == -1) continue;
float value2 = input2.getValue(index2);
if (Float.isNaN(value1) || Float.isNaN(value2)) return;
log.info("Input is " + value1 + " and " + value2);
float outputValue = (value1 * option1 + value2 * option2) / 2;
log.info("Output is " + outputValue);
output.setValue(i, outputValue);
}
}
}
|
...
Below you can find an example of a custom fews transformation.
Code Block |
---|
<?xml version="1.0" encoding="UTF-8"?>
<transformationModule xmlns="http://www.wldelft.nl/fews" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.wldelft.nl/fews http://fews.wldelft.nl/schemas/version1.0/transformationModule.xsd" version="1.0">
<variable>
<variableId>input1</variableId>
<timeSeriesSet>
<moduleInstanceId>POFlathead_MergeMAP_UpdateStates</moduleInstanceId>
<valueType>scalar</valueType>
<parameterId>MAP</parameterId>
<locationId>WGCM8U</locationId>
<timeSeriesType>simulated historical</timeSeriesType>
<timeStep unit="hour" multiplier="6"/>
<relativeViewPeriod unit="week" start="-520" end="0"/>
<readWriteMode>add originals</readWriteMode>
</timeSeriesSet>
</variable>
<variable>
<variableId>input2</variableId>
<timeSeriesSet>
<moduleInstanceId>POFlathead_MergeMAP_UpdateStates</moduleInstanceId>
<valueType>scalar</valueType>
<parameterId>MAP</parameterId>
<locationId>WGCM8L</locationId>
<timeSeriesType>simulated historical</timeSeriesType>
<timeStep unit="hour" multiplier="6"/>
<relativeViewPeriod unit="week" start="-520" end="0"/>
<readWriteMode>add originals</readWriteMode>
</timeSeriesSet>
</variable>
<variable>
<variableId>output</variableId>
<timeSeriesSet>
<moduleInstanceId>example1</moduleInstanceId>
<valueType>scalar</valueType>
<parameterId>MAP</parameterId>
<locationId>WGCM8</locationId>
<timeSeriesType>simulated forecasting</timeSeriesType>
<timeStep unit="hour" multiplier="6"/>
<relativeViewPeriod unit="week" start="-520" end="0"/>
<readWriteMode>add originals</readWriteMode>
</timeSeriesSet>
</variable>
<transformation id="Example1">
<custom>
<userDefined>
<input>
<fieldName>input1</fieldName>
<inputVariable>
<variableId>input1</variableId>
</inputVariable>
</input>
<input>
<fieldName>input2</fieldName>
<inputVariable>
<variableId>input2</variableId>
</inputVariable>
</input>
<options>
<float value="0.3" key="option1"></float>
<float value="0.7" key="option2"></float>
</options>
<output>
<fieldName>output</fieldName>
<outputVariable>
<variableId>output</variableId>
</outputVariable>
</output>
<binDir>example</binDir>
<className>example.Example1</className>
</userDefined>
</custom>
</transformation>
</transformationModule>
|
...
- The attibuteId's should be passed via the options as string key / value pairs
- Have \@Input fields with the same name as the key from the string options
- The custom transformation should implement the interface LocationAttributeValuesProviderConsumer
- Store the LocationAttributeValuesProvider from the setLocationAttributeValuesProvider method as a field
- Get the attribute values from the LocationAttributeValuesProvider via the getXXXValue (single value) or getXXXValues (multivalued) methods
Example config:
Code Block | ||||
---|---|---|---|---|
| ||||
<?xml version="1.0" encoding="UTF-8"?>
<transformationModule version="1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.wldelft.nl/fews" xsi:schemaLocation="http://www.wldelft.nl/fews http://fews.wldelft.nl/schemas/version1.0/transformationModule.xsd">
<variable>
<variableId>input</variableId>
<timeSeriesSet>
<moduleInstanceId>UserDefinedFunctionTest</moduleInstanceId>
<valueType>scalar</valueType>
<parameterId>Q.m</parameterId>
<locationSetId>locationAttributeTest</locationSetId>
<timeSeriesType>external historical</timeSeriesType>
<timeStep unit="day" multiplier="1"/>
<relativeViewPeriod unit="day" start="0" end="1"/>
<readWriteMode>editing visible to all future task runs</readWriteMode>
</timeSeriesSet>
</variable>
<variable>
<variableId>output</variableId>
<timeSeriesSet>
<moduleInstanceId>UserDefinedFunctionTest</moduleInstanceId>
<valueType>scalar</valueType>
<parameterId>Q.dis</parameterId>
<locationSetId>locationAttributeTest</locationSetId>
<timeSeriesType>external historical</timeSeriesType>
<timeStep unit="day" multiplier="1"/>
<relativeViewPeriod unit="day" start="0" end="1"/>
<readWriteMode>editing visible to all future task runs</readWriteMode>
</timeSeriesSet>
</variable>
<transformation id="userDefinedFunctionTestWithLocationAttributes">
<custom>
<userDefined>
<input>
<fieldName>input</fieldName>
<inputVariable>
<variableId>input</variableId>
</inputVariable>
</input>
<options>
<string key="stringAttributeID" value="stringAttributeKey"></string>
<string key="booleanAttributeID" value="booleanAttributeKey"></string>
<string key="dateTimeAttributeID" value="dateTimeAttributeKey"></string>
<string key="doubleAttributeID" value="doubleAttributeKey"></string>
</options>
<output>
<fieldName>output</fieldName>
<outputVariable>
<variableId>output</variableId>
</outputVariable>
</output>
<className>nl.wldelft.fews.openapi.transformationmodule.CustomTestFunctionWithLocationAttributes</className>
</userDefined>
</custom>
</transformation>
</transformationModule>
|
Example Java class:
Code Block | ||||
---|---|---|---|---|
| ||||
package nl.wldelft.fews.openapi.transformationmodule;
import nl.wldelft.util.timeseries.Variable;
import java.util.Arrays;
public class CustomTestFunctionWithLocationAttributes implements Calculation, LocationAttributeValuesProviderConsumer {
@Input
Variable input = null;
@Input
String stringAttributeID = null;
@Input
String booleanAttributeID = null;
@Input
String dateTimeAttributeID = null;
@Input
String doubleAttributeID = null;
@Output
Variable output = null;
private LocationAttributeValuesProvider locationAttributeValuesProvider = null;
@Override
public void calculate() throws Exception {
String locationId = input.header.getLocationId();
// Single value
locationAttributeValuesProvider.getBooleanValue(locationId, booleanAttributeID);
locationAttributeValuesProvider.getStringValue(locationId, stringAttributeID);
locationAttributeValuesProvider.getNumericValue(locationId, doubleAttributeID);
locationAttributeValuesProvider.getDateTimeValue(locationId, dateTimeAttributeID);
// Multivalued
locationAttributeValuesProvider.getBooleanValues(locationId, booleanAttributeID);
locationAttributeValuesProvider.getStringValues(locationId, stringAttributeID);
locationAttributeValuesProvider.getNumericValues(locationId, doubleAttributeID);
locationAttributeValuesProvider.getDateTimeValues(locationId, dateTimeAttributeID);
}
@Override
public void setLocationAttributeValuesProvider(LocationAttributeValuesProvider locationAttributeValuesProvider) {
this.locationAttributeValuesProvider = locationAttributeValuesProvider;
}
}
|
Specifying whether unreliable data should be used as input
Since 2023.02 custom transformations can implement the UseUnreliableInputValueFunction to determine whether or not unreliable data is allowed as input for the transformation.
When this interface is implemented the method useUnreliables() must be specified. A boolean must be returned which defines whether or not unreliable data should be included in the input.
Unfortunately it is not possible to use a configured option to determine if unreliables are allowed, because the usage of unreliables are handled before the configured options are processed.
Code Block | ||||
---|---|---|---|---|
| ||||
package nl.wldelft.fews.openapi.transformationmodule;
import nl.wldelft.util.timeseries.TimeSeriesArray;
public class CustomIncludeUnreliablesTestFunction implements Calculation, UseUnreliableInputValueFunction {
@Input
TimeSeriesArray inputSeries = null;
@Output
TimeSeriesArray outputSeries = null;
@Override
public boolean useUnreliables() {
return true;
}
@Override
public void calculate() throws Exception {
for (int i = 0, size = inputSeries.size(); i < size; i++) {
long time = inputSeries.getTime(i);
outputSeries.putValue(time, inputSeries.getValue(i));
int indexOfTime = outputSeries.indexOfTime(time);
outputSeries.setFlag(indexOfTime, inputSeries.getFlag(i));
}
}
}
|