/** Finds shortcuts, does not change the underlying graph. */ void findShortcuts(ShortcutHandler sch) { long tmpDegreeCounter = 0; EdgeIterator incomingEdges = vehicleInExplorer.setBaseNode(sch.getNode()); // collect outgoing nodes (goal-nodes) only once while (incomingEdges.next()) { int u_fromNode = incomingEdges.getAdjNode(); // accept only uncontracted nodes if (g.getLevel(u_fromNode) != 0) continue; double v_u_weight = incomingEdges.getDistance(); int skippedEdge1 = incomingEdges.getEdge(); int incomingEdgeOrigCount = getOrigEdgeCount(skippedEdge1); // collect outgoing nodes (goal-nodes) only once EdgeIterator outgoingEdges = vehicleOutExplorer.setBaseNode(sch.getNode()); // force fresh maps etc as this cannot be determined by from node alone (e.g. same from node // but different avoidNode) algo.clear(); tmpDegreeCounter++; while (outgoingEdges.next()) { int w_toNode = outgoingEdges.getAdjNode(); // add only uncontracted nodes if (g.getLevel(w_toNode) != 0 || u_fromNode == w_toNode) { continue; } // Limit weight as ferries or forbidden edges can increase local search too much. // If we decrease the correct weight we only explore less and introduce more shortcuts. // I.e. no change to accuracy is made. double existingDirectWeight = v_u_weight + outgoingEdges.getDistance(); algo.setLimitWeight(existingDirectWeight) .setLimitVisitedNodes((int) meanDegree * 100) .setEdgeFilter(levelEdgeFilter.setAvoidNode(sch.getNode())); dijkstraSW.start(); dijkstraCount++; int endNode = algo.findEndNode(u_fromNode, w_toNode); dijkstraSW.stop(); // compare end node as the limit could force dijkstra to finish earlier if (endNode == w_toNode && algo.getWeight(endNode) <= existingDirectWeight) // FOUND witness path, so do not add shortcut continue; sch.foundShortcut( u_fromNode, w_toNode, existingDirectWeight, outgoingEdges, skippedEdge1, incomingEdgeOrigCount); } } if (sch instanceof AddShortcutHandler) { // sliding mean value when using "*2" => slower changes meanDegree = (meanDegree * 2 + tmpDegreeCounter) / 3; // meanDegree = (meanDegree + tmpDegreeCounter) / 2; } }
@Override public PrepareContractionHierarchies doWork() { checkGraph(); if (prepareEncoder == null) throw new IllegalStateException("No vehicle encoder set."); if (prepareWeighting == null) throw new IllegalStateException("No weight calculation set."); allSW.start(); super.doWork(); initFromGraph(); if (!prepareEdges()) return this; if (!prepareNodes()) return this; contractNodes(); return this; }
void contractNodes() { meanDegree = g.getAllEdges().getMaxId() / g.getNodes(); int level = 1; counter = 0; int initSize = sortedNodes.getSize(); int logSize = (int) Math.round(Math.max(10, sortedNodes.getSize() / 100 * logMessagesPercentage)); if (logMessagesPercentage == 0) logSize = Integer.MAX_VALUE; // preparation takes longer but queries are slightly faster with preparation // => enable it but call not so often boolean periodicUpdate = true; StopWatch periodSW = new StopWatch(); int updateCounter = 0; int periodicUpdatesCount = Math.max(10, sortedNodes.getSize() / 100 * periodicUpdatesPercentage); if (periodicUpdatesPercentage == 0) periodicUpdate = false; // disable as preparation is slower and query time does not benefit int lastNodesLazyUpdates = lastNodesLazyUpdatePercentage == 0 ? 0 : sortedNodes.getSize() / 100 * lastNodesLazyUpdatePercentage; StopWatch lazySW = new StopWatch(); // Recompute priority of uncontracted neighbors. // Without neighborupdates preparation is faster but we need them // to slightly improve query time. Also if not applied too often it decreases the shortcut // number. boolean neighborUpdate = true; if (neighborUpdatePercentage == 0) neighborUpdate = false; StopWatch neighborSW = new StopWatch(); LevelGraphStorage lg = ((LevelGraphStorage) g); while (!sortedNodes.isEmpty()) { // periodically update priorities of ALL nodes if (periodicUpdate && counter > 0 && counter % periodicUpdatesCount == 0) { periodSW.start(); sortedNodes.clear(); int len = g.getNodes(); for (int node = 0; node < len; node++) { if (g.getLevel(node) != 0) continue; int priority = oldPriorities[node] = calculatePriority(node); sortedNodes.insert(node, priority); } periodSW.stop(); updateCounter++; } if (counter % logSize == 0) { // TODO necessary? System.gc(); logger.info( Helper.nf(counter) + ", updates:" + updateCounter + ", nodes: " + Helper.nf(sortedNodes.getSize()) + ", shortcuts:" + Helper.nf(newShortcuts) + ", dijkstras:" + Helper.nf(dijkstraCount) + ", t(dijk):" + (int) dijkstraSW.getSeconds() + ", t(period):" + (int) periodSW.getSeconds() + ", t(lazy):" + (int) lazySW.getSeconds() + ", t(neighbor):" + (int) neighborSW.getSeconds() + ", meanDegree:" + (long) meanDegree + ", algo:" + algo.getMemoryUsageAsString() + ", " + Helper.getMemInfo()); dijkstraSW = new StopWatch(); periodSW = new StopWatch(); lazySW = new StopWatch(); neighborSW = new StopWatch(); } counter++; int polledNode = sortedNodes.pollKey(); if (sortedNodes.getSize() < lastNodesLazyUpdates) { lazySW.start(); int priority = oldPriorities[polledNode] = calculatePriority(polledNode); if (!sortedNodes.isEmpty() && priority > sortedNodes.peekValue()) { // current node got more important => insert as new value and contract it later sortedNodes.insert(polledNode, priority); lazySW.stop(); continue; } lazySW.stop(); } // contract! newShortcuts += addShortcuts(polledNode); g.setLevel(polledNode, level); level++; EdgeSkipIterator iter = vehicleAllExplorer.setBaseNode(polledNode); while (iter.next()) { int nn = iter.getAdjNode(); if (g.getLevel(nn) != 0) // already contracted no update necessary continue; if (neighborUpdate && rand.nextInt(100) < neighborUpdatePercentage) { neighborSW.start(); int oldPrio = oldPriorities[nn]; int priority = oldPriorities[nn] = calculatePriority(nn); if (priority != oldPrio) sortedNodes.update(nn, oldPrio, priority); neighborSW.stop(); } if (removesHigher2LowerEdges) lg.disconnect(vehicleAllTmpExplorer, iter); } } // Preparation works only once so we can release temporary data. // The preparation object itself has to be intact to create the algorithm. close(); logger.info( "took:" + (int) allSW.stop().getSeconds() + ", new shortcuts: " + newShortcuts + ", " + prepareWeighting + ", " + prepareEncoder + ", removeHigher2LowerEdges:" + removesHigher2LowerEdges + ", dijkstras:" + dijkstraCount + ", t(dijk):" + (int) dijkstraSW.getSeconds() + ", t(period):" + (int) periodSW.getSeconds() + ", t(lazy):" + (int) lazySW.getSeconds() + ", t(neighbor):" + (int) neighborSW.getSeconds() + ", meanDegree:" + (long) meanDegree + ", initSize:" + initSize + ", periodic:" + periodicUpdatesPercentage + ", lazy:" + lastNodesLazyUpdatePercentage + ", neighbor:" + neighborUpdatePercentage); }
@Override public GHResponse route(GHRequest request) { request.check(); StopWatch sw = new StopWatch().start(); GHResponse rsp = new GHResponse(); if (!setSupportsVehicle(request.getVehicle())) { rsp.addError( new IllegalArgumentException( "Vehicle " + request.getVehicle() + " unsupported. Supported are: " + getEncodingManager())); return rsp; } EdgeFilter edgeFilter = new DefaultEdgeFilter(encodingManager.getEncoder(request.getVehicle())); int from = index .findClosest(request.getFrom().lat, request.getFrom().lon, edgeFilter) .getClosestNode(); int to = index.findClosest(request.getTo().lat, request.getTo().lon, edgeFilter).getClosestNode(); String debug = "idLookup:" + sw.stop().getSeconds() + "s"; if (from < 0) rsp.addError(new IllegalArgumentException("Cannot find point 1: " + request.getFrom())); if (to < 0) rsp.addError(new IllegalArgumentException("Cannot find point 2: " + request.getTo())); if (from == to) rsp.addError(new IllegalArgumentException("Point 1 is equal to point 2")); sw = new StopWatch().start(); RoutingAlgorithm algo = null; if (chUsage) { if (request.getAlgorithm().equals("dijkstrabi")) algo = prepare.createAlgo(); else if (request.getAlgorithm().equals("astarbi")) algo = ((PrepareContractionHierarchies) prepare).createAStar(); else rsp.addError( new IllegalStateException( "Only dijkstrabi and astarbi is supported for LevelGraph (using contraction hierarchies)!")); } else { prepare = NoOpAlgorithmPreparation.createAlgoPrepare( graph, request.getAlgorithm(), encodingManager.getEncoder(request.getVehicle()), request.getType()); algo = prepare.createAlgo(); } if (rsp.hasErrors()) { return rsp; } debug += ", algoInit:" + sw.stop().getSeconds() + "s"; sw = new StopWatch().start(); Path path = algo.calcPath(from, to); debug += ", " + algo.getName() + "-routing:" + sw.stop().getSeconds() + "s" + ", " + path.getDebugInfo(); PointList points = path.calcPoints(); simplifyRequest = request.getHint("simplifyRequest", simplifyRequest); if (simplifyRequest) { sw = new StopWatch().start(); int orig = points.getSize(); double minPathPrecision = request.getHint("douglas.minprecision", 1d); if (minPathPrecision > 0) { new DouglasPeucker().setMaxDistance(minPathPrecision).simplify(points); } debug += ", simplify (" + orig + "->" + points.getSize() + "):" + sw.stop().getSeconds() + "s"; } enableInstructions = request.getHint("instructions", enableInstructions); if (enableInstructions) { sw = new StopWatch().start(); rsp.setInstructions(path.calcInstructions()); debug += ", instructions:" + sw.stop().getSeconds() + "s"; } return rsp.setPoints(points) .setDistance(path.getDistance()) .setTime(path.getTime()) .setDebugInfo(debug); }