#ifndef FILEREADER_H #define FILEREADER_H #include #include #include #include #include "../../math/Interpolator.h" #include "../../sensors/radio/WiFiMeasurements.h" #include "../../sensors/pressure/BarometerData.h" #include "../../sensors/imu/AccelerometerData.h" #include "../../sensors/imu/GyroscopeData.h" #include "../../sensors/imu/GravityData.h" #include "../../sensors/imu/LinearAccelerationData.h" #include "../../sensors/beacon/BeaconMeasurements.h" #include "../../sensors/gps/GPSData.h" #include "../../sensors/imu/CompassData.h" #include "../../geo/Point2.h" #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" #include "Listener.h" #warning "adjust to to use the new splitter for all parsers [gps, compass, etc. already do!]" namespace Offline { /** * read and parse previously recorded ["offline"] files */ class FileReader { public: using GroundTruth = Interpolator; /** all entries grouped by sensor */ std::vector> groundTruth; std::vector> wifi; std::vector> beacon; std::vector> acc; std::vector> gyro; std::vector> barometer; std::vector> lin_acc; std::vector> gravity; std::vector> gps; std::vector> compass; /** all entries in linear order as they appeared while recording */ std::vector entries; static constexpr char sep = ';'; public: /** empty ctor. call open() */ FileReader() { ; } /** ctor with filename */ FileReader(const std::string& file) { open(file); } /** open the given file */ 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& getEntries() const {return entries;} const std::vector>& getGroundTruth() const {return groundTruth;} const std::vector>& getWiFiGroupedByTime() const {return wifi;} const std::vector>& getBeacons() const {return beacon;} const std::vector>& getAccelerometer() const {return acc;} const std::vector>& getGyroscope() const {return gyro;} const std::vector>& getGPS() const {return gps;} const std::vector>& getCompass() const {return compass;} const std::vector>& getBarometer() const {return barometer;} const std::vector>& getLinearAcceleration() const {return lin_acc;} const std::vector>& 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 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 gt = FloorplanHelper::getGroundTruthPoints(map); // process each "tap smartphone when reaching ground-truth-point" for (const TS& 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) { std::ifstream inp(file); if (!inp.is_open() || inp.bad() || inp.eof()) {throw Exception("failed to open file" + file);} while(!inp.eof() && !inp.bad()) { uint64_t ts; char delim; int idx = -1; std::string data; inp >> ts; inp >> delim; inp >> idx; inp >> delim; inp >> data; if (idx == (int)Sensor::WIFI) {parseWiFi(ts, data);} else if (idx == (int)Sensor::BEACON) {parseBeacons(ts, data);} else if (idx == (int)Sensor::GROUND_TRUTH) {parseGroundTruth(ts, data);} else if (idx == (int)Sensor::ACC) {parseAccelerometer(ts, data);} else if (idx == (int)Sensor::GYRO) {parseGyroscope(ts, data);} else if (idx == (int)Sensor::BARO) {parseBarometer(ts, data);} else if (idx == (int)Sensor::LIN_ACC) {parseLinearAcceleration(ts,data);} else if (idx == (int)Sensor::GRAVITY) {parseGravity(ts,data);} else if (idx == (int)Sensor::COMPASS) {parseCompass(ts,data);} else if (idx == (int)Sensor::GPS) {parseGPS(ts,data);} // TODO: this is a hack... // the loop is called one additional time after the last entry // and keeps the entries of entry } inp.close(); } void parseLinearAcceleration(const uint64_t ts, const std::string& data){ const auto pos1 = data.find(';'); const auto pos2 = data.find(';', pos1+1); const std::string x = data.substr(0, pos1); const std::string y = data.substr(pos1+1, pos2-pos1-1); const std::string z = data.substr(pos2+1); TS elem(ts, LinearAccelerationData(std::stof(x), std::stof(y), std::stof(z))); lin_acc.push_back(elem); entries.push_back(Entry(Sensor::LIN_ACC, ts, lin_acc.size()-1)); } void parseGravity(const uint64_t ts, const std::string& data){ GravityData gravData; const auto pos1 = data.find(';'); const auto pos2 = data.find(';', pos1+1); gravData.x = std::stof(data.substr(0, pos1)); gravData.y = std::stof(data.substr(pos1+1, pos2-pos1-1)); gravData.z = std::stof(data.substr(pos2+1)); TS elem(ts, gravData); gravity.push_back(elem); entries.push_back(Entry(Sensor::GRAVITY, ts, gravity.size()-1)); // inform listener //if (listener) {listener->onGravity(Timestamp::fromMS(ts), gravData);} } void parseAccelerometer(const uint64_t ts, const std::string& data) { const auto pos1 = data.find(';'); const auto pos2 = data.find(';', pos1+1); const std::string x = data.substr(0, pos1); const std::string y = data.substr(pos1+1, pos2-pos1-1); const std::string z = data.substr(pos2+1); const AccelerometerData accData(std::stof(x), std::stof(y), std::stof(z)); TS elem(ts, accData); acc.push_back(elem); entries.push_back(Entry(Sensor::ACC, ts, acc.size()-1)); // inform listener //if (listener) {listener->onAccelerometer(Timestamp::fromMS(ts), accData);} } void parseGyroscope(const uint64_t ts, const std::string& data) { const auto pos1 = data.find(';'); const auto pos2 = data.find(';', pos1+1); const std::string x = data.substr(0, pos1); const std::string y = data.substr(pos1+1, pos2-pos1-1); const std::string z = data.substr(pos2+1); const GyroscopeData gyroData(std::stof(x), std::stof(y), std::stof(z)); TS elem(ts, gyroData); gyro.push_back(elem); entries.push_back(Entry(Sensor::GYRO, ts, gyro.size()-1)); // inform listener //if (listener) {listener->onGyroscope(Timestamp::fromMS(ts), gyroData);} } void parseWiFi(const uint64_t ts, const std::string& data) { WiFiMeasurements wifi; Splitter s(data, sep); // the -1 is due to some old files containing a trailing ";" resulting in one additional stray column for (size_t i = 0; i < s.size()-1; i += 3) { const std::string mac = s.get(i+0); const float freq = s.getFloat(i+1); const float rssi = s.getFloat(i+2); // append AP to current scan-entry WiFiMeasurement e(AccessPoint(mac), rssi, freq, Timestamp::fromMS(ts)); wifi.entries.push_back(e); } // add new wifi reading this->wifi.push_back(TS(ts, wifi)); entries.push_back(Entry(Sensor::WIFI, ts, this->wifi.size()-1)); // inform listener //if (listener) {listener->onWiFi(Timestamp::fromMS(ts), wifi);} } void parseBeacons(const uint64_t ts, const std::string& data) { const auto pos1 = data.find(';'); const auto pos2 = data.find(';', pos1+1); const auto pos3 = data.find(';', pos2+1); const std::string mac = data.substr(0, pos1); const std::string rssi = data.substr(pos1+1, pos2); const std::string txp = data.substr(pos2+1, pos3); //yes the timestamp is redundant here, but in case of multiusage... TS e(ts, BeaconMeasurement(Timestamp::fromMS(ts), Beacon(mac), std::stoi(rssi))); beacon.push_back(e); entries.push_back(Entry(Sensor::BEACON, ts, beacon.size()-1)); } void parseGroundTruth(const uint64_t ts, const std::string& data) { const auto pos1 = data.find(';'); std::string gtIndex = data.substr(0, pos1); TS elem(ts, std::stoi(gtIndex)); groundTruth.push_back(elem); } void parseBarometer(const uint64_t ts, const std::string& data) { BarometerData baro; Splitter s(data, sep); baro.hPa = s.has(0) ? (s.getFloat(0)) : (NAN); TS elem(ts, baro); barometer.push_back(elem); entries.push_back(Entry(Sensor::BARO, ts, barometer.size()-1)); // inform listener //if (listener) {listener->onBarometer(Timestamp::fromMS(ts), baro);} } void parseCompass(const uint64_t ts, const std::string& data) { CompassData compass; Splitter s(data, sep); compass.azimuth = s.has(0) ? (s.getFloat(0)) : (NAN); compass.quality01 = s.has(1) ? (s.getFloat(1)) : (NAN); TS elem(ts, compass); this->compass.push_back(elem); entries.push_back(Entry(Sensor::COMPASS, ts, this->compass.size()-1)); // inform listener //if (listener) {listener->onCompass(Timestamp::fromMS(ts), compass);} } /** parse the given GPS entry */ void parseGPS(const uint64_t ts, const std::string& data) { GPSData gps; Splitter s(data, sep); gps.lat = s.has(0) ? (s.getFloat(0)) : (NAN); gps.lon = s.has(1) ? (s.getFloat(1)) : (NAN); gps.alt = s.has(2) ? (s.getFloat(2)) : (NAN); gps.accuracy = s.has(3) ? (s.getFloat(3)) : (NAN); gps.speed = s.has(4) ? (s.getFloat(4)) : (NAN); TS elem(ts, gps); this->gps.push_back(elem); entries.push_back(Entry(Sensor::GPS, ts, this->gps.size()-1)); // inform listener //if (listener) {listener->onGPS(Timestamp::fromMS(ts), gps);} } public: const Interpolator getGroundTruthPath(Floorplan::IndoorMap* map, std::vector gtPath) const { // finde alle positionen der waypoints im gtPath aus map std::unordered_map waypointsMap; for(Floorplan::Floor* f : map->floors){ float h = f->atHeight; for (Floorplan::GroundTruthPoint* gtp : f->gtpoints){ //wenn die gleiche id 2x vergeben wurde, knallt es if(waypointsMap.find(gtp->id) == waypointsMap.end()){ waypointsMap.insert({gtp->id, Point3(gtp->pos.x,gtp->pos.y, h)}); } else{ throw std::string("the floorplan's ground truth contains two points with identical id's!"); } } } // bringe diese in richtige reihenfolge und füge timestamp hinzu Interpolator interpol; int it = 0; for(int id : gtPath){ auto itMap = waypointsMap.find(id); if(itMap == waypointsMap.end()) {throw std::string("waypoint not found in xml");} //the time, when the gt button was clicked on the app uint64_t tsGT = groundTruth[it++].ts; interpol.add(tsGT, itMap->second); } if(gtPath.empty() || waypointsMap.empty() || groundTruth.empty()){ throw std::string("No Ground Truth points found within the map.xml file"); } return interpol; } }; } #endif // FILEREADER_H