/** * @file SmartRangeFactor.h * * @brief A smart factor for range-only SLAM that does initialization and marginalization * * @date Sep 10, 2012 * @author Alex Cunningham */ #pragma once #include #include #include #include #include #include namespace gtsam { /** * Smart factor for range SLAM * @addtogroup SLAM */ class GTSAM_UNSTABLE_EXPORT SmartRangeFactor: public NoiseModelFactor { protected: struct Circle2 { Circle2(const Point2& p, double r) : center(p), radius(r) { } Point2 center; double radius; }; typedef SmartRangeFactor This; std::vector measurements_; ///< Range measurements double variance_; ///< variance on noise public: /** Default constructor: don't use directly */ SmartRangeFactor() { } /** * Constructor * @param s standard deviation of range measurement noise */ SmartRangeFactor(double s) : NoiseModelFactor(noiseModel::Isotropic::Sigma(1, s)), variance_(s * s) { } virtual ~SmartRangeFactor() { } /// Add a range measurement to a pose with given key. void addRange(Key key, double measuredRange) { keys_.push_back(key); measurements_.push_back(measuredRange); size_t n = keys_.size(); // Since we add the errors, the noise variance adds noiseModel_ = noiseModel::Isotropic::Variance(1, n * variance_); } // Testable /** print */ virtual void print(const std::string& s = "", const KeyFormatter& keyFormatter = DefaultKeyFormatter) const { } /** Check if two factors are equal */ virtual bool equals(const NonlinearFactor& f, double tol = 1e-9) const { return false; } // factor interface /** * Triangulate a point from at least three pose-range pairs * Checks for best pair that includes first point */ static Point2 triangulate(const std::list& circles) { Circle2 circle1 = circles.front(); boost::optional best_fh; boost::optional best_circle; BOOST_FOREACH(const Circle2& it, circles) { // distance between circle centers. double d = circle1.center.dist(it.center); if (d < 1e-9) continue; boost::optional fh = Point2::CircleCircleIntersection( circle1.radius / d, it.radius / d); if (fh && (!best_fh || fh->y() > best_fh->y())) { best_fh = fh; best_circle = it; } } std::list solutions = Point2::CircleCircleIntersection( circle1.center, best_circle->center, best_fh); // TODO, pick winner based on other measurement return solutions.front(); } /** * Error function *without* the NoiseModel, \f$ z-h(x) \f$. */ virtual Vector unwhitenedError(const Values& x, boost::optional&> H = boost::none) const { size_t n = size(); if (H) assert(H->size()==n); Vector errors = zero(1); if (n >= 3) { // create n circles corresponding to measured range around each pose std::list circles; for (size_t j = 0; j < n; j++) { const Pose2& pose = x.at(keys_[j]); circles.push_back(Circle2(pose.translation(), measurements_[j])); } // triangulate to get the optimized point // TODO: Should we have a (better?) variant that does this in relative coordinates ? Point2 optimizedPoint = triangulate(circles); // now evaluate the errors between predicted and measured range for (size_t j = 0; j < n; j++) { const Pose2& pose = x.at(keys_[j]); if (H) // also calculate 1*3 derivative for each of the n poses errors[0] += pose.range(optimizedPoint, (*H)[j]) - measurements_[j]; else errors[0] += pose.range(optimizedPoint) - measurements_[j]; } } return errors; } /// @return a deep copy of this factor virtual gtsam::NonlinearFactor::shared_ptr clone() const { return boost::static_pointer_cast( gtsam::NonlinearFactor::shared_ptr(new This(*this))); } }; } // \namespace gtsam