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

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