Mercurial > dive4elements > gnv-client
comparison gnv-artifacts/src/main/java/de/intevation/gnv/raster/Vectorizer.java @ 875:5e9efdda6894
merged gnv-artifacts/1.0
author | Thomas Arendsen Hein <thomas@intevation.de> |
---|---|
date | Fri, 28 Sep 2012 12:13:56 +0200 |
parents | d766fe2d917a |
children | f953c9a559d8 |
comparison
equal
deleted
inserted
replaced
722:bb3ffe7d719e | 875:5e9efdda6894 |
---|---|
1 package de.intevation.gnv.raster; | |
2 | |
3 import gnu.trove.TIntObjectHashMap; | |
4 import gnu.trove.TIntStack; | |
5 | |
6 import java.util.ArrayList; | |
7 import java.util.BitSet; | |
8 import java.util.List; | |
9 | |
10 import org.apache.log4j.Logger; | |
11 | |
12 /** | |
13 * Instances of this class are able to vectorize 2D integer arrays | |
14 * with a kind of flood filling regions detection mechanism to | |
15 * a set of line strings and polygons. | |
16 * | |
17 * @author <a href="mailto:sascha.teichmann@intevation.de">Sascha L. Teichmann</a> | |
18 */ | |
19 public class Vectorizer | |
20 { | |
21 private static Logger log = Logger.getLogger(Vectorizer.class); | |
22 | |
23 /** | |
24 * Callback to process the found line strings and polygons. | |
25 */ | |
26 public interface RingsHandler { | |
27 | |
28 /** | |
29 * Called from {@link #process(de.intevation.gnv.raster.Vectorizer.RingsHandler) } | |
30 * to give the found features to the postprocessing backend. | |
31 * @param rings The found line strings and polygons. | |
32 * @param value The integer value of the raster surrounded by | |
33 * the features. | |
34 * @param width The width of the index space of the vectorized data. | |
35 * @param height The height of the index space of the vectorized data. | |
36 */ | |
37 void handleRings( | |
38 List<Edge> rings, | |
39 int value, | |
40 int width, | |
41 int height); | |
42 | |
43 } // interface RingsHandler | |
44 | |
45 /** | |
46 * Doubly link list representing a line string or a polygon. | |
47 */ | |
48 public static final class Edge { | |
49 | |
50 /** | |
51 * The predecessor. | |
52 */ | |
53 public Edge prev; | |
54 /** | |
55 * The successor. | |
56 */ | |
57 public Edge next; | |
58 | |
59 /** | |
60 * Index of first vertex. To separate x and y values | |
61 * you have to divide the value by the width of the | |
62 * index space. | |
63 */ | |
64 public int a; | |
65 /** | |
66 * Index of second vertex. To separate x and y values | |
67 * you have to divide the value by the width of the | |
68 * index space. | |
69 */ | |
70 public int b; | |
71 | |
72 /** | |
73 * Default constructor. | |
74 */ | |
75 public Edge() { | |
76 } | |
77 | |
78 /** | |
79 * Constructor to create Edge with two vertices. | |
80 * @param a The first vertex. | |
81 * @param b The second vertex. | |
82 */ | |
83 public Edge(int a, int b) { | |
84 this.a = a; | |
85 this.b = b; | |
86 } | |
87 | |
88 /** | |
89 * Copy constructor | |
90 * @param other The edge to clone. | |
91 */ | |
92 public Edge(Edge other) { | |
93 a = other.a; | |
94 b = other.b; | |
95 } | |
96 | |
97 /** | |
98 * Chain a given edge segment to this segment. The | |
99 * side of the chaining is determined by a given | |
100 * parameter. | |
101 * @param other The segment to chain. | |
102 * @param found The side to chain. | |
103 */ | |
104 public void chain(Edge other, int found) { | |
105 | |
106 if (found == a) { | |
107 other.next = this; | |
108 prev = other; | |
109 return; | |
110 } | |
111 | |
112 if (found == b) { | |
113 next = other; | |
114 other.prev = this; | |
115 return; | |
116 } | |
117 | |
118 throw new IllegalStateException("cannot chain"); | |
119 } | |
120 | |
121 /** | |
122 * Tells if the list is complete which means that this | |
123 * edge list is a closed polygon. | |
124 * @return true if edge list is closed else false. | |
125 */ | |
126 public boolean isComplete() { | |
127 Edge current = this; | |
128 do { | |
129 if (current.prev == null || current.next == null) { | |
130 return false; | |
131 } | |
132 current = current.next; | |
133 } | |
134 while (current != this); | |
135 return true; | |
136 } | |
137 | |
138 /** | |
139 * Returns the length of this edge list in next direction. | |
140 * Segments in prev direction are ignored. | |
141 * @return The length of this edge list. | |
142 */ | |
143 public int length() { | |
144 int length = 0; | |
145 Edge current = this; | |
146 do { ++length; } | |
147 while ((current = current.next) != null && current != this); | |
148 return length; | |
149 } | |
150 | |
151 /** | |
152 * Returns the head node of this edge list. | |
153 * @return The head node. | |
154 */ | |
155 public Edge head() { | |
156 Edge current = this; | |
157 while (current.prev != null) { | |
158 current = current.prev; | |
159 } | |
160 return current; | |
161 } | |
162 | |
163 /** | |
164 * Hashes the two vertex indices to a common value. | |
165 * @return The hash value. | |
166 */ | |
167 @Override | |
168 public int hashCode() { | |
169 return (a << 16) | b; | |
170 } | |
171 | |
172 /** | |
173 * Two edges are considered equal if they have the same | |
174 * a and b vertices. | |
175 * @param other The other edge. | |
176 * @return true if the edges are equal else false. | |
177 */ | |
178 @Override | |
179 public boolean equals(Object other) { | |
180 Edge e = (Edge)other; | |
181 return a == e.a && b == e.b; | |
182 } | |
183 } // class Edge | |
184 | |
185 /** | |
186 * Simplifies a given edge list by removing collinear vertices. | |
187 * Attention: The original list is modified. | |
188 * @param edge The edge list to simplify. | |
189 * @param width The width of the vertex index space. | |
190 * @return The simplified list. | |
191 */ | |
192 protected static Edge simplify(Edge edge, int width) { | |
193 | |
194 Edge e1 = edge, start = edge; | |
195 | |
196 int length = edge.length(); | |
197 | |
198 if (length < 2) { | |
199 return e1; | |
200 } | |
201 | |
202 Edge e2 = edge.next; | |
203 | |
204 int count = 0; | |
205 | |
206 do { | |
207 int e1x = e1.a % width; | |
208 int e1y = e1.a / width; | |
209 int e2x = e1.b % width; | |
210 int e2y = e1.b / width; | |
211 int e3x = e2.b % width; | |
212 int e3y = e2.b / width; | |
213 | |
214 if ((e1x == e2x && e2x == e3x && e1x == e3x) | |
215 || (e1y == e2y && e2y == e3y && e1y == e3y)) { | |
216 e1.b = e2.b; | |
217 Edge removed = e1.next; | |
218 e1.next = e2.next; | |
219 if (e1.next != null) { | |
220 e1.next.prev = e1; | |
221 } | |
222 e2 = e1.next; | |
223 count = 0; | |
224 --length; | |
225 if (removed == start) { | |
226 start = e1; | |
227 } | |
228 } | |
229 else { | |
230 e1 = e1.next; | |
231 e2 = e2.next; | |
232 ++count; | |
233 } | |
234 } | |
235 while (length > 1 && e2 != null && count < length + 2); | |
236 | |
237 return start; | |
238 } | |
239 | |
240 /** | |
241 * The raster to be traced. | |
242 */ | |
243 protected int [] raster; | |
244 /** | |
245 * The width of the raster. | |
246 */ | |
247 protected int width; | |
248 /** | |
249 * Map of the currently open edges. | |
250 */ | |
251 protected TIntObjectHashMap openEdges; | |
252 /** | |
253 * List of rings already found. | |
254 */ | |
255 protected List<Edge> rings; | |
256 /** | |
257 * Flag to signal if a simplification should be performed | |
258 * after a ring is completed. | |
259 */ | |
260 protected boolean simplify; | |
261 | |
262 /** | |
263 * Default constructor. Simplification is turned on. | |
264 */ | |
265 public Vectorizer() { | |
266 this(true); | |
267 } | |
268 | |
269 /** | |
270 * Constructor to create a vectorized with an explicit | |
271 * simplification policy. | |
272 * @param simplify Indicates if simplification should be | |
273 * used on ring completion. | |
274 */ | |
275 public Vectorizer(boolean simplify) { | |
276 openEdges = new TIntObjectHashMap(); | |
277 rings = new ArrayList<Edge>(); | |
278 this.simplify = simplify; | |
279 } | |
280 | |
281 /** | |
282 * Constructor to create a vectorizer with a given raster and width. | |
283 * Simplification is turn on. | |
284 * @param raster The raster to be vectorized. | |
285 * @param width The width of the raster. | |
286 */ | |
287 public Vectorizer(int [] raster, int width) { | |
288 this(true, raster, width); | |
289 } | |
290 | |
291 /** | |
292 * Constructor to create a vectorizer with a given raster, width | |
293 * and an explicit simplification policy. | |
294 * @param simplify Indicates if simplification should be | |
295 * used on ring completion. | |
296 * @param raster The raster to be vectorized. | |
297 * @param width The width of the raster. | |
298 */ | |
299 public Vectorizer(boolean simplify, int [] raster, int width) { | |
300 this(simplify); | |
301 this.raster = raster; | |
302 this.width = width; | |
303 } | |
304 | |
305 /** | |
306 * Returns (x, y+1) for given vertex in index space. | |
307 * @param i vertex in index space. | |
308 * @param w width of raster. | |
309 * @return (x, y+1) in index space. | |
310 */ | |
311 public static final int tl(int i, int w) { | |
312 int x = i % w; | |
313 int y = i / w; | |
314 return x + (w + 1)*y; | |
315 } | |
316 | |
317 /** | |
318 * Returns tl(i, w) + 1. | |
319 * @param i vertex in index space. | |
320 * @param w width of raster. | |
321 * @return tl(i, w) + 1. | |
322 */ | |
323 public static final int tr(int i, int w) { | |
324 return tl(i, w) + 1; | |
325 } | |
326 | |
327 /** | |
328 * Returns tl(i, w) + w + 1. | |
329 * @param i vertex in index space. | |
330 * @param w width of raster. | |
331 * @return tl(i, w) + w + 1. | |
332 */ | |
333 public static final int bl(int i, int w) { | |
334 return tl(i, w) + w + 1; | |
335 } | |
336 | |
337 /** | |
338 * Returns bl(i, w) + 1. | |
339 * @param i vertex in index space. | |
340 * @param w width of raster. | |
341 * @return bl(i, w) + 1. | |
342 */ | |
343 public static final int br(int i, int w) { | |
344 return bl(i, w) + 1; | |
345 } | |
346 | |
347 /** | |
348 * Resets open resources after a set of features were found. | |
349 */ | |
350 protected void resetRegion() { | |
351 openEdges.clear(); | |
352 rings.clear(); | |
353 } | |
354 | |
355 /** | |
356 * Adds an edge to the map of open edges, joins it | |
357 * with its direct neighbors of if complete add the | |
358 * list to the complete features. | |
359 * @param edge | |
360 */ | |
361 protected void emit(Edge edge) { | |
362 | |
363 Edge otherA = (Edge)openEdges.remove(edge.a); | |
364 if (otherA != null) { | |
365 otherA.chain(edge, edge.a); | |
366 } | |
367 | |
368 Edge otherB = (Edge)openEdges.remove(edge.b); | |
369 if (otherB != null) { | |
370 otherB.chain(edge, edge.b); | |
371 } | |
372 | |
373 if (edge.isComplete()) { | |
374 rings.add(simplify ? simplify(edge, width + 1) : edge); | |
375 } | |
376 else { | |
377 if (otherA == null) { | |
378 openEdges.put(edge.a, edge); | |
379 } | |
380 if (otherB == null) { | |
381 openEdges.put(edge.b, edge); | |
382 } | |
383 } | |
384 } | |
385 | |
386 /** | |
387 * Vectorize the raster. The found features are fed into | |
388 * the given ring handler. | |
389 * @param handler The RingHandler to postprocess the found features. | |
390 * @return The number of regions found. | |
391 */ | |
392 public int process(RingsHandler handler) { | |
393 | |
394 BitSet visited = new BitSet(raster.length); | |
395 | |
396 TIntStack stack = new TIntStack(); | |
397 | |
398 int regions = 0; | |
399 | |
400 int height = raster.length / width; | |
401 | |
402 for (int i = 0; i < raster.length; ++i) { | |
403 if (visited.get(i)) { | |
404 continue; | |
405 } | |
406 | |
407 ++regions; | |
408 | |
409 int currentValue = raster[i]; | |
410 visited.set(i); | |
411 | |
412 int current = i; | |
413 visited.set(current); | |
414 | |
415 for (;;) { | |
416 int tl = tl(current, width); | |
417 int tr = tr(current, width); | |
418 int bl = bl(current, width); | |
419 int br = br(current, width); | |
420 | |
421 int t = current - width; | |
422 | |
423 if (t < 0) { | |
424 emit(new Edge(tr, tl)); | |
425 } | |
426 else { | |
427 if (raster[t] != currentValue) { | |
428 emit(new Edge(tr, tl)); | |
429 } | |
430 else { | |
431 if (!visited.get(t)) { | |
432 visited.set(t); | |
433 stack.push(t); | |
434 } | |
435 } | |
436 } | |
437 | |
438 int b = current + width; | |
439 | |
440 if (b >= raster.length) { | |
441 emit(new Edge(bl, br)); | |
442 } | |
443 else { | |
444 if (raster[b] != currentValue) { | |
445 emit(new Edge(bl, br)); | |
446 } | |
447 else { | |
448 if (!visited.get(b)) { | |
449 visited.set(b); | |
450 stack.push(b); | |
451 } | |
452 } | |
453 } | |
454 | |
455 int x = current % width; | |
456 | |
457 if (x == 0) { | |
458 emit(new Edge(tl, bl)); | |
459 } | |
460 else { | |
461 int l = current - 1; | |
462 if (raster[l] != currentValue) { | |
463 emit(new Edge(tl, bl)); | |
464 } | |
465 else { | |
466 if (!visited.get(l)) { | |
467 visited.set(l); | |
468 stack.push(l); | |
469 } | |
470 } | |
471 } | |
472 | |
473 if (x == width - 1) { | |
474 emit(new Edge(br, tr)); | |
475 } | |
476 else { | |
477 int r = current + 1; | |
478 if (raster[r] != currentValue) { | |
479 emit(new Edge(br, tr)); | |
480 } | |
481 else { | |
482 if (!visited.get(r)) { | |
483 visited.set(r); | |
484 stack.push(r); | |
485 } | |
486 } | |
487 } | |
488 | |
489 if (stack.size() == 0) { | |
490 break; | |
491 } | |
492 | |
493 current = stack.pop(); | |
494 } | |
495 | |
496 handler.handleRings( | |
497 rings, | |
498 currentValue, | |
499 width + 1, | |
500 height + 1); | |
501 | |
502 resetRegion(); | |
503 } | |
504 | |
505 return regions; | |
506 } | |
507 } | |
508 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 : |