/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2002-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.build.line; import java.util.HashMap; import java.util.List; import java.util.Map; import com.vividsolutions.jts.index.bintree.Bintree; import com.vividsolutions.jts.index.bintree.Interval; import org.geotools.graph.build.GraphBuilder; import org.geotools.graph.build.GraphGenerator; import org.geotools.graph.build.basic.BasicGraphBuilder; import org.geotools.graph.structure.Edge; import org.geotools.graph.structure.Graph; import org.geotools.graph.structure.Graphable; import org.geotools.graph.structure.Node; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.LineSegment; /** * An implementation of GraphGenerator used to generate a graph representing a * line network. Graphs are generated by supplying the generator with objects * of type LineSegment via the add(Object) method. <BR> * <BR> * For each line segment added, an edge in the graph is created. The builder * records the end coordinates of each line added, and maintains a map of * coordinates to nodes, creating nodes when neccessary.<BR> * <BR> * If a tolerance distance is set, the end coordinates matched to nodes with * a tolerance distance (using a spatial index). <BR> * <BR> * Edges created by the generator are of type BasicEdge and contain an object * of type LineSegment.<BR> * Nodes created by the generator are of type BasicXYNode and contain an object * of type Coordinate. * * @see org.geotools.graph.structure.line.BasicXYNode * @see org.geotools.graph.structure.basic.BasicEdge * @see com.vividsolutions.jts.geom.LineSegment * @see com.vividsolutions.jts.geom.Coordinate * * @author Justin Deoliveira, Refractions Research Inc, jdeolive@refractions.net * @author Anders Bakkevold, Bouvet AS, bakkedev@gmail.com * * @source $URL$ */ public class BasicLineGraphGenerator implements LineGraphGenerator { /** coordinate to node map **/ private HashMap<Coordinate,Node> m_coord2node; /** underlying builder **/ private GraphBuilder m_builder; /** tolerance distance **/ private double tolerance = 0.0; /** used when tolerance is greater than 0.0 */ private Bintree spatialIndex; /** * Constructs a new BasicLineGraphGenerator. * <p> * Tolerance is 0.0 as default, meaning coordinates must be equal for lines to connect * at a node. * </p> */ public BasicLineGraphGenerator () { m_coord2node = new HashMap<Coordinate, Node>(); setGraphBuilder(new BasicGraphBuilder()); } /** * Constructs a new BasicLineGraphGenerator. * <p> * If two coordinates are considered equal (and should be snapped to the same Node), * the distance between them must be less than the tolerance value. * </p> * @param tolerance threshold distance value for coordinates to be considered equal */ public BasicLineGraphGenerator (double tolerance) { this.tolerance = tolerance; spatialIndex = new Bintree(); m_coord2node = new HashMap<Coordinate,Node>(); setGraphBuilder(new BasicGraphBuilder()); } /** * Adds a line to the graph. * * @param obj An instance of LineSegment. * * @return A BasicEdge. * * @see LineSegment * @see GraphGenerator#add(Object) */ public Graphable add(Object obj) { LineSegment line = (LineSegment)obj; Coordinate first,last; Node n1, n2; //check first coordinate first = line.p0; n1 = retrieveNode(first); if (n1 == null) { n1 = createNode(first); } //check second coordinate last = line.p1; n2 = retrieveNode(last); if (n2 == null) { n2 = createNode(last); } //build the edge setting underlying object to line Edge e = getGraphBuilder().buildEdge(n1,n2); getGraphBuilder().addEdge(e); if (useTolerance()) { line = alterLine(line, n1, n2); } setObject(e, line); //return the created edge return(e); } protected LineSegment alterLine(LineSegment line, Node n1, Node n2) { Coordinate c1added = ((Coordinate) n1.getObject()); Coordinate c2added = ((Coordinate) n2.getObject()); if (!c1added.equals2D(line.p0) || c2added.equals2D(line.p1)) { return new LineSegment(c1added,c2added); } return line; } /** * Returns the edge which represents a line. Note that if the exact same line * has been added to the graph multiple times, then only one of the edges that * represents it will be returned. It is undefined which edge will be returned. * * @param obj An instance of LineSegment. * * @return Edge that represents the line. * * @see GraphGenerator#get(Object) */ public Graphable get(Object obj) { LineSegment line = (LineSegment)obj; //get nodes representing coordinate Node n1 = retrieveNode(line.p0); Node n2 = retrieveNode(line.p1); if (n1 == null || n2 == null) return(null); //return edge shared between them return(n1.getEdge(n2)); //note: if there are identical lines in the graph then it is undefined //which of them will be returned } /** * Removes the edge from the graph that represents a line. * * @return Edge that represents the line. * * @see GraphGenerator#remove(Object) */ public Graphable remove(Object obj) { LineSegment line = (LineSegment)obj; Node n1 = retrieveNode(line.p0); Node n2 = retrieveNode(line.p1); if (n1 == null || n2 == null) return(null); Edge e = n1.getEdge(n2); getGraphBuilder().removeEdge(e); return(e); } /** * @see GraphGenerator#setGraphBuilder(GraphBuilder) */ public void setGraphBuilder(GraphBuilder builder) { m_builder = builder; } /** * @see GraphGenerator#getGraphBuilder() */ public GraphBuilder getGraphBuilder() { return(m_builder); } /** * @see GraphGenerator#getGraph() */ public Graph getGraph() { return(getGraphBuilder().getGraph()); } /** * Returns the coordinate to node map used to build nodes representing line * endpoint coordinates. * * @return coordinate to node map. */ public Map getNodeMap() { return(m_coord2node); } //TODO COMMENT ME! public Node getNode(Coordinate c) { return retrieveNode(c); } public Edge getEdge(Coordinate c1, Coordinate c2) { Node n1 = retrieveNode(c1); Node n2 = retrieveNode(c2); return(n1.getEdge(n2)); } protected void setObject(Edge e, Object obj) { e.setObject(obj); } protected void setObject(Node n, Object obj) { n.setObject(obj); } private Node createNode(Coordinate c) { Node node; node = getGraphBuilder().buildNode(); setObject(node, c); getGraphBuilder().addNode(node); m_coord2node.put(c, node); if (useTolerance()) { spatialIndex.insert(new Interval(c.y, c.y), c); } return node; } private Node retrieveNode(Coordinate c) { Node node = m_coord2node.get(c); if (node == null && useTolerance()) { node = findClosestNodeWithinTolerance(c); } return node; } protected boolean useTolerance() { return tolerance > 0.0; } // spatial search with tolerance private Node findClosestNodeWithinTolerance(Coordinate inCoord) { double closestDistance = Double.MAX_VALUE; Coordinate closestCoordinate = null; List<Coordinate> list = spatialIndex.query(new Interval(inCoord.y - tolerance, inCoord.y + tolerance)); for (Coordinate c : list) { double distance = inCoord.distance(c); if (distance < closestDistance) { closestDistance = distance; closestCoordinate = c; } } if (closestCoordinate != null && closestCoordinate.distance(inCoord) < tolerance) { return m_coord2node.get(closestCoordinate); } return null; } }