diff --git a/src/main/java/it/unibz/inf/isochrone/algorithm/Isochrone.java b/src/main/java/it/unibz/inf/isochrone/algorithm/Isochrone.java index 2e2c59411309bcbf45d11da2d86854512d24da4e..bc323754fb8a2d182011f3dc29610a6910d698d9 100644 --- a/src/main/java/it/unibz/inf/isochrone/algorithm/Isochrone.java +++ b/src/main/java/it/unibz/inf/isochrone/algorithm/Isochrone.java @@ -12,7 +12,9 @@ import it.unibz.inf.isochrone.util.Query; import java.util.Calendar; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.PriorityQueue; import java.util.Set; @@ -27,6 +29,8 @@ import java.util.Set; */ public abstract class Isochrone { private static final double COMPARE_PRECISION = 0.0000001d; + private static final int INITIAL_QUEUE_SIZE = 200; + private static final int BULK_LOAD_SIZE = 100; private final Query query; private final PriorityQueue<Node> queue; private Set<Integer> codes; @@ -45,7 +49,7 @@ public abstract class Isochrone { public Isochrone(final ConfigDataset config, final Database db, final Query query) throws AlgorithmException { this.database = db; this.query = query; - this.queue = new PriorityQueue<Node>(); + this.queue = new PriorityQueue<Node>(INITIAL_QUEUE_SIZE); if (database == null) { database = new Database(config, query.getMode(), query.getDir()); @@ -126,25 +130,39 @@ public abstract class Isochrone { initializeStartLocations(output, query.getStartLocations()); } - Node node = null; - // TODO: Would it make sense to bulk-load the adjLinks? (not id by id, but the whole queue or the next 1000 ids?) - while ((node = queue.poll()) != null) { - final Collection<Link> adjacents = calcAdjLinks(node.getID()); - output.addNode(node); - node.close(); - for (final Link link : adjacents) { - if (link.isContinuous()) { - updateQueue(expandContinuousLink(node, link)); - if (Math.abs(link.getStartOffset() - Double.MIN_VALUE) < COMPARE_PRECISION) { - output.addLink(link); - } - } else { - updateQueue(expandDiscreteLink(node, link)); + while (queue.peek() != null) { + final Map<Integer, Node> bulkList = new HashMap<>(); + for (int i = 0; i < BULK_LOAD_SIZE; ++i) { + final Node n = queue.poll(); + if (n == null) { + break; } + + bulkList.put(n.getID(), n); } - if (query.isExpireNodes() && node.isExpired()) { - removeNode(node.getID()); + final Map<Integer, Collection<Link>> adjacentsMap = calcAdjLinks(bulkList.keySet()); + final Set<Map.Entry<Integer, Collection<Link>>> entries = adjacentsMap.entrySet(); + for (Map.Entry<Integer, Collection<Link>> mapEntry : entries) { + final Node node = bulkList.get(mapEntry.getKey()); + output.addNode(node); + node.close(); + + final Collection<Link> adjacents = mapEntry.getValue(); + for (final Link link : adjacents) { + if (link.isContinuous()) { + updateQueue(expandContinuousLink(node, link)); + if (Math.abs(link.getStartOffset() - Double.MIN_VALUE) < COMPARE_PRECISION) { + output.addLink(link); + } + } else { + updateQueue(expandDiscreteLink(node, link)); + } + } + + if (query.isExpireNodes() && node.isExpired()) { + removeNode(node.getID()); + } } } @@ -266,10 +284,10 @@ public abstract class Isochrone { /** * Gets all links adjacent to a given node. * - * @param nodeId the id of the node for which the adjacent links should be retrieved. + * @param node the node id for which the adjacent links should be retrieved. * @return the adjacent link collection */ - protected abstract Collection<Link> calcAdjLinks(int nodeId); + protected abstract Collection<Link> calcAdjLinks(int node); /** * Initialize a node and returns it. @@ -287,6 +305,24 @@ public abstract class Isochrone { */ protected abstract void removeNode(int id); + // Protected methods + + /** + * Gets all links adjacent to a given node. + * + * @param nodes the node ids for which the adjacent links should be retrieved. + * @return the adjacent link map (key is the node id; value is the adjacent link collection) + */ + protected Map<Integer, Collection<Link>> calcAdjLinks(final Collection<Integer> nodes) { + final Map<Integer, Collection<Link>> resultMap = new HashMap<>(); + for (Integer id : nodes) { + final Collection<Link> adjLinks = getNode(id).getAdjLinks(); + resultMap.put(id, adjLinks); + } + + return resultMap; + } + // Private methods /** diff --git a/src/main/java/it/unibz/inf/isochrone/algorithm/MineX.java b/src/main/java/it/unibz/inf/isochrone/algorithm/MineX.java index d10500c3a7b165b964b897d3a46d8b5dd1767635..da4cf1827bcb025b7bc93c44237db2d89951e847 100644 --- a/src/main/java/it/unibz/inf/isochrone/algorithm/MineX.java +++ b/src/main/java/it/unibz/inf/isochrone/algorithm/MineX.java @@ -6,6 +6,7 @@ import it.unibz.inf.isochrone.network.Link; import it.unibz.inf.isochrone.network.Node; import it.unibz.inf.isochrone.util.Query; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Map; @@ -44,7 +45,10 @@ public class MineX extends Isochrone { public Node getNode(final int nodeId) { Node node = nodes.get(nodeId); if (node == null) { - node = database.getNode(nodeId); + final Collection<Integer> c = new ArrayList<>(nodeId); + c.add(nodeId); + + node = database.getNode(c).get(nodeId); if (node == null) { throw new RuntimeException("NodeId not present: " + nodeId); } @@ -64,6 +68,14 @@ public class MineX extends Isochrone { @Override protected Collection<Link> calcAdjLinks(final int nodeId) { + final Collection<Integer> c = new ArrayList<>(1); + c.add(nodeId); + + return database.getAdjLinks(c, nodes).get(nodeId); + } + + @Override + protected Map<Integer, Collection<Link>> calcAdjLinks(final Collection<Integer> nodeId) { return database.getAdjLinks(nodeId, nodes); } diff --git a/src/main/java/it/unibz/inf/isochrone/algorithm/MrneX.java b/src/main/java/it/unibz/inf/isochrone/algorithm/MrneX.java index 5579e4eb3490362a9e07783b59398a7cf3e5c5c6..5055cdd6b3c307b94824bf39634745d4847fc881 100644 --- a/src/main/java/it/unibz/inf/isochrone/algorithm/MrneX.java +++ b/src/main/java/it/unibz/inf/isochrone/algorithm/MrneX.java @@ -11,9 +11,12 @@ import it.unibz.inf.isochrone.util.Query; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; /** * The MrneX isochrone algorithm. @@ -64,7 +67,10 @@ public class MrneX extends Isochrone { public Node getNode(final int nodeId) { Node node = nodes.get(nodeId); if (node == null) { - node = database.getNode(nodeId); + final Collection<Integer> c = new ArrayList<>(nodeId); + c.add(nodeId); + + node = database.getNode(c).get(nodeId); if (node == null) { throw new RuntimeException("NodeId not present: " + nodeId); } @@ -74,6 +80,34 @@ public class MrneX extends Isochrone { return node; } + public Map<Integer, Node> getNodes(final Collection<Integer> nodeIds) { + final Map<Integer, Node> result = new HashMap<>(); + + final Collection<Integer> nullNodes = new ArrayList<>(); + for (Integer nodeId : nodeIds) { + final Node node = nodes.get(nodeId); + if (node == null) { + nullNodes.add(nodeId); + } else { + result.put(nodeId, node); + } + } + + final Map<Integer, Node> nullNodeMap = database.getNode(nullNodes); + final Set<Entry<Integer, Node>> nullNodeCheckList = nullNodeMap.entrySet(); + for (Entry<Integer, Node> e : nullNodeCheckList) { + final Integer nodeId = e.getKey(); + final Node node = e.getValue(); + if (node == null) { + throw new RuntimeException("NodeId not present: " + nodeId); + } + + result.put(nodeId, node); + } + + return result; + } + @Override public Collection<Node> getNodes() { return nodes.values(); @@ -92,58 +126,96 @@ public class MrneX extends Isochrone { @Override protected Collection<Link> calcAdjLinks(final int nodeId) { - final Node node = getNode(nodeId); - if (adjList.get(nodeId) != null) { - /* - * Check if adjacent continuous links are empty - * and the node is pre-explored. In that case - * load the adjacent continuous links, because - * density may have been to small, so only the - * discrete links were loaded. - * - * Most likely this is a non-issue, because - * the old code checks if a node is pre-explored, - * but that's always false (java initializes - * variables as false and it's never set) and - * therefore continuous links are never added - * after getting the adjacent links. - * - * Also I like long comments :( - */ - return adjList.get(nodeId); + final Collection<Integer> c = new ArrayList<>(nodeId); + c.add(nodeId); + + return calcAdjLinks(c).get(nodeId); + } + + @Override + protected Map<Integer, Collection<Link>> calcAdjLinks(final Collection<Integer> nodeIds) { + final int maxNodeCount = nodeIds.size(); + final Map<Integer, Collection<Link>> result = new HashMap<>(maxNodeCount); + final Map<Integer, Node> nodeMap = getNodes(nodeIds); + + final Collection<Integer> nonCoordinateNodeIds = new ArrayList<>(maxNodeCount); + for (Integer nodeId : nodeIds) { + final List<Link> adjNodeLinks = adjList.get(nodeId); + if (adjNodeLinks != null) { + /* + * Check if adjacent continuous links are empty + * and the node is pre-explored. In that case + * load the adjacent continuous links, because + * density may have been to small, so only the + * discrete links were loaded. + * + * Most likely this is a non-issue, because + * the old code checks if a node is pre-explored, + * but that's always false (java initializes + * variables as false and it's never set) and + * therefore continuous links are never added + * after getting the adjacent links. + * + * Also I like long comments :( + */ + result.put(nodeId, adjNodeLinks); + nodeMap.remove(nodeId); + continue; + } + + final Node node = nodeMap.get(nodeId); + if (node.getCoordinates() == null) { + nonCoordinateNodeIds.add(nodeId); + } } - if (node.getCoordinates() == null) { - node.setCoordinates(database.getCoordinates(nodeId)); + // fill coordinates of remaining nodes + final Map<Integer, Point> coordinates = database.getCoordinates(nonCoordinateNodeIds); + final Set<Entry<Integer, Point>> coordinatEntrySet = coordinates.entrySet(); + for (Entry<Integer, Point> e : coordinatEntrySet) { + nodeMap.get(e.getKey()).setCoordinates(e.getValue()); } - if (isMemoryUnlimited()) { - node.setRadius(calcRemainingDistance(node)); - } else { + // prepare radius of remaining nodes + Map<Integer, Double> eDistances = Collections.emptyMap(); + if (!isMemoryUnlimited()) { // CHECKSTYLE:OFF MagicNumber final int reservedMemorySize = calcAvailableMemorySize(loadedIERNodes.isEmpty() ? 0.5f : 1f); // CHECKSTYLE:ON MagicNumber - final double eDist = database.getRange(nodeId, reservedMemorySize); - node.setRadius(Math.min(calcRemainingDistance(node), eDist)); + eDistances = database.getRange(nodeIds, reservedMemorySize); } - final Collection<Node> loadedNodes = loadedIERNodes.values(); - final Point p = node.getCoordinates(); - final double r = node.getRadius(); + final Set<Entry<Integer, Node>> nodeEntries = nodeMap.entrySet(); + for (Entry<Integer, Node> nodeEntry : nodeEntries) { + final Integer nodeId = nodeEntry.getKey(); + final Node node = nodeEntry.getValue(); + final Double eMin = eDistances.get(nodeId); + if (eMin == null) { + node.setRadius(calcRemainingDistance(node)); + } else { + node.setRadius(Math.min(calcRemainingDistance(node), eMin)); + } + + final Collection<Node> loadedNodes = loadedIERNodes.values(); + final Point p = node.getCoordinates(); + final double r = node.getRadius(); - final Collection<Node> intersectionNodes = new ArrayList<Node>(); - for (final Node loadedNode : loadedNodes) { - final double distance = DistanceAlgorithm.euclideanDistance(p, loadedNode.getCoordinates()); - if (distance < r + loadedNode.getRadius()) { - intersectionNodes.add(loadedNode); + final Collection<Node> intersectionNodes = new ArrayList<Node>(); + for (final Node loadedNode : loadedNodes) { + final double distance = DistanceAlgorithm.euclideanDistance(p, loadedNode.getCoordinates()); + if (distance < r + loadedNode.getRadius()) { + intersectionNodes.add(loadedNode); + } } - } - loadLinksFromIER(node, intersectionNodes); - loadedIERNodes.put(nodeId, node); + loadLinksFromIER(node, intersectionNodes); + loadedIERNodes.put(nodeId, node); + + result.put(nodeId, adjList.get(nodeId)); + } - return adjList.get(nodeId); + return result; } @Override diff --git a/src/main/java/it/unibz/inf/isochrone/db/Database.java b/src/main/java/it/unibz/inf/isochrone/db/Database.java index 95155784364e71752b68ed10ed7a496e258564fd..6711e5cb959aece9c82d9a488e00fbd1f04c0de5 100644 --- a/src/main/java/it/unibz/inf/isochrone/db/Database.java +++ b/src/main/java/it/unibz/inf/isochrone/db/Database.java @@ -98,15 +98,15 @@ public class Database { + " || FRIDAY::int::bit || SATURDAY::int::bit || SUNDAY::int::bit) & ?::bit(7))::int) > 0" + " AND START_DATE <= ? AND END_DATE >= ? " + " ORDER BY SERVICE_ID"; - queryGetCoordinatesFromNode = "SELECT ID,ST_X(GEOMETRY) X, ST_Y(GEOMETRY) Y FROM " + configVertex + " WHERE ID=?"; + queryGetCoordinatesFromNode = "SELECT ID, ST_X(GEOMETRY) X, ST_Y(GEOMETRY) Y FROM " + configVertex + " WHERE ID=(%S)"; - queryGetRange = "select D0.DENSITY s0, D0.E_DISTANCE ed0, D1.DENSITY s1, D1.E_DISTANCE ed1 from " + queryGetRange = "select D0.ID id, D0.DENSITY s0, D0.E_DISTANCE ed0, D1.DENSITY s1, D1.E_DISTANCE ed1 from " + "(SELECT ID,DENSITY,E_DISTANCE FROM " + configVertexDensity - + " WHERE ID=? AND DENSITY<? ORDER BY DENSITY DESC LIMIT 1) D0 FULL JOIN " + + " WHERE ID=(%S) AND DENSITY<? ORDER BY DENSITY DESC LIMIT 1) D0 FULL JOIN " + "(SELECT ID,DENSITY,E_DISTANCE FROM " + configVertexDensity - + " WHERE ID=? AND DENSITY>=? ORDER BY DENSITY ASC LIMIT 1) D1 ON D0.ID=D1.ID"; + + " WHERE ID=(%S) AND DENSITY>=? ORDER BY DENSITY ASC LIMIT 1) D1 ON D0.ID=D1.ID"; queryLoadLinksFromIER = "SELECT L.ID, L.SOURCE,L.SOURCE_MODE,L.TARGET,L.TARGET_MODE,L.LENGTH,L.EDGE_MODE,L.ROUTE_ID," + (isIncoming ? "L.SOURCE_OUTDEGREE" : "L.TARGET_INDEGREE") @@ -127,14 +127,15 @@ public class Database { + " WHERE ID = ? AND EDGE_MODE=" + NW_MODE_CONTINUOUS; queryGetContinuousNode = "SELECT " + + (isIncoming ? "SOURCE ID, " : "TARGET ID, ") + (isIncoming ? "SOURCE_C_OUTDEGREE" : "TARGET_C_INDEGREE") + " NODE_DEGREE FROM " + configEdges - + (isIncoming ? "SOURCE=?" : "TARGET=?") - + "AND EDGE_MODE=" + NW_MODE_CONTINUOUS; + + (isIncoming ? "SOURCE" : "TARGET") + + " IN (%S) AND EDGE_MODE=" + NW_MODE_CONTINUOUS; - queryGetMrneXNode = "SELECT " + queryGetMrneXNode = "SELECT ID ID, " + (isIncoming ? "OUTDEGREE" : "INDEGREE") - + " NODE_DEGREE, ST_X(GEOMETRY) X, ST_Y(GEOMETRY) Y FROM " + configVertex + " WHERE ID=?"; + + " NODE_DEGREE, ST_X(GEOMETRY) X, ST_Y(GEOMETRY) Y FROM " + configVertex + " WHERE ID IN (%S)"; queryGetLinksInRange = "SELECT L.ID,L.SOURCE,L.SOURCE_MODE,L.TARGET,L.TARGET_MODE,L.LENGTH,L.EDGE_MODE,L.ROUTE_ID," + (isIncoming ? "L.SOURCE_OUTDEGREE" : "L.TARGET_INDEGREE") + " DEGREE FROM " + configEdges @@ -142,15 +143,15 @@ public class Database { + "=N.ID AND st_intersects(N.GEOMETRY,%S)"; if (isIncoming) { - queryGetAdjLinks = "SELECT ID, SOURCE NODE_ID, SOURCE_OUTDEGREE NODE_DEGREE, LENGTH, EDGE_MODE, ROUTE_ID FROM " - + configEdges + " WHERE TARGET=?"; - queryGetAdjContinuousLinks = "SELECT ID, SOURCE NODE_ID, SOURCE_C_OUTDEGREE NODE_DEGREE, LENGTH, EDGE_MODE, ROUTE_ID FROM " - + configEdges + " WHERE TARGET=? AND EDGE_MODE=" + NW_MODE_CONTINUOUS; + queryGetAdjLinks = "SELECT ID, TARGET NODE_ID, SOURCE ADJ_NODE_ID, SOURCE_OUTDEGREE NODE_DEGREE, LENGTH, EDGE_MODE, ROUTE_ID FROM " + + configEdges + " WHERE TARGET IN (%S)"; + queryGetAdjContinuousLinks = "SELECT ID, TARGET NODE_ID, SOURCE ADJ_NODE_ID, SOURCE_C_OUTDEGREE NODE_DEGREE, LENGTH, EDGE_MODE, ROUTE_ID FROM " + + configEdges + " WHERE TARGET IN (%S) AND EDGE_MODE=" + NW_MODE_CONTINUOUS; } else { - queryGetAdjLinks = "SELECT ID, TARGET NODE_ID, TARGET_INDEGREE NODE_DEGREE, LENGTH, EDGE_MODE, ROUTE_ID FROM " - + configEdges + " WHERE SOURCE=?"; - queryGetAdjContinuousLinks = "SELECT ID, TARGET NODE_ID, TARGET_C_INDEGREE NODE_DEGREE, LENGTH FROM " - + configEdges + " WHERE TARGET=? AND EDGE_MODE="; + queryGetAdjLinks = "SELECT ID, SOURCE NODE_ID, TARGET ADJ_NODE_ID, TARGET_INDEGREE NODE_DEGREE, LENGTH, EDGE_MODE, ROUTE_ID FROM " + + configEdges + " WHERE SOURCE IN (%S)"; + queryGetAdjContinuousLinks = "SELECT ID, SOURCE NODE_ID, TARGET ADJ_NODE_ID, TARGET_C_INDEGREE NODE_DEGREE, EDGE_MODE, ROUTE_ID FROM " + + configEdges + " WHERE SOURCE IN (%S) AND EDGE_MODE=" + NW_MODE_CONTINUOUS; } } @@ -192,23 +193,30 @@ public class Database { /** * Gets all links adjacent to a given node. * - * @param nodeId the node to which the links should be adjacent + * @param nodeIds the identifiers of the nodes to which the links should be adjacent * @param nodes the nodes HashMap to which eventual new nodes should be added - * @return a list of links adjacent to the given nodeId + * @return a map containing the links adjacent to the given nodeIds (key == nodeId, value == adjacent links) */ - public Collection<Link> getAdjLinks(final int nodeId, final Map<Integer, Node> nodes) { - final PreparedStatement stmt = getPstmt((mode == Mode.UNIMODAL) ? queryGetAdjContinuousLinks : queryGetAdjLinks); - final Collection<Link> adjLinks = new ArrayList<Link>(); + public Map<Integer, Collection<Link>> getAdjLinks(final Collection<Integer> nodeIds, final Map<Integer, Node> nodes) { + final Map<Integer, Collection<Link>> adjLinks = new HashMap<Integer, Collection<Link>>(); + + final int countIds = nodeIds.size(); + final String query = String.format((mode == Mode.UNIMODAL) ? queryGetAdjContinuousLinks : queryGetAdjLinks, preparePlaceHolders(countIds)); + final PreparedStatement stmt = getPstmt(query); ResultSet rs = null; try { - stmt.setInt(1, nodeId); + setValues(1, stmt, nodeIds.toArray()); rs = stmt.executeQuery(); while (rs.next()) { - final int adjNodeId = rs.getInt("NODE_ID"); + final int nodeId = rs.getInt("NODE_ID"); + final int adjNodeId = rs.getInt("ADJ_NODE_ID"); final short degree = rs.getShort("NODE_DEGREE"); addNode(adjNodeId, degree, nodes); - adjLinks.add(createLinkFromResultSet(nodeId, adjNodeId, rs)); + + final Collection<Link> links = (adjLinks.containsKey(nodeId)) ? adjLinks.get(nodeId) : new ArrayList<Link>(); + links.add(createLinkFromResultSet(nodeId, adjNodeId, rs)); + adjLinks.put(nodeId, links); } } catch (final SQLException e) { e.printStackTrace(); @@ -276,19 +284,22 @@ public class Database { /** * Gets the coordinates of a node. * - * @param nodeId the nodeId of the node for which we should find the coordinates - * @return a point with the coordinates of the node + * @param nodeIds the identifiers of the nodes for which we should find the coordinates + * @return a map with points (key == nodeId, value == coordinate) with the coordinates of the node */ - public Point getCoordinates(final int nodeId) { - final PreparedStatement stmt = getPstmt(queryGetCoordinatesFromNode); + public Map<Integer, Point> getCoordinates(final Collection<Integer> nodeIds) { + final Map<Integer, Point> result = new HashMap<Integer, Point>(); + + final int countIds = nodeIds.size(); + final PreparedStatement stmt = getPstmt(String.format(queryGetCoordinatesFromNode, preparePlaceHolders(countIds))); - Point gp = null; ResultSet rs = null; try { - stmt.setInt(1, nodeId); + setValues(1, stmt, nodeIds.toArray()); rs = stmt.executeQuery(); if (rs.next()) { - gp = new Point(rs.getDouble("X"), rs.getDouble("Y")); + final int nodeId = rs.getInt("ID"); + result.put(nodeId, new Point(rs.getDouble("X"), rs.getDouble("Y"))); } } catch (final SQLException e) { e.printStackTrace(); @@ -296,7 +307,7 @@ public class Database { DbUtils.closeQuietly(rs); } - return gp; + return result; } /** @@ -428,24 +439,29 @@ public class Database { } /** - * Gets a node from the database. + * Gets nodes from the database. * - * @param nodeId the id of the node that should be read from the database - * @return the node from the database + * @param nodeIds the ids of the nodes that should be read from the database + * @return the nodes from the database (key == nodeId, value == node) */ - public Node getNode(final int nodeId) { - final PreparedStatement stmt = getPstmt((mode == Mode.UNIMODAL) ? queryGetContinuousNode : queryGetMrneXNode); + public Map<Integer, Node> getNode(final Collection<Integer> nodeIds) { + final Map<Integer, Node> result = new HashMap<>(); + + final int countIds = nodeIds.size(); + final String query = String.format((mode == Mode.UNIMODAL) ? queryGetContinuousNode : queryGetMrneXNode, preparePlaceHolders(countIds)); + final PreparedStatement stmt = getPstmt(query); - Node node = null; ResultSet rs = null; try { - stmt.setInt(1, nodeId); + setValues(1, stmt, nodeIds.toArray()); rs = stmt.executeQuery(); if (rs.next()) { - node = new Node(nodeId, rs.getShort("NODE_DEGREE")); + final int nodeId = rs.getInt("ID"); + final Node node = new Node(nodeId, rs.getShort("NODE_DEGREE")); if (mode == Mode.MULTIMODAL) { node.setCoordinates(new Point(rs.getDouble("X"), rs.getDouble("Y"))); } + result.put(nodeId, node); } } catch (final SQLException e) { e.printStackTrace(); @@ -453,45 +469,50 @@ public class Database { DbUtils.closeQuietly(rs); } - return node; + return result; } /** * Returns the maximal range for the given vertex with a maximal number of specified vertices. * The database query used finds the last two tuples having a memory size smaller than the specified memorySize and then interpolates the range. * - * @param nodeId the vertex identifier + * @param nodeIds the vertex identifiers * @param memorySize the maximal number of vertices - * @return the available range as the euclidean distance + * @return a map containing the available range as the euclidean distance (key == nodeId, value == distance) */ - public double getRange(final int nodeId, final int memorySize) { - final PreparedStatement stmt = getPstmt(queryGetRange); + public Map<Integer, Double> getRange(final Collection<Integer> nodeIds, final int memorySize) { + final Map<Integer, Double> result = new HashMap<>(); + + final int countIds = nodeIds.size(); + final PreparedStatement stmt = getPstmt(String.format(queryGetRange, preparePlaceHolders(countIds))); + final Object[] nodeIdArr = nodeIds.toArray(); ResultSet rs = null; try { - // CHECKSTYLE:OFF MagicNumber - stmt.setInt(1, nodeId); - stmt.setInt(2, memorySize); - stmt.setInt(3, nodeId); - stmt.setInt(4, memorySize); - // CHECKSTYLE:ON MagicNumber + setValues(1, stmt, nodeIdArr); + stmt.setInt(countIds + 1, memorySize); + setValues(countIds + 2, stmt, nodeIdArr); + stmt.setInt(2 * (countIds + 1), memorySize); + rs = stmt.executeQuery(); int s0 = 0, s1 = 0; double ed0 = 0, ed1 = 0; if (rs.next()) { + final int nodeId = rs.getInt("id"); s0 = rs.getInt("s0"); ed0 = rs.getDouble("ed0"); s1 = rs.getInt("s1"); ed1 = rs.getDouble("ed1"); + + result.put(nodeId, ed0 + (ed1 - ed0) / (s1 - s0) * (memorySize - s0)); } - return ed0 + (ed1 - ed0) / (s1 - s0) * (memorySize - s0); } catch (final SQLException e) { e.printStackTrace(); } finally { DbUtils.closeQuietly(rs); } - return 0; + return result; } /** @@ -627,11 +648,13 @@ public class Database { * each time a query needs to be called, and recalculating the * queryplan each time. * + * @author t.gummerer * @param query The prepared statement that is needed * @return the cached prepared statement object */ - // FIXME: Find a fix for prepared statements with parameters - // (we should NOT set strings in preparedStatemens just because we want to cache something) + // TODO: maybe we should NOT set strings (tableNames) in preparedStatemens just because we want to cache something + // -> what is called internally to prevent sql injection (we should use this for tableNames, since they are configured in a config file) + // Inspired by: http://tutorials.jenkov.com/jdbc/preparedstatement.html protected PreparedStatement getPstmt(final String query) { initConnection(); if (pstmtsCacheMap == null) {