diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gnv-artifacts/src/main/java/de/intevation/gnv/raster/Vectorizer.java	Fri Sep 28 12:13:56 2012 +0200
@@ -0,0 +1,508 @@
+package de.intevation.gnv.raster;
+
+import gnu.trove.TIntObjectHashMap;
+import gnu.trove.TIntStack;
+
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Instances of this class are able to vectorize 2D integer arrays
+ * with a kind of flood filling regions detection mechanism to
+ * a set of line strings and polygons.
+ *
+ * @author <a href="mailto:sascha.teichmann@intevation.de">Sascha L. Teichmann</a>
+ */
+public class Vectorizer
+{
+    private static Logger log = Logger.getLogger(Vectorizer.class);
+
+    /**
+     * Callback to process the found line strings and polygons.
+     */
+    public interface RingsHandler {
+
+        /**
+         * Called from {@link #process(de.intevation.gnv.raster.Vectorizer.RingsHandler) }
+         * to give the found features to the postprocessing backend.
+         * @param rings The found line strings and polygons.
+         * @param value The integer value of the raster surrounded by
+         * the features.
+         * @param width The width of the index space of the vectorized data.
+         * @param height The height of the index space of the vectorized data.
+         */
+        void handleRings(
+            List<Edge> rings,
+            int        value,
+            int        width,
+            int        height);
+
+    } // interface RingsHandler
+
+    /**
+     * Doubly link list representing a line string or a polygon.
+     */
+    public static final class Edge {
+
+        /**
+         * The predecessor.
+         */
+        public Edge prev;
+        /**
+         * The successor.
+         */
+        public Edge next;
+
+        /**
+         * Index of first vertex. To separate x and y values
+         * you have to divide the value by the width of the
+         * index space.
+         */
+        public int a;
+        /**
+         * Index of second vertex. To separate x and y values
+         * you have to divide the value by the width of the
+         * index space.
+         */
+        public int b;
+
+        /**
+         * Default constructor.
+         */
+        public Edge() {
+        }
+
+        /**
+         * Constructor to create Edge with two vertices.
+         * @param a The first vertex.
+         * @param b The second vertex.
+         */
+        public Edge(int a, int b) {
+            this.a = a;
+            this.b = b;
+        }
+
+        /**
+         * Copy constructor
+         * @param other The edge to clone.
+         */
+        public Edge(Edge other) {
+            a = other.a;
+            b = other.b;
+        }
+
+        /**
+         * Chain a given edge segment to this segment. The
+         * side of the chaining is determined by a given
+         * parameter.
+         * @param other The segment to chain.
+         * @param found The side to chain.
+         */
+        public void chain(Edge other, int found) {
+
+            if (found == a) {
+                other.next = this;
+                prev = other;
+                return;
+            }
+
+            if (found == b) {
+                next = other;
+                other.prev = this;
+                return;
+            }
+
+            throw new IllegalStateException("cannot chain");
+        }
+
+        /**
+         * Tells if the list is complete which means that this
+         * edge list is a closed polygon.
+         * @return true if edge list is closed else false.
+         */
+        public boolean isComplete() {
+            Edge current = this;
+            do {
+                if (current.prev == null || current.next == null) {
+                    return false;
+                }
+                current = current.next;
+            }
+            while (current != this);
+            return true;
+        }
+
+        /**
+         * Returns the length of this edge list in next direction.
+         * Segments in prev direction are ignored.
+         * @return The length of this edge list.
+         */
+        public int length() {
+            int length = 0;
+            Edge current = this;
+            do { ++length; }
+            while ((current = current.next) != null && current != this);
+            return length;
+        }
+
+        /**
+         * Returns the head node of this edge list.
+         * @return The head node.
+         */
+        public Edge head() {
+            Edge current = this;
+            while (current.prev != null) {
+                current = current.prev;
+            }
+            return current;
+        }
+
+        /**
+         * Hashes the two vertex indices to a common value.
+         * @return The hash value.
+         */
+        @Override
+        public int hashCode() {
+            return (a << 16) | b;
+        }
+
+        /**
+         * Two edges are considered equal if they have the same
+         * a and b vertices.
+         * @param other The other edge.
+         * @return true if the edges are equal else false.
+         */
+        @Override
+        public boolean equals(Object other) {
+            Edge e = (Edge)other;
+            return a == e.a && b == e.b;
+        }
+    } // class Edge
+
+    /**
+     * Simplifies a given edge list by removing collinear vertices.
+     * Attention: The original list is modified.
+     * @param edge The edge list to simplify.
+     * @param width The width of the vertex index space.
+     * @return The simplified list.
+     */
+    protected static Edge simplify(Edge edge, int width) {
+
+        Edge e1 = edge, start = edge;
+
+        int length = edge.length();
+
+        if (length < 2) {
+            return e1;
+        }
+
+        Edge e2 = edge.next;
+
+        int count = 0;
+
+        do {
+            int e1x = e1.a % width;
+            int e1y = e1.a / width;
+            int e2x = e1.b % width;
+            int e2y = e1.b / width;
+            int e3x = e2.b % width;
+            int e3y = e2.b / width;
+
+            if ((e1x == e2x && e2x == e3x && e1x == e3x)
+                    ||  (e1y == e2y && e2y == e3y && e1y == e3y)) {
+                e1.b = e2.b;
+                Edge removed = e1.next;
+                e1.next = e2.next;
+                if (e1.next != null) {
+                    e1.next.prev = e1;
+                }
+                e2 = e1.next;
+                count = 0;
+                --length;
+                if (removed == start) {
+                    start = e1;
+                }
+            }
+            else {
+                e1 = e1.next;
+                e2 = e2.next;
+                ++count;
+            }
+        }
+        while (length > 1 && e2 != null && count < length + 2);
+
+        return start;
+    }
+
+    /**
+     * The raster to be traced.
+     */
+    protected int []            raster;
+    /**
+     * The width of the raster.
+     */
+    protected int               width;
+    /**
+     * Map of the currently open edges.
+     */
+    protected TIntObjectHashMap openEdges;
+    /**
+     * List of rings already found.
+     */
+    protected List<Edge>        rings;
+    /**
+     * Flag to signal if a simplification should be performed
+     * after a ring is completed.
+     */
+    protected boolean           simplify;
+
+    /**
+     * Default constructor. Simplification is turned on.
+     */
+    public Vectorizer() {
+        this(true);
+    }
+
+    /**
+     * Constructor to create a vectorized with an explicit
+     * simplification policy.
+     * @param simplify Indicates if simplification should be
+     * used on ring completion.
+     */
+    public Vectorizer(boolean simplify) {
+        openEdges     = new TIntObjectHashMap();
+        rings         = new ArrayList<Edge>();
+        this.simplify = simplify;
+    }
+
+    /**
+     * Constructor to create a vectorizer with a given raster and width.
+     * Simplification is turn on.
+     * @param raster The raster to be vectorized.
+     * @param width The width of the raster.
+     */
+    public Vectorizer(int [] raster, int width) {
+        this(true, raster, width);
+    }
+
+    /**
+     * Constructor to create a vectorizer with a given raster, width
+     * and an explicit simplification policy.
+     * @param simplify Indicates if simplification should be
+     * used on ring completion.
+     * @param raster The raster to be vectorized.
+     * @param width The width of the raster.
+     */
+    public Vectorizer(boolean simplify, int [] raster, int width) {
+        this(simplify);
+        this.raster = raster;
+        this.width  = width;
+    }
+
+    /**
+     * Returns (x, y+1) for given vertex in index space.
+     * @param i vertex in index space.
+     * @param w width of raster.
+     * @return (x, y+1) in index space.
+     */
+    public static final int tl(int i, int w) {
+        int x = i % w;
+        int y = i / w;
+        return x + (w + 1)*y;
+    }
+
+    /**
+     * Returns tl(i, w) + 1.
+     * @param i vertex in index space.
+     * @param w width of raster.
+     * @return tl(i, w) + 1.
+     */
+    public static final int tr(int i, int w) {
+        return tl(i, w) + 1;
+    }
+
+    /**
+     * Returns tl(i, w) + w + 1.
+     * @param i vertex in index space.
+     * @param w width of raster.
+     * @return tl(i, w) + w + 1.
+     */
+    public static final int bl(int i, int w) {
+        return tl(i, w) + w + 1;
+    }
+
+    /**
+     * Returns bl(i, w) + 1.
+     * @param i vertex in index space.
+     * @param w width of raster.
+     * @return bl(i, w) + 1.
+     */
+    public static final int br(int i, int w) {
+        return bl(i, w) + 1;
+    }
+
+    /**
+     * Resets open resources after a set of features were found.
+     */
+    protected void resetRegion() {
+        openEdges.clear();
+        rings.clear();
+    }
+
+    /**
+     * Adds an edge to the map of open edges, joins it
+     * with its direct neighbors of if complete add the
+     * list to the complete features.
+     * @param edge
+     */
+    protected void emit(Edge edge) {
+
+        Edge otherA = (Edge)openEdges.remove(edge.a);
+        if (otherA != null) {
+            otherA.chain(edge, edge.a);
+        }
+
+        Edge otherB = (Edge)openEdges.remove(edge.b);
+        if (otherB != null) {
+            otherB.chain(edge, edge.b);
+        }
+
+        if (edge.isComplete()) {
+            rings.add(simplify ? simplify(edge, width + 1) : edge);
+        }
+        else {
+            if (otherA == null) {
+                openEdges.put(edge.a, edge);
+            }
+            if (otherB == null) {
+                openEdges.put(edge.b, edge);
+            }
+        }
+    }
+
+    /**
+     * Vectorize the raster. The found features are fed into
+     * the given ring handler.
+     * @param handler The RingHandler to postprocess the found features.
+     * @return The number of regions found.
+     */
+    public int process(RingsHandler handler) {
+
+        BitSet visited = new BitSet(raster.length);
+
+        TIntStack stack = new TIntStack();
+
+        int regions = 0;
+
+        int height = raster.length / width;
+
+        for (int i = 0; i < raster.length; ++i) {
+            if (visited.get(i)) {
+                continue;
+            }
+
+            ++regions;
+
+            int currentValue = raster[i];
+            visited.set(i);
+
+            int current = i;
+            visited.set(current);
+
+            for (;;) {
+                int tl = tl(current, width);
+                int tr = tr(current, width);
+                int bl = bl(current, width);
+                int br = br(current, width);
+
+                int t = current - width;
+
+                if (t < 0) {
+                    emit(new Edge(tr, tl));
+                }
+                else {
+                    if (raster[t] != currentValue) {
+                        emit(new Edge(tr, tl));
+                    }
+                    else {
+                        if (!visited.get(t)) {
+                            visited.set(t);
+                            stack.push(t);
+                        }
+                    }
+                }
+
+                int b = current + width;
+
+                if (b >= raster.length) {
+                    emit(new Edge(bl, br));
+                }
+                else {
+                    if (raster[b] != currentValue) {
+                        emit(new Edge(bl, br));
+                    }
+                    else {
+                        if (!visited.get(b)) {
+                            visited.set(b);
+                            stack.push(b);
+                        }
+                    }
+                }
+
+                int x = current % width;
+
+                if (x == 0) {
+                    emit(new Edge(tl, bl));
+                }
+                else {
+                    int l = current - 1;
+                    if (raster[l] != currentValue) {
+                        emit(new Edge(tl, bl));
+                    }
+                    else {
+                        if (!visited.get(l)) {
+                            visited.set(l);
+                            stack.push(l);
+                        }
+                    }
+                }
+
+                if (x == width - 1) {
+                    emit(new Edge(br, tr));
+                }
+                else {
+                    int r = current + 1;
+                    if (raster[r] != currentValue) {
+                        emit(new Edge(br, tr));
+                    }
+                    else {
+                        if (!visited.get(r)) {
+                            visited.set(r);
+                            stack.push(r);
+                        }
+                    }
+                }
+
+                if (stack.size() == 0) {
+                    break;
+                }
+
+                current = stack.pop();
+            }
+
+            handler.handleRings(
+                rings,
+                currentValue,
+                width + 1,
+                height + 1);
+
+            resetRegion();
+        }
+
+        return regions;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org