/* * This file is part of the Owl Positioning System (OwlPS) project. * It is subject to the copyright notice and license terms in the * COPYRIGHT.t2t file found in the top-level directory of this * distribution and at * https://code.lm7.fr/mcy/owlps/src/master/COPYRIGHT.t2t * No part of the OwlPS Project, including this file, may be copied, * modified, propagated, or distributed except according to the terms * contained in the COPYRIGHT.t2t file; the COPYRIGHT.t2t file must be * distributed along with this file, either separately or by replacing * this notice by the COPYRIGHT.t2t file's contents. */ #include "stock.hh" #include "configuration.hh" #include "posexcept.hh" #include "area.hh" #include "csvstringreader.hh" #include "autocalibrationmesh.hh" #include "autocalibrationline.hh" #include #include using namespace std ; /* *** Attribute definitions *** */ unordered_map Stock::buildings ; unordered_map Stock::waypoints ; unordered_map Stock::mobiles ; unordered_map Stock::cps ; unordered_set Stock::reference_points ; unordered_set Stock::calibration_requests ; void Stock::clear() { buildings.clear() ; // Waypoints are normally deleted in Building::~Building(), so we must // not clear waypoints before buildings, but we clear it *after*, just // in case. waypoints.clear() ; mobiles.clear() ; cps.clear() ; reference_points.clear() ; calibration_requests.clear() ; } /* *** Building operations *** */ /** * The name of the Building is initialised. */ const Building& Stock::find_create_building(const string &name) { auto i = buildings.find(name) ; if (i != buildings.end()) return i->second ; Building &building = buildings[name] ; building.set_name(name) ; return building ; } /** * @param name The name of the Building to search for. * It must be a valid name, as no check is performed. * @returns A const reference to the Building. * @throw element_not_found is thrown if the Building corresponding * to `name` does not exist. */ const Building& Stock::get_building(const string &name) { auto i = buildings.find(name) ; if (i != buildings.end()) return i->second ; throw element_not_found("No Building with name \"" + name + "\"!") ; } /** * @returns A pointer to the first Area in which `point` was found. * @returns nullptr if `point` does not belong to any Area. */ const Area* Stock::in_which_area_is(const Point3D &point) { for (auto b = buildings.begin() ; b != buildings.end() ; ++b) { const unordered_map &areas = b->second.get_areas() ; for (auto a = areas.begin() ; a != areas.end() ; ++a) if (a->second->contains_point(point)) return a->second ; } return nullptr ; } /* *** Waypoint operations *** */ const Waypoint& Stock:: find_create_waypoint(const Point3D &point) { auto i = waypoints.find(point) ; if (i != waypoints.end()) return i->second ; Waypoint &waypoint = waypoints[point] ; waypoint.set_coordinates(point) ; return waypoint ; } void Stock::waypoint_remove_building(const Waypoint &point, Building *const building) { auto i = waypoints.find(point) ; Waypoint *waypoint = const_cast(&i->second) ; waypoint->remove_building(building) ; // If the Waypoint is not linked to any Building any more, we // delete it if (! waypoint->get_1st_building()) waypoints.erase(i) ; } /* *** Mobile operations *** */ /** * @param mac The MAC address of the Mobile to search for. * It must be a valid MAC address, as no check is performed. * @returns `true` if a mobile with this MAC address exists. * @returns `false` if this MAC address was not found. */ bool Stock::mobile_exists(const std::string &mac) { auto i = mobiles.find(mac) ; return i != mobiles.end() ; } /** * @param mac The MAC address of the Mobile to search for. * It must be a valid MAC address, as no check is performed. * @returns A const reference to the Mobile. * @throw element_not_found is thrown if the Mobile corresponding * to `mac` does not exist. */ const Mobile& Stock::get_mobile(const string &mac) { auto i = mobiles.find(mac) ; if (i != mobiles.end()) return i->second ; throw element_not_found("No Mobile with MAC address \"" + mac + "\"!") ; } /** * If created, the MAC address of the Mobile is initialised. */ const Mobile& Stock::find_create_mobile(const string &mac) { auto i = mobiles.find(mac) ; if (i != mobiles.end()) return i->second ; Mobile &mobile = mobiles[mac] ; mobile.set_mac_addr(mac) ; return mobile ; } /** * If the Mobile already exists, it is replaced by `source.` */ const Mobile& Stock::find_create_mobile(const Mobile &source) { const string &mac = source.get_mac_addr() ; auto i = mobiles.find(mac) ; if (i != mobiles.end()) return i->second ; mobiles[mac] = source ; return mobiles[mac] ; } /* *** CapturePoint operations *** */ /** * @param mac The MAC address of the CapturePoint to search for. * It must be a valid MAC address, as no check is performed. * @returns `true` if a CP with this MAC address exists. * @returns `false` if this MAC address was not found. */ bool Stock::cp_exists(const std::string &mac) { auto i = cps.find(mac) ; return i != cps.end() ; } /** * @param mac The MAC address of the CapturePoint to search for. * It must be a valid MAC address, as no check is performed. * @returns A const reference to the CapturePoint. * @throw element_not_found is thrown if the CapturePoint corresponding * to `mac` does not exist. */ const CapturePoint& Stock::get_cp(const string &mac) { auto i = cps.find(mac) ; if (i != cps.end()) return i->second ; throw element_not_found("No CapturePoint with MAC address \"" + mac + "\"!") ; } /** * The MAC address of the CapturePoint is initialised. */ const CapturePoint& Stock::find_create_cp(const string &mac) { auto i = cps.find(mac) ; if (i != cps.end()) return i->second ; CapturePoint &cp = cps[mac] ; cp.set_mac_addr(mac) ; if (Configuration::is_configured("verbose")) cerr << "New CP \"" << mac << "\" (total: " << nb_cps() << " CPs).\n" ; return cp ; } /** * If the CapturePoint already exists, it is replaced by `source.` */ const CapturePoint& Stock::find_create_cp(const CapturePoint &source) { const string &mac = source.get_mac_addr() ; auto i = cps.find(mac) ; if (i != cps.end()) return i->second ; cps[mac] = source ; return cps[mac] ; } void Stock::update_all_friis_indexes() { for (auto cp = cps.begin() ; cp != cps.end() ; ++cp) { double friis_idx_sum = 0 ; int nb_friis_idx = 0 ; // Compute main general term, independant from scans double const_term = cp->second.friis_constant_term() ; /* * Compute indexes for each ReferencePoint. The Friis index for an * CP is the average of all indexes in all ReferencePoint. */ for (auto rp = reference_points.begin() ; rp != reference_points.end() ; ++rp) { int nb_idx = 0 ; float cp_friis_idx_sum = rp->friis_indexes_for_cp(cp->second, const_term, nb_idx) ; if (nb_idx != 0) { friis_idx_sum += cp_friis_idx_sum ; nb_friis_idx += nb_idx ; } } if (nb_friis_idx > 0) cp->second.set_friis_index(friis_idx_sum / nb_friis_idx) ; } } double Stock::cp_matrix_get_ss(const std::string &mac_transmitter, const std::string &mac_receiver) { const CapturePoint &receiver = get_cp(mac_receiver) ; const ReferencePoint &rp = get_reference_point(receiver.get_coordinates()) ; return rp.average_measurements(mac_transmitter) ; } /** * @returns A const pointer to the CP `coord` is the coordinates of. * @returns nullptr if `coord` is not the coordinates of any CP. */ const CapturePoint* Stock::is_cp_coordinate(const Point3D &coord) { for (auto cp = cps.begin() ; cp != cps.end() ; ++cp) if (cp->second.get_coordinates() == coord) return &cp->second ; return nullptr ; } /** * @returns The distance from `location` to the closest CP. * @returns -1 if there are no CPs in stock. */ float Stock::distance_from_closest_cp(const Point3D &location) { auto cp = cps.begin() ; if (cp == cps.end()) return -1 ; float min_dist = location.square_distance(cp->second.get_coordinates()) ; ++cp ; while (cp != cps.end()) { float tmp_dist = location.square_distance(cp->second.get_coordinates()) ; if (tmp_dist < min_dist) min_dist = tmp_dist ; ++cp ; } return sqrtf(min_dist) ; } /* *** ReferencePoint operations *** */ bool Stock::reference_point_exists(const ReferencePoint &point) { auto i = reference_points.find(point) ; return i != reference_points.end() ; } /** * @param point Coordinates of the wanted ReferencePoint. * @returns The ReferencePoint at the given coordinates, if found. * @throw element_not_found is thrown if no ReferencePoint exists at the * given coordinates. */ const ReferencePoint& Stock:: get_reference_point(const Point3D &point) { /* Note that we can look up a ReferencePoint in the unordered_set * #reference_points using only its coordinates (Point3D) only because * we defined `hash` and `equal_to` to take into consideration only * the coordinates of the ReferencePoint (see referencepoint.hh). */ auto i = reference_points.find(ReferencePoint(point)); if (i != reference_points.end()) return *i ; throw element_not_found("No ReferencePoint with coordinates " + static_cast(point) + "!") ; } const ReferencePoint& Stock:: find_create_reference_point(const ReferencePoint &point) { // unordered_set::insert() do all the job: see the documentation at // http://en.cppreference.com/w/cpp/container/unordered_set/insert auto ret = reference_points.insert(point) ; return *ret.first ; } const ReferencePoint& Stock:: closest_reference_point(const Request &request) { if (reference_points.empty()) throw element_not_found( "Cannot search for the closest reference point: reference points'" " list is empty!") ; bool ignore_cps = Configuration::bool_value( "positioning.nss.ignore-cp-reference-points") ; auto i = reference_points.begin() ; if (ignore_cps) { // Fast-forward to the next non-CP reference point while (i != reference_points.end() && is_cp_coordinate(*i)) ++i ; // No non-CP reference point was found, we are forced to consider // the CP reference points if (i == reference_points.end()) ignore_cps = false ; } float similarity = i->similarity(request) ; auto closest = i ; for (++i ; i != reference_points.end() ; ++i) { if (ignore_cps && is_cp_coordinate(*i)) continue ; float tmp_similarity = i->similarity(request) ; if (tmp_similarity < similarity) { similarity = tmp_similarity ; closest = i ; } } return *closest ; } void Stock::regenerate_reference_points() { assert(Configuration::autocalibration_enabled()) ; assert(! cps.empty()) ; assert(! reference_points.empty()) ; delete_non_cp_calibration_requests() ; const string &ac_mode = Configuration::string_value( "positioning.generate-reference-points") ; assert(! ac_mode.empty()) ; if (ac_mode.find("mesh") != string::npos) generate_reference_points_mesh() ; if (ac_mode.find("line") != string::npos) generate_reference_points_line() ; if (ac_mode.find("list") != string::npos) generate_reference_points_list() ; } void Stock::generate_reference_points_mesh() { if (! (Configuration::is_configured("positioning.area-start") && Configuration::is_configured("positioning.area-stop"))) throw missing_configuration( "In order to generate a meshing of reference points, you must" " define the deployment area.") ; if (reference_points.size() < 3) { if (Configuration::is_configured("verbose")) cerr << reference_points.size() << " true reference points is" << " not enough to generate a meshing of reference" << " points.\n" ; return ; } Point3D start(Configuration::string_value("positioning.area-start")) ; Point3D stop(Configuration::string_value("positioning.area-stop")) ; float step_x = Configuration::float_value("positioning.generated-meshing-grain-x") ; float step_y = Configuration::float_value("positioning.generated-meshing-grain-y") ; float step_z = Configuration::float_value("positioning.generated-meshing-grain-z") ; /* Generate RPs */ for (float x = start.get_x() ; x <= stop.get_x() ; x += step_x) for (float y = start.get_y() ; y <= stop.get_y() ; y += step_y) for (float z = start.get_z() ; z <= stop.get_z() ; z += step_z) { AutocalibrationMesh ac(Point3D(x,y,z)) ; generate_reference_point(ac) ; } } void Stock::generate_reference_points_line() { if (! Configuration::is_configured("positioning.generated-line-path")) throw missing_configuration( "In order to generate a line of reference points, you must" " define a path.") ; if (reference_points.empty()) { if (Configuration::is_configured("verbose")) cerr << "At least one true reference points is necessary to" << " generate a path of reference points.\n" ; return ; } const string &path_csv = Configuration::string_value("positioning.generated-line-path") ; CSVStringReader path_parser(path_csv) ; Point3D p ; vector read_points ; const CapturePoint *cp1 = nullptr ; // Read points until the first CP coordinate while (path_parser.read_point3d(p)) { cp1 = Stock::is_cp_coordinate(p) ; if (cp1) break ; read_points.push_back(p) ; } // We reached the end of the points' list without finding the // coordinates of a CP if (! cp1) { cerr << "Warning! positioning.generated-line-path doesn't contain" << "any point that is the coordinates of capture point!" << endl ; return ; } // Generate the first RPs (between the begining and cp1) if (! read_points.empty()) { auto first = read_points.begin() ; for (auto end = first + 1 ; end != read_points.end() ; ++end) { generate_interpolated_reference_points(*first, *end, cp1) ; first = end ; } generate_interpolated_reference_points( *first, cp1->get_coordinates(), cp1) ; read_points.clear() ; } // Read the next points and generate the RPs const CapturePoint *cp2 = nullptr ; while (path_parser.read_point3d(p)) { cp2 = Stock::is_cp_coordinate(p) ; if (! cp2) { read_points.push_back(p) ; continue ; } // We don't have any intermediate points between the two CPs if (read_points.empty()) generate_interpolated_reference_points( cp1->get_coordinates(), cp2->get_coordinates(), cp1, cp2) ; // Generate the RPs between cp1, the intermediate points, and cp2 else { auto first = read_points.begin() ; generate_interpolated_reference_points( cp1->get_coordinates(), *first, cp1, cp2) ; for (auto end = first + 1 ; end != read_points.end() ; ++end) { generate_interpolated_reference_points(*first, *end, cp1, cp2) ; first = end ; } generate_interpolated_reference_points( *first, cp2->get_coordinates(), cp1, cp2) ; read_points.clear() ; } cp1 = cp2 ; cp2 = nullptr ; } // Generate the last RPs (after the last RP) if (read_points.empty()) return ; auto first = read_points.begin() ; generate_interpolated_reference_points( cp1->get_coordinates(), *first, cp1) ; for (auto end = first + 1 ; end != read_points.end() ; ++end) { generate_interpolated_reference_points(*first, *end, cp1) ; first = end ; } } /** * This function interpolates the coordinates between `start` and `end` * and generates all the corresponding reference points (the two * extremities are generated as well). * * @param start The first point of the line. * @param end The last point of the line. * @param cp1 The first capture point to use to generate the reference * points. * @param cp2 The second capture point to use (optional). */ void Stock::generate_interpolated_reference_points( const Point3D &start, const Point3D &end, const CapturePoint *const cp1, const CapturePoint *const cp2) { assert(cp1) ; // Generate the first RP AutocalibrationLine ac_start(start, cp1, cp2) ; generate_reference_point(ac_start) ; /* Compute the intermediate coordinates */ float step_hint = Configuration::float_value("positioning.generated-line-step") ; vector interpolated ; // List of intermediate points // Generate the intermediate coordinates start.interpolate(end, step_hint, interpolated) ; // Generate the corresponding reference points for (auto i = interpolated.begin() ; i != interpolated.end() ; ++i) { AutocalibrationLine ac(*i, cp1, cp2) ; generate_reference_point(ac) ; } // Generate the last RP AutocalibrationLine ac_end(end, cp1, cp2) ; generate_reference_point(ac_end) ; } void Stock::generate_reference_points_list() { if (! Configuration::is_configured("positioning.generated-points-list")) throw missing_configuration( "In order to generate a list of reference points, you must" " define the list.") ; if (reference_points.size() < 3) { if (Configuration::is_configured("verbose")) cerr << reference_points.size() << " true reference points is" << " not enough to generate a meshing of reference" << " points.\n" ; return ; } const string &rp_list_csv = Configuration::string_value("positioning.generated-points-list") ; CSVStringReader rp_list_parser(rp_list_csv) ; Point3D rp ; while (rp_list_parser.read_point3d(rp)) { AutocalibrationMesh ac(rp) ; generate_reference_point(ac) ; } } /** * @param ac The pre-initialised Autocalibration to use. */ void Stock::generate_reference_point(Autocalibration &ac) { try { ac.generate_reference_point() ; } catch (autocalibration_error &e) { if (Configuration::is_configured("verbose")) cerr << e.what() << '\n' ; } } /* *** CalibrationRequest operations *** */ /** * @returns The CalibrationRequest created or found. */ const CalibrationRequest& Stock:: store_calibration_request(const CalibrationRequest &request) { const CalibrationRequest &calibration_request = find_create_calibration_request(request) ; calibration_request.reference_point_backward_link() ; return calibration_request ; } void Stock:: delete_calibration_request(const CalibrationRequest &request) { #ifndef NDEBUG auto found = calibration_requests.find(request) ; if (found == calibration_requests.end()) { cerr << "Error! Cannot delete an unstored CalibrationRequest: " << request << endl ; return ; } #endif // NDEBUG calibration_requests.erase(request) ; } /** * @param timeout The maximum age of the calibration requests (seconds). */ void Stock::delete_calibration_requests_older_than(const int timeout) { assert(timeout > 0) ; auto cr = calibration_requests.begin() ; while (cr != calibration_requests.end()) { Timestamp request_time ; if (Configuration::bool_value("replay")) request_time = cr->get_time_sent() ; else request_time = cr->get_time_received() ; Timestamp elapsed(request_time.elapsed()) ; uint64_t elapsed_sec = static_cast(elapsed) / 1000 ; if (elapsed_sec >= static_cast(timeout)) { ReferencePoint *rp = cr->get_reference_point() ; if (rp) { rp->delete_request(&*cr) ; if (rp->get_requests().empty()) reference_points.erase(*rp) ; } cr = calibration_requests.erase(cr) ; } else ++cr ; } } const CalibrationRequest& Stock:: find_create_calibration_request(const CalibrationRequest &request) { auto ret = calibration_requests.insert(request) ; return *ret.first ; } const CalibrationRequest& Stock:: closest_calibration_request(const Request &request) { if (calibration_requests.empty()) throw element_not_found( "Cannot search for the closest calibration request: calibration" " requests' list is empty!") ; bool ignore_cps = Configuration::bool_value( "positioning.nss.ignore-cp-reference-points") ; auto i = calibration_requests.begin() ; if (ignore_cps) { // Fast-forward to the next non-CP reference point while (i != calibration_requests.end() && is_cp_coordinate(*i->get_reference_point())) ++i ; // No non-CP reference point was found, we are forced to consider // the CP reference points if (i == calibration_requests.end()) { i = calibration_requests.begin() ; ignore_cps = false ; } } float similarity = i->similarity(request) ; auto closest = i ; for (++i ; i != calibration_requests.end() ; ++i) { if (ignore_cps && is_cp_coordinate(*i->get_reference_point())) continue ; assert(! i->get_measurements().empty()) ; float tmp_similarity = i->similarity(request) ; if (tmp_similarity < similarity) { similarity = tmp_similarity ; closest = i ; } } return *closest ; } void Stock::delete_non_cp_calibration_requests() { auto rp = reference_points.begin() ; while (rp != reference_points.end()) { ReferencePoint rp_copy(*rp) ; if (rp_copy.delete_generated_requests()) // rp_copy was modified { rp = reference_points.erase(rp) ; // Reinsert the modified copy if it still contains at least // one CalibrationRequest: if (! rp_copy.get_requests().empty()) reference_points.insert(rp_copy) ; /* __Note on the above insert__ * From the boost::unordered documentation: * "insert is only allowed to invalidate iterators when the * insertion causes the load factor to be greater than or * equal to the maximum load factor." * In our case, we always remove an element prior to insert, * so the load factor will never change; therefore insert() * should be safe here. */ } else // rp_copy was not modified ++rp ; } }