/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2006-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.delaunay; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Vector; import java.util.logging.Logger; import org.geotools.graph.structure.Edge; import org.geotools.graph.structure.Graph; import org.geotools.graph.structure.Node; import org.geotools.graph.structure.basic.BasicGraph; /** * * This class implements the AUTOCLUST algorithm of Estivill-Castro and Lee (2002) * "Argument free clustering for large spatial point-data sets via boundary extraction * from Delaunay Diagram" in Computers, Environment and Urban Systems, 26:315-334. * * @author jfc173 * * * @source $URL$ */ public class AutoClust { private static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.geotools.graph"); /** Creates a new instance of AutoClust */ public AutoClust() { } public static Graph runAutoClust(Graph d){ //this is assuming d is a delaunay triangulation. If it isn't, results are unspecified. //Such a diagram can be obtained through org.geotools.graph.util.delaunay.DelaunayTriangulator. HashMap map = new HashMap(); Collection nodes = d.getNodes(); Collection edges = d.getEdges(); showGraph(nodes, edges, 0); Iterator nodeIt = nodes.iterator(); double[] localDevs = new double[nodes.size()]; int index = 0; while (nodeIt.hasNext()){ DelaunayNode next = (DelaunayNode) nodeIt.next(); AutoClustData acd = new AutoClustData(); List localEdges = AutoClustUtils.findAdjacentEdges(next, edges); double totalLength = 0; Iterator edgeIt = localEdges.iterator(); while (edgeIt.hasNext()){ DelaunayEdge nextEdge = (DelaunayEdge) edgeIt.next(); totalLength = totalLength + nextEdge.getEuclideanDistance(); } double meanLength = totalLength/localEdges.size(); double sumOfSquaredDiffs = 0; Iterator anotherEdgeIt = localEdges.iterator(); while (anotherEdgeIt.hasNext()){ DelaunayEdge nextEdge = (DelaunayEdge) anotherEdgeIt.next(); sumOfSquaredDiffs = sumOfSquaredDiffs + Math.pow((nextEdge.getEuclideanDistance() - meanLength), 2); } double variance = sumOfSquaredDiffs/(localEdges.size()); double stDev = Math.sqrt(variance); localDevs[index] = stDev; index++; acd.setLocalMean(meanLength); acd.setLocalStDev(stDev); map.put(next, acd); } double total = 0; for (int i = 0; i < localDevs.length; i++){ total = total + localDevs[i]; } double meanStDev = total/localDevs.length; //these three are for coloring the graph in the poster, not for algorithmic purposes Vector allShortEdges = new Vector(); Vector allLongEdges = new Vector(); Vector allOtherEdges = new Vector(); Iterator anotherNodeIt = nodes.iterator(); while (anotherNodeIt.hasNext()){ DelaunayNode next = (DelaunayNode) anotherNodeIt.next(); List localEdges = AutoClustUtils.findAdjacentEdges(next, edges); AutoClustData acd = (AutoClustData) map.get(next); Iterator edgeIt = localEdges.iterator(); Vector shortEdges = new Vector(); Vector longEdges = new Vector(); Vector otherEdges = new Vector(); LOGGER.fine("local mean is " + acd.getLocalMean()); LOGGER.fine("mean st dev is " + meanStDev); while (edgeIt.hasNext()){ DelaunayEdge nextEdge = (DelaunayEdge) edgeIt.next(); double length = nextEdge.getEuclideanDistance(); if (length < acd.getLocalMean() - meanStDev){ shortEdges.add(nextEdge); LOGGER.finer(nextEdge + ": length " + nextEdge.getEuclideanDistance() + " is short"); } else if (length > acd.getLocalMean() + meanStDev){ longEdges.add(nextEdge); LOGGER.finer(nextEdge + ": length " + nextEdge.getEuclideanDistance() + " is long"); } else { otherEdges.add(nextEdge); LOGGER.finer(nextEdge + ": length " + nextEdge.getEuclideanDistance() + " is medium"); } } acd.setShortEdges(shortEdges); acd.setLongEdges(longEdges); acd.setOtherEdges(otherEdges); allLongEdges.addAll(longEdges); allShortEdges.addAll(shortEdges); allOtherEdges.addAll(otherEdges); } //for the poster Graph gp = new BasicGraph(nodes, edges); javax.swing.JFrame frame = new javax.swing.JFrame(); GraphViewer viewer = new GraphViewer(); viewer.setLongEdges(allLongEdges); viewer.setShortEdges(allShortEdges); viewer.setOtherEdges(allOtherEdges); viewer.setColorEdges(true); viewer.setGraph(gp); frame.getContentPane().add(viewer); frame.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE); frame.setSize(new java.awt.Dimension(800, 800)); frame.setTitle("Assigned edge categories"); frame.setVisible(true); //Phase I Iterator nodeIt3 = nodes.iterator(); while (nodeIt3.hasNext()){ DelaunayNode next = (DelaunayNode) nodeIt3.next(); AutoClustData acd = (AutoClustData) map.get(next); List shortEdges = acd.getShortEdges(); List longEdges = acd.getLongEdges(); edges.removeAll(shortEdges); LOGGER.finer("removed " + shortEdges); edges.removeAll(longEdges); LOGGER.finer("removed " + longEdges); } LOGGER.fine("End of phase one and "); LOGGER.fine("Nodes are " + nodes); LOGGER.fine("Edges are " + edges); showGraph(nodes, edges, 1); Vector connectedComponents = AutoClustUtils.findConnectedComponents(nodes, edges); //Phase II Iterator nodeIt4 = nodes.iterator(); while (nodeIt4.hasNext()){ DelaunayNode next = (DelaunayNode) nodeIt4.next(); AutoClustData acd = (AutoClustData) map.get(next); List shortEdges = acd.getShortEdges(); if (!(shortEdges.isEmpty())){ Vector shortlyConnectedComponents = new Vector(); Iterator shortIt = shortEdges.iterator(); while (shortIt.hasNext()){ Edge nextEdge = (Edge) shortIt.next(); Node other = nextEdge.getOtherNode(next); Graph g = getMyComponent(other, connectedComponents); if (!(shortlyConnectedComponents.contains(g))){ shortlyConnectedComponents.add(g); } } Graph cv = null; if (shortlyConnectedComponents.size() > 1){ Iterator sccIt = shortlyConnectedComponents.iterator(); int maxSize = 0; while (sccIt.hasNext()){ Graph nextGraph = (Graph) sccIt.next(); int size = nextGraph.getNodes().size(); if (size > maxSize){ maxSize = size; cv = nextGraph; } } } else { cv = (Graph) shortlyConnectedComponents.get(0); } Iterator shortIt2 = shortEdges.iterator(); while (shortIt2.hasNext()){ Edge nextEdge = (Edge) shortIt2.next(); Node other = nextEdge.getOtherNode(next); if (cv.equals(getMyComponent(other, shortlyConnectedComponents))){ edges.add(nextEdge); } } } //end if shortEdges isn't empty Graph gr = getMyComponent(next, connectedComponents); if (gr.getNodes().size() == 1){ Vector shortlyConnectedComponents = new Vector(); Iterator shortIt = shortEdges.iterator(); while (shortIt.hasNext()){ Edge nextEdge = (Edge) shortIt.next(); Node other = nextEdge.getOtherNode(next); Graph g = getMyComponent(other, connectedComponents); if (!(shortlyConnectedComponents.contains(g))){ shortlyConnectedComponents.add(g); } } if (shortlyConnectedComponents.size() == 1){ edges.addAll(shortEdges); } } } //end nodeIt4 while loop. LOGGER.fine("End of phase two and "); LOGGER.fine("Nodes are " + nodes); LOGGER.fine("Edges are " + edges); showGraph(nodes, edges, 2); connectedComponents = AutoClustUtils.findConnectedComponents(nodes, edges); //Phase III Iterator nodeIt5 = nodes.iterator(); while (nodeIt5.hasNext()){ DelaunayNode next = (DelaunayNode) nodeIt5.next(); Vector edgesWithinTwo = new Vector(); List adjacentEdges = AutoClustUtils.findAdjacentEdges(next, edges); //yes, next.getEdges() could work, but there's no guarantee that next's edge list is current anymore edgesWithinTwo.addAll(adjacentEdges); Iterator adjacentIt = adjacentEdges.iterator(); while (adjacentIt.hasNext()){ Edge nextEdge = (Edge) adjacentIt.next(); Node other = nextEdge.getOtherNode(next); List adjacentToOther = AutoClustUtils.findAdjacentEdges(other, edges); //yes, other.getEdges() could work, but there's no guarantee that other's edge list is current anymore Iterator atoIt = adjacentToOther.iterator(); while (atoIt.hasNext()){ Edge nextEdge2 = (Edge) atoIt.next(); if (!(edgesWithinTwo.contains(nextEdge2))){ edgesWithinTwo.add(nextEdge2); } } } double totalLength = 0; Iterator ewtIt = edgesWithinTwo.iterator(); while (ewtIt.hasNext()){ totalLength = totalLength + ((DelaunayEdge) ewtIt.next()).getEuclideanDistance(); } double local2Mean = totalLength/edgesWithinTwo.size(); Iterator ewtIt2 = edgesWithinTwo.iterator(); while (ewtIt2.hasNext()){ DelaunayEdge dEdge = (DelaunayEdge) ewtIt2.next(); if (dEdge.getEuclideanDistance() > (local2Mean + meanStDev)){ edges.remove(dEdge); } } } //end nodeIt5 loop LOGGER.fine("End of phase three and "); LOGGER.fine("Nodes are " + nodes); LOGGER.fine("Edges are " + edges); showGraph(nodes, edges, 3); connectedComponents = AutoClustUtils.findConnectedComponents(nodes, edges); return new BasicGraph(nodes, edges); } private static Graph getMyComponent(Node node, Vector components){ Iterator it = components.iterator(); Graph ret = null; boolean found = false; while ((it.hasNext()) && (!(found))){ Graph next = (Graph) it.next(); if (next.getNodes().contains(node)){ found = true; ret = next; } } if (ret == null){ throw new RuntimeException("Couldn't find the graph component containing node: " + node); } return ret; } private static void showGraph(Collection nodes, Collection edges, int phase){ Graph g = new BasicGraph(nodes, edges); javax.swing.JFrame frame = new javax.swing.JFrame(); GraphViewer viewer = new GraphViewer(); viewer.setGraph(g); frame.getContentPane().add(viewer); frame.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE); frame.setSize(new java.awt.Dimension(800, 800)); frame.setTitle("Phase " + phase); frame.setVisible(true); } }