interface changes
added new data-strcutures for new sensors new helper methods fixed some issues
This commit is contained in:
68
data/HistoryTS.h
Executable file
68
data/HistoryTS.h
Executable file
@@ -0,0 +1,68 @@
|
||||
#ifndef HISTORYTS_H
|
||||
#define HISTORYTS_H
|
||||
|
||||
#include <vector>
|
||||
#include "Timestamp.h"
|
||||
#include <algorithm>
|
||||
|
||||
/**
|
||||
* keep the history of values for a given amount of time
|
||||
*/
|
||||
template <typename T> class HistoryTS {
|
||||
|
||||
private:
|
||||
|
||||
/** timestamp -> value combination */
|
||||
struct Entry {
|
||||
Timestamp ts;
|
||||
T value;
|
||||
Entry(const Timestamp ts, const T& value) : ts(ts), value(value) {;}
|
||||
};
|
||||
|
||||
/** the time-window to keep */
|
||||
Timestamp window;
|
||||
|
||||
/** the value history for the window-size */
|
||||
std::vector<Entry> history;
|
||||
|
||||
public:
|
||||
|
||||
|
||||
/** ctor with the time-window to keep */
|
||||
HistoryTS(const Timestamp window) : window(window) {
|
||||
|
||||
}
|
||||
|
||||
/** add a new entry */
|
||||
void add(const Timestamp ts, const T& data) {
|
||||
|
||||
// append to history
|
||||
history.push_back(Entry(ts, data));
|
||||
|
||||
// remove too-old history entries
|
||||
const Timestamp oldest = ts - window;
|
||||
while(history.front().ts < oldest) {
|
||||
|
||||
// remove from history
|
||||
history.erase(history.begin());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** get the most recent entry */
|
||||
T getMostRecent() const {
|
||||
return history.back().value;
|
||||
}
|
||||
|
||||
/** get the oldest entry available */
|
||||
T getOldest() const {
|
||||
return history.front().value;
|
||||
}
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif // HISTORYTS_H
|
||||
@@ -46,28 +46,62 @@ namespace Floorplan {
|
||||
|
||||
}
|
||||
|
||||
/** get the number of ceilings between z1 and z2 */
|
||||
float numCeilingsBetweenFloat(const Point3 pos1, const Point3 pos2) const {
|
||||
std::vector<float> getCeilings() const {
|
||||
return ceilingsAtHeight_m;
|
||||
}
|
||||
|
||||
void addCeiling(const float height_m) {
|
||||
ceilingsAtHeight_m.push_back(height_m);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
ceilingsAtHeight_m.clear();
|
||||
}
|
||||
|
||||
/** get a fading number of floors between ap and person using sigmod in the area where the ceiling is */
|
||||
float numCeilingsBetweenFloat(const Point3 ap, const Point3 person) const {
|
||||
|
||||
// sanity checks
|
||||
Assert::isNot0(ceilingsAtHeight_m.size(), "no ceilings available for testing! incorrect map?");
|
||||
|
||||
const float zMin = std::min(pos1.z, pos2.z);
|
||||
const float zMax = std::max(pos1.z, pos2.z);
|
||||
|
||||
// fading between floors using sigmoid
|
||||
const float near = 1.0;
|
||||
float cnt = 0;
|
||||
|
||||
|
||||
for (const float z : ceilingsAtHeight_m) {
|
||||
if (zMin < z && zMax > z) {
|
||||
const float dmax = zMax - z;
|
||||
cnt += (dmax > 1) ? (1) : (dmax);
|
||||
|
||||
const float myDistToCeil = (ap.z < person.z) ? (person.z - z) : (z - person.z);
|
||||
if ( std::abs(myDistToCeil) < near ) {
|
||||
cnt += sigmoid(myDistToCeil * 6);
|
||||
} else if (ap.z < z && person.z >= z+near) { // AP below celing, me above ceiling
|
||||
cnt += 1;
|
||||
} else if (ap.z > z && person.z <= z-near) { // AP above ceiling, me below ceiling
|
||||
cnt += 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return cnt;
|
||||
|
||||
}
|
||||
|
||||
float numCeilingsBetweenLinearInt(const Point3 ap, const Point3 person) const {
|
||||
|
||||
// sanity checks
|
||||
Assert::isNot0(ceilingsAtHeight_m.size(), "no ceilings available for testing! incorrect map?");
|
||||
|
||||
int cnt = 0;
|
||||
float sum = 0;
|
||||
for (float z = -1.0; z <= +1.0; z+= 0.25) {
|
||||
sum += numCeilingsBetween(ap, person + Point3(0,0,z));
|
||||
++cnt;
|
||||
}
|
||||
|
||||
return sum/cnt;
|
||||
|
||||
}
|
||||
|
||||
/** get the number of ceilings between z1 and z2 */
|
||||
int numCeilingsBetween(const Point3 pos1, const Point3 pos2) const {
|
||||
|
||||
@@ -80,14 +114,14 @@ namespace Floorplan {
|
||||
|
||||
#ifdef WITH_ASSERTIONS
|
||||
|
||||
static int numNear = 0;
|
||||
static int numFar = 0;
|
||||
static size_t numNear = 0;
|
||||
static size_t numFar = 0;
|
||||
for (const float z : ceilingsAtHeight_m) {
|
||||
const float diff = std::min( std::abs(z-zMin), std::abs(z-zMax) );
|
||||
if (diff < 0.1) {++numNear;} else {++numFar;}
|
||||
}
|
||||
if ((numNear + numFar) > 150000) {
|
||||
Assert::isTrue(numNear < numFar*0.15,
|
||||
Assert::isTrue(numNear < numFar*0.5,
|
||||
"many requests to Floorplan::Ceilings::numCeilingsBetween address nodes (very) near to a ground! \
|
||||
due to rounding issues, determining the number of floors between AP and point-in-question is NOT possible! \
|
||||
expect very wrong outputs! \
|
||||
@@ -105,6 +139,12 @@ namespace Floorplan {
|
||||
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
static inline float sigmoid(const float val) {
|
||||
return 1.0f / (1.0f + std::exp(-val));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -97,6 +97,11 @@ namespace Floorplan {
|
||||
checkStair(res, floor, s);
|
||||
}
|
||||
|
||||
// check elevators
|
||||
for (const Elevator* e : floor->elevators) {
|
||||
checkElevator(res, floor, e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// done
|
||||
@@ -190,7 +195,7 @@ namespace Floorplan {
|
||||
|
||||
for (int i = 0; i < (int) parts.size(); ++i) {
|
||||
|
||||
const Floorplan::Quad3& quad = quads[i];
|
||||
//const Floorplan::Quad3& quad = quads[i];
|
||||
|
||||
// disconnected within?
|
||||
if (i > 0) {
|
||||
@@ -201,7 +206,17 @@ namespace Floorplan {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void checkElevator(Issues& res, const Floor* floor, const Elevator* e) {
|
||||
|
||||
if (e->depth < 0.5) {
|
||||
res.push_back(Issue(Type::ERROR, floor, "elevator's depth @" + e->center.asString() + " is too small: " + std::to_string(e->depth) + "m"));
|
||||
}
|
||||
|
||||
if (e->width < 0.5) {
|
||||
res.push_back(Issue(Type::ERROR, floor, "elevator's width @" + e->center.asString() + " is too small: " + std::to_string(e->width) + "m"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ public:
|
||||
};
|
||||
|
||||
/** get the part of outline the given location belongs to. currently: none, indoor, outdoor */
|
||||
PartOfOutline isPartOfFloorOutline(const int x_cm, const int y_cm, const Floorplan::FloorOutline& outline) {
|
||||
static PartOfOutline isPartOfFloorOutline(const int x_cm, const int y_cm, const Floorplan::FloorOutline& outline) {
|
||||
|
||||
// assume the point is not part of the outline
|
||||
PartOfOutline res = PartOfOutline::NO;
|
||||
|
||||
@@ -111,9 +111,6 @@ public:
|
||||
Assert::isBetween(walkImportance, 0.0f, 2.5f, "grid-node's walk-importance is out of range. Did you forget to calculate the importance values after building the grid?");
|
||||
probability *= walkImportance;// < 0.4f ? (0.1) : (1.0); // "kill" particles that walk near walls (most probably trapped ones)
|
||||
|
||||
|
||||
//probability = std::pow(probability, 5);
|
||||
|
||||
// sanity check
|
||||
Assert::isNotNaN(probability, "detected NaN grid-walk probability");
|
||||
|
||||
|
||||
@@ -74,9 +74,8 @@ public:
|
||||
// get the difference
|
||||
const float angularDiff = head.getDiffHalfRAD(stateHead);
|
||||
|
||||
if (angularDiff > Angle::degToRad(180)) {return 0.05;}
|
||||
if (angularDiff > Angle::degToRad(90)) {return 0.25;}
|
||||
{return 0.70;}
|
||||
if (angularDiff > Angle::degToRad(100)) {return 0.10;}
|
||||
{return 0.90;}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ public:
|
||||
// if (potentialNode.getType() == GridNode::TYPE_FLOOR) {return kappa;}
|
||||
// {return 1-kappa;}
|
||||
default:
|
||||
throw Exception("not yet implemented");
|
||||
throw Exception("not yet implemented activity within WalkModuleActivityControl::getProbability");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,5 +9,6 @@
|
||||
#include "distribution/Region.h"
|
||||
#include "distribution/Triangle.h"
|
||||
#include "distribution/NormalN.h"
|
||||
#include "distribution/Rectangular.h"
|
||||
|
||||
#endif // DISTRIBUTIONS_H
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "../Assertions.h"
|
||||
#include "../Exception.h"
|
||||
#include "../data/Timestamp.h"
|
||||
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
@@ -49,7 +50,7 @@ public:
|
||||
|
||||
// interpolate
|
||||
const int idx1 = (idx2 > 0) ? (idx2 - 1) : (idx2);
|
||||
const float percent = (key - entries[idx1].key) / (float) (entries[idx2].key - entries[idx1].key);
|
||||
const float percent = getPercent(key, entries[idx1].key, entries[idx2].key); //(key - entries[idx1].key) / (float) (entries[idx2].key - entries[idx1].key);
|
||||
const Value res = entries[idx1].value + (entries[idx2].value - entries[idx1].value) * percent;
|
||||
return res;
|
||||
|
||||
@@ -57,6 +58,16 @@ public:
|
||||
|
||||
protected:
|
||||
|
||||
/** special interpolation for the timestamp class */
|
||||
static inline float getPercent(const Timestamp key, const Timestamp t1, const Timestamp t2) {
|
||||
return (key - t1).ms() / (float) (t2 - t1).ms();
|
||||
}
|
||||
|
||||
/** interpolation for generic datatypes [int, float, double, ..] */
|
||||
template <typename T> static inline float getPercent(const T key, const T t1, const T t2) {
|
||||
return (key - t1) / (float) (t2 - t1);
|
||||
}
|
||||
|
||||
/** get the nearest index for the given key */
|
||||
int getIdxAfter(const Key key) const {
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ private:
|
||||
std::vector<T> values;
|
||||
|
||||
/** track the current sum of the vector's values */
|
||||
T curSum = 0;
|
||||
T curSum;
|
||||
|
||||
/** the number of elements to average */
|
||||
int size;
|
||||
@@ -19,7 +19,7 @@ private:
|
||||
public:
|
||||
|
||||
/** ctor */
|
||||
MovingAVG(const int size) : size(size) {;}
|
||||
MovingAVG(const int size) : curSum(), size(size) {;}
|
||||
|
||||
/** add a new value */
|
||||
void add(const T val) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,12 +69,25 @@ 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;
|
||||
|
||||
@@ -78,6 +100,50 @@ public:
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
@@ -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;}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <fstream>
|
||||
|
||||
#include "WiFiFingerprint.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
/**
|
||||
* helper class to load and save fingerprints.
|
||||
|
||||
@@ -49,12 +49,12 @@ public:
|
||||
//gp << "set hidden3d front\n";
|
||||
|
||||
splot.add(&nodes); nodes.setPointSize(0.5);
|
||||
splot.add(&lines); lines.setColorHex("#999999");
|
||||
splot.add(&lines); lines.getStroke().getColor().setHexStr("#999999");
|
||||
splot.add(&floors);
|
||||
splot.add(&particles); particles.setColorHex("#0000ff"); particles.setPointSize(0.5);
|
||||
splot.add(&particles); particles.getColor().setHexStr("#0000ff"); particles.setPointSize(0.5);
|
||||
|
||||
floors.setLineWidth(2);
|
||||
floors.setColorHex("#008800");
|
||||
floors.getStroke().setWidth(2);
|
||||
floors.getStroke().getColor().setHexStr("#008800");
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ TEST(TestAll, Nav) {
|
||||
gi.addImportance(g, d.getNode(start), d.getNode(end));
|
||||
|
||||
// plot path
|
||||
K::GnuplotSplotElementLines path; path.setColorHex("#0000ff"); path.setLineWidth(2);
|
||||
K::GnuplotSplotElementLines path; path.getStroke().getColor().setHexStr("#0000ff"); path.getStroke().setWidth(2);
|
||||
const DijkstraNode<GP>* dn = d.getNode(end);
|
||||
while (dn->previous != nullptr) {
|
||||
path.add(K::GnuplotPoint3(dn->element->x_cm, dn->element->y_cm, dn->element->z_cm+50));
|
||||
|
||||
@@ -131,8 +131,8 @@ TEST(GgridWalk2, LIVE_walkHeading) {
|
||||
|
||||
K::Gnuplot gp;
|
||||
K::GnuplotSplot splot;
|
||||
K::GnuplotSplotElementPoints nodes; nodes.setPointSize(0.1); nodes.setColorHex("#888888"); splot.add(&nodes);
|
||||
K::GnuplotSplotElementPoints states; states.setPointSize(0.5); states.setColorHex("#0000ff"); splot.add(&states);
|
||||
K::GnuplotSplotElementPoints nodes; nodes.setPointSize(0.1); nodes.getColor().setHexStr("#888888"); splot.add(&nodes);
|
||||
K::GnuplotSplotElementPoints states; states.setPointSize(0.5); states.getColor().setHexStr("#0000ff"); splot.add(&states);
|
||||
|
||||
for (const MyNode1239& n : grid) {
|
||||
static int cnt = 0;
|
||||
|
||||
@@ -50,8 +50,8 @@ TEST(Stairs, live_testWalk) {
|
||||
|
||||
K::Gnuplot gp;
|
||||
K::GnuplotSplot splot;
|
||||
K::GnuplotSplotElementPoints pnodes; splot.add(&pnodes); pnodes.setColorHex("#888888"); pnodes.setPointType(7);
|
||||
K::GnuplotSplotElementPoints pstates; splot.add(&pstates); pstates.setColorHex("#0000ff"); pstates.setPointType(7); pstates.setPointSize(1);
|
||||
K::GnuplotSplotElementPoints pnodes; splot.add(&pnodes); pnodes.getColor().setHexStr("#888888"); pnodes.setPointType(7);
|
||||
K::GnuplotSplotElementPoints pstates; splot.add(&pstates); pstates.getColor().setHexStr("#0000ff"); pstates.setPointType(7); pstates.setPointSize(1);
|
||||
|
||||
for (int i = 0; i < g.getNumNodes(); i+=2) {
|
||||
const MyNode345092134& gp = g[i];
|
||||
|
||||
@@ -101,7 +101,7 @@ TEST(Walk, DISABLED_plot) {
|
||||
std::normal_distribution<float> dWalk(0.3, 0.3);
|
||||
std::normal_distribution<float> dTurn(0.3, 0.3);
|
||||
|
||||
K::GnuplotSplotElementPoints pStates; pStates.setColorHex("#880000");
|
||||
K::GnuplotSplotElementPoints pStates; pStates.getColor().setHexStr("#880000");
|
||||
|
||||
// setup starting states
|
||||
std::vector<GridWalkState<GP>> states;
|
||||
|
||||
@@ -57,7 +57,7 @@ TEST(Butterworth, offlineSinus) {
|
||||
linesInput.addSegment(input_p1, input_p2);
|
||||
linesOutput.addSegment(output_p1, output_p2);
|
||||
}
|
||||
linesOutput.setColorHex("#00FF00");
|
||||
linesOutput.getStroke().getColor().setHexStr("#00FF00");
|
||||
|
||||
plot.add(&linesInput);
|
||||
plot.add(&linesOutput);
|
||||
@@ -104,7 +104,7 @@ TEST(Butterworth, onlineSinus) {
|
||||
linesInput.addSegment(input_p1, input_p2);
|
||||
linesOutput.addSegment(output_p1, output_p2);
|
||||
}
|
||||
linesOutput.setColorHex("#00FF00");
|
||||
linesOutput.getStroke().getColor().setHexStr("#00FF00");
|
||||
|
||||
plot.add(&linesInput);
|
||||
plot.add(&linesOutput);
|
||||
@@ -171,7 +171,7 @@ TEST(Butterworth, offlineOctaveBaro) {
|
||||
linesInput.addSegment(input_p1, input_p2);
|
||||
linesOutput.addSegment(output_p1, output_p2);
|
||||
}
|
||||
linesOutput.setColorHex("#00FF00");
|
||||
linesOutput.getStroke().getColor().setHexStr("#00FF00");
|
||||
|
||||
plot.add(&linesInput);
|
||||
plot.add(&linesOutput);
|
||||
@@ -242,7 +242,7 @@ TEST(Butterworth, onlineOctaveBaro) {
|
||||
linesInput.addSegment(input_p1, input_p2);
|
||||
linesOutput.addSegment(output_p1, output_p2);
|
||||
}
|
||||
linesOutput.setColorHex("#00FF00");
|
||||
linesOutput.getStroke().getColor().setHexStr("#00FF00");
|
||||
|
||||
plot.add(&linesInput);
|
||||
plot.add(&linesOutput);
|
||||
|
||||
Reference in New Issue
Block a user