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