#ifndef FLOORPLANLINT_H #define FLOORPLANLINT_H #include "Floorplan.h" #include "../../geo/BBox2.h" #include #include namespace Floorplan { class LINT { public: /** possible issue types */ enum class Type { WARN, ERR, }; /** type -> string */ static std::string getTypeStr(const Type t) { switch(t) { case Type::WARN: return "WARNING"; case Type::ERR: return "ERROR"; default: throw Exception("code error. invalid type. todo!"); } } /** a detected issue */ struct Issue { Type type; const Floor* floor; std::string desc; Issue(const Type type, const Floor* floor, const std::string& desc) : type(type), floor(floor), desc(desc) {;} /** issue to string */ std::string asString() const { return getTypeStr(type) + ": " + "floor '" + floor->name + "': " + desc; } }; using Issues = std::vector; public: /** throw in case of errors within the map */ static void assertOK(IndoorMap* map) { const Issues issues = check(map); int err = 0; for (const Issue& i : issues) { std::cout << i.asString() << std::endl; if (i.type == Type::ERR) {++err;} } if (err > 0) { // throw Exception("detected floorplan errors"); } } /** get all errors within the map */ static Issues check(IndoorMap* map) { Issues res; for (const Floor* floor : map->floors) { // outline present? if (floor->outline.empty()) { res.push_back(Issue(Type::ERR, floor, "has no outline")); } // check outline for (const FloorOutlinePolygon* poly : floor->outline) { checkOutline(res, floor, poly); } // check obstacles for (const FloorObstacle* obs : floor->obstacles) { checkObstacle(res, map, floor, obs); } // check fingerprints for (const FingerprintLocation* fpl : floor->fpLocations) { checkFingerprintLoc(res, floor, fpl); } // check stairs for (const Stair* s : floor->stairs) { checkStair(res, map, floor, s); } // check elevators for (const Elevator* e : floor->elevators) { checkElevator(res, map, floor, e); } } // done return res; } private: /** check a floor's outline */ static void checkOutline(Issues& res, const Floor* floor, const FloorOutlinePolygon* poly) { // number of points valid? if (poly->poly.points.size() < 3) {res.push_back(Issue(Type::ERR, floor, "' outline '" + poly->name + "' needs at least 3 edges"));} if (poly->poly.points.size() > 1024) {res.push_back(Issue(Type::ERR, floor, "' outline '" + poly->name + "' has too many edges"));} // outline size [bbox] valid? BBox2 outline; for (const Point2 pt : poly->poly.points) { outline.add(pt); } const Point2 size = outline.getSize(); if (size.x < 1.0 || size.y < 1.0) {res.push_back(Issue(Type::ERR, floor, "' outline '" + poly->name + "' seems too small"));} } /** check walls, doors, ... */ static void checkObstacle(Issues& res, const IndoorMap* map, const Floor* floor, const FloorObstacle* fo) { // line? -> check const FloorObstacleLine* line = dynamic_cast(fo); if (line) { const float len_m = line->from.getDistance(line->to); if (len_m < 0.15) { res.push_back(Issue(Type::WARN, floor, "' line-obstacle is too short: " + std::to_string(len_m) + " meter from " + line->from.asString() + " to " + line->to.asString())); } } // door? -> check const FloorObstacleDoor* door = dynamic_cast(fo); if (door) { const float len_m = door->from.getDistance(door->to); if (len_m < 0.40) { res.push_back(Issue(Type::ERR, floor, "' door is too narrow: " + std::to_string(len_m) + " meter from " + door->from.asString() + " to " + door->to.asString())); } #pragma message ("TODO!") // try { // Ray3D::ModelFactory fac(map); // fac.getDoorAbove(floor, door); // } catch (Exception e) { // res.push_back(Issue(Type::ERR, floor, std::string(e.what()) + "[from" + door->from.asString() + " to " + door->to.asString() + "]")); // } } // pillar? -> check const FloorObstacleCircle* circle = dynamic_cast(fo); if (circle) { const float len_m = circle->radius; if (len_m < 0.20) { res.push_back(Issue(Type::ERR, floor, "' pillar is too narrow: " + std::to_string(len_m) + " meter at " + circle->center.asString())); } } } static void checkFingerprintLoc(Issues& res, const Floor* floor, const FingerprintLocation* fpl) { const std::string note = "does it belong to a stair? if so: fine! Note: fingerprints are currently measured using smartphones and smartphone are held by the pedestian. thus: fingerprints are ~1.3 meter above ground"; if (fpl->heightAboveFloor < 0.8) { res.push_back(Issue(Type::ERR, floor, std::string() + "fingerprint " + fpl->name + " @ " + fpl->getPosition(*floor).asString() + " is too near to the floor. " + note)); } else if (fpl->heightAboveFloor > 2.0) { res.push_back(Issue(Type::WARN, floor, std::string() + "fingerprint " + fpl->name + " @ " + fpl->getPosition(*floor).asString() + " is too high above the floor. " + note)); } } static void checkStair(Issues& res, const IndoorMap* map, const Floor* floor, const Stair* stair) { // list of all heights where there is a floor; std::vector floorAtHeight_cm; for (const Floor* f : map->floors) { const int floorZ_cm = std::round(f->atHeight * 100); floorAtHeight_cm.push_back(floorZ_cm); // integer height in cm } if (stair->getParts().empty()) { res.push_back(Issue(Type::ERR, floor, "stair does not contain any parts! [empty stair]")); return; } const std::vector parts = stair->getParts(); const std::vector quads = Floorplan::getQuads(parts, floor); const Floorplan::Quad3& quadS = quads.front(); const Floorplan::Quad3& quadE = quads.back(); // start == end? if (quadS.p1.z == quadE.p3.z) { res.push_back(Issue(Type::ERR, floor, "stair start and end must not belong to the same floor!")); } // disconnected start? (MUST belong to the floor the stair is attached to) if (quadS.p1.z != floor->getStartingZ()) { res.push_back(Issue(Type::ERR, floor, "stair is not connected to the starting floor's ground! [open stair start]")); } // disconnected end? (must be long to ANY other floor within the map) //if (quadE.p3.z != floor->getEndingZ()) { const int stairEndingZ_cm = std::round( quadE.p3.z * 100 ); if(std::find(floorAtHeight_cm.begin(), floorAtHeight_cm.end(), stairEndingZ_cm) == floorAtHeight_cm.end()) { res.push_back(Issue(Type::ERR, floor, "stair is not connected to the ending floor's ground! [open stair end]")); } for (int i = 0; i < (int) parts.size(); ++i) { //const Floorplan::Quad3& quad = quads[i]; // disconnected within? if (i > 0) { if (quads[i-1].p4.z != quads[i-0].p1.z) { res.push_back(Issue(Type::ERR, floor, "stair is disconnected within!")); } } } } static void checkElevator(Issues& res, const IndoorMap* map, const Floor* floor, const Elevator* e) { if (e->depth < 0.5) { res.push_back(Issue(Type::ERR, floor, "elevator's @" + e->center.asString() + ": depth is too small: " + std::to_string(e->depth) + "m")); } if (e->width < 0.5) { res.push_back(Issue(Type::ERR, floor, "elevator's @" + e->center.asString() + ": width is too small: " + std::to_string(e->width) + "m")); } if (e->height_m < 0.1) { res.push_back(Issue(Type::ERR, floor, "elevator's @" + e->center.asString() + ": height is too small: " + std::to_string(e->height_m) + "m")); } // list of all heights where there is a floor; std::vector floorAtHeight_cm; for (const Floor* f : map->floors) { const int floorZ_cm = std::round(f->atHeight * 100); floorAtHeight_cm.push_back(floorZ_cm); // integer height in cm } // disconnected end? (must be long to ANY other floor within the map) const int elevEndZ_cm = std::round( (floor->getStartingZ() + e->height_m) * 100 ); if(std::find(floorAtHeight_cm.begin(), floorAtHeight_cm.end(), elevEndZ_cm) == floorAtHeight_cm.end()) { res.push_back(Issue(Type::ERR, floor, "elevator @" + e->center.asString() + " is not connected to the ending floor's ground! [open elevator end]")); } } }; } #endif // FLOORPLANLINT_H