interface changes

added new data-strcutures for new sensors
new helper methods
fixed some issues
This commit is contained in:
2017-05-24 09:23:27 +02:00
parent f67f95d1ce
commit d40032ca74
29 changed files with 471 additions and 68 deletions

View File

@@ -2,7 +2,7 @@
#define GPSDATA_H
#include "../../data/Timestamp.h"
#include "../../geo/EarthPos.h"
struct GPSData {
@@ -25,6 +25,11 @@ struct GPSData {
/** ctor */
GPSData(const Timestamp tsReceived, const float lat, const float lon, const float alt, const float accuracy) : tsReceived(tsReceived), lat(lat), lon(lon), alt(alt), accuracy(accuracy), speed(NAN) {;}
/** get as EarthPos struct */
EarthPos toEarthPos() const {
return EarthPos(lat, lon, alt);
}
/** data valid? */
bool isValid() const {
return (lat == lat) && (lon == lon);

View File

@@ -21,6 +21,7 @@
#include "../../grid/factory/v2/GridFactory.h"
#include "../../grid/factory/v2/Importance.h"
#include "../../floorplan/v2/Floorplan.h"
#include "../../floorplan/v2/FloorplanHelper.h"
#include "Splitter.h"
#include "Sensors.h"
@@ -30,10 +31,16 @@
namespace Offline {
/**
* read and parse previously recorded ["offline"] files
*/
class FileReader {
public:
using GroundTruth = Interpolator<Timestamp, Point3>;
/** all entries grouped by sensor */
std::vector<TS<int>> groundTruth;
std::vector<TS<WiFiMeasurements>> wifi;
std::vector<TS<BeaconMeasurement>> beacon;
@@ -45,13 +52,11 @@ namespace Offline {
std::vector<TS<GPSData>> gps;
std::vector<TS<CompassData>> compass;
/** ALL entries */
/** all entries in linear order as they appeared while recording */
std::vector<Entry> entries;
static constexpr char sep = ';';
Listener* listener = nullptr;
public:
/** empty ctor. call open() */
@@ -65,11 +70,26 @@ namespace Offline {
}
/** open the given file */
void open(const std::string& file, Listener* listener = nullptr) {
this->listener = listener;
void open(const std::string& file) {
clear();
parse(file);
}
/** remove all parsed entries */
void clear() {
entries.clear();
groundTruth.clear();
wifi.clear();
beacon.clear();
acc.clear();
gyro.clear();
gps.clear();
compass.clear();
barometer.clear();
lin_acc.clear();
gravity.clear();
}
const std::vector<Entry>& getEntries() const {return entries;}
@@ -93,6 +113,35 @@ namespace Offline {
const std::vector<TS<GravityData>>& getGravity() const {return gravity;}
/** get an interpolateable ground-truth based on the time-clicks during recording */
GroundTruth getGroundTruth(const Floorplan::IndoorMap* map, const std::vector<int> groundTruthPoints) const {
// sanity check: given path [indices to ground-truth points within the map]
// must have the same size as the number of clicks during recording
Assert::equal(groundTruthPoints.size(), groundTruth.size(), "mismatch of ground-truth points between given path and recording");
// allows getting a position on the ground-truth given a timestamp
GroundTruth interpol;
// all ground-truth points within the map
static std::unordered_map<int, Point3> gt = FloorplanHelper::getGroundTruthPoints(map);
// process each "tap smartphone when reaching ground-truth-point"
for (const TS<int>& entry : groundTruth) {
const Timestamp ts = Timestamp::fromMS(entry.ts);
const int idx = entry.data; // starting at 0, incrementing over time [1st point, 2nd points, 3d points, ...]
const int id = groundTruthPoints[idx]; // convert point number to point-id within floorplan
const auto& it = gt.find(id);
if (it == gt.end()) {throw Exception("missing ground-truth point ID:" + std::to_string(id));}
const Point3 pos = it->second;
interpol.add(ts, pos);
}
// done
return interpol;
}
private:
void parse(const std::string& file) {
@@ -165,7 +214,7 @@ namespace Offline {
entries.push_back(Entry(Sensor::GRAVITY, ts, gravity.size()-1));
// inform listener
if (listener) {listener->onGravity(Timestamp::fromMS(ts), gravData);}
//if (listener) {listener->onGravity(Timestamp::fromMS(ts), gravData);}
}
@@ -184,7 +233,7 @@ namespace Offline {
entries.push_back(Entry(Sensor::ACC, ts, acc.size()-1));
// inform listener
if (listener) {listener->onAccelerometer(Timestamp::fromMS(ts), accData);}
//if (listener) {listener->onAccelerometer(Timestamp::fromMS(ts), accData);}
}
@@ -203,7 +252,7 @@ namespace Offline {
entries.push_back(Entry(Sensor::GYRO, ts, gyro.size()-1));
// inform listener
if (listener) {listener->onGyroscope(Timestamp::fromMS(ts), gyroData);}
//if (listener) {listener->onGyroscope(Timestamp::fromMS(ts), gyroData);}
}
@@ -230,7 +279,7 @@ namespace Offline {
entries.push_back(Entry(Sensor::WIFI, ts, this->wifi.size()-1));
// inform listener
if (listener) {listener->onWiFi(Timestamp::fromMS(ts), wifi);}
//if (listener) {listener->onWiFi(Timestamp::fromMS(ts), wifi);}
}
@@ -273,7 +322,7 @@ namespace Offline {
entries.push_back(Entry(Sensor::BARO, ts, barometer.size()-1));
// inform listener
if (listener) {listener->onBarometer(Timestamp::fromMS(ts), baro);}
//if (listener) {listener->onBarometer(Timestamp::fromMS(ts), baro);}
}
@@ -290,7 +339,7 @@ namespace Offline {
entries.push_back(Entry(Sensor::COMPASS, ts, this->compass.size()-1));
// inform listener
if (listener) {listener->onCompass(Timestamp::fromMS(ts), compass);}
//if (listener) {listener->onCompass(Timestamp::fromMS(ts), compass);}
}
@@ -311,12 +360,13 @@ namespace Offline {
entries.push_back(Entry(Sensor::GPS, ts, this->gps.size()-1));
// inform listener
if (listener) {listener->onGPS(Timestamp::fromMS(ts), gps);}
//if (listener) {listener->onGPS(Timestamp::fromMS(ts), gps);}
}
public:
const Interpolator<uint64_t, Point3> getGroundTruthPath(Floorplan::IndoorMap* map, std::vector<int> gtPath) const {
// finde alle positionen der waypoints im gtPath aus map

View File

@@ -51,9 +51,9 @@ public:
K::GnuplotPlotElementLines tendence;
Debug() {
plot.add(&raw); raw.setColorHex("#999999");
plot.add(&avg); avg.setColorHex("#000000");
plot.add(&tendence); tendence.setLineWidth(2);
plot.add(&raw); raw.getStroke().getColor().setHexStr("#999999");
plot.add(&avg); avg.getStroke().getColor().setHexStr("#000000");
plot.add(&tendence); tendence.getStroke().setWidth(2);
tendence.setCustomAttr(" axes x1y2 ");
gp << "set y2tics\n";
gp << "set y2range[-0.3:+0.3]\n";

View File

@@ -45,6 +45,11 @@ public:
;
}
/** equals? */
bool operator == (const AccessPoint& o) {
return (o.mac == mac) && (o.ssid == ssid);
}
public:
/** get the AP's MAC address */

View File

@@ -50,18 +50,27 @@ private:
/** the signal-strength aggregation algorithm to use */
const Aggregation agg;
/** respect only outputs with at-least X occurences of one physical hardware [can be used to prevent issues] */
int minOccurences;
public:
/** ctor */
VAPGrouper(const Mode mode, const Aggregation agg) : mode(mode), agg(agg) {
VAPGrouper(const Mode mode, const Aggregation agg, const int minOccurences = 2) :
mode(mode), agg(agg), minOccurences(minOccurences) {
;
}
/** set the number of needed occurences per VAP-group to be accepted */
void setMinOccurences(const int min) {
this->minOccurences = min;
}
/** get a vap-grouped version of the given input */
WiFiMeasurements group(const WiFiMeasurements& original) const {
// first, group all VAPs into a vector [one vector per VAP-group]
// by using the modified MAC address that all VAPs have in common
std::unordered_map<MACAddress, std::vector<WiFiMeasurement>> grouped;
for (const WiFiMeasurement& m : original.entries) {
@@ -72,8 +81,9 @@ public:
}
// output
// to-be-constructed output
WiFiMeasurements result;
int skipped = 0;
// perform aggregation on each VAP-group
for (auto it : grouped) {
@@ -81,6 +91,9 @@ public:
const MACAddress& base = it.first;
const std::vector<WiFiMeasurement>& vaps = it.second;
// remove entries that do not satify the min-occurences metric
if ((int)vaps.size() < minOccurences) {++skipped; continue;}
// group all VAPs into one measurement
const WiFiMeasurement groupedMeasurement = groupVAPs(base, vaps);
@@ -89,7 +102,12 @@ public:
}
Log::add(name, "grouped " + std::to_string(original.entries.size()) + " measurements into " + std::to_string(result.entries.size()), true);
// debug
Log::add(name,
"grouped " + std::to_string(original.entries.size()) + " measurements " +
"into " + std::to_string(result.entries.size()) + " [omitted: " + std::to_string(skipped) + "]",
true
);
// done

View File

@@ -23,8 +23,8 @@ struct WiFiMeasurements {
}
/** get the measurements for the given MAC [if available] otherwise null */
WiFiMeasurement* getForMac(const MACAddress& mac) {
for (WiFiMeasurement& m : entries) {
const WiFiMeasurement* getForMac(const MACAddress& mac) const {
for (const WiFiMeasurement& m : entries) {
if (m.getAP().getMAC() == mac) {
return &m;
}
@@ -32,6 +32,46 @@ struct WiFiMeasurements {
return nullptr;
}
/** remove the entry for the given MAC (if any) */
void remove(const MACAddress& mac) {
for (size_t i = 0; i < entries.size(); ++i) {
if (entries[i].getAP().getMAC() == mac) {
entries.erase(entries.begin() + i);
break;
}
}
}
/** create a combination */
static WiFiMeasurements mix(const WiFiMeasurements& a, const WiFiMeasurements& b, float sec = 3) {
Timestamp max;
WiFiMeasurements res;
for (const WiFiMeasurement& m : a.entries) {
res.entries.push_back(m);
if (m.getTimestamp() > max) {max = m.getTimestamp();}
}
for (const WiFiMeasurement& m : b.entries) {
res.entries.push_back(m);
if (m.getTimestamp() > max) {max = m.getTimestamp();}
}
std::vector<WiFiMeasurement> tmp;
std::swap(res.entries, tmp);
for (const WiFiMeasurement& m : tmp) {
if ((max - m.getTimestamp()).sec() < sec) {
res.entries.push_back(m);
}
}
return res;
}
};
#endif // WIFIMEASUREMENTS_H

View File

@@ -27,15 +27,25 @@ private:
/** the map's floorplan */
Floorplan::IndoorMap* map;
std::vector<AccessPoint> allAPs;
bool useError = false;
public:
WiFiObserverFree(const float sigma, WiFiModel& model) : sigma(sigma), model(model) {
allAPs = model.getAllAPs();
}
void setUseError(const bool useError) {
this->useError = useError;
}
double getProbability(const Point3& pos_m, const Timestamp curTime, const WiFiMeasurements& obs) const {
double prob = 1.0;
//double prob = 0;
int numMatchingAPs = 0;
// process each measured AP
@@ -50,7 +60,6 @@ public:
// NaN? -> AP not known to the model -> skip
if (modelRSSI != modelRSSI) {continue;}
// the scan's RSSI
const float scanRSSI = entry.getRSSI();
@@ -60,24 +69,81 @@ public:
Assert::isTrue(age.ms() >= 0, "found a negative wifi measurement age. this does not make sense");
Assert::isTrue(age.ms() <= 60000, "found a 60 second old wifi measurement. maybe there is a coding error?");
Assert::isBetween(scanRSSI, -100.0f, -30.0f, "scan-rssi out of range");
//Assert::isBetween(modelRSSI, -160.0f, -10.0f, "model-rssi out of range");
// sigma grows with measurement age
const float sigma = this->sigma + this->sigmaPerSecond * age.sec();
// probability for this AP
double local = Distribution::Normal<double>::getProbability(modelRSSI, sigma, scanRSSI);
//double local = Distribution::Exponential<double>::getProbability(0.1, std::abs(modelRSSI-scanRSSI));
// also add the error value? [location is OK but model is wrong]
if (useError) {
local = 0.95 * local + 0.05 * (1.0-local);
//local = 0.95 * local + 0.05;
#warning "TODO"
}
// update probability
prob *= Distribution::Normal<double>::getProbability(modelRSSI, sigma, scanRSSI);
//prob *= Distribution::Region<double>::getProbability(modelRSSI, sigma, scanRSSI);
prob *= local;
++numMatchingAPs;
}
// sanity check
//Assert::isTrue(numMatchingAPs > 0, "not a single measured AP was matched against known ones. coding error? model error?");
//Assert::isTrue(numMatchingAPs > 0, "not a single measured AP was matched against known ones. coding error? model error?");
return prob;
}
/**
* for some locations it might make sense to also look at APs
* that have NOT been discovered by the smartphone but SHOULD
* be very well receivable at a location.
* such locations can be opted-out by using this veto-probability
*/
double getVeto(const Point3& pos_m, const Timestamp curTime, const WiFiMeasurements& obs) const {
(void) curTime;
struct APR {
AccessPoint ap;
float rssi;
APR(const AccessPoint& ap, const float rssi) : ap(ap), rssi(rssi) {;}
};
std::vector<APR> all;
for (const AccessPoint& ap : allAPs) {
const float rssi = model.getRSSI(ap.getMAC(), pos_m);
if (rssi != rssi) {continue;} // unknown to the model
all.push_back(APR(ap, rssi));
}
// stort by RSSI
auto comp = [&] (const APR& apr1, const APR& apr2) {return apr1.rssi > apr2.rssi;};
std::sort(all.begin(), all.end(), comp);
int numVetos = 0;
for (int i = 0; i < 3; ++i) {
const APR& apr = all[i];
const WiFiMeasurement* mes = obs.getForMac(apr.ap.getMAC());
const float rssiScan = (mes) ? (mes->getRSSI()) : (-100);
const float rssiModel = apr.rssi;
const float diff = std::abs(rssiScan - rssiModel);
if (diff > 20) {++numVetos;}
}
if (numVetos == 0) {return 0.70;}
if (numVetos == 1) {return 0.29;}
else {return 0.01;}
}
template <typename Node> double getProbability(const Node& n, const Timestamp curTime, const WiFiMeasurements& obs, const int age_ms = 0) const {
return this->getProbability(n.inMeter() + Point3(0,0,1.3), curTime, obs);

View File

@@ -68,8 +68,8 @@ private:
const float stdDev = std::sqrt(avg2 - avg*avg);
// avg rssi score
const float minRSSI = -90;
const float maxRSSI = -65;
const float minRSSI = -85;
const float maxRSSI = -70;
float score1 = (avg-minRSSI) / (maxRSSI-minRSSI); // min = 0; max = 1
if (score1 > 1) {score1 = 1;}
if (score1 < 0) {score1 = 0;}

View File

@@ -3,6 +3,11 @@
#include "../AccessPoint.h"
#include "../../../geo/Point3.h"
#include <vector>
#include "../../../data/XMLserialize.h"
/**
* interface for signal-strength prediction models.
@@ -10,7 +15,7 @@
* the model is passed a MAC-address of an AP in question, and a position.
* hereafter the model returns the RSSI for this AP at the questioned location.
*/
class WiFiModel {
class WiFiModel : public XMLserialize {
public:
@@ -31,6 +36,7 @@ public:
*/
virtual float getRSSI(const MACAddress& accessPoint, const Point3 position_m) const = 0;
};
#endif // WIFIMODEL_H

View File

@@ -77,6 +77,14 @@ public:
}
void readFromXML(XMLDoc* doc, XMLElem* src) override {
throw Exception("not yet implemented");
}
void writeToXML(XMLDoc* doc, XMLElem* dst) override {
throw Exception("not yet implemented");
}
};
#endif // WIFIMODELLOGDIST_H

View File

@@ -10,6 +10,8 @@
#include "../VAPGrouper.h"
#include "../../../misc/Debug.h"
#include "../../../data/XMLserialize.h"
/**
* signal-strength estimation using log-distance model
* including ceilings between AP and position
@@ -52,6 +54,13 @@ public:
}
/** get the entry for the given mac. exception if missing */
const APEntry& getAP(const MACAddress& mac) const {
const auto& it = accessPoints.find(mac);
if (it == accessPoints.end()) {throw Exception("model does not contain an entry for " + mac.asString());}
return it->second;
}
/** get a list of all APs known to the model */
std::vector<AccessPoint> getAllAPs() const {
std::vector<AccessPoint> aps;
@@ -109,6 +118,14 @@ public:
}
/**
* make the given AP (and its parameters) known to the model
* usually txp,exp,waf are checked for a sane range. if you know what you are doing, set assertSafe to false!
*/
void addAP(const MACAddress& accessPoint, const Point3 pos_m, const float txp, const float exp, const float waf, const bool assertSafe = true) {
addAP(accessPoint, APEntry(pos_m, txp, exp, waf), assertSafe);
}
/** remove all added APs */
void clear() {
accessPoints.clear();
@@ -130,7 +147,9 @@ public:
const float rssiLOS = LogDistanceModel::distanceToRssi(params.txp, params.exp, distance_m);
// WAF loss (params.waf is a negative value!) -> WAF loss is also a negative value
const float wafLoss = params.waf * ceilings.numCeilingsBetween(position_m, params.position_m);
//const float wafLoss = params.waf * ceilings.numCeilingsBetween(position_m, params.position_m);
const float wafLoss = params.waf * ceilings.numCeilingsBetweenFloat(position_m, params.position_m);
//const float wafLoss = params.waf * ceilings.numCeilingsBetweenLinearInt(position_m, params.position_m);
// combine
const float res = rssiLOS + wafLoss;
@@ -143,6 +162,59 @@ public:
}
void writeToXML(XMLDoc* doc, XMLElem* dst) override {
// set my type
dst->SetAttribute("type", "WiFiModelLogDistCeiling");
for (const auto& it : accessPoints) {
const MACAddress& mac = it.first;
const APEntry& ape = it.second;
XMLElem* xap = doc->NewElement("ap");
xap->SetAttribute("mac", mac.asString().c_str());
xap->SetAttribute("px", ape.position_m.x);
xap->SetAttribute("py", ape.position_m.y);
xap->SetAttribute("pz", ape.position_m.z);
xap->SetAttribute("txp", ape.txp);
xap->SetAttribute("exp", ape.exp);
xap->SetAttribute("waf", ape.waf);
dst->InsertEndChild(xap);
}
for (const float atHeight_m : ceilings.getCeilings()) {
XMLElem* xceil = doc->NewElement("ceiling");
xceil->SetAttribute("atHeight", atHeight_m);
dst->InsertEndChild(xceil);
}
}
void readFromXML(XMLDoc* doc, XMLElem* src) override {
// check type
if (std::string("WiFiModelLogDistCeiling") != src->Attribute("type")) {throw Exception("invalid model type");}
accessPoints.clear();
ceilings.clear();
XML_FOREACH_ELEM_NAMED("ap", xap, src) {
MACAddress mac = MACAddress(xap->Attribute("mac"));
APEntry ape(
Point3(xap->FloatAttribute("px"), xap->FloatAttribute("py"), xap->FloatAttribute("pz")),
xap->FloatAttribute("txp"),
xap->FloatAttribute("exp"),
xap->FloatAttribute("waf")
);
accessPoints.insert( std::make_pair(mac, ape) );
}
XML_FOREACH_ELEM_NAMED("ceiling", xceil, src) {
const float atHeight_m = xceil->FloatAttribute("atHeight");
ceilings.addCeiling(atHeight_m);
}
}
};

View File

@@ -29,6 +29,8 @@ struct WiFiFingerprint {
/** ctor */
WiFiFingerprint(const Point3 pos_m) : pos_m(pos_m) {;}
/** ctor */
WiFiFingerprint(const Point3 pos_m, const WiFiMeasurements& measurements) : pos_m(pos_m), measurements(measurements) {;}
/** as each AP might be contained more than once (scanned more than once), group them by MAC and use the average RSSI */

View File

@@ -4,7 +4,7 @@
#include <fstream>
#include "WiFiFingerprint.h"
#include <iostream>
/**
* helper class to load and save fingerprints.