Plugins / OiPluginTemplate
OiPluginTemplate is a predefined Qt project which we recommend you to use if you want to write a plugin for OpenIndy. In the next chapters it is assumed that you know the terminology of OpenIndy (like element, feature, function, sensor etc.). If you do not know the concept of OpenIndy yet, you may want to inform yourself here. Read the following steps to learn how to write and run your own plugins using this project as a template.
First you need to get a copy of the Qt-project “OiTemplatePlugin” on your local machine. Therefor you have two options available:
On the one hand you might fork this repository (OiPluginTemplate) and get a copy of it on your local machine. If you are new to GitHub you may want to follow the guidance at https://help.github.com/articles/fork-a-repo.
On the other hand you might also fork and clone the OpenIndy repository. That repository contains a folder
plugins which may look like this:
To get your own plugin you have to copy the folder
OiPluginTemplate and paste it again next to it. You may choose any name for your new folder.
No matter which option you choose you get one Qt-project for your own plugin. Every Qt-project is represented by a
*.pro-file. In your case it is the file
OiTemplatePlugin.pro. If you choose the first option the folder structure of the Qt-project will look like this:
However if you choose the second option the folder structure will look as follows:
How to work on your own plugin and what you have to take in account is explained in the next chapters.
You are now ready to get started with the implementation of your own plugin. Notice that the content of the folders
lib, which came with the template (only if you have chosen option 1 above), must not be changed. You may add other libraries to the
lib folder, but existing content has to stay as it is if you do not want to get undesirable behaviour.
OpenIndy provides interfaces for functions, sensors and network adjustments. How to implement them and integrate them in a plugin is described in the next three subchapters. It is explained in detail what you have to take in account when implementing such a plugin. In contrast there is also a step by step guide in the next chapter that leads you through a tutorial where you implement a small plugin with a function and a sensor.
In general it is important to know that neither the name of the pro-file
OiTemplatePlugin.pro nor the names of the files
metaInfo.json must be changed.
Plugin meta data
metaInfo.json should contain meta information about your plugin. The meaning of each attribute is described in the table below.
|isOiPlugin||Set this parameter to
|name||The name of your plugin.|
|pluginVersion||The version of your plugin. This version has to correspond with the version
|author||You can enter your name or the name of your company here.|
|compiler||This attribute has to contain the compiler with which you are going to compile your plugin. You might choose
|operatingSystem||Here you have to enter the operating system on which you compile your plugin. There are three options: “windows”, “linux” or “mac os”.|
|description||A description of your plugin.|
|dependencies||Set this parameter to
|libPaths||If you set the above parameter to
Now that you have set up the Json-file you can start with the actual implementation of your plugin.
That interface looks like this:
As you can see there are six methods which must be implemented. The methods
createNetworkAdjustments return a list of pointers to sensors, functions and network adjustments respectively. These lists shall contain all sensors, functions and network adjustments which you have implemented in your plugin. The methods
createNetworkAdjustment, which expect the string
name as a parameter, return only one sensor, function and network adjustment respectively. The parameter
name represents the name of the sensor, function or network adjustment. So each of these three methods shall return only the proper pointer or NULL if no sensor, function or network adjustment with the given name is available. Certainly this becomes more clear when looking at an example:
Assuming that you have implemented two sensors called “sensor a” and “sensor b” and in addition two functions called “function a” and “function b” before, your implementation of the six methods above might look as follows:
If you do not add your functions, sensors and network adjustments, which are implemented in your plugin, to the six methods above then OpenIndy will not be able to use them. In other words you can think of the class OiTemplatePlugin as a factory which creates and delivers function-, sensor- and network adjustment objects from the plugin to OpenIndy as soon as it is required.
Implement functions, sensors and network adjustments
Now you know how to implement the plugin interface of OpenIndy, what is important to instantiate your function-, sensor- and network adjustment classes and deliver the resulting objects to OpenIndy. To learn how to implement your own functions, sensors and network adjustment you can follow the links below.
Every feature in OpenIndy can have any number of functions. There are construct-, fit- and geodetic functions and furthermore object transformations and system transformations. The list below gives you a review of those function types.
|fit function||If overdetermination is present, fit functions have the purpose to calculate the adjusted parameters of a geometry by using observations. So for example a fit function can calculate the center of n xyz-observations.|
|construct function||Construct functions are used to calculate geometries by using other geometries. For example the intersection of a line and a plane results in a new point.|
|geodetic function||Geodetic functions are intended to calculate special geodetic tasks like spatial intersection etc..|
|object transformation||In contrast to fit- and construct functions, object transformations do not define a geometry, but change a previously defined one. A point which has been fit before can for instance be moved along a line by a certain amount.|
|system transformation||System transformations are used to calculate the parameters of a transformation from one coordinate system to another one.|
These function types are implemented as classes which extend the class Function. When you want to write your own function in your plugin then you have to extend one of the classes FitFunction, ConstructFunction, GeodeticFunction, ObjectTransformation or SystemTransformation. Please do not try to extend the class Function itself. While this currently works for some kind of function types for others it does not because additional methods and attributes are necessary which are defined only in the subclasses.
Let us first look at the class Function itself. There are some methods that you always have to override (pure virtual methods). This methods are pure virtual in the five subclasses of the class Function, too.:
Pure virtual methods
getNeededElements has to return a list of
InputParams is a struct and is defined in the upper part of the header file from class Function. An input parameter represents an element that is required for the calculation of the actual function. For example if you want to implement a construct function which calculates the intersection of a line and a plane, you might implement the method as follows:
The class Configuration contains the enumeration
ElementTypes which has a value for each available element. Each input parameter that you add to the result list has to “know” which element type it represents (
typeOfElement) and how often this element type can be included (
true a user can enter as many elements of the specified type as he wants. Otherwise only one element is expected. In addition you can optionally define a description for each input parameter, so that the user of your plugin will know about the use of each parameter.
The second method
applicableFor has to return a list of feature types. As well as
FeatureTypes is an enumeration defined in the class Configuration and has one value for each available feature. The result list has to contain every feature type which this function can be assigned to. Assuming you wrote a function called “TranslateByLine” which is able to move a point or a sphere along a line by a specified amount then you have to add the enum values of the features point and sphere to the result list. Your implementation of the method might look like this:
Last but not least there is the method
getMetaData which has to return a pointer to an object of the type PluginMetaData. In the meta data object that is returned in your implementation of this method you can define for example a name and a description for your function. It is not necessary that you fill all attributes that you can find in the class PluginMetaData, but a minimal implementation should contain an iid, a name, a description and the name of the plugin which the function belongs to. The name and the description that you chose for your function are later shown in the function plugin loader of OpenIndy (after you have successfully installed your plugin) where users can assign functions to features. As the name of the plugin you have to chose the name which you already defined in the json file (next to the pro file of your Qt project). The iid has a special structure:
[TYPE_OF_FUNCTION] by “FitFunction”, “ConstructFunction”, “GeodeticFunction”, “ObjectTransformation” or “SystemTransformation” for your specific case. As
[VERSION_OF_PLUGIN] please chose the same as you did in your json file, but format it like “v001”.
For each feature that is currently defined in OpenIndy there is an
exec method in the class Function. OpenIndy will call the
exec method of a function each time a feature which that function is assigned to has to be recalculated. A recalculation of a feature is necessary whenever one or more elements which that feature depends on are changed. This is the case for example when you add a new observation to the fit function of the feature or indirectly when your feature is constructed from another feature which is now changed (for example because of a new observation added). Therefore your
exec methods have to contain the actual implementation of your algorithm (of course you can subdivide your implementation into your own methods which you then call from the
exec method). As the
exec methods are not pure virtual you do not have to implement all of them. If you want to implement the intersection of a line and a plane then of course you only have to implement the
exec method for the type point. But be sure that your choice of
exec methods which you want to implement is consistent with how you implemented the method
applicableFor. Read the part about the implementation of
exec methods below to get a detailed description on what you have to consider when implementing an
getStringParameter are meant to give you the chance to define special parameters for your function. As you learned before the method
getNeededElements allows you to define elements (e.g. observations, points, planes etc.) as input parameters. Your function can use this elements to calculate whatever you want. But in some cases you maybe need to ask an user for special parameters (like wether you shall calculate a transformation with or without scale). Therefore you can implement those three methods and thereby tell OpenIndy to ask users for those parameters. Let us look at an example:
You can define three different types of parameters: integer-values, double-values and string-values. Each parameter you define has a key and a default value. In contrast to integer- and double-parameters string-parameters are defined as lists. So you define multiple options and users can select one of those optionse or they can type in a custom string. Default value is always the first entry in the list, in this case that would be the string “option A”. We recommend you to use only one single string as a key and define the description of each parameter in the description of the whole function. In OpenIndy your parameters appear as follows:
In the above illustration you can see a string-parameter with the key “invert” and the possible options “yes” and “no”.
Furthermore there is the method
getResultProtocol which you can override. Your implementation of this method might return a list of strings which shows special results or information on your calculations that otherwise would not have been shown.
Last but not least you might override the methods
clearResults. As the name implies the method
clearResults erases all results of the last execution of the function. That concerns for example the statistic of that function. On the contrary the method
clear erases not only those results, but the whole setup of the function. This also includes all elements which were assigned to the function. If you plan to reimplement this two methods then you might want to take a look at the implementation in the class Function.
Further non-virtual methods
There are some further methods which you cannot reimplement, but which are important to know and use when implementing a function. First of all there is the method
setUseState. In your implementation you mostly use other elements to calculate one feature. As you will learn in the next part not every element is valid, so it may occur that while most of the given elements are used for calculation some are not. So for each element you should call this method and tell it wether to use the element or not.
Moreover there is the method
isValid which returns true if all elements that are necessary for calculation are present. This method uses your implementation of
getNeededElements to decide wether all elements are present or not.
It is also possible to access the console of OpenIndy from the function class. Therefore you can use the method
writeToConsole by passing your message as a parameter. This might be useful for execptions for example. Furthermore there are methods to get a special element by its id or a whole list of all elements of a special type (e.g. observation, point, plane etc.).
As you can see when looking at the definition of the class Function there are a a few other methods not mentioned yet. Those methods are not important when implementing a function. They are called from OpenIndy to get information on the current setup of the function (e.g. statistic or assigned elements etc.).
As you have learned recently an
exec method contains the actual implementation of you algorithm. As input you get a reference to the feature which this function is assigned to and which your implementation has to modify. This input feature may have been solved by previous functions. If so you can access this feature’s attributes to get the result of those functions. If the feature is a geometry there is a statistic object, defined in the class Geometry, with the current statistic of the geometry. Besides that input feature you also need the elements which you defined in your implementation of the method
getNeededElements for your calculation.
##Plugin implementation -
A step by step guide
This section leads you through the implementation of a simple plugin for OpenIndy. You will implement a function and you will learn how to make your plugin ready for the use in OpenIndy.
- Initial steps
After you have completed the Get Started chapter you have got a local copy of the OiTemplatePlugin-project. The folder structure of your local project should look like the left image if you have chosen to fork the OiPluginTemplate repository. Otherwise, if you have forked the OpenIndy repository, the folder structure of your local project should look like the right image.
Before you open the plugin-project you should open and compile the “openIndyLib”-project. That project contains the classes for linear algebra. If you have forked the OiPluginTemplate repository you can find the
/lib/openIndyLib. In contrast if you have forked the OpenIndy repository you can find the
In both cases it is assumed that you navigate to the
openIndyLib-folder starting at the folder which is shown in the images above respectively.
You can open Qt projects (in Qt Creator) by clicking “File” > “Open File or Project…” and selecting your
*.pro-file. To compile a project select “Build” > “Run qmake” and afterwards “Build” > “Rebuild Project xy”.
Now, after you have compiled the “openIndyLib”-project, please open the OiTemplatePlugin-project.
Plugin meta data
Let us first set up the file
metaInfo.json. Please copy the following lines to your
If you are not working on a Windows system with the Microsoft Visual Studio Compiler you have to replace “windows” or “MSVC 64bit” respectively with the appropriate value. You might replace “windows” by either “linux” or “mac os” if you are working on a Linux or Mac system. The following table gives you a review of all possible values for the compiler.
|Microsoft Visual Studio||MVSC 64bit|
|IBM XL C/C++||intelC|
|Portland Group PGCC/PGCPP||portland|
|Oracle Solaris Studio||oracle|
So for example if you are working on a linux system with the GNU GCC/G++ compiler you have to replace “windows” by “linux” and “MSVC 64bit” by “minGW”.
As there are no dependencies (e.g. external libraries) for this example plugin the parameter “dependencies” is set to
You are now ready to start with the implementation of your first function.
- Your first function
In this subchapter you will implement a function for fitting a point. As input that function expects any number of XYZ-observations. The function then calculates the central point and the covariance matrix of the averaged point.
Set up the function
Initially you have to create two empty files
pointFit.cpp next to the
OiTemplatePlugin.pro-file. Afterwards you can add that two files to your Qt-project by right clicking on the project entry (OiTemplatePlugin) in the project treeview of Qt Creator and selecting the menu item “add existing files”. Browse to those two files,
pointFit.cpp, and add them to your project. They also appear in the
OiTemplatePlugin.pro-file now, so you do not have to modify it yourself.
Now you can start implementing your function. Please copy the following lines to the
pointFit.cpp has to look as follows:
Now you have a minimum implementation of a function. The name and the description you have defined in the method
getMetaData are important, because they appear later when using your function in OpenIndy. That description tells the users of your function how this function works and what it does. In the method
getNeededElements it is determined what elements your function needs to be able to calculate something. As your function calculates the center of n observations, of course, you need some observations. This is defined in the parameter
Again there is a description parameter which will also appear later in OpenIndy. That description tells the user of your function what the element (observation) is used for in the function. The parameter
infinite determines that the user of your function might add as many observations to the function as he wants. At last there is the method
applicableFor which tells OpenIndy that your function can only be assigned to a point feature.
For test purpose you might also compile your plugin now, if you want. There is no actual algorithm implemented, so this function is useless at the moment. Therefor let us continue by adding the algorithm for fitting a point.
Add the actual functionality to the function
Please add the definition of an
exec method for a point feature to the header file
pointFit.h. Your header file should look like this now:
At the very bottom of the corresponding
pointFit.cpp) please add the implementation of that
exec method as follows:
isValid, which is called here, is defined in the function class and checks wether all needed elements are available. In this
exec method you can now implement the calculation of the central point. The first thing you need is a list of all observations that shall be used for calculation. Therefor please change the implementation of your
exec method as follows:
featureOrder is a map, defined in the function class, which contains the id’s of all elements that were assigned to your function by the user of OpenIndy in a special order. In this case that map has only got the key “0”, because you only defined one needed element (observations) in the method
getNeededElements. You get a list of objects, which hold the id’s of the observations, by accessing the value of the map with the key “0”.
To get a list with the actual observations you have to iterate through a loop and call the method
getObservation each time with the id of the observation you are searching for. The result is a pointer to an observation object. You should check for NULL and you should also check wether that observation is valid or not. An observation is valid if it is available in the current coordinate system. If an observation is not valid the method
setUseState is called to tell OpenIndy that your function won’t include that observation in its calculations. To not overfill the
exec method please define a private method in the header file
pointFit.h as follows:
Again you can add the following implementation at the very bottom of the
Now, please replace the
//TODO in the
exec method by calling the method you just created and returning its result:
As you will see the classes OiVec and OiMat, mentioned before, are used to do all calculations. At first we create and fill the l vector with the XYZ-coordinates of all observations. For this purpose please replace the
//TODO in the method
calculateCentralPoint by the following lines:
The next step is to set up the A matrix. Again please replace the
//TODO by the following lines:
The three values
unknownZ are enumeration values. The corresponding enumeration is defined in the class Point. This enumeration values specify the order of unknowns for the A matrix and thus also the order of the covariance matrix. OpenIndy itself and further functions have to know about this order, because otherwise the covariance matrix you calculate would be useless.
Again please replace the
//TODO by the following lines:
In the above code snippet the actual algorithm is implemented. The
x vector holds the coordinates of the central point and the matrix
qxx is the cofactor matrix of that central point. As the point feature
p, which your function shall calculate, is passed as a reference you can easily set the results to the reference. Furthermore the point
p has got an attribute
myStatistic which you can assign your statistical results to. Not only the point, but also the function itself has got an attribute
After you have set the points statistic you have to assign that statistic object to the statistic object of your function, too. This is because the statistic of the point might later be changed by another function that is assigned to the point. The statistic of a function is only modified by the function itself (and no other functions) and can be reviewed via special dialogs in OpenIndy.
Now one last step is missing. For the residuals to be displayed you have to fill the attribute
displayResiduals of the point object. Therefor please replace the
//TODO by the following snippet:
Maybe you ask yourself why OpenIndy does not simply display the residuals which you have previously assigned to the
v vector. This is because OpenIndy does not no anything about your algorithm. The
v vector could contain anything in any unit. The residual objects you add to the list
displayResiduals above define what a residual represents by specifying a header (e.g. “vx”) and they also determine the unit in which the residuals are saved.
To make sure that your implementation equals the reference implementation of this function you can compare your one to the implementations below.
Now you have implemented your first function. With this function you are able to calculate the central point out of any number of XYZ-observations. Next you will learn how to make your plugin ready for the use in OpenIndy.
- Make your plugin ready
To make your plugin ready you have to update the files
p_factory.cpp first. Please add the include
#include "pointFit.h" at the upper part of the file
p_factory.h. In the file
p_factory.cpp you can find the following two methods:
You have to instantiate your function there, so that OpenIndy is able to work with it. Please change these two methods as follows:
Now please compile your plugin and then it will be ready for the use in OpenIndy. The last step is to learn how your plugin can be installed in OpenIndy.
To install a plugin in OpenIndy you first have to compile and run OpenIndy itself. Afterwards in OpenIndy select “Plugin” > “load plugins”. This opens a dialog where you can enter the path to the plugin you want to load. After the plugin was checked by OpenIndy you can click “Ok” to load the plugin.
If you have completed the step by step guide you can now load your own plugin as described above. Then you might create a point feature in OpenIndy and mark it as the active feature. Accordingly select “Function” > “set function” which opens another dialog. Switch to the tab “new function”. There you can see the function “PointFit” that you created in your own plugin before.