/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2003-2008, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.graph.util.graph; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.geotools.graph.build.GraphBuilder; import org.geotools.graph.structure.Edge; import org.geotools.graph.structure.Graph; import org.geotools.graph.structure.GraphVisitor; import org.geotools.graph.structure.Graphable; import org.geotools.graph.structure.Node; import org.geotools.graph.traverse.GraphTraversal; import org.geotools.graph.traverse.GraphWalker; import org.geotools.graph.traverse.basic.BasicGraphTraversal; import org.geotools.graph.traverse.basic.SourceGraphIterator; import org.geotools.graph.traverse.standard.DepthFirstTopologicalIterator; import org.geotools.graph.traverse.standard.NoBifurcationIterator; /** * Removes all nodes of degree 2 from a graph. The following are examples * of graphs being fused.<BR> * <BR> * <IMG src="doc-files/fuse_0.gif"/><BR> * <BR> * <IMG src="doc-files/fuse_1.gif"/><BR> * <BR> * <IMG src="doc-files/fuse_2.gif"/><BR> * <BR> * When a node of degree 2 is removed from tan unfused graph, the two edges * is is adjacent to must be merged into a single edge. More generally if n * adjacent nodes of degree 2 are removed, n+1 edges must be merged into a * single edge. This change in graph structure has an effect on the entities * modelled by the graph. Since each edge models a single object, replacing * multiple edges with a single edge results in an inconsistet model. Therefore * an EdgeMerger is used to merge the objects represented by the multiple edges * into a single object. This new object becomes the underlying object of the * merged edge. * * @author Justin Deoliveira, Refractions Research Inc, jdeolive@refractions.net * * * @source $URL$ */ public class GraphFuser { /** the graph being fused **/ private Graph m_graph; /** the builder used to modify the graph being fused **/ private GraphBuilder m_builder; /** the edge merger **/ private EdgeMerger m_merger; /** traversal used during the graph fuse **/ private GraphTraversal m_traversal; /** walker used in the traversal during graph fuse **/ private GraphWalker m_walker; /** counter used to track number of unvisited nodes of degree 2 **/ private int m_ndegree2; /** collection of node sets to fuse **/ private ArrayList m_sets; /** individual node set **/ private ArrayList m_nodes; /** * Constructs a GraphFuser. * * @param graph Graph to fuse. * @param builder GraphBuilder used to fuse graph. * @param merger Used to merge edges. */ public GraphFuser(Graph graph, GraphBuilder builder, EdgeMerger merger) { m_graph = graph; m_builder = builder; m_merger = merger; } /** * Performs the fuse. * * @return True if the fuse was successful, otherwise false. */ public boolean fuse() { //create walker for first stage // if the walker sees a node of degree 2 it adds it to the current // set of nodes, else it starts a new set m_walker = new GraphWalker() { public int visit(Graphable element, GraphTraversal traversal) { Node node = (Node)element; //if the node is not of degree 2, start a new set if (node.getDegree() != 2) { finish(); } else { //add node to current set m_nodes.add(node); m_ndegree2--; } return(GraphTraversal.CONTINUE); } public void finish() { //no need to recreate if empty if (!m_nodes.isEmpty()) { m_sets.add(m_nodes); m_nodes = new ArrayList(); } } }; //perform a topological depth first traversal m_traversal = new BasicGraphTraversal( m_graph, m_walker, new DepthFirstTopologicalIterator() ); //initialise set and node collections m_sets = new ArrayList(); m_nodes = new ArrayList(); m_ndegree2 = m_graph.getNodesOfDegree(2).size(); if (m_ndegree2 == 0) return(true); // nothing to fuse m_traversal.init(); //reset edge visited flags m_graph.visitNodes( new GraphVisitor() { public int visit(Graphable component) { component.setVisited(false); return 0; } } ); //perform the traversal m_traversal.traverse(); //if all nodes of degree 2 have been visited, we are finished if (m_ndegree2 > 0) { //if there are still nodes of degree 2 that havent been visited, it means // that the graph has a cycle and that the remaining degree 2 nodes are // internal to the cycle, so the strategy for the second stage is to // find all unvisited nodes of degree 2 that are not visited and start // a no bifurcation traversal from them Iterator sources = m_graph.queryNodes( new GraphVisitor() { public int visit(Graphable component) { Node node = (Node)component; if (!node.isVisited() && node.getDegree() == 2) { //check for adjacent node of degree > 2 for (Iterator itr = node.getRelated(); itr.hasNext(); ) { Node rel = (Node)itr.next(); if (rel.getDegree() > 2) return(Graph.PASS_AND_CONTINUE); } } return(Graph.FAIL_QUERY); } } ).iterator(); //if the query returned no nodes, it means that all the cycle is // disconnected from the rest of graph, so just pick any node of degree 2 if (!sources.hasNext()) { sources = m_graph.queryNodes( new GraphVisitor() { public int visit(Graphable component) { if (!component.isVisited()) return(Graph.PASS_AND_STOP); return(Graph.FAIL_QUERY); } } ).iterator(); } //create stage 2 walker, simple add any nodes visited nodes to the // current node set m_walker = new GraphWalker() { public int visit(Graphable element, GraphTraversal traversal) { m_ndegree2--; m_nodes.add(element); return(GraphTraversal.CONTINUE); } public void finish() { m_sets.add(m_nodes); m_nodes = new ArrayList(); } }; m_traversal.setWalker(m_walker); m_traversal.setIterator(new NoBifurcationIterator()); //clear current node list m_nodes = new ArrayList(); Node source = null; while(sources.hasNext()) { while(sources.hasNext()) { source = (Node)sources.next(); if (source.isVisited()) continue; ((SourceGraphIterator)m_traversal.getIterator()).setSource(source); m_traversal.traverse(); } } } // should be zero, if not something wierd not accounted for if(m_ndegree2 == 0) { //build the fused graph for(Iterator sitr = m_sets.iterator(); sitr.hasNext();) { ArrayList nodes = (ArrayList)sitr.next(); ArrayList edges = new ArrayList(); Node first = null; //node of degree != 2 adjacent to first node in set Node last = null; //node of degree != 2 adjacent to last node in set if (nodes.size() == 1) { //set first and last to be related nodes Iterator related = ((Node)nodes.get(0)).getRelated(); first = (Node)related.next(); last = (Node)related.next(); edges.addAll(((Node)nodes.get(0)).getEdges()); } else { //get the node of degree != 2 adjacent to first node in set Node node = (Node)nodes.get(0); Iterator rel = node.getRelated(); first = (Node)rel.next(); if (first.equals(nodes.get(1))) first = (Node)rel.next(); //get the node of degree != 2 adjacent to last node in set node = (Node)nodes.get(nodes.size()-1); rel = node.getRelated(); last = (Node)rel.next(); if (last.equals(nodes.get(nodes.size()-2))) last = (Node)rel.next(); //check to see that the first node is not of degree 2, if it is we // have a set of nodes that forms a cycle with no bifurcations // set first to point to node at index 1, and last index x, also // remove node at index 0 from node set so it doesn't get removed if (first.getDegree() == 2) { first = (Node)nodes.get(1); last = (Node)nodes.get(0); first = last = (Node)nodes.get(0); //remove first node from list so that it doesn't get deleted nodes.remove(0); } //add edge between first node in set, and the node of degree != 2 // that is adjacent to it edges.add(first.getEdge((Node)nodes.get(0))); //add middle edges for (int i = 1; i < nodes.size(); i++) { Node curr = (Node)nodes.get(i); Node prev = (Node)nodes.get(i-1); edges.add(curr.getEdge(prev)); } //add edge between last node in set, and the node of degree != 2 // that is adjacent to it edges.add(last.getEdge((Node)nodes.get(nodes.size()-1))); } //merge the underlying objects of the edges into a single object Object obj = m_merger.merge(edges); //remove the old nodes from the graph m_builder.removeNodes(nodes); //add merged object to the generator //create the new edge Edge newEdge = m_builder.buildEdge(first, last); //set the underlying object to be the merged object we created m_merger.setMergedObject(newEdge, obj, edges); //add the edge to the builder m_builder.addEdge(newEdge); } return(true); } return(false); } // /** // * Sets the object for the newly created edge. // * // * @param newEdge The edge created to represented the merged object. // * @param merged The merged object. // */ // public void setObject(Edge newEdge, Object merged) { // newEdge.setObject(merged); // } /** * Merges the underlying objects represented by a number of edges into a * single object. * * @author Justin Deoliveira, Refractions Research Inc, jdeolive@refractions.net * */ public static interface EdgeMerger { /** * Creates a single object from collection of underlying objects represented * by a collection of edges. * * @param edges A collection of edges. * * @return A single object. */ public Object merge(List edges); /** * Sets the object for the edge created to represented the merged object. * * @param newEdge The edge created to represent the merged object. * @param merged The merged object. * @param edges The original edges that were merged */ public void setMergedObject(Edge newEdge, Object merged, List edges); } }