Create an application - 52North/IlwisCore GitHub Wiki
Applications are in ilwis speak the operations on data sources : one or more input parameters, an algorithm and zero or more output parameters. Applications can be translated to string expressions and vice versa. The string expression is the way how Ilwis passes applications through the system. The expression has the following syntax
(<parameter>?(,<parameter>)* '=' <applicationname>'(' <parameter> (,<parameter>*) ')'
so:
newraster = resample(myraster, newgeoref, bicubic)
is a valid expression. All input names must be known to ilwis at that moment. So it must be
- In the master catalog
- In the symbol table
- A defined constant.
Applications are implemented as classes, classes that have a number of required methods. All application classes must be derived from : OperantionImplementation. This parent class determines the interface. As a consequence it must implement the following methods
- < Constructor >(quint64 metaid, const OperationExpression &expr)
- static OperationImplementation *create(quint64 metaid,const OperationExpression& expr)
- static quint64 createMetadata()
- State prepare(ExecutionContext *ctx, const SymbolTable &)
- bool execute(ExecutionContext *ctx,SymbolTable& symTable)
This constructs the actual application object. It has all the information needed because of the OperationExpression instance. The metaid is just there for administrative purposes. The implementation is usually trivial
LinearRasterFilter::LinearRasterFilter(quint64 metaid, const Ilwis::OperationExpression &expr) : OperationImplementation(metaid, expr){
}
This method is registered in the framework to do the actual creation of an instance of this application. It is a static method as these can exist at global level. Just like the previous method the implementation is rather trivial.
OperationImplementation *LinearRasterFilter::create(quint64 metaid, const OperationExpression &expr){
return new LinearRasterFilter(metaid, expr);
}
This is a key method. The metadata created by this method allows the system the link a textual expression to a certain Application class and create an instance of it. The system does this by trying to match the combination of application name and (ordered) sequence of input types with the names and types in the textual expression. Optionally there can be an extra 'namespace' specifier to distinguish between identical methods in different connectors. If a combination is found the create method can be called, an instance is created and the application can start working. Note that an OperationMetadata class is an IlwisObject and instances of it can be found in the master-catalog.
A typical implementation of createMetaData
quint64 LinearRasterFilter::createMetadata(){
1 OperationResource operation({"ilwis://operations/linearrasterfilter"});
2 operation.setSyntax("linearrasterfilter(raster1, linearfiltername | expression");
3 operation.setDescription("generates a new raster based on the conditions as specified in the linear filter definition");
4 operation.setInParameterCount({2});
5 operation.addInParameter(0,itRASTER , "rastercoverage"),"input rastercoverage with numeric domain");
6 operation.addInParameter(1,itSTRING , "filter definition"),"Definition of the filter. This can be a predefined filter name or an expression defining the filter");
7 operation.setOutParameterCount({1});
8 operation.addOutParameter(0,itTABLE, "output raster","output table with a numeric domain");
9 operation.setKeywords("filter,raster,numeric");
10 mastercatalog()->addItems({operation});
11 return operation.id();
}
In line 1 a resource is created to describe the whole Application. An OperationResource is basically identical to a Resource but it has some extra interface methods to make it easier to enter the metadata for an Application. The resource can have two parameters. One mandatory, the url under which the application is known to the system, second an optional namespace. By default this is 'ilwis'.
Line 2 describes the syntax of the application. Which parameters are expected and what is possible. It is not a purley descriptive text and there are certain rules it is has to follow. The text is used in the user interface to create a automatic form. Things like types and labels come from the rest of the metadata but information about the optionality, allowed input lists, default choices come from this text.
- input lists are sets for strings seperared by '|'. There is always a mandatory default choice identified with the '!' character in front.
- optional parameter (blocks) are identified with '[]' ( surrounding) brackets
- a default value can be marker by a '=' character. e.g. distance=400
Line 3 is a (short) description of what the application is supposed to do.
Line 4 tells the system of how many parameters the application can expect. This number can be variable as there are applications which have several possible parameter lists which run the same algorithm. Sometimes this has to do with optional parameters, sometimes this has to with parameters being available through some other means (members of an input value, generated, ...). The variability can be expressed by changing line 4 in say:
operation.setInParameterCount({2,3,4});
which would indicate an application with either 2 or 3 or 4 (input)parameters.
Line 5 adds a definition of a parameter. A type, an index, a name and an optional description. The index simply says which is the order number in the sequence. The type tells the expected type. This maybe an 'or' ed combination of types, so e.g. itRASTER | itTABLE. This happens espacially with application which have multiple parameter lists.
Line 6 set the output parameters count. Output parameters are identical (in description) to input parameters.
Line 8 sets optional keywords for operations. It allows for easier categorizing and searching of methods within the system.
Line 10 and 11 register the whole stuff in the system.
The prepare method checks if all input parameters are correct and instantiates them were possible. The idea is that the execute method should not have to worry anymore about initialization stuff and validity of things as that has all been resolved in the prepare method. Note that the general type and number of the parameters already has been checked at this point else it wouldnt have arrived here.
Prepare methods can sometimes be somewhat lengthy as lot of initialization and error checking is done here. For the example we use the method is fortunately quite small
OperationImplementation::State LinearRasterFilter::prepare(ExecutionContext *ctx, const SymbolTable &)
{
1 QString raster1 = _expression.parm(0).value();
2 QString outputName = _expression.parm(0,false).value();
3 if (!_inputRaster.prepare(raster1, itRASTER)) {
4 ERROR2(ERR_COULD_NOT_LOAD_2,raster1,"");
5 return sPREPAREFAILED;
}
6 QString expr = _expression.parm(1).value();
7 int copylist = itDOMAIN | itCOORDSYSTEM | itGEOREF;
8 _outputRaster = OperationHelperRaster::initialize(_inputRaster.as<IlwisObject>(),itRASTER, copylist);
9 if ( !_outputRaster.isValid()) {
10 ERROR1(ERR_NO_INITIALIZED_1, "output rastercoverage");
11 return sPREPAREFAILED;
12 }
13 if ( outputName != sUNDEF)
14 _outputRaster->name(outputName);
15 _filter.reset(new LinearGridFilter(expr));
16 if ( !_filter->isValid())
return sPREPAREFAILED;
17 return sPREPARED;
}
Line 1 and 3 initializes the rastercoverage that is used in the filter operation. If the raster can not be initialized (for whatever reason) an error code is returned and an error is written to the log.
Line 8 initializes the output raster. The 'OperationHelperRaster::initialize' method is a helper method that copies properties (indicated by 'copylist') from the input raster to the output raster as they often share a lot of properties.
line 13 sets the name of the output raster. Sometimes the output name is not set in which case the application generates an anonymous object (usually a temporary object).
line 15-17 take care of creating an appropriate linear filter. Determing the validity of the linear expression is left to the filter class. We only know that if ann invalid filter expression is passed, the filter object will not be valid and we can abort the operation.
if everything is ok an 'sPREPARED' is returned and the execute can start working.
This method does the actual 'work'. It is of course very different for each different algorithm. The method can only be run if the object has been 'prepared' and it must store/register its result(s) in the context of Ilwis-objects. This last point is very important. It means that
- The name of the output(s) must be stored in the execution context in the same order as the metadata of the application defines.
- The actual data is stored as a key-value pair in the symbol table.
- were appropriate the object is added the mastercatalog.
The ExecutionContext instance which is always available in an execute method (its one of the parameters) has two methods ( setOutput, addOutput) to make this simplify this.
For our example the execute looks like this:
bool LinearRasterFilter::execute(ExecutionContext *ctx, SymbolTable &symTable)
{
1 if (_prepState == sNOTPREPARED)
if((_prepState = prepare(ctx,symTable)) != sPREPARED)
return false;
2 BoxedAsyncFunc filterFun = [&](const BoundingBox& box) -> bool {
PixelIterator iterOut(_outputRaster, box);
BlockIterator blockIter(_inputRaster,_filter->size(), box, Size<>(1,1,1));
PixelIterator iterEnd = iterOut.end();
while(iterOut != iterEnd) {
*iterOut = _filter->applyTo(*blockIter);
++iterOut;
++blockIter;
}
return true;
};
3 bool res = OperationHelperRaster::execute(ctx, filterFun, _outputRaster);
if ( res && ctx != 0) {
QVariant value;
value.setValue<IRasterCoverage>(_outputRaster);
4 ctx->setOutput(symTable,value,_outputRaster->name(), itRASTER, _outputRaster->source() );
}
return res;
}
- ensure that the application is prepared before being run
- Is a lamba method that is the actual algorithm
- Is a helper method that parallelizes the algorithm and executes it
- stores the output in the context of ilwis-objects
Note that how 2 and 3 are done is optional. You are free to write your own way of doing things. The method used here conveniantly uses multiple precessing cores to execute the algorithm.