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