1 | using System;
|
---|
2 | using System.Linq;
|
---|
3 | using System.Diagnostics;
|
---|
4 | using System.Runtime.Serialization;
|
---|
5 | using System.Windows;
|
---|
6 | using System.Windows.Media;
|
---|
7 | using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
---|
8 | using Microsoft.Research.DynamicDataDisplay.DataSources;
|
---|
9 | using System.Collections.Generic;
|
---|
10 |
|
---|
11 | namespace Microsoft.Research.DynamicDataDisplay.Charts.Isolines
|
---|
12 | {
|
---|
13 | /// <summary>
|
---|
14 | /// Generates geometric object for isolines of the input 2d scalar field.
|
---|
15 | /// </summary>
|
---|
16 | public sealed class IsolineBuilder
|
---|
17 | {
|
---|
18 | /// <summary>
|
---|
19 | /// The density of isolines means the number of levels to draw.
|
---|
20 | /// </summary>
|
---|
21 | private int density = 12;
|
---|
22 |
|
---|
23 | private bool[,] processed;
|
---|
24 |
|
---|
25 | /// <summary>Number to be treated as missing value. NaN if no missing value is specified</summary>
|
---|
26 | private double missingValue = Double.NaN;
|
---|
27 |
|
---|
28 | static IsolineBuilder()
|
---|
29 | {
|
---|
30 | SetCellDictionaries();
|
---|
31 | }
|
---|
32 |
|
---|
33 | /// <summary>
|
---|
34 | /// Initializes a new instance of the <see cref="IsolineBuilder"/> class.
|
---|
35 | /// </summary>
|
---|
36 | public IsolineBuilder() { }
|
---|
37 |
|
---|
38 | /// <summary>
|
---|
39 | /// Initializes a new instance of the <see cref="IsolineBuilder"/> class for specified 2d scalar data source.
|
---|
40 | /// </summary>
|
---|
41 | /// <param name="dataSource">The data source with 2d scalar data.</param>
|
---|
42 | public IsolineBuilder(IDataSource2D<double> dataSource)
|
---|
43 | {
|
---|
44 | DataSource = dataSource;
|
---|
45 | }
|
---|
46 |
|
---|
47 | public double MissingValue
|
---|
48 | {
|
---|
49 | get
|
---|
50 | {
|
---|
51 | return missingValue;
|
---|
52 | }
|
---|
53 | set
|
---|
54 | {
|
---|
55 | missingValue = value;
|
---|
56 | }
|
---|
57 | }
|
---|
58 |
|
---|
59 | #region Private methods
|
---|
60 |
|
---|
61 | private static Dictionary<int, Dictionary<int, Edge>> dictChooser = new Dictionary<int, Dictionary<int, Edge>>();
|
---|
62 | private static void SetCellDictionaries()
|
---|
63 | {
|
---|
64 | var bottomDict = new Dictionary<int, Edge>();
|
---|
65 | bottomDict.Add((int)CellBitmask.RightBottom, Edge.Right);
|
---|
66 | bottomDict.Add(Edge.Left,
|
---|
67 | CellBitmask.LeftTop,
|
---|
68 | CellBitmask.LeftBottom | CellBitmask.RightBottom | CellBitmask.RightTop,
|
---|
69 | CellBitmask.LeftTop | CellBitmask.RightBottom | CellBitmask.RightTop,
|
---|
70 | CellBitmask.LeftBottom);
|
---|
71 | bottomDict.Add(Edge.Right,
|
---|
72 | CellBitmask.RightTop,
|
---|
73 | CellBitmask.LeftBottom | CellBitmask.RightBottom | CellBitmask.LeftTop,
|
---|
74 | CellBitmask.LeftBottom | CellBitmask.LeftTop | CellBitmask.RightTop);
|
---|
75 | bottomDict.Add(Edge.Top,
|
---|
76 | CellBitmask.RightBottom | CellBitmask.RightTop,
|
---|
77 | CellBitmask.LeftBottom | CellBitmask.LeftTop);
|
---|
78 |
|
---|
79 | var leftDict = new Dictionary<int, Edge>();
|
---|
80 | leftDict.Add(Edge.Top,
|
---|
81 | CellBitmask.LeftTop,
|
---|
82 | CellBitmask.LeftBottom | CellBitmask.RightBottom | CellBitmask.RightTop);
|
---|
83 | leftDict.Add(Edge.Right,
|
---|
84 | CellBitmask.LeftTop | CellBitmask.RightTop,
|
---|
85 | CellBitmask.LeftBottom | CellBitmask.RightBottom);
|
---|
86 | leftDict.Add(Edge.Bottom,
|
---|
87 | CellBitmask.RightBottom | CellBitmask.RightTop | CellBitmask.LeftTop,
|
---|
88 | CellBitmask.LeftBottom);
|
---|
89 |
|
---|
90 | var topDict = new Dictionary<int, Edge>();
|
---|
91 | topDict.Add(Edge.Right,
|
---|
92 | CellBitmask.RightTop,
|
---|
93 | CellBitmask.LeftTop | CellBitmask.LeftBottom | CellBitmask.RightBottom);
|
---|
94 | topDict.Add(Edge.Right,
|
---|
95 | CellBitmask.RightBottom,
|
---|
96 | CellBitmask.LeftTop | CellBitmask.LeftBottom | CellBitmask.RightTop);
|
---|
97 | topDict.Add(Edge.Left,
|
---|
98 | CellBitmask.RightBottom | CellBitmask.RightTop | CellBitmask.LeftTop,
|
---|
99 | CellBitmask.LeftBottom,
|
---|
100 | CellBitmask.LeftTop,
|
---|
101 | CellBitmask.LeftBottom | CellBitmask.RightBottom | CellBitmask.RightTop);
|
---|
102 | topDict.Add(Edge.Bottom,
|
---|
103 | CellBitmask.RightBottom | CellBitmask.RightTop,
|
---|
104 | CellBitmask.LeftTop | CellBitmask.LeftBottom);
|
---|
105 |
|
---|
106 | var rightDict = new Dictionary<int, Edge>();
|
---|
107 | rightDict.Add(Edge.Top,
|
---|
108 | CellBitmask.RightTop,
|
---|
109 | CellBitmask.LeftBottom | CellBitmask.RightBottom | CellBitmask.LeftTop);
|
---|
110 | rightDict.Add(Edge.Left,
|
---|
111 | CellBitmask.LeftTop | CellBitmask.RightTop,
|
---|
112 | CellBitmask.LeftBottom | CellBitmask.RightBottom);
|
---|
113 | rightDict.Add(Edge.Bottom,
|
---|
114 | CellBitmask.RightBottom,
|
---|
115 | CellBitmask.LeftTop | CellBitmask.LeftBottom | CellBitmask.RightTop);
|
---|
116 |
|
---|
117 | dictChooser.Add((int)Edge.Left, leftDict);
|
---|
118 | dictChooser.Add((int)Edge.Right, rightDict);
|
---|
119 | dictChooser.Add((int)Edge.Bottom, bottomDict);
|
---|
120 | dictChooser.Add((int)Edge.Top, topDict);
|
---|
121 | }
|
---|
122 |
|
---|
123 | private Edge GetOutEdge(Edge inEdge, ValuesInCell cv, IrregularCell rect, double value)
|
---|
124 | {
|
---|
125 | // value smaller than all values in corners or
|
---|
126 | // value greater than all values in corners
|
---|
127 | if (!cv.ValueBelongTo(value))
|
---|
128 | {
|
---|
129 | throw new IsolineGenerationException(Strings.Exceptions.IsolinesValueIsOutOfCell);
|
---|
130 | }
|
---|
131 |
|
---|
132 | CellBitmask cellVal = cv.GetCellValue(value);
|
---|
133 | var dict = dictChooser[(int)inEdge];
|
---|
134 | if (dict.ContainsKey((int)cellVal))
|
---|
135 | {
|
---|
136 | Edge result = dict[(int)cellVal];
|
---|
137 | switch (result)
|
---|
138 | {
|
---|
139 | case Edge.Left:
|
---|
140 | if (cv.LeftTop.IsNaN() || cv.LeftBottom.IsNaN())
|
---|
141 | result = Edge.None;
|
---|
142 | break;
|
---|
143 | case Edge.Right:
|
---|
144 | if (cv.RightTop.IsNaN() || cv.RightBottom.IsNaN())
|
---|
145 | result = Edge.None;
|
---|
146 | break;
|
---|
147 | case Edge.Top:
|
---|
148 | if (cv.RightTop.IsNaN() || cv.LeftTop.IsNaN())
|
---|
149 | result = Edge.None;
|
---|
150 | break;
|
---|
151 | case Edge.Bottom:
|
---|
152 | if (cv.LeftBottom.IsNaN() || cv.RightBottom.IsNaN())
|
---|
153 | result = Edge.None;
|
---|
154 | break;
|
---|
155 | }
|
---|
156 | return result;
|
---|
157 | }
|
---|
158 | else if (cellVal.IsDiagonal())
|
---|
159 | {
|
---|
160 | return GetOutForOpposite(inEdge, cellVal, value, cv, rect);
|
---|
161 | }
|
---|
162 |
|
---|
163 | const double near_zero = 0.0001;
|
---|
164 | const double near_one = 1 - near_zero;
|
---|
165 |
|
---|
166 | double lt = cv.LeftTop;
|
---|
167 | double rt = cv.RightTop;
|
---|
168 | double rb = cv.RightBottom;
|
---|
169 | double lb = cv.LeftBottom;
|
---|
170 |
|
---|
171 | switch (inEdge)
|
---|
172 | {
|
---|
173 | case Edge.Left:
|
---|
174 | if (value == lt)
|
---|
175 | value = near_one * lt + near_zero * lb;
|
---|
176 | else if (value == lb)
|
---|
177 | value = near_one * lb + near_zero * lt;
|
---|
178 | else
|
---|
179 | return Edge.None;
|
---|
180 | // Now this is possible because of missing value
|
---|
181 | //throw new IsolineGenerationException(Strings.Exceptions.IsolinesUnsupportedCase);
|
---|
182 | break;
|
---|
183 | case Edge.Top:
|
---|
184 | if (value == rt)
|
---|
185 | value = near_one * rt + near_zero * lt;
|
---|
186 | else if (value == lt)
|
---|
187 | value = near_one * lt + near_zero * rt;
|
---|
188 | else
|
---|
189 | return Edge.None;
|
---|
190 | // Now this is possibe because of missing value
|
---|
191 | //throw new IsolineGenerationException(Strings.Exceptions.IsolinesUnsupportedCase);
|
---|
192 | break;
|
---|
193 | case Edge.Right:
|
---|
194 | if (value == rb)
|
---|
195 | value = near_one * rb + near_zero * rt;
|
---|
196 | else if (value == rt)
|
---|
197 | value = near_one * rt + near_zero * rb;
|
---|
198 | else
|
---|
199 | return Edge.None;
|
---|
200 | // Now this is possibe because of missing value
|
---|
201 | //throw new IsolineGenerationException(Strings.Exceptions.IsolinesUnsupportedCase);
|
---|
202 | break;
|
---|
203 | case Edge.Bottom:
|
---|
204 | if (value == rb)
|
---|
205 | value = near_one * rb + near_zero * lb;
|
---|
206 | else if (value == lb)
|
---|
207 | value = near_one * lb + near_zero * rb;
|
---|
208 | else
|
---|
209 | return Edge.None;
|
---|
210 | // Now this is possibe because of missing value
|
---|
211 | //throw new IsolineGenerationException(Strings.Exceptions.IsolinesUnsupportedCase);
|
---|
212 | break;
|
---|
213 | }
|
---|
214 |
|
---|
215 | // Recursion?
|
---|
216 | //return GetOutEdge(inEdge, cv, rect, value);
|
---|
217 |
|
---|
218 | return Edge.None;
|
---|
219 | }
|
---|
220 |
|
---|
221 | private Edge GetOutForOpposite(Edge inEdge, CellBitmask cellVal, double value, ValuesInCell cellValues, IrregularCell rect)
|
---|
222 | {
|
---|
223 | Edge outEdge;
|
---|
224 |
|
---|
225 | SubCell subCell = GetSubCell(inEdge, value, cellValues);
|
---|
226 |
|
---|
227 | int iters = 1000; // max number of iterations
|
---|
228 | do
|
---|
229 | {
|
---|
230 | ValuesInCell subValues = cellValues.GetSubCell(subCell);
|
---|
231 | IrregularCell subRect = rect.GetSubRect(subCell);
|
---|
232 | outEdge = GetOutEdge(inEdge, subValues, subRect, value);
|
---|
233 | if (outEdge == Edge.None)
|
---|
234 | return Edge.None;
|
---|
235 | bool isAppropriate = subCell.IsAppropriate(outEdge);
|
---|
236 | if (isAppropriate)
|
---|
237 | {
|
---|
238 | ValuesInCell sValues = subValues.GetSubCell(subCell);
|
---|
239 |
|
---|
240 | Point point = GetPointXY(outEdge, value, subValues, subRect);
|
---|
241 | segments.AddPoint(point);
|
---|
242 | return outEdge;
|
---|
243 | }
|
---|
244 | else
|
---|
245 | {
|
---|
246 | subCell = GetAdjacentEdge(subCell, outEdge);
|
---|
247 | }
|
---|
248 |
|
---|
249 | byte e = (byte)outEdge;
|
---|
250 | inEdge = (Edge)((e > 2) ? (e >> 2) : (e << 2));
|
---|
251 | iters--;
|
---|
252 | } while (iters >= 0);
|
---|
253 |
|
---|
254 | throw new IsolineGenerationException(Strings.Exceptions.IsolinesDataIsUndetailized);
|
---|
255 | }
|
---|
256 |
|
---|
257 | private static SubCell GetAdjacentEdge(SubCell sub, Edge edge)
|
---|
258 | {
|
---|
259 | SubCell res = SubCell.LeftBottom;
|
---|
260 |
|
---|
261 | switch (sub)
|
---|
262 | {
|
---|
263 | case SubCell.LeftBottom:
|
---|
264 | res = edge == Edge.Top ? SubCell.LeftTop : SubCell.RightBottom;
|
---|
265 | break;
|
---|
266 | case SubCell.LeftTop:
|
---|
267 | res = edge == Edge.Bottom ? SubCell.LeftBottom : SubCell.RightTop;
|
---|
268 | break;
|
---|
269 | case SubCell.RightBottom:
|
---|
270 | res = edge == Edge.Top ? SubCell.RightTop : SubCell.LeftBottom;
|
---|
271 | break;
|
---|
272 | case SubCell.RightTop:
|
---|
273 | default:
|
---|
274 | res = edge == Edge.Bottom ? SubCell.RightBottom : SubCell.LeftTop;
|
---|
275 | break;
|
---|
276 | }
|
---|
277 |
|
---|
278 | return res;
|
---|
279 | }
|
---|
280 |
|
---|
281 | private static SubCell GetSubCell(Edge inEdge, double value, ValuesInCell vc)
|
---|
282 | {
|
---|
283 | double lb = vc.LeftBottom;
|
---|
284 | double rb = vc.RightBottom;
|
---|
285 | double rt = vc.RightTop;
|
---|
286 | double lt = vc.LeftTop;
|
---|
287 |
|
---|
288 | SubCell res = SubCell.LeftBottom;
|
---|
289 | switch (inEdge)
|
---|
290 | {
|
---|
291 | case Edge.Left:
|
---|
292 | res = (Math.Abs(value - lb) < Math.Abs(value - lt)) ? SubCell.LeftBottom : SubCell.LeftTop;
|
---|
293 | break;
|
---|
294 | case Edge.Top:
|
---|
295 | res = (Math.Abs(value - lt) < Math.Abs(value - rt)) ? SubCell.LeftTop : SubCell.RightTop;
|
---|
296 | break;
|
---|
297 | case Edge.Right:
|
---|
298 | res = (Math.Abs(value - rb) < Math.Abs(value - rt)) ? SubCell.RightBottom : SubCell.RightTop;
|
---|
299 | break;
|
---|
300 | case Edge.Bottom:
|
---|
301 | default:
|
---|
302 | res = (Math.Abs(value - lb) < Math.Abs(value - rb)) ? SubCell.LeftBottom : SubCell.RightBottom;
|
---|
303 | break;
|
---|
304 | }
|
---|
305 |
|
---|
306 | ValuesInCell subValues = vc.GetSubCell(res);
|
---|
307 | bool valueInside = subValues.ValueBelongTo(value);
|
---|
308 | if (!valueInside)
|
---|
309 | {
|
---|
310 | throw new IsolineGenerationException(Strings.Exceptions.IsolinesDataIsUndetailized);
|
---|
311 | }
|
---|
312 |
|
---|
313 | return res;
|
---|
314 | }
|
---|
315 |
|
---|
316 | private static Point GetPoint(double value, double a1, double a2, Vector v1, Vector v2)
|
---|
317 | {
|
---|
318 | double ratio = (value - a1) / (a2 - a1);
|
---|
319 |
|
---|
320 | Verify.IsTrue(0 <= ratio && ratio <= 1);
|
---|
321 |
|
---|
322 | Vector r = (1 - ratio) * v1 + ratio * v2;
|
---|
323 | return new Point(r.X, r.Y);
|
---|
324 | }
|
---|
325 |
|
---|
326 | private Point GetPointXY(Edge edge, double value, ValuesInCell vc, IrregularCell rect)
|
---|
327 | {
|
---|
328 | double lt = vc.LeftTop;
|
---|
329 | double lb = vc.LeftBottom;
|
---|
330 | double rb = vc.RightBottom;
|
---|
331 | double rt = vc.RightTop;
|
---|
332 |
|
---|
333 | switch (edge)
|
---|
334 | {
|
---|
335 | case Edge.Left:
|
---|
336 | return GetPoint(value, lb, lt, rect.LeftBottom, rect.LeftTop);
|
---|
337 | case Edge.Top:
|
---|
338 | return GetPoint(value, lt, rt, rect.LeftTop, rect.RightTop);
|
---|
339 | case Edge.Right:
|
---|
340 | return GetPoint(value, rb, rt, rect.RightBottom, rect.RightTop);
|
---|
341 | case Edge.Bottom:
|
---|
342 | return GetPoint(value, lb, rb, rect.LeftBottom, rect.RightBottom);
|
---|
343 | default:
|
---|
344 | throw new InvalidOperationException();
|
---|
345 | }
|
---|
346 | }
|
---|
347 |
|
---|
348 | private bool BelongsToEdge(double value, double edgeValue1, double edgeValue2, bool onBoundary)
|
---|
349 | {
|
---|
350 | if (!Double.IsNaN(missingValue) && (edgeValue1 == missingValue || edgeValue2 == missingValue))
|
---|
351 | return false;
|
---|
352 |
|
---|
353 | if (onBoundary)
|
---|
354 | {
|
---|
355 | return (edgeValue1 <= value && value < edgeValue2) ||
|
---|
356 | (edgeValue2 <= value && value < edgeValue1);
|
---|
357 | }
|
---|
358 | else
|
---|
359 | {
|
---|
360 | return (edgeValue1 < value && value < edgeValue2) ||
|
---|
361 | (edgeValue2 < value && value < edgeValue1);
|
---|
362 | }
|
---|
363 | }
|
---|
364 |
|
---|
365 | private bool IsPassed(Edge edge, int i, int j, byte[,] edges)
|
---|
366 | {
|
---|
367 | switch (edge)
|
---|
368 | {
|
---|
369 | case Edge.Left:
|
---|
370 | return (i == 0) || (edges[i, j] & (byte)edge) != 0;
|
---|
371 | case Edge.Bottom:
|
---|
372 | return (j == 0) || (edges[i, j] & (byte)edge) != 0;
|
---|
373 | case Edge.Top:
|
---|
374 | return (j == edges.GetLength(1) - 2) || (edges[i, j + 1] & (byte)Edge.Bottom) != 0;
|
---|
375 | case Edge.Right:
|
---|
376 | return (i == edges.GetLength(0) - 2) || (edges[i + 1, j] & (byte)Edge.Left) != 0;
|
---|
377 | default:
|
---|
378 | throw new InvalidOperationException();
|
---|
379 | }
|
---|
380 | }
|
---|
381 |
|
---|
382 | private void MakeEdgePassed(Edge edge, int i, int j)
|
---|
383 | {
|
---|
384 | switch (edge)
|
---|
385 | {
|
---|
386 | case Edge.Left:
|
---|
387 | case Edge.Bottom:
|
---|
388 | edges[i, j] |= (byte)edge;
|
---|
389 | break;
|
---|
390 | case Edge.Top:
|
---|
391 | edges[i, j + 1] |= (byte)Edge.Bottom;
|
---|
392 | break;
|
---|
393 | case Edge.Right:
|
---|
394 | edges[i + 1, j] |= (byte)Edge.Left;
|
---|
395 | break;
|
---|
396 | default:
|
---|
397 | throw new InvalidOperationException();
|
---|
398 | }
|
---|
399 | }
|
---|
400 |
|
---|
401 | private Edge TrackLine(Edge inEdge, double value, ref int x, ref int y, out double newX, out double newY)
|
---|
402 | {
|
---|
403 | // Getting output edge
|
---|
404 | ValuesInCell vc = (missingValue.IsNaN()) ?
|
---|
405 | (new ValuesInCell(values[x, y],
|
---|
406 | values[x + 1, y],
|
---|
407 | values[x + 1, y + 1],
|
---|
408 | values[x, y + 1])) :
|
---|
409 | (new ValuesInCell(values[x, y],
|
---|
410 | values[x + 1, y],
|
---|
411 | values[x + 1, y + 1],
|
---|
412 | values[x, y + 1],
|
---|
413 | missingValue));
|
---|
414 |
|
---|
415 | IrregularCell rect = new IrregularCell(
|
---|
416 | grid[x, y],
|
---|
417 | grid[x + 1, y],
|
---|
418 | grid[x + 1, y + 1],
|
---|
419 | grid[x, y + 1]);
|
---|
420 |
|
---|
421 | Edge outEdge = GetOutEdge(inEdge, vc, rect, value);
|
---|
422 | if (outEdge == Edge.None)
|
---|
423 | {
|
---|
424 | newX = newY = -1; // Impossible cell indices
|
---|
425 | return Edge.None;
|
---|
426 | }
|
---|
427 |
|
---|
428 | // Drawing new segment
|
---|
429 | Point point = GetPointXY(outEdge, value, vc, rect);
|
---|
430 | newX = point.X;
|
---|
431 | newY = point.Y;
|
---|
432 | segments.AddPoint(point);
|
---|
433 | processed[x, y] = true;
|
---|
434 |
|
---|
435 | // Whether out-edge already was passed?
|
---|
436 | if (IsPassed(outEdge, x, y, edges)) // line is closed
|
---|
437 | {
|
---|
438 | //MakeEdgePassed(outEdge, x, y); // boundaries should be marked as passed too
|
---|
439 | return Edge.None;
|
---|
440 | }
|
---|
441 |
|
---|
442 | // Make this edge passed
|
---|
443 | MakeEdgePassed(outEdge, x, y);
|
---|
444 |
|
---|
445 | // Getting next cell's indices
|
---|
446 | switch (outEdge)
|
---|
447 | {
|
---|
448 | case Edge.Left:
|
---|
449 | x--;
|
---|
450 | return Edge.Right;
|
---|
451 | case Edge.Top:
|
---|
452 | y++;
|
---|
453 | return Edge.Bottom;
|
---|
454 | case Edge.Right:
|
---|
455 | x++;
|
---|
456 | return Edge.Left;
|
---|
457 | case Edge.Bottom:
|
---|
458 | y--;
|
---|
459 | return Edge.Top;
|
---|
460 | default:
|
---|
461 | throw new InvalidOperationException();
|
---|
462 | }
|
---|
463 | }
|
---|
464 |
|
---|
465 | private void TrackLineNonRecursive(Edge inEdge, double value, int x, int y)
|
---|
466 | {
|
---|
467 | int s = x, t = y;
|
---|
468 |
|
---|
469 | ValuesInCell vc = (missingValue.IsNaN()) ?
|
---|
470 | (new ValuesInCell(values[x, y],
|
---|
471 | values[x + 1, y],
|
---|
472 | values[x + 1, y + 1],
|
---|
473 | values[x, y + 1])) :
|
---|
474 | (new ValuesInCell(values[x, y],
|
---|
475 | values[x + 1, y],
|
---|
476 | values[x + 1, y + 1],
|
---|
477 | values[x, y + 1],
|
---|
478 | missingValue));
|
---|
479 |
|
---|
480 | IrregularCell rect = new IrregularCell(
|
---|
481 | grid[x, y],
|
---|
482 | grid[x + 1, y],
|
---|
483 | grid[x + 1, y + 1],
|
---|
484 | grid[x, y + 1]);
|
---|
485 |
|
---|
486 | Point point = GetPointXY(inEdge, value, vc, rect);
|
---|
487 |
|
---|
488 | segments.StartLine(point, (value - minMax.Min) / (minMax.Max - minMax.Min), value);
|
---|
489 |
|
---|
490 | MakeEdgePassed(inEdge, x, y);
|
---|
491 |
|
---|
492 | //processed[x, y] = true;
|
---|
493 |
|
---|
494 | double x2, y2;
|
---|
495 | do
|
---|
496 | {
|
---|
497 | inEdge = TrackLine(inEdge, value, ref s, ref t, out x2, out y2);
|
---|
498 | } while (inEdge != Edge.None);
|
---|
499 | }
|
---|
500 |
|
---|
501 | #endregion
|
---|
502 |
|
---|
503 | private bool HasIsoline(int x, int y)
|
---|
504 | {
|
---|
505 | return (edges[x, y] != 0 &&
|
---|
506 | ((x < edges.GetLength(0) - 1 && edges[x + 1, y] != 0) ||
|
---|
507 | (y < edges.GetLength(1) - 1 && edges[x, y + 1] != 0)));
|
---|
508 | }
|
---|
509 |
|
---|
510 | /// <summary>Finds isoline for specified reference value</summary>
|
---|
511 | /// <param name="value">Reference value</param>
|
---|
512 | private void PrepareCells(double value)
|
---|
513 | {
|
---|
514 | double currentRatio = (value - minMax.Min) / (minMax.Max - minMax.Min);
|
---|
515 |
|
---|
516 | if (currentRatio < 0 || currentRatio > 1)
|
---|
517 | return; // No contour lines for such value
|
---|
518 |
|
---|
519 | int xSize = dataSource.Width;
|
---|
520 | int ySize = dataSource.Height;
|
---|
521 | int x, y;
|
---|
522 | for (x = 0; x < xSize; x++)
|
---|
523 | for (y = 0; y < ySize; y++)
|
---|
524 | edges[x, y] = 0;
|
---|
525 |
|
---|
526 | processed = new bool[xSize, ySize];
|
---|
527 |
|
---|
528 | // Looking in boundaries.
|
---|
529 | // left
|
---|
530 | for (y = 1; y < ySize; y++)
|
---|
531 | {
|
---|
532 | if (BelongsToEdge(value, values[0, y - 1], values[0, y], true) &&
|
---|
533 | (edges[0, y - 1] & (byte)Edge.Left) == 0)
|
---|
534 | {
|
---|
535 | TrackLineNonRecursive(Edge.Left, value, 0, y - 1);
|
---|
536 | }
|
---|
537 | }
|
---|
538 |
|
---|
539 | // bottom
|
---|
540 | for (x = 0; x < xSize - 1; x++)
|
---|
541 | {
|
---|
542 | if (BelongsToEdge(value, values[x, 0], values[x + 1, 0], true)
|
---|
543 | && (edges[x, 0] & (byte)Edge.Bottom) == 0)
|
---|
544 | {
|
---|
545 | TrackLineNonRecursive(Edge.Bottom, value, x, 0);
|
---|
546 | };
|
---|
547 | }
|
---|
548 |
|
---|
549 | // right
|
---|
550 | x = xSize - 1;
|
---|
551 | for (y = 1; y < ySize; y++)
|
---|
552 | {
|
---|
553 | // Is this correct?
|
---|
554 | //if (BelongsToEdge(value, values[0, y - 1], values[0, y], true) &&
|
---|
555 | // (edges[0, y - 1] & (byte)Edge.Left) == 0)
|
---|
556 | //{
|
---|
557 | // TrackLineNonRecursive(Edge.Left, value, 0, y - 1);
|
---|
558 | //};
|
---|
559 |
|
---|
560 | if (BelongsToEdge(value, values[x, y - 1], values[x, y], true) &&
|
---|
561 | (edges[x, y - 1] & (byte)Edge.Left) == 0)
|
---|
562 | {
|
---|
563 | TrackLineNonRecursive(Edge.Right, value, x - 1, y - 1);
|
---|
564 | };
|
---|
565 | }
|
---|
566 |
|
---|
567 | // horizontals
|
---|
568 | for (x = 1; x < xSize - 1; x++)
|
---|
569 | for (y = 1; y < ySize - 1; y++)
|
---|
570 | {
|
---|
571 | if ((edges[x, y] & (byte)Edge.Bottom) == 0 &&
|
---|
572 | BelongsToEdge(value, values[x, y], values[x + 1, y], false) &&
|
---|
573 | !processed[x, y - 1])
|
---|
574 | {
|
---|
575 | TrackLineNonRecursive(Edge.Top, value, x, y - 1);
|
---|
576 | }
|
---|
577 | if ((edges[x, y] & (byte)Edge.Bottom) == 0 &&
|
---|
578 | BelongsToEdge(value, values[x, y], values[x + 1, y], false) &&
|
---|
579 | !processed[x, y])
|
---|
580 | {
|
---|
581 | TrackLineNonRecursive(Edge.Bottom, value, x, y);
|
---|
582 | }
|
---|
583 | if ((edges[x, y] & (byte)Edge.Left) == 0 &&
|
---|
584 | BelongsToEdge(value, values[x, y], values[x, y - 1], false) &&
|
---|
585 | !processed[x - 1, y - 1])
|
---|
586 | {
|
---|
587 | TrackLineNonRecursive(Edge.Right, value, x - 1, y - 1);
|
---|
588 | }
|
---|
589 | if ((edges[x, y] & (byte)Edge.Left) == 0 &&
|
---|
590 | BelongsToEdge(value, values[x, y], values[x, y - 1], false) &&
|
---|
591 | !processed[x, y - 1])
|
---|
592 | {
|
---|
593 | TrackLineNonRecursive(Edge.Left, value, x, y - 1);
|
---|
594 | }
|
---|
595 | }
|
---|
596 | }
|
---|
597 |
|
---|
598 | /// <summary>
|
---|
599 | /// Builds isoline data for 2d scalar field contained in data source.
|
---|
600 | /// </summary>
|
---|
601 | /// <returns>Collection of data describing built isolines.</returns>
|
---|
602 | public IsolineCollection BuildIsoline()
|
---|
603 | {
|
---|
604 | VerifyDataSource();
|
---|
605 |
|
---|
606 | segments = new IsolineCollection();
|
---|
607 |
|
---|
608 | // Cannot draw isolines for fields with one dimension lesser than 2
|
---|
609 | if (dataSource.Width < 2 || dataSource.Height < 2)
|
---|
610 | return segments;
|
---|
611 |
|
---|
612 | Init();
|
---|
613 |
|
---|
614 | if (!minMax.IsEmpty)
|
---|
615 | {
|
---|
616 | values = dataSource.Data;
|
---|
617 | double[] levels = GetLevelsForIsolines();
|
---|
618 |
|
---|
619 | foreach (double level in levels)
|
---|
620 | {
|
---|
621 | PrepareCells(level);
|
---|
622 | }
|
---|
623 |
|
---|
624 | if (segments.Lines.Count > 0 && segments.Lines[segments.Lines.Count - 1].OtherPoints.Count == 0)
|
---|
625 | segments.Lines.RemoveAt(segments.Lines.Count - 1);
|
---|
626 | }
|
---|
627 | return segments;
|
---|
628 | }
|
---|
629 |
|
---|
630 | private void Init()
|
---|
631 | {
|
---|
632 | if (dataSource.Range.HasValue)
|
---|
633 | minMax = dataSource.Range.Value;
|
---|
634 | else
|
---|
635 | minMax = (Double.IsNaN(missingValue) ? dataSource.GetMinMax() : dataSource.GetMinMax(missingValue));
|
---|
636 |
|
---|
637 | if (dataSource.MissingValue.HasValue)
|
---|
638 | missingValue = dataSource.MissingValue.Value;
|
---|
639 |
|
---|
640 | segments.Min = minMax.Min;
|
---|
641 | segments.Max = minMax.Max;
|
---|
642 | }
|
---|
643 |
|
---|
644 | /// <summary>
|
---|
645 | /// Builds isoline data for the specified level in 2d scalar field.
|
---|
646 | /// </summary>
|
---|
647 | /// <param name="level">The level.</param>
|
---|
648 | /// <returns></returns>
|
---|
649 | public IsolineCollection BuildIsoline(double level)
|
---|
650 | {
|
---|
651 | VerifyDataSource();
|
---|
652 |
|
---|
653 | segments = new IsolineCollection();
|
---|
654 |
|
---|
655 | Init();
|
---|
656 |
|
---|
657 | if (!minMax.IsEmpty)
|
---|
658 | {
|
---|
659 | values = dataSource.Data;
|
---|
660 |
|
---|
661 |
|
---|
662 | PrepareCells(level);
|
---|
663 |
|
---|
664 | if (segments.Lines.Count > 0 && segments.Lines[segments.Lines.Count - 1].OtherPoints.Count == 0)
|
---|
665 | segments.Lines.RemoveAt(segments.Lines.Count - 1);
|
---|
666 | }
|
---|
667 | return segments;
|
---|
668 | }
|
---|
669 |
|
---|
670 | private void VerifyDataSource()
|
---|
671 | {
|
---|
672 | if (dataSource == null)
|
---|
673 | throw new InvalidOperationException(Strings.Exceptions.IsolinesDataSourceShouldBeSet);
|
---|
674 | }
|
---|
675 |
|
---|
676 | IsolineCollection segments;
|
---|
677 |
|
---|
678 | private double[,] values;
|
---|
679 | private byte[,] edges;
|
---|
680 | private Point[,] grid;
|
---|
681 |
|
---|
682 | private Range<double> minMax;
|
---|
683 | private IDataSource2D<double> dataSource;
|
---|
684 | /// <summary>
|
---|
685 | /// Gets or sets the data source - 2d scalar field.
|
---|
686 | /// </summary>
|
---|
687 | /// <value>The data source.</value>
|
---|
688 | public IDataSource2D<double> DataSource
|
---|
689 | {
|
---|
690 | get { return dataSource; }
|
---|
691 | set
|
---|
692 | {
|
---|
693 | if (dataSource != value)
|
---|
694 | {
|
---|
695 | value.VerifyNotNull("value");
|
---|
696 |
|
---|
697 | dataSource = value;
|
---|
698 | grid = dataSource.Grid;
|
---|
699 | edges = new byte[dataSource.Width, dataSource.Height];
|
---|
700 | }
|
---|
701 | }
|
---|
702 | }
|
---|
703 |
|
---|
704 | private const double shiftPercent = 0.05;
|
---|
705 | private double[] GetLevelsForIsolines()
|
---|
706 | {
|
---|
707 | double[] levels;
|
---|
708 | double min = minMax.Min;
|
---|
709 | double max = minMax.Max;
|
---|
710 |
|
---|
711 | double step = (max - min) / (density - 1);
|
---|
712 | double delta = (max - min);
|
---|
713 |
|
---|
714 | levels = new double[density];
|
---|
715 | levels[0] = min + delta * shiftPercent;
|
---|
716 | levels[levels.Length - 1] = max - delta * shiftPercent;
|
---|
717 |
|
---|
718 | for (int i = 1; i < levels.Length - 1; i++)
|
---|
719 | levels[i] = min + i * step;
|
---|
720 |
|
---|
721 | return levels;
|
---|
722 | }
|
---|
723 | }
|
---|
724 | }
|
---|