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

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