Download version 1.0 with documentation: indoor-positioning-1.1.zip (v 1.1 with Android 7 support)
Sample Android Studio project (Wifi + Compass)
Content:
7.1 Ignoring not available access points
The indoor positioning library provides all functionalities to realize a localization in a short time and with few lines of code. It has been tested with Android and Linux systems. It has not been tested to convert the jar-file into other files like dll's. The functionality of the library is also very easily customizable and expandable. It provides the following functions:
All positioning or tracking applications will run in two steps:
Offline mode:
The location must be mapped: You need to manually map every point of interest with your application one time for initialization.
Online mode:
The library compares the current data of the added technologies (hardware) with the persisted data and returns the current position or alternatively a list of the positions according to probability.
The library supports all types of hardware which are suitable for tracking. Here you can find some supported hardware components:
In the following chapters you will see how to implement an easy indoor positioning and your options to adapt them.
The class where you want to get a notification about new position information has to implement the interface PositionListener
.
You have to implement two methods with the name positionReceived
. One of them receives the most accurate position result (type: PositionInformation
). The other method receives a sorted List
. The first element of this List
is the most likely position and the last element the most improbable position. You can let one of those two methods be empty and handle all your work in the other one.
Here you can see an example:
@Override
public void positionReceived(final PositionInformation positionInformation) {
// Do nothing
}
@Override
public void positionReceived(final List<PositionInformation> positionInformation) {
String positionName = positionInformation.get(0).getName();
doSomething(positionName);
}
This methods will be called from the positioning thread so it is not running on the main thread. If you want to update your user interface you have different options.
Example for Android:
TextView positionTv;
@Override
public void positionReceived(final PositionInformation positionInformation) {
positionTv.post(new Runnable() {
public void run() {
positionTv.setText(positionInformation.getName());
}
});
}
Here you can find an example of how to initialize the positioning functionality on Android. That will be the same way on any other Java software. Only the File object has to get another path. The example will use the root directory of the sd-card (smartphone / tablet):
File file = new File(Environment.getExternalStorageDirectory(),
"positioningPersistence.xml");
try {
positionManager = new PositionManager(file);
} catch (PositioningPersistenceException e) {
e.printStackTrace();
}
List<String> keyWhiteList = new ArrayList<String>();
keyWhiteList.add("00:19:07:c4:9d:61".toLowerCase());
keyWhiteList.add("f8:1e:df:dc:b9:04".toLowerCase());
keyWhiteList.add("00:19:07:c4:9d:63".toLowerCase());
Technology wifiTechnology = new WifiTechnology(this, "WIFI", keyWhiteList);
try {
positionManager.addTechnology(wifiTechnology);
} catch (PositioningException e) {
e.printStackTrace();
}
positionManager.registerPositionListener(this);
You need to initialize the position manager in the online (matching) and in the offline (mapping) mode.
At first you have to create a File
object where the mapping data should be persisted.
The keyWhiteList
includes all ID's which should be mapped in the offline mode and matched in the online mode.
If you let it empty it will take all available access points of the technology.
You also have to set a name of the technology when you create it. This technology class has to extend from the abstract class Technology
which will be described in another chapter.
The last step is the registering of the listener which you can see above. In this example the class itself will be set as a listener.
To remove all position listeners you can use the following code:
positionManager.removeAllPositionListener();
To remove just a single position listener you can do that by the method removePositionListener
. In this example you can see how to remove the current class which implements the interface PositionListener
from the listeners:
positionManager.removePositionListener(this);
You can remove all technologies with this call:
positionManager.removeAllTechnologies();
or remove a single technology:
positionManager.removeTechnology(wifiTechnology);
To map positions in the offline phase before matching you have to call the method map
of the PositionManager
class:
positionManager.map(mapName.getText().toString());
In the following few lines you can see more usefull methods to manage the positioning library properly:
Resetting all mapped positions of the current file:
positionManager.removeAllMappedPositions();
Remove a single mapped position:
positionManager.removeMappedPosition("yourPoi");
If you mapped a position with different technologies like Wifi and Bluetooth you can remove one of those by the following line:
positionManager.removeMappedPosition("yourPoi", "WIFI");
You can also get all mapped position names. A use case would be to display them and refresh the mapping by selecting each persisted position:
List<String> positions = positionManager.getMappedPositions();
To start the online mode you need to call:
positionManager.startPositioning();
You can also set a time in milliseconds. This will refresh the position every second:
positionManager.startPositioning(1000);
The default value is 3 seconds.
To stop the positioning you need to call:
positionManager.stopPositioning();
You do not ever have to start the offline mode. You can map positions every time you want but it is recommended to stop the online mode before doing so.
Technologies like Wifi or Bluetooth have to implement the abstract class Technology
and define the method getSignalData
. There you have to deliver a Map
which includes a key (for example the bssid or another identifier) and the signal information (strength). All the other functions are already handled by the library.
If you got a technology which delivers accurate position information like a bluetooth beacon with a short range (for example 1 meter) you can define this in the technology. This will override all other technologies if this technology gets signal information:
technology.providesExactlyPosition();
import android.content.Context;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiManager;
import de.hadizadeh.positioning.controller.Technology;
import de.hadizadeh.positioning.model.SignalInformation;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class WifiTechnology extends Technology {
private WifiManager wifiManager;
public WifiTechnology(Context context, String name, List<String> keyWhiteList) {
super(name, keyWhiteList);
wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
}
@Override
public Map<String, SignalInformation> getSignalData() {
Map<String, SignalInformation> signalData =
new HashMap<String, SignalInformation>();
wifiManager.startScan();
List<ScanResult> scanResults = wifiManager.getScanResults();
for (ScanResult scanResult : scanResults) {
signalData.put(scanResult.BSSID, new SignalInformation(scanResult.level));
}
return signalData;
}
}
To get Wifi data on Linux distributions you can use the command line tool iwlist
. You need to parse the output and deliver it in the method getSignalData
.
The analysis of the data can be done with the command line tool grep
as well so you do not have to parse it in your application code.
There is a specialized technology for elements which you want to use to exclude positions if they have defined meta data. For example you can exclude all points of interest which are not in the field of vision. An example is a compass. You can define an angle with 80 degree and map the positions with this exclusion technology. In the online mode the matcher will exclude all positions which are not inside the current degree. You can see an exemplary implementation of a compass with the exclusion technology in the following section.
The constructor needs an allowedDelta
which describes the field of vision.
import android.app.Activity;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import de.hadizadeh.positioning.controller.ExclusionTechnology;
import de.hadizadeh.positioning.model.SignalInformation;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class CompassTechnology extends ExclusionTechnology {
private float bearing;
private Context context;
public CompassTechnology(Context context, String name, double angle) {
super(name, allowedDelta / 2);
this.context = context;
SensorManager mySensorManager = (SensorManager) context.
getSystemService(Context.SENSOR_SERVICE);
List<Sensor> mySensors = mySensorManager.getSensorList(
Sensor.TYPE_ORIENTATION);
if (mySensors.size() > 0) {
mySensorManager.registerListener(mySensorEventListener, mySensors.get(0),
SensorManager.SENSOR_DELAY_UI);
}
}
@Override
public Map<String, SignalInformation> getSignalData() {
Map<String, SignalInformation> signalData = new HashMap
<String, SignalInformation>();
signalData.put("compassSignal", new SignalInformation(bearing));
return signalData;
}
private SensorEventListener mySensorEventListener = new SensorEventListener() {
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
@Override
public void onSensorChanged(SensorEvent event) {
bearing = (float) event.values[0];
}
};
@Override
protected boolean isValueOutOfExclusionRange(
Map<String, SignalInformation> signalData, double persistedValue) {
boolean inRange = true;
for (Map.Entry<String, SignalInformation> data : signalData.entrySet()) {
double currentValue = data.getValue().getStrength();
double min = persistedValue - allowedDelta;
double max = persistedValue + allowedDelta;
if (max >= 360) {
max -= 360;
}
if (min <= 0) {
min = 360 - min;
}
if (min > max) {
if (!(currentValue >= min || currentValue <= max)) {
inRange = false;
}
} else {
if (!(currentValue >= min && currentValue <= max)) {
inRange = false;
}
}
}
return inRange;
}
}
You can attache this technology to your position manager with the following lines of code:
CompassTechnology compassTechnology = new CompassTechnology(this, "compass", 80);
positionManager.addTechnology(compassTechnology);
To realize technologies like NFC, RFID, QR codes, infrared or light barriers you have to use active technologies. They are very easy to use and you do not have to implement an interface. You just need to create an object of the class ActiveTechnology
. Active technologies support setting an exact value and resetting it after a defined time. For example you can read an QR code in your application and deliver it to the positioning library. So the current position will be set to the mapped position with this QR code ID. You can do that the same way with NFC tags which is implemented in the next section.
Initializing the technology:
ActiveTechnology nfcTechnology = new ActiveTechnology("NFC");
positionManager.addTechnology(nfcTechnology);
In your android application you get the NFC tag data in the method onNewIntent
. There you can send it to the nfcTechnology and set the time in milliseconds. In this example the positioning library will tell you (method: positionReceived
) for 5 seconds that you are at the point of the NFC tag.
@Override
protected void onNewIntent(Intent intent) {
try {
if (nfcHandler.tagMatches(intent.getAction())) {
String tagId = nfcHandler.getTagId(intent);
nfcTechnology.idDetected(tagId, 5000);
}
} catch (Exception ex) {
ex.printStackTrace();
}
super.onNewIntent(intent);
}
In the offline mode you need to scan the NFC tag and call the method map
right after it.
The positioning library includes a caching functionality in the online mode.
The CachingManager
can handle multiple signal data and interpolate them.
If you do not define it at the initialization of the PositionManager
it will set the caching size to 1. So it will not use any caching and interpolation.
You can set the caching size and activate it with the following lines of code:
Technology wifiTechnology = new WifiTechnology(this, "WIFI", keyWhiteList);
wifiTechnology.setCachingManager(new CachingManager(10));
In this case the caching size is 10. If you set the repetition rate of the matching to 1 second the caching manager will store signal data of the last 10 seconds and will interpolate them.
You can also reset the caching manager:
wifiTechnology.restoreCachingManager();
This will set the caching size back to 1.
The default implementation of the interpolation classifies the newest signal as most important. You can see this interpolation algorithm in the following code:
public Map<String, SignalInformation> interpolateData() {
Map<String, SignalInformation> result = new HashMap<String, SignalInformation>();
Map<String, Double> multiplicators = new HashMap<String, Double>();
double factor = 0.0;
if (cachingData != null) {
for (int i = 0; i < cachingData.size(); i++) {
factor = i + 1;
for (Map.Entry<String, SignalInformation> positionElement : cachingData.
get(i).entrySet()) {
if (result.containsKey(positionElement.getKey())) {
SignalInformation signalInformation = result.get(positionElement.
getKey());
signalInformation.setStrength(signalInformation.getStrength()
+ factor * positionElement.getValue().getStrength());
result.put(positionElement.getKey(), signalInformation);
multiplicators.put(positionElement.getKey(), multiplicators.
get(positionElement.getKey()) + factor);
} else {
result.put(positionElement.getKey(), new SignalInformation(
positionElement.getValue().getStrength()));
multiplicators.put(positionElement.getKey(), factor);
}
}
}
for (Map.Entry<String, SignalInformation> signalInformation : result.
entrySet()) {
signalInformation.getValue().setStrength(signalInformation.getValue().
getStrength() / multiplicators.get(signalInformation.getKey()));
}
}
return result;
}
There are many different ways and algorithms to interpolate signal data. You can easily adapt the default interpolation by creating an own caching class which extends of CachingManager
and overrides the interpolation method:
import de.hadizadeh.positioning.controller.CachingManager;
import de.hadizadeh.positioning.model.SignalInformation;
import java.util.Map;
public class WifiCachingManager extends CachingManager {
@Override
public Map<String, SignalInformation> interpolateData() {
// Adapted interpolation
return super.interpolateData();
}
}
The default nearest neighbour matcher is the euclidean algorithm. You can adapt it by creating a class which extends of the Matcher
class.
There you have to override the method nearestNeighbour
. It is important to check the parameters because there is also a method with less parameters which you do not have to override.
In the following example you can see how to do it:
import de.hadizadeh.positioning.controller.CachingManager;
import de.hadizadeh.positioning.controller.Matcher;
import de.hadizadeh.positioning.model.PositionInformation;
import de.hadizadeh.positioning.model.SignalInformation;
import java.util.List;
import java.util.Map;
public class WifiMatcher extends Matcher{
@Override
public Map<PositionInformation, Double> nearestNeighbour(CachingManager
cachingManager, Map<String, SignalInformation> fingerPrint,
List<PositionInformation> persistedPositions, boolean ignoreDisabledAPs) {
// Adapted matcher code (nearest neighbour)
return super.nearestNeighbour(cachingManager, fingerPrint, persistedPositions,
ignoreDisabledAPs);
}
}
To use that matcher you have to hand it over to the technology. This can be done as following:
wifiTechnology.setMatcher(new WifiMatcher());
You can also restore the default matcher:
wifiTechnology.restoreDefaultMatcher();
The default matcher will not match a position if there are access points available which are not in the current signal data but in the persisted data. There is another matcher which just looks at the access points which are available:
wifiTechnology.setMatcher(new InexactMatcher());
The default persistence manager saves all data in a XML file. You can also implement an alternative persistence manager to store the data in a database or any other file.
The only thing you have to do is to implement the PersistenceManager
-Interface and create the PositionManager with another constructor like in the following example:
positionManager = new PositionManager(new SqlPersistenceManager());