/** * H2GIS is a library that brings spatial support to the H2 Database Engine * <http://www.h2database.com>. H2GIS is developed by CNRS * <http://www.cnrs.fr/>. * * This code is part of the H2GIS project. H2GIS 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 3.0 of the License. * * H2GIS 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 <http://www.gnu.org/licenses/>. * * * For more information, please consult: <http://www.h2gis.org/> * or contact directly: info_at_h2gis.org */ package org.h2gis.network.functions; import java.sql.*; import static org.h2gis.network.functions.GraphConstants.EDGE_ID; import static org.h2gis.network.functions.GraphConstants.END_NODE; import static org.h2gis.network.functions.GraphConstants.START_NODE; import static org.h2gis.network.functions.GraphFunction.logTime; import org.h2gis.utilities.TableUtilities; import org.javanetworkanalyzer.data.VId; import org.javanetworkanalyzer.model.*; import org.jgrapht.WeightedGraph; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Creates a JGraphT graph from an edges table produced by {@link * org.h2gis.functions.spatial.topology.ST_Graph}. The graph has the * vertex and edge classes passed to the constructor. Its global and edge * orientations and weights are specified by the strings passed to the * constructor. * * @author Adam Gouge */ public class GraphCreator<V extends VId, E extends Edge> { private final Class<? extends V> vertexClass; private final Class<? extends E> edgeClass; private final Connection connection; private int startNodeIndex = -1; private int endNodeIndex = -1; private int edgeIDIndex = -1; private int weightColumnIndex = -1; private int edgeOrientationIndex = -1; private final String inputTable; private final String weightColumn; private final GraphFunctionParser.Orientation globalOrientation; private final String edgeOrientationColumnName; public static final int DIRECTED_EDGE = 1; public static final int REVERSED_EDGE = -DIRECTED_EDGE; public static final int UNDIRECTED_EDGE = DIRECTED_EDGE + REVERSED_EDGE; private static final Logger LOGGER = LoggerFactory.getLogger("gui." + GraphCreator.class); /** * Constructor. * * @param connection Connection * @param inputTable Name of edges table from {@link org.h2gis.functions.spatial.topology.ST_Graph}. * @param globalOrientation Global orientation * @param edgeOrientationColumnName Edge orientation * @param weightColumn Weight column name * @param vertexClass Vertex class * @param edgeClass Edge class */ public GraphCreator(Connection connection, String inputTable, GraphFunctionParser.Orientation globalOrientation, String edgeOrientationColumnName, String weightColumn, Class<? extends V> vertexClass, Class<? extends E> edgeClass) { this.connection = connection; this.inputTable = inputTable; this.weightColumn = weightColumn; this.globalOrientation = globalOrientation; this.edgeOrientationColumnName = edgeOrientationColumnName; this.vertexClass = vertexClass; this.edgeClass = edgeClass; } /** * Prepares a graph. * * @return The newly prepared graph, or null if the graph could not * be created * * @throws java.sql.SQLException */ protected KeyedGraph<V, E> prepareGraph() throws SQLException { LOGGER.info("Loading graph into memory..."); final long start = System.currentTimeMillis(); // Initialize the graph. KeyedGraph<V, E> graph; if (!globalOrientation.equals(GraphFunctionParser.Orientation.UNDIRECTED)) { if (weightColumn != null) { graph = new DirectedWeightedPseudoG<V, E>(vertexClass, edgeClass); } else { graph = new DirectedPseudoG<V, E>(vertexClass, edgeClass); } } else { if (weightColumn != null) { graph = new WeightedPseudoG<V, E>(vertexClass, edgeClass); } else { graph = new PseudoG<V, E>(vertexClass, edgeClass); } } final Statement st = connection.createStatement(); final ResultSet edges = st.executeQuery("SELECT * FROM " + TableUtilities.parseInputTable(connection, inputTable)); // Initialize the indices. initIndices(edges); try { // Add the edges. while (edges.next()) { loadEdge(graph, edges); } logTime(LOGGER, start); return graph; } catch (SQLException e) { LOGGER.error("Could not store edges in graph.", e); return null; } finally { edges.close(); st.close(); } } /** * Recovers the indices from the metadata. */ private void initIndices(ResultSet edges) { try { ResultSetMetaData metaData = edges.getMetaData(); for (int i = 1; i <= metaData.getColumnCount(); i++) { final String columnName = metaData.getColumnName(i); if (columnName.equalsIgnoreCase(START_NODE)) startNodeIndex = i; if (columnName.equalsIgnoreCase(END_NODE)) endNodeIndex = i; if (columnName.equalsIgnoreCase(EDGE_ID)) edgeIDIndex = i; if (columnName.equalsIgnoreCase(edgeOrientationColumnName)) edgeOrientationIndex = i; if (columnName.equalsIgnoreCase(weightColumn)) weightColumnIndex = i; } } catch (SQLException e) { LOGGER.error("Problem accessing edge table metadata.", e); } verifyIndex(startNodeIndex, START_NODE); verifyIndex(endNodeIndex, END_NODE); verifyIndex(edgeIDIndex, EDGE_ID); if (!globalOrientation.equals(GraphFunctionParser.Orientation.UNDIRECTED)) { verifyIndex(edgeOrientationIndex, edgeOrientationColumnName); } if (weightColumn != null) { verifyIndex(weightColumnIndex, weightColumn); } } /** * Verifies that the given index is not equal to -1; if it is, then throws * an exception saying that the given field is missing. * * @param index The index. * @param missingField The field. */ private static void verifyIndex(int index, String missingField) { if (index == -1) { throw new IndexOutOfBoundsException("Column \"" + missingField + "\" not found."); } } /** * Loads an edge into the graph from the current row. * * @param graph The graph to which the edges will be added. * * @return The newly loaded edge. */ private E loadEdge(KeyedGraph<V, E> graph, ResultSet edges) throws SQLException { final int startNode = edges.getInt(startNodeIndex); final int endNode = edges.getInt(endNodeIndex); final int edgeID = edges.getInt(edgeIDIndex); double weight = WeightedGraph.DEFAULT_EDGE_WEIGHT; if (weightColumnIndex != -1) { weight = edges.getDouble(weightColumnIndex); } E edge; // Undirected graphs are either pseudographs or weighted pseudographs, // so there is no need to add edges in both directions. if (globalOrientation.equals(GraphFunctionParser.Orientation.UNDIRECTED)) { edge = graph.addEdge(endNode, startNode, edgeID); } else { // Directed graphs are either directed pseudographs or directed // weighted pseudographs and must specify an orientation for each // individual edge. If no orientations are specified, every edge // is considered to be directed with orientation given by the // geometry. int edgeOrientation = (edgeOrientationIndex == -1) ? DIRECTED_EDGE : edges.getInt(edgeOrientationIndex); if (edges.wasNull()) { throw new IllegalArgumentException("Invalid edge orientation: NULL."); } if (edgeOrientation == UNDIRECTED_EDGE) { if (globalOrientation.equals(GraphFunctionParser.Orientation.DIRECTED)) { edge = loadDoubleEdge(graph, startNode, endNode, edgeID, weight); } // globalOrientation == Orientation.REVERSED else { edge = loadDoubleEdge(graph, endNode, startNode, edgeID, weight); } } else if (edgeOrientation == DIRECTED_EDGE) { // Reverse a directed edge (global). if (globalOrientation.equals(GraphFunctionParser.Orientation.REVERSED)) { edge = graph.addEdge(endNode, startNode, edgeID); } // No reversal. else { edge = graph.addEdge(startNode, endNode, edgeID); } } else if (edgeOrientation == REVERSED_EDGE) { // Reversing twice is the same as no reversal. if (globalOrientation.equals(GraphFunctionParser.Orientation.REVERSED)) { edge = graph.addEdge(startNode, endNode, edgeID); } // Otherwise reverse just once (local). else { edge = graph.addEdge(endNode, startNode, edgeID); } } else { throw new IllegalArgumentException("Invalid edge orientation: " + edgeOrientation); } } setEdgeWeight(edge, weight); return edge; } /** * In directed graphs, undirected edges are represented by directed edges * in both directions. The edges are assigned ids with opposite signs. * * @param graph The graph to which the edges will be added. * @param startNode Start node id * @param endNode End Node id * @param edgeID Edge id * * @return One of the two directed edges used to represent an undirected * edge in a directed graph (the one with a negative id). */ private E loadDoubleEdge(KeyedGraph<V, E> graph, final int startNode, final int endNode, final int edgeID, final double weight) throws SQLException { // Note: row is ignored since we only need it for weighted graphs. final E edgeTo = graph.addEdge(startNode, endNode, edgeID); setEdgeWeight(edgeTo, weight); final E edgeFrom = graph.addEdge(endNode, startNode, -edgeID); setEdgeWeight(edgeFrom, weight); return edgeFrom; } /** * Set this edge's weight to the weight contained in the current row. * * @param edge Edge * @throws SQLException If the weight cannot be retrieved */ private void setEdgeWeight(E edge, final double weight) throws SQLException { if (edge != null && weightColumnIndex != -1) { edge.setWeight(weight); } } }