/** * 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.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.*; import org.h2gis.functions.factory.H2GISDBFactory; import org.h2gis.functions.factory.H2GISFunctions; import static org.h2gis.network.functions.GraphConstants.CONNECTED_COMPONENT; import static org.h2gis.network.functions.GraphConstants.EDGE_COMP_SUFFIX; import static org.h2gis.network.functions.GraphConstants.NODE_COMP_SUFFIX; import static org.h2gis.network.functions.ST_ConnectedComponents.NULL_CONNECTED_COMPONENT_NUMBER; import static org.h2gis.network.functions.ST_GraphAnalysisTest.LINE_GRAPH_TABLE; import static org.h2gis.network.functions.ST_GraphAnalysisTest.createLineGraphTable; import org.junit.*; import static org.junit.Assert.*; /** * @author Adam Gouge * @author Erwan Bocher */ public class ST_ConnectedComponentsTest { private static Connection connection; private Statement st; private static final String DO = "'directed - edge_orientation'"; private static final String RO = "'reversed - edge_orientation'"; private static final String U = "'undirected'"; private static final String EDGES = "EDGES"; @BeforeClass public static void setUp() throws Exception { // Keep a connection alive to not close the DataBase on each unit test connection = H2GISDBFactory.createSpatialDataBase("ST_ConnectedComponentsTest", true); H2GISFunctions.registerFunction(connection.createStatement(), new ST_ConnectedComponents(), ""); registerEdges(connection); } @Before public void setUpStatement() throws Exception { st = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); } @After public void tearDownStatement() throws Exception { st.close(); } @AfterClass public static void tearDown() throws Exception { connection.close(); } public static void registerEdges(Connection connection) throws SQLException { // 1 2 5,7 // 1-------->2-------->3<------->4 12<- 11 // | /| | ^ / | ^ // | 3 / | | | / /18 | // 9| / 4| 6| 8|13 \___/ |17 // | / | | | | // |<-/ 10 v 11,12 v 14 v 15,16 | // 5-------->6<------->7<--------8 9<------>10 final Statement st = connection.createStatement(); st.execute("CREATE TABLE " + EDGES + "(" + "EDGE_ID INT AUTO_INCREMENT PRIMARY KEY, " + "START_NODE INT, END_NODE INT, EDGE_ORIENTATION INT);" + "INSERT INTO " + EDGES + "(START_NODE, END_NODE, EDGE_ORIENTATION) VALUES " + "(1, 2, 1)," + "(2, 3, 1)," + "(2, 5, 1)," + "(2, 6, 1)," + "(3, 4, 1)," + "(3, 7, 1)," + "(4, 3, 1)," + "(4, 8, 1)," + "(5, 1, 1)," + "(5, 6, 1)," + "(6, 7, 1)," + "(7, 6, 1)," + "(8, 4, 1)," + "(8, 7, 1)," + "(9, 10, 1)," + "(10, 9, 1)," + "(10, 11, 1)," + "(12, 12, 1);"); } @Test public void DO() throws Exception { dropTables(); // SELECT ST_ConnectedComponents('" + EDGES + "', 'directed - edge_orientation') checkBoolean(compute(DO)); assertEquals(getDOROVertexPartition(), getVertexPartition(st.executeQuery("SELECT * FROM " + EDGES + NODE_COMP_SUFFIX))); checkStronglyConnectedComponentEdges(getDOROEdgeMap()); } @Test public void RO() throws Exception { // Note that strongly connected components are invariant under global // edge orientation reversal. dropTables(); // SELECT ST_ConnectedComponents('" + EDGES + "', 'reversed - edge_orientation') checkBoolean(compute(RO)); assertEquals(getDOROVertexPartition(), getVertexPartition(st.executeQuery("SELECT * FROM " + EDGES + NODE_COMP_SUFFIX))); checkStronglyConnectedComponentEdges(getDOROEdgeMap()); } @Test public void U() throws Exception { dropTables(); // SELECT ST_ConnectedComponents('" + EDGES + "', 'undirected') checkBoolean(compute(U)); assertEquals(getUVertexPartition(), getVertexPartition(st.executeQuery("SELECT * FROM " + EDGES + NODE_COMP_SUFFIX))); assertEquals(getUEdgePartition(), getEdgePartition(st.executeQuery("SELECT * FROM " + EDGES + EDGE_COMP_SUFFIX))); } @Test public void testLineGraph() throws Exception { final int n = 200; final String name = LINE_GRAPH_TABLE + n; createLineGraphTable(connection, n); checkBoolean(st.executeQuery("SELECT ST_ConnectedComponents('" + name + "', " + U + ")")); assertEquals(getOneElementPartition(n), getVertexPartition(st.executeQuery("SELECT * FROM " + name + NODE_COMP_SUFFIX))); assertEquals(getOneElementPartition(n - 1), getEdgePartition(st.executeQuery("SELECT * FROM " + name + EDGE_COMP_SUFFIX))); } private Set<Set<Integer>> getOneElementPartition(int n) { Set<Set<Integer>> p = new HashSet<Set<Integer>>(); Set<Integer> component = new HashSet<Integer>(); for (int i = 0; i < n; i++) { component.add(i + 1); } p.add(component); return p; } private void dropTables() throws SQLException { st.execute("DROP TABLE IF EXISTS " + EDGES + NODE_COMP_SUFFIX); st.execute("DROP TABLE IF EXISTS " + EDGES + EDGE_COMP_SUFFIX); } private ResultSet compute(String orientation) throws SQLException { return st.executeQuery("SELECT ST_ConnectedComponents('" + EDGES + "', " + orientation + ")"); } private void checkBoolean(ResultSet rs) throws SQLException { try{ assertTrue(rs.next()); assertTrue(rs.getBoolean(1)); assertFalse(rs.next()); } finally { rs.close(); } } private void checkStronglyConnectedComponentEdges(Map<Integer, Set<Integer>> expectedEdgeMap) throws SQLException { final Map<Integer, Set<Integer>> actualEdgeMap = getCCMap(st.executeQuery("SELECT * FROM " + EDGES + EDGE_COMP_SUFFIX), GraphConstants.EDGE_ID); // Check edges in no strongly connected component. assertEquals(expectedEdgeMap.get(NULL_CONNECTED_COMPONENT_NUMBER), actualEdgeMap.get(NULL_CONNECTED_COMPONENT_NUMBER)); // Check edge partition. assertEquals(getPartition(expectedEdgeMap), getPartition(actualEdgeMap)); } private Set<Set<Integer>> getDOROVertexPartition() { Set<Set<Integer>> p = new HashSet<Set<Integer>>(); p.add(getIntSet(1, 2, 5)); p.add(getIntSet(3, 4, 8)); p.add(getIntSet(6, 7)); p.add(getIntSet(9, 10)); p.add(getIntSet(11)); p.add(getIntSet(12)); return p; } private Map<Integer, Set<Integer>> getDOROEdgeMap() { Map<Integer, Set<Integer>> p = new HashMap<Integer, Set<Integer>>(); // We'll actually ignore all component numbers except for // NULL_CONNECTED_COMPONENT_NUMBER. p.put(1, getIntSet(1, 3, 9)); p.put(2, getIntSet(1, 3, 9)); p.put(3, getIntSet(5, 7, 8, 13)); p.put(4, getIntSet(11, 12)); p.put(5, getIntSet(15, 16)); p.put(6, getIntSet(18)); p.put(NULL_CONNECTED_COMPONENT_NUMBER, getIntSet(2, 4, 6, 10, 14, 17)); return p; } private Set<Set<Integer>> getUVertexPartition() { Set<Set<Integer>> p = new HashSet<Set<Integer>>(); p.add(getIntSet(1, 2, 3, 4, 5, 6, 7, 8)); p.add(getIntSet(9, 10, 11)); p.add(getIntSet(12)); return p; } private Set<Set<Integer>> getUEdgePartition() { Set<Set<Integer>> p = new HashSet<Set<Integer>>(); p.add(getIntSet(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14)); p.add(getIntSet(15, 16, 17)); p.add(getIntSet(18)); return p; } private Set<Integer> getIntSet(Integer... ints) { return new HashSet<Integer>(Arrays.asList(ints)); } private Set<Set<Integer>> getVertexPartition(ResultSet nodeComponents) throws SQLException { return getPartition(getCCMap(nodeComponents, GraphConstants.NODE_ID)); } private Set<Set<Integer>> getEdgePartition(ResultSet edgeComponents) throws SQLException { return getPartition(getCCMap(edgeComponents, GraphConstants.EDGE_ID)); } private Set<Set<Integer>> getPartition(Map<Integer, Set<Integer>> map) { Set<Set<Integer>> p = new HashSet<Set<Integer>>(); for (Set<Integer> cc : map.values()) { p.add(cc); } return p; } private Map<Integer, Set<Integer>> getCCMap(ResultSet components, String id) throws SQLException { Map<Integer, Set<Integer>> map = new HashMap<Integer, Set<Integer>>(); try { while (components.next()) { final int ccID = components.getInt(CONNECTED_COMPONENT); if (map.get(ccID) == null) { map.put(ccID, new HashSet<Integer>()); } map.get(ccID).add(components.getInt(id)); } return map; } finally { components.close(); } } }