// ************************************************************************** /** @class Interpolator2D @brief Extra- and interpolate in 2D This class implements a kind of Delaunay triangulation. It calculated the Voronoi points and the corresponding Delaunay triangles. Within each triangle a bi-linear interpolation is provided. A special selection criterion is applied for points outside the grid, so that extrapolation is possible. Note that extrapolation of far away points (as in the 1D case) is not recommended. */ // ************************************************************************** #ifndef FACT_Interpolator2D #define FACT_Interpolator2D #include #include #include class Interpolator2D { public: struct vec { double x; double y; vec(double _x=0, double _y=0) : x(_x), y(_y) { } vec orto() const { return vec(-y, x); } double dist(const vec &v) const { return hypot(x-v.x, y-v.y); } double operator^(const vec &v) const { return x*v.y - y*v.x; } vec operator-(const vec &v) const { return vec(x-v.x, y-v.y); } vec operator+(const vec &v) const { return vec(x+v.x, y+v.y); } vec operator/(double b) const { return vec(x/b, y/b); } }; struct point : vec { unsigned int i; point(unsigned int _i=0, double _x=0, double _y=0) : vec(_x, _y), i(_i) { } }; struct circle : point { point p[3]; double r; static bool sameSide(const vec &p1, const vec &p2, const vec &a, const vec &b) { return ((b-a)^(p1-a))*((b-a)^(p2-a)) > 0; } bool isInsideTriangle(const vec &v) const { return sameSide(v, p[0], p[1], p[2]) && sameSide(v, p[1], p[0], p[2]) && sameSide(v, p[2], p[0], p[1]); } bool isInsideCircle(const vec &v) const { return dist(v) < r; } }; struct weight : point { circle c; double w[3]; }; private: std::vector inputGrid; /// positions of the data points (e.g. sensors) std::vector outputGrid; /// positions at which inter-/extrapolated values should be provided std::vector circles; /// the calculated circles/triangles std::vector weights; /// the weights used for the interpolation // -------------------------------------------------------------------------- // //! Calculate the collection of circles/triangles which describe the //! input grid. This is the collection of circles which are calculated //! from any three points and do not contain any other point of the grid. // void CalculateGrid() { circles.reserve(2*inputGrid.size()); // Loop over all triplets of points for (auto it0=inputGrid.cbegin(); it0isInsideTriangle(*ip)) { if (mint==circles.cend() || ic->rr) mint = ic; } // If we have found such a triangle, no need to check for more if (mint!=circles.cend()) continue; // maybe at least inside the circle const double dd = ic->dist(*ip); if (ddr) { if (minc==circles.cend() || ic->rr) minc = ic; } // If we found such a circle, no need to check for more if (minc!=circles.cend()) continue; // then look for the closest circle center if (ddp[0]; const vec &p2 = it->p[1]; const vec &p3 = it->p[2]; const double dy23 = p2.y - p3.y; const double dy31 = p3.y - p1.y; const double dy12 = p1.y - p2.y; const double dx32 = p3.x - p2.x; const double dx13 = p1.x - p3.x; const double dx21 = p2.x - p1.x; const double dxy23 = p2^p3; const double dxy31 = p3^p1; const double dxy12 = p1^p2; const double det = dxy12 + dxy23 + dxy31; const double w1 = (dy23*ip->x + dx32*ip->y + dxy23)/det; const double w2 = (dy31*ip->x + dx13*ip->y + dxy31)/det; const double w3 = (dy12*ip->x + dx21*ip->y + dxy12)/det; // Store the original grid-point, the circle's parameters // and the calculate weights weight w; w.x = ip->x; w.y = ip->y; w.c = *it; w.w[0] = w1; w.w[1] = w2; w.w[2] = w3; weights.push_back(w); } return true; } public: // -------------------------------------------------------------------------- // //! Default constructor. Does nothing. // Interpolator2D() { } // -------------------------------------------------------------------------- // //! Initialize the input grid (the points at which values are known). //! //! @param n //! number of data points //! //! @param x //! x coordinates of data points //! //! @param n //! y coordinates of data points // Interpolator2D(int n, double *x, double *y) { SetInputGrid(n, x, y); } Interpolator2D(const std::vector &v) { SetInputGrid(v); } const std::vector getWeights() const { return weights; } const std::vector getInputGrid() const { return inputGrid; } const std::vector getOutputGrid() const { return outputGrid; } // -------------------------------------------------------------------------- // //! Set a new input grid (the points at which values are known). //! Invalidates the output grid and the calculated weights. //! Calculates the triangles corresponding to the new grid. //! //! @param n //! number of data points //! //! @param x //! x coordinates of data points //! //! @param n //! y coordinates of data points // void SetInputGrid(int n, double *x, double *y) { circles.clear(); weights.clear(); outputGrid.clear(); inputGrid.clear(); inputGrid.reserve(n); for (int i=0; i &v) { circles.clear(); weights.clear(); outputGrid.clear(); inputGrid.clear(); inputGrid.reserve(v.size()); for (std::size_t i=0; i> &v) { if (inputGrid.empty()) return false; weights.clear(); outputGrid.clear(); outputGrid.reserve(v.size()); for (std::size_t i=0; i is returned with the interpolated values in the //! same order than the putput grid. If the provided vector does //! not match the size of the inputGrid, an empty vector is returned. // std::vector Interpolate(const std::vector &z) const { if (z.size()!=inputGrid.size()) return std::vector(); std::vector rc; rc.reserve(z.size()); for (auto it=weights.cbegin(); itc.p[0].i] * it->w[0] + z[it->c.p[1].i] * it->w[1] + z[it->c.p[2].i] * it->w[2]); return rc; } }; #endif