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 :

http://dive4elements.wald.intevation.org