This page provides guidance on how to write an
InputPlugin for the
SensorPlatformFramework.
To find information about existing plugins, please go to
SensorPlatformFramework#Input.
Using the API
First of all, you need the
SPF API (
SensorPlatformFramework#API). The easiest way is to download the latest release of the SPFramework API (
SensorPlatformFramework#Download). You will need the spf-api-[version].jar on the class path of your plugin project. Assuming you are using Eclipse IDE, you can manually add it to the projects build path (Right-click / Build Path / Add to Build Path).
If you need access to the sources of the API, you can check out the project from SVN. See
SensorPlatformFramework#Development for details. All SPFramework projects are managed using maven. For clean integration into Eclipse the
m2eclipse plugin is recommended. Follow these steps to set up your project in Eclipse:
- In the SVN repository perspective, navigate to <project-name>/trunk (here: spf-api/trunk)
- Right-click on trunk and choose "Find/Check Out as"
- Choose "Check out as a project configured using the New Project Wizard"
- Choose "Java project" and specify a name of your choice (e.g., spf-api)
- After succesful ceckout, right-click on the project and choose "Maven / Enable Dependency Management" or "Configure / Conver to Maven Project" depending on the m2eclipse version
- m2eclipse will do some work. After finished, right-click on the project and choose "Maven / Update Project Configuration"
Create a new class inside your project. Let this class implement org.n52.ifgicopter.spf.input.IInputPlugin. Find below an example skeleton excerpt of an
IInputPlugin implementation.
package org.n52.ifgicopter.spf.input;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import org.n52.ifgicopter.spf.gui.PluginGUI;
public class ExampleInputPlugin implements IInputPlugin {
@Override
public void init() throws Exception {
}
@Override
public void shutdown() throws Exception {
}
@Override
public InputStream getConfigFile() {
return null;
}
...
} |
The
init() method will be called during the framework startup. In analogy, the
shutdown() method is called during the shutdown phase. These methods can be used to initialized/free needed resources such as streams, connections or serial ports.
Every
InputPlugin must provide a plugin description as an XML document. The framework retrieves this information as an
InputStream using the
getConfigFile() method. An easy way for providing a File as an
InputStream is outlined in the following.
public InputStream getConfigFile() {
try {
return new FileInputStream(new File(
"config/spf/input-example.xml"));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return null;
} |
Providing Data
As the framework makes use of the inversion of control paradigm, the data provided by
InputPlugins gets pulled regularly. A plugin implementations must provide two methods to ensure a smooth data processing. The
hasNewData() should return
true
, if the plugin has new data available. If
true
is returned, the
getNewData() method gets called. Find below an example of this method.
public synchronized List<Map<String, Object>> getNewData() {
List<Map<String, Object>> result = new ArrayList<Map<String, Object>>(
this.dataBuffer);
this.dataBuffer.clear();
return result;
} |
As you can see, the data must be provided as a
List of Maps. The inline map instances hold the actual key/value pair data including a timestamp identifier. A best practice is to buffer the gathered data using an internal List. Note the
synchronized
modifier for this method. This ensures that the buffer always has a valid state. Thus, adding data must also be encapsulated in a synchronized block. A simple implementation of the
init() method which creates a worker thread to gather data is illustrated in the following.
public void init() throws Exception {
new Thread(new Runnable() {
@Override
public void run() {
while (running) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Map<String,Object> data = new HashMap<String, Object>();
data.put("time", System.currentTimeMillis());
data.put("temperature", 24.3);
synchronized (this) {
dataBuffer.add(data);
}
}
}
}).start();
} |
Other
For information on the other interface methods please consider the
SPF Api Javadocs (
SensorPlatformFramework#Download).
Plugin Description XML
The plugin description used by the
getConfigFile() method (see
#Providing_Metadata) is based on
SensorML version 1.0.1. It is used to identify the observed phenomena of the
InputPlugin implementation. Only those phenomena defined in the XML description can be processed by the framework. Java property key names (see
#Providing_Data) are directly mapped to the
inputs
of the
SensorML markup. Find below an example of a plugin description.
<?xml version="1.0" encoding="UTF-8"?>
<spf:plugin xmlns:spf="http://ifgi.uni-muenster.de/~m_riek02/spf/0.1" name="urn:ifgi:id:ifgicopter2">
<spf:output>
<spf:AvailabilityBehaviour>
<spf:outputProperties>
<spf:property>temperature</spf:property>
</spf:outputProperties>
</spf:AvailabilityBehaviour>
<spf:mandatoryProperties>
<spf:property>position</spf:property>
</spf:mandatoryProperties>
</spf:output>
<SensorML xmlns="http://www.opengis.net/sensorML/1.0.1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:swe="http://www.opengis.net/swe/1.0.1"
xmlns:gml="http://www.opengis.net/gml" xmlns:xlink="http://www.w3.org/1999/xlink"
xsi:schemaLocation="http://www.opengis.net/sensorML/1.0.1 http://schemas.opengis.net/sensorML/1.0.1/sensorML.xsd"
version="1.0.1">
<member>
<System>
<!-- if the platform is mobile use this instead of sml:position -->
<gml:boundedBy>
<gml:Envelope srsName="urn:ogc:def:crs:EPSG::4326">
<gml:lowerCorner>51.5 6.5</gml:lowerCorner>
<gml:upperCorner>52.5 7.5</gml:upperCorner>
</gml:Envelope>
</gml:boundedBy>
<!--~~~~~~~~~~~~~~~~~~~~~~~~ -->
<!--Keywords -->
<!--~~~~~~~~~~~~~~~~~~~~~~~ -->
<keywords>
<KeywordList>
<keyword>ifgicotper sensor platform</keyword>
<keyword>humidity</keyword>
<keyword>temperature</keyword>
</KeywordList>
</keywords>
<!--~~~~~~~~~~~~~~~~~~~~~~~~ -->
<!--Identification -->
<!--~~~~~~~~~~~~~~~~~~~~~~~ -->
<identification>
<IdentifierList>
<identifier name="uniqueID">
<Term definition="urn:ogc:def:identifier:OGC:uniqueID">
<value>urn:ogc:object:feature:platform:IFGI:ifgicopter1</value>
</Term>
</identifier>
<identifier name="longName">
<Term definition="urn:ogc:def:identifier:OGC:1.0:longName">
<value>ifgicopter sensor platform for environmental monitoring</value>
</Term>
</identifier>
<identifier name="shortName">
<Term definition="urn:ogc:def:identifier:OGC:1.0:shortName">
<value>ifgicopter sensor platform</value>
</Term>
</identifier>
</IdentifierList>
</identification>
<!--~~~~~~~~~~~~~~~~~~~~~~~~ -->
<!--Classification -->
<!--~~~~~~~~~~~~~~~~~~~~~~~ -->
<classification>
<ClassifierList>
<classifier name="intendedApplication">
<Term definition="urn:ogc:def:classifier:OGC:1.0:application">
<value>climate</value>
</Term>
</classifier>
</ClassifierList>
</classification>
<!--~~~~~~~~~~~~~~~~~~~~~~~~ -->
<!--Valid time -->
<!--~~~~~~~~~~~~~~~~~~~~~~~ -->
<validTime>
<gml:TimePeriod>
<gml:beginPosition>2010-09-15</gml:beginPosition>
<gml:endPosition>2013-03-15</gml:endPosition>
</gml:TimePeriod>
</validTime>
<!--~~~~~~~~~~~~~~~~~~~~~~~ -->
<!--Contact -->
<!--~~~~~~~~~~~~~~~~~~~~~~~ -->
<!-- profile-specific: A "contact" element has to be present. -->
<contact>
<ResponsibleParty gml:id="WWU_IfGI_ifgcopter_contact">
<organizationName>Institute for Geoinformatics - Westfaelische
Wilhelms
Universitaet Muenster - Sensor Web and Simulation Lab
</organizationName>
<contactInfo>
<address>
<electronicMailAddress>m.rieke@uni-muenster.de
</electronicMailAddress>
</address>
</contactInfo>
</ResponsibleParty>
</contact>
<!--~~~~~~~~~~~~~ -->
<!--System Inputs -->
<!--~~~~~~~~~~~~~ -->
<inputs>
<InputList>
<input name="position">
<swe:Position referenceFrame="urn:ogc:def:crs:EPSG::4326">
<swe:location>
<swe:Vector>
<swe:coordinate name="latitude">
<swe:Quantity>
<swe:uom code="deg" />
</swe:Quantity>
</swe:coordinate>
<swe:coordinate name="longitude">
<swe:Quantity>
<swe:uom code="deg" />
</swe:Quantity>
</swe:coordinate>
<swe:coordinate name="altitude">
<swe:Quantity>
<swe:uom code="m" />
</swe:Quantity>
</swe:coordinate>
</swe:Vector>
</swe:location>
</swe:Position>
</input>
<input name="temperature">
<swe:Quantity definition="urn:ogc:def:property:OGC:1.0:temperature">
<swe:uom code="Cel" />
</swe:Quantity>
</input>
<input name="altitude">
<swe:Quantity>
<swe:uom code="m" />
</swe:Quantity>
</input>
<input name="time">
<swe:Time definition="urn:ogc:def:phenomenon:time" referenceFrame="urn:ogc:def:unit:iso8601" />
</input>
</InputList>
</inputs>
<!--~~~~~~~~~~~~~~ -->
<!--System Outputs -->
<!--~~~~~~~~~~~~~~ -->
<outputs>
<OutputList>
<output name="position">
<swe:Position referenceFrame="urn:ogc:def:crs:EPSG::4326">
<swe:location>
<swe:Vector>
<swe:coordinate name="latitude">
<swe:Quantity>
<swe:uom code="deg" />
</swe:Quantity>
</swe:coordinate>
<swe:coordinate name="longitude">
<swe:Quantity>
<swe:uom code="deg" />
</swe:Quantity>
</swe:coordinate>
<swe:coordinate name="altitude">
<swe:Quantity>
<swe:uom code="m" />
</swe:Quantity>
</swe:coordinate>
</swe:Vector>
</swe:location>
</swe:Position>
</output>
<output name="temperature">
<swe:Quantity definition="urn:ogc:def:property:OGC:1.0:temperature">
<swe:uom code="Cel" />
</swe:Quantity>
</output>
<output name="altitude">
<swe:Quantity>
<swe:uom code="m" />
</swe:Quantity>
</output>
<output name="time">
<swe:Time definition="urn:ogc:def:phenomenon:time" referenceFrame="urn:ogc:def:unit:iso8601" />
</output>
</OutputList>
</outputs>
</System>
</member>
</SensorML>
</spf:plugin>
|
At first glance this might look quite complex. But there are only small adjustments you need to make for a quick implementation. If your
InputPlugin provides positions as WGS84 lat/long (thus being mobile) you only need to adjust the other
inputs
and
outputs
. Every input must define its identifier by setting the
name
attribute. This must also be the key used in the Java implementation (see above).
Phenomena
The type of the phenomena is defined using Swe Common (part of the
SensorML specification). Currently only numerical phenomena are supported, e.g.
<swe:Quantity definition="urn:ogc:def:property:OGC:1.0:temperature"><swe:uom code="Cel" /></swe:Quantity>
The definition should be defined as
OutputPlugins could depend on it. the
swe:uom
is used to define the unit of measure as
UCUM codes.
Note the special
input
for representing the time of a measurement. The SPFramework searches for inputs which use
swe:Time
. The identifier of this input is used to temporally order data. Thus every Map (see #) should provide an property with this key. Otherwise the SPFramework will use the current system time which could lead to unintended behaviour. Internal times can be represented as ISO 8601 strings or unix timestamp as milliseconds (as a long variable).
inputs
and
outputs
are mostly structured in the same way and contents can be reused.
Plugin Behaviour
The first portion of the XML markup defines the dissemination and interpolation behaviour of the plugin.
AvailabilityBehaviour
should be used to trigger an output when certain phenomena are available (e.g., temperature). Multiple entries are allowed and result in creating multiple outputs if the timestamps differ.
<spf:output>
<spf:AvailabilityBehaviour>
<spf:outputProperties>
<spf:property>temperature</spf:property>
</spf:outputProperties>
</spf:AvailabilityBehaviour>
<spf:mandatoryProperties>
<spf:property>position</spf:property>
</spf:mandatoryProperties>
</spf:output>
|
Every property listed in the
mandatoryProperties
is then interpolated at the timestamp of the temperature measurement. Alternatively, the
mandatoryProperties
can be replaced by two other elements:
<spf:outputOnAllItems>true</spf:outputOnAllItems>
Every phenomena defined in the
SensorML part of the XML must be present for generating output.
<spf:singleOutputAllowed>true</spf:singleOutputAllowed>
All outputProperties can generate single outputs (results in no data tuples).
Providing a User Interface
A Plugin can provide a GUI for enabling user interactions. Therefor the
getUserInterface() method must return an instance of
org.n52.ifgicopter.spf.gui.PluginGUI
(see API Javadoc for details). An example of such a user interface is the
MKInputPlugin.
--
MatthesRieke - 2012-01-25