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 :

http://dive4elements.wald.intevation.org