source: trunk/Mars/mcore/Interpolator2D.h@ 17265

Last change on this file since 17265 was 17143, checked in by tbretz, 11 years ago
Added some includes to allow include in any order; this also makes the namesapce for size_t necessary
File size: 12.6 KB
Line 
1// **************************************************************************
2/** @class Interpolator2D
3
4@brief Extra- and interpolate in 2D
5
6This class implements a kind of Delaunay triangulation. It calculated the
7Voronoi points and the corresponding Delaunay triangles. Within each
8triangle a bi-linear interpolation is provided.
9
10A special selection criterion is applied for points outside the grid,
11so that extrapolation is possible. Note that extrapolation of far away
12points (as in the 1D case) is not recommended.
13
14*/
15// **************************************************************************
16#ifndef FACT_Interpolator2D
17#define FACT_Interpolator2D
18
19#include <float.h>
20#include <math.h>
21#include <vector>
22
23class Interpolator2D
24{
25public:
26 struct vec
27 {
28 double x;
29 double y;
30
31 vec(double _x=0, double _y=0) : x(_x), y(_y) { }
32
33 vec orto() const { return vec(-y, x); }
34
35 double dist(const vec &v) const { return hypot(x-v.x, y-v.y); }
36 double operator^(const vec &v) const { return x*v.y - y*v.x; }
37 vec operator-(const vec &v) const { return vec(x-v.x, y-v.y); }
38 vec operator+(const vec &v) const { return vec(x+v.x, y+v.y); }
39 vec operator/(double b) const { return vec(x/b, y/b); }
40 };
41
42
43 struct point : vec
44 {
45 unsigned int i;
46 point(unsigned int _i=0, double _x=0, double _y=0) : vec(_x, _y), i(_i) { }
47 };
48
49 struct circle : point
50 {
51 point p[3];
52 double r;
53
54 static bool sameSide(const vec &p1, const vec &p2, const vec &a, const vec &b)
55 {
56 return ((b-a)^(p1-a))*((b-a)^(p2-a)) > 0;
57 }
58
59 bool isInsideTriangle(const vec &v) const
60 {
61 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]);
62 }
63
64 bool isInsideCircle(const vec &v) const
65 {
66 return dist(v) < r;
67 }
68 };
69
70 struct weight : point
71 {
72 circle c;
73 double w[3];
74 };
75
76private:
77 std::vector<point> inputGrid; /// positions of the data points (e.g. sensors)
78 std::vector<point> outputGrid; /// positions at which inter-/extrapolated values should be provided
79 std::vector<circle> circles; /// the calculated circles/triangles
80 std::vector<weight> weights; /// the weights used for the interpolation
81
82 // --------------------------------------------------------------------------
83 //
84 //! Calculate the collection of circles/triangles which describe the
85 //! input grid. This is the collection of circles which are calculated
86 //! from any three points and do not contain any other point of the grid.
87 //
88 void CalculateGrid()
89 {
90 circles.reserve(2*inputGrid.size());
91
92 // Loop over all triplets of points
93 for (auto it0=inputGrid.cbegin(); it0<inputGrid.cend(); it0++)
94 {
95 for (auto it1=inputGrid.cbegin(); it1<it0; it1++)
96 {
97 for (auto it2=inputGrid.cbegin(); it2<it1; it2++)
98 {
99 // Calculate the circle through the three points
100
101 // Vectors along the side of the corresponding triangle
102 const vec v1 = *it1 - *it0;
103 const vec v2 = *it2 - *it1;
104
105 // Orthogonal vectors on the sides
106 const vec n1 = v1.orto();
107 const vec n2 = v2.orto();
108
109 // Center point of two of the three sides
110 const vec p1 = (*it0 + *it1)/2;
111 const vec p2 = (*it1 + *it2)/2;
112
113 // Calculate the crossing point of the two
114 // orthogonal vectors originating in the
115 // center of the sides.
116 const double denom = n1^n2;
117 if (denom==0)
118 continue;
119
120 const vec x(n1.x, n2.x);
121 const vec y(n1.y, n2.y);
122
123 const vec w(p1^(p1+n1), p2^(p2+n2));
124
125 circle c;
126
127 // This is the x and y coordinate of the circle
128 // through the three points and the circle's radius.
129 c.x = (x^w)/denom;
130 c.y = (y^w)/denom;
131 c.r = c.dist(*it1);
132
133 // Check if any other grid point lays within this circle
134 auto it3 = inputGrid.cbegin();
135 for (; it3<inputGrid.cend(); it3++)
136 {
137 if (it3==it0 || it3==it1 || it3==it2)
138 continue;
139
140 if (c.isInsideCircle(*it3))
141 break;
142 }
143
144 // If a point was found inside, reject the circle
145 if (it3!=inputGrid.cend())
146 continue;
147
148 // Store the three points of the triangle
149 c.p[0] = *it0;
150 c.p[1] = *it1;
151 c.p[2] = *it2;
152
153 // Keep in list
154 circles.push_back(c);
155 }
156 }
157 }
158 }
159
160 // --------------------------------------------------------------------------
161 //
162 //! Calculate the weights corresponding to the points in the output grid.
163 //! Weights are calculated by bi-linear interpolation. For interpolation,
164 //! the triangle which contains the point and has the smallest radius
165 //! is searched. If this is not available in case of extrapolation,
166 //! the condition is relaxed and requires only the circle to contain
167 //! the point. If such circle is not available, the circle with the
168 //! closest center is chosen.
169 //
170 bool CalculateWeights()
171 {
172 weights.reserve(outputGrid.size());
173
174 // Loop over all points in the output grid
175 for (auto ip=outputGrid.cbegin(); ip<outputGrid.cend(); ip++)
176 {
177 double mindd = DBL_MAX;
178
179 auto mint = circles.cend();
180 auto minc = circles.cend();
181 auto mind = circles.cend();
182
183 for (auto ic=circles.cbegin(); ic<circles.cend(); ic++)
184 {
185 // Check if point is inside the triangle
186 if (ic->isInsideTriangle(*ip))
187 {
188 if (mint==circles.cend() || ic->r<mint->r)
189 mint = ic;
190 }
191
192 // If we have found such a triangle, no need to check for more
193 if (mint!=circles.cend())
194 continue;
195
196 // maybe at least inside the circle
197 const double dd = ic->dist(*ip);
198 if (dd<ic->r)
199 {
200 if (minc==circles.cend() || ic->r<minc->r)
201 minc = ic;
202 }
203
204 // If we found such a circle, no need to check for more
205 if (minc!=circles.cend())
206 continue;
207
208 // then look for the closest circle center
209 if (dd<mindd)
210 {
211 mindd = dd;
212 mind = ic;
213 }
214 }
215
216 // Choose the best of the three options
217 const auto it = mint==circles.cend() ? (minc==circles.cend() ? mind : minc) : mint;
218 if (it==circles.cend())
219 return false;
220
221 // Calculate the bi-linear interpolation
222 const vec &p1 = it->p[0];
223 const vec &p2 = it->p[1];
224 const vec &p3 = it->p[2];
225
226 const double dy23 = p2.y - p3.y;
227 const double dy31 = p3.y - p1.y;
228 const double dy12 = p1.y - p2.y;
229
230 const double dx32 = p3.x - p2.x;
231 const double dx13 = p1.x - p3.x;
232 const double dx21 = p2.x - p1.x;
233
234 const double dxy23 = p2^p3;
235 const double dxy31 = p3^p1;
236 const double dxy12 = p1^p2;
237
238 const double det = dxy12 + dxy23 + dxy31;
239
240 const double w1 = (dy23*ip->x + dx32*ip->y + dxy23)/det;
241 const double w2 = (dy31*ip->x + dx13*ip->y + dxy31)/det;
242 const double w3 = (dy12*ip->x + dx21*ip->y + dxy12)/det;
243
244 // Store the original grid-point, the circle's parameters
245 // and the calculate weights
246 weight w;
247 w.x = ip->x;
248 w.y = ip->y;
249 w.c = *it;
250 w.w[0] = w1;
251 w.w[1] = w2;
252 w.w[2] = w3;
253
254 weights.push_back(w);
255 }
256
257 return true;
258 }
259
260public:
261 // --------------------------------------------------------------------------
262 //
263 //! Default constructor. Does nothing.
264 //
265 Interpolator2D()
266 {
267 }
268
269 // --------------------------------------------------------------------------
270 //
271 //! Initialize the input grid (the points at which values are known).
272 //!
273 //! @param n
274 //! number of data points
275 //!
276 //! @param x
277 //! x coordinates of data points
278 //!
279 //! @param n
280 //! y coordinates of data points
281 //
282 Interpolator2D(int n, double *x, double *y)
283 {
284 SetInputGrid(n, x, y);
285 }
286
287 Interpolator2D(const std::vector<Interpolator2D::vec> &v)
288 {
289 SetInputGrid(v);
290 }
291
292 const std::vector<Interpolator2D::weight> getWeights() const { return weights; }
293 const std::vector<Interpolator2D::point> getInputGrid() const { return inputGrid; }
294 const std::vector<Interpolator2D::point> getOutputGrid() const { return outputGrid; }
295
296 // --------------------------------------------------------------------------
297 //
298 //! Set a new input grid (the points at which values are known).
299 //! Invalidates the output grid and the calculated weights.
300 //! Calculates the triangles corresponding to the new grid.
301 //!
302 //! @param n
303 //! number of data points
304 //!
305 //! @param x
306 //! x coordinates of data points
307 //!
308 //! @param n
309 //! y coordinates of data points
310 //
311 void SetInputGrid(int n, double *x, double *y)
312 {
313 circles.clear();
314 weights.clear();
315 outputGrid.clear();
316
317 inputGrid.clear();
318 inputGrid.reserve(n);
319 for (int i=0; i<n; i++)
320 inputGrid.emplace_back(i, x[i], y[i]);
321
322 CalculateGrid();
323 }
324
325 void SetInputGrid(const std::vector<Interpolator2D::vec> &v)
326 {
327 circles.clear();
328 weights.clear();
329 outputGrid.clear();
330
331 inputGrid.clear();
332 inputGrid.reserve(v.size());
333 for (std::size_t i=0; i<v.size(); i++)
334 inputGrid.emplace_back(i, v[i].x, v[i].y);
335
336 CalculateGrid();
337 }
338
339 // --------------------------------------------------------------------------
340 //
341 //! Set a new output grid (the points at which you want interpolated
342 //! or extrapolated values). Calculates new weights.
343 //!
344 //! @param n
345 //! number of points
346 //!
347 //! @param x
348 //! x coordinates of points
349 //!
350 //! @param n
351 //! y coordinates of points
352 //!
353 //! @returns
354 //! false if the calculation of the weights failed, true in
355 //! case of success
356 //
357 bool SetOutputGrid(int n, double *x, double *y)
358 {
359 if (inputGrid.empty())
360 return false;
361
362 weights.clear();
363
364 outputGrid.clear();
365 outputGrid.reserve(n);
366 for (int i=0; i<n; i++)
367 outputGrid.emplace_back(i, x[i], y[i]);
368
369 return CalculateWeights();
370 }
371
372 bool SetOutputGrid(const std::vector<std::pair<double,double>> &v)
373 {
374 if (inputGrid.empty())
375 return false;
376
377 weights.clear();
378
379 outputGrid.clear();
380 outputGrid.reserve(v.size());
381 for (std::size_t i=0; i<v.size(); i++)
382 outputGrid.emplace_back(i, v[i].first, v[i].second);
383
384 return CalculateWeights();
385 }
386
387 // --------------------------------------------------------------------------
388 //
389 //! Perform interpolation.
390 //!
391 //! @param z
392 //! Values at the coordinates of the input grid. The order
393 //! must be identical.
394 //!
395 //! @returns
396 //! A vector<double> is returned with the interpolated values in the
397 //! same order than the putput grid. If the provided vector does
398 //! not match the size of the inputGrid, an empty vector is returned.
399 //
400 std::vector<double> Interpolate(const std::vector<double> &z) const
401 {
402 if (z.size()!=inputGrid.size())
403 return std::vector<double>();
404
405 std::vector<double> rc;
406 rc.reserve(z.size());
407
408 for (auto it=weights.cbegin(); it<weights.cend(); it++)
409 rc.push_back(z[it->c.p[0].i] * it->w[0] + z[it->c.p[1].i] * it->w[1] + z[it->c.p[2].i] * it->w[2]);
410
411 return rc;
412 }
413};
414#endif
Note: See TracBrowser for help on using the repository browser.