/** 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; } }
public RoutingAlgorithmSpecialAreaTests(Graph graph) { this.unterfrankenGraph = graph; StopWatch sw = new StopWatch().start(); Location2IDQuadtree index; if (graph instanceof GraphStorage) { Directory dir = ((GraphStorage) graph).getDirectory(); index = new Location2IDQuadtree(unterfrankenGraph, dir); // logger.info("dir: " + dir.getLocation()); } else index = new Location2IDQuadtree(unterfrankenGraph, new RAMDirectory("loc2idIndex", false)); // Location2IDPreciseIndex index = new Location2IDPreciseIndex(unterfrankenGraph, dir); // Location2IDFullIndex index = new Location2IDFullIndex(graph); if (!index.loadExisting()) { index.prepareIndex(200000); index.flush(); } idx = index; logger.info( index.getClass().getSimpleName() + " index. Size:" + idx.calcMemInMB() + " MB, took:" + sw.stop().getSeconds()); }
@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 void doPost(HttpServletRequest httpReq, HttpServletResponse httpRes) throws ServletException, IOException { String infoStr = httpReq.getRemoteAddr() + " " + httpReq.getLocale() + " " + httpReq.getHeader("User-Agent"); String type = httpReq.getContentType(); GPXFile gpxFile; if (type.contains("application/xml")) { try { gpxFile = parseXML(httpReq); } catch (Exception ex) { logger.warn("Cannot parse XML for " + httpReq.getQueryString() + ", " + infoStr); httpRes.setStatus(SC_BAD_REQUEST); httpRes.getWriter().append(errorsToXML(Collections.<Throwable>singletonList(ex))); return; } } else { throw new IllegalArgumentException("content type not supported " + type); } final String format = getParam(httpReq, "type", "json"); boolean writeGPX = GPX_FORMAT.equals(format); boolean pointsEncoded = getBooleanParam(httpReq, "points_encoded", true); boolean enableInstructions = writeGPX || getBooleanParam(httpReq, "instructions", true); boolean enableElevation = getBooleanParam(httpReq, "elevation", false); boolean forceRepair = getBooleanParam(httpReq, "force_repair", false); // TODO export OSM IDs instead, use https://github.com/karussell/graphhopper-osm-id-mapping boolean enableTraversalKeys = getBooleanParam(httpReq, "traversal_keys", false); int maxNodesToVisit = (int) getLongParam(httpReq, "max_nodes_to_visit", 500); int separatedSearchDistance = (int) getLongParam(httpReq, "separated_search_distance", 300); String vehicle = getParam(httpReq, "vehicle", "car"); Locale locale = Helper.getLocale(getParam(httpReq, "locale", "en")); GHResponse matchGHRsp = new GHResponse(); MatchResult matchRsp = null; StopWatch sw = new StopWatch().start(); try { FlagEncoder encoder = hopper.getEncodingManager().getEncoder(vehicle); MapMatching matching = new MapMatching(hopper.getGraphHopperStorage(), locationIndexMatch, encoder); matching.setForceRepair(forceRepair); matching.setMaxNodesToVisit(maxNodesToVisit); matching.setSeparatedSearchDistance(separatedSearchDistance); matchRsp = matching.doWork(gpxFile.getEntries()); // fill GHResponse for identical structure Path path = matching.calcPath(matchRsp); Translation tr = trMap.getWithFallBack(locale); new PathMerger().doWork(matchGHRsp, Collections.singletonList(path), tr); } catch (Exception ex) { matchGHRsp.addError(ex); } logger.info( httpReq.getQueryString() + ", " + infoStr + ", took:" + sw.stop().getSeconds() + ", entries:" + gpxFile.getEntries().size() + ", " + matchGHRsp.getDebugInfo()); if (EXTENDED_JSON_FORMAT.equals(format)) { if (matchGHRsp.hasErrors()) { httpRes.setStatus(SC_BAD_REQUEST); httpRes.getWriter().append(new JSONArray(matchGHRsp.getErrors()).toString()); } else { httpRes.getWriter().write(new MatchResultToJson(matchRsp).exportTo().toString()); } } else if (GPX_FORMAT.equals(format)) { String xml = createGPXString(httpReq, httpRes, matchGHRsp); if (matchGHRsp.hasErrors()) { httpRes.setStatus(SC_BAD_REQUEST); httpRes.getWriter().append(xml); } else { writeResponse(httpRes, xml); } } else { Map<String, Object> map = routeSerializer.toJSON( matchGHRsp, true, pointsEncoded, enableElevation, enableInstructions); if (matchGHRsp.hasErrors()) { writeJsonError(httpRes, SC_BAD_REQUEST, new JSONObject(map)); } else { if (enableTraversalKeys) { if (matchRsp == null) { throw new IllegalStateException( "match response has to be none-null if no error happened"); } // encode edges as traversal keys which includes orientation // decode simply by multiplying with 0.5 List<Integer> traversalKeylist = new ArrayList<Integer>(); for (EdgeMatch em : matchRsp.getEdgeMatches()) { EdgeIteratorState edge = em.getEdgeState(); traversalKeylist.add( GHUtility.createEdgeKey( edge.getBaseNode(), edge.getAdjNode(), edge.getEdge(), false)); } map.put("traversal_keys", traversalKeylist); } writeJson(httpReq, httpRes, new JSONObject(map)); } } }
@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); }
private void start(CmdArgs args) { String action = args.get("action", "").toLowerCase(); args.put("graph.location", "./graph-cache"); if (action.equals("import")) { String vehicle = args.get("vehicle", "car").toLowerCase(); args.put("graph.flagEncoders", vehicle); args.put("osmreader.osm", args.get("datasource", "")); // standard should be to remove disconnected islands args.put("prepare.minNetworkSize", 200); args.put("prepare.minOneWayNetworkSize", 200); GraphHopper hopper = new GraphHopper().init(args); hopper.setCHEnable(false); hopper.importOrLoad(); } else if (action.equals("match")) { GraphHopper hopper = new GraphHopper().init(args); hopper.setCHEnable(false); logger.info("loading graph from cache"); hopper.load("./graph-cache"); FlagEncoder firstEncoder = hopper.getEncodingManager().fetchEdgeEncoders().get(0); GraphHopperStorage graph = hopper.getGraphHopperStorage(); int gpxAccuracy = args.getInt("gpxAccuracy", 15); String instructions = args.get("instructions", ""); logger.info("Setup lookup index. Accuracy filter is at " + gpxAccuracy + "m"); LocationIndexMatch locationIndex = new LocationIndexMatch(graph, (LocationIndexTree) hopper.getLocationIndex(), gpxAccuracy); MapMatching mapMatching = new MapMatching(graph, locationIndex, firstEncoder); mapMatching.setSeparatedSearchDistance(args.getInt("separatedSearchDistance", 500)); mapMatching.setMaxNodesToVisit(args.getInt("maxNodesToVisit", 1000)); mapMatching.setForceRepair(args.getBool("forceRepair", false)); // do the actual matching, get the GPX entries from a file or via stream String gpxLocation = args.get("gpx", ""); File[] files = getFiles(gpxLocation); logger.info("Now processing " + files.length + " files"); StopWatch importSW = new StopWatch(); StopWatch matchSW = new StopWatch(); Translation tr = new TranslationMap().doImport().get(instructions); for (File gpxFile : files) { try { importSW.start(); List<GPXEntry> inputGPXEntries = new GPXFile().doImport(gpxFile.getAbsolutePath()).getEntries(); importSW.stop(); matchSW.start(); MatchResult mr = mapMatching.doWork(inputGPXEntries); matchSW.stop(); System.out.println(gpxFile); System.out.println( "\tmatches:\t" + mr.getEdgeMatches().size() + ", gps entries:" + inputGPXEntries.size()); System.out.println( "\tgpx length:\t" + (float) mr.getGpxEntriesLength() + " vs " + (float) mr.getMatchLength()); System.out.println( "\tgpx time:\t" + mr.getGpxEntriesMillis() / 1000f + " vs " + mr.getMatchMillis() / 1000f); String outFile = gpxFile.getAbsolutePath() + ".res.gpx"; System.out.println("\texport results to:" + outFile); InstructionList il; if (instructions.isEmpty()) { il = new InstructionList(null); } else { AltResponse matchGHRsp = new AltResponse(); Path path = mapMatching.calcPath(mr); new PathMerger().doWork(matchGHRsp, Collections.singletonList(path), tr); il = matchGHRsp.getInstructions(); } new GPXFile(mr, il).doExport(outFile); } catch (Exception ex) { importSW.stop(); matchSW.stop(); logger.error("Problem with file " + gpxFile + " Error: " + ex.getMessage()); } } System.out.println( "gps import took:" + importSW.getSeconds() + "s, match took: " + matchSW.getSeconds()); } else if (action.equals("getbounds")) { String gpxLocation = args.get("gpx", ""); File[] files = getFiles(gpxLocation); BBox bbox = BBox.createInverse(false); for (File gpxFile : files) { List<GPXEntry> inputGPXEntries = new GPXFile().doImport(gpxFile.getAbsolutePath()).getEntries(); for (GPXEntry entry : inputGPXEntries) { if (entry.getLat() < bbox.minLat) { bbox.minLat = entry.getLat(); } if (entry.getLat() > bbox.maxLat) { bbox.maxLat = entry.getLat(); } if (entry.getLon() < bbox.minLon) { bbox.minLon = entry.getLon(); } if (entry.getLon() > bbox.maxLon) { bbox.maxLon = entry.getLon(); } } } System.out.println("max bounds: " + bbox); // show download only for small areas if (bbox.maxLat - bbox.minLat < 0.1 && bbox.maxLon - bbox.minLon < 0.1) { double delta = 0.01; System.out.println( "Get small areas via\n" + "wget -O extract.osm 'http://overpass-api.de/api/map?bbox=" + (bbox.minLon - delta) + "," + (bbox.minLat - delta) + "," + (bbox.maxLon + delta) + "," + (bbox.maxLat + delta) + "'"); } } else { System.out.println( "Usage: Do an import once, then do the matching\n" + "./map-matching action=import datasource=your.pbf\n" + "./map-matching action=match gpx=your.gpx\n" + "./map-matching action=match gpx=.*gpx\n\n" + "Or start in-built matching web service\n" + "./map-matching action=start-server\n\n"); } }
public void runShortestPathPerf(int runs, RoutingAlgorithm algo) throws Exception { BBox bbox = unterfrankenGraph.getBounds(); double minLat = bbox.minLat, minLon = bbox.minLon; double maxLat = bbox.maxLat, maxLon = bbox.maxLon; if (unterfrankenGraph instanceof LevelGraph) { if (algo instanceof DijkstraBidirectionRef) algo = new PrepareContractionHierarchies().setGraph(unterfrankenGraph).createAlgo(); // algo = new // PrepareSimpleShortcuts().setGraph(unterfrankenGraph).createAlgo(); else if (algo instanceof AStarBidirection) algo = new PrepareSimpleShortcuts().setGraph(unterfrankenGraph).createAStar(); else // level graph accepts all algorithms but normally we want to use an optimized one throw new IllegalStateException( "algorithm which boosts query time for levelgraph not found " + algo); logger.info("[experimental] using shortcuts with " + algo); } else logger.info("running " + algo); Random rand = new Random(123); StopWatch sw = new StopWatch(); // System.out.println("cap:" + ((GraphStorage) unterfrankenGraph).capacity()); for (int i = 0; i < runs; i++) { double fromLat = rand.nextDouble() * (maxLat - minLat) + minLat; double fromLon = rand.nextDouble() * (maxLon - minLon) + minLon; // sw.start(); int from = idx.findID(fromLat, fromLon); double toLat = rand.nextDouble() * (maxLat - minLat) + minLat; double toLon = rand.nextDouble() * (maxLon - minLon) + minLon; int to = idx.findID(toLat, toLon); // sw.stop(); // logger.info(i + " " + sw + " from (" + from + ")" + fromLat + ", " + fromLon // + " to (" + to + ")" + toLat + ", " + toLon); if (from == to) { logger.warn("skipping i " + i + " from==to " + from); continue; } algo.clear(); sw.start(); Path p = algo.calcPath(from, to); sw.stop(); if (!p.found()) { // there are still paths not found cause of oneway motorways => only routable in one // direction // e.g. unterfrankenGraph.getLatitude(798809) + "," + unterfrankenGraph.getLongitude(798809) logger.warn( "no route found for i=" + i + " !? " + "graph-from " + from + "(" + fromLat + "," + fromLon + "), " + "graph-to " + to + "(" + toLat + "," + toLon + ")"); continue; } if (i % 20 == 0) logger.info(i + " " + sw.getSeconds() / (i + 1) + " secs/run"); // (" + p + ")"); } }
@Override public GHResponse route(GHRequest request) { StopWatch sw = new StopWatch().start(); double took = 0; try { String url = serviceUrl + "?point=" + request.getFrom().lat + "," + request.getFrom().lon + "&point=" + request.getTo().lat + "," + request.getTo().lon + "&type=json" + "&points_encoded=" + pointsEncoded + "&min_path_precision=" + request.getHint("douglas.minprecision", 1) + "&algo=" + request.getAlgorithm(); String str = downloader.downloadAsString(url); JSONObject json = new JSONObject(str); took = json.getJSONObject("info").getDouble("took"); JSONArray paths = json.getJSONArray("paths"); JSONObject firstPath = paths.getJSONObject(0); boolean is3D = false; if (firstPath.has("points_dim")) is3D = "3".equals(firstPath.getString("points_dim")); double distance = firstPath.getDouble("distance"); int time = firstPath.getInt("time"); PointList pointList; if (pointsEncoded) { pointList = WebHelper.decodePolyline(firstPath.getString("points"), 100, is3D); } else { JSONArray coords = firstPath.getJSONObject("points").getJSONArray("coordinates"); pointList = new PointList(coords.length(), is3D); for (int i = 0; i < coords.length(); i++) { JSONArray arr = coords.getJSONArray(i); double lon = arr.getDouble(0); double lat = arr.getDouble(1); if (is3D) pointList.add(lat, lon, arr.getDouble(2)); else pointList.add(lat, lon); } } GHResponse res = new GHResponse(); if (instructions) { JSONArray instrArr = firstPath.getJSONArray("instructions"); InstructionList il = new InstructionList(); for (int instrIndex = 0; instrIndex < instrArr.length(); instrIndex++) { JSONObject jsonObj = instrArr.getJSONObject(instrIndex); double instDist = jsonObj.getDouble("distance"); String text = jsonObj.getString("text"); long instTime = jsonObj.getLong("time"); int sign = jsonObj.getInt("sign"); JSONArray iv = jsonObj.getJSONArray("interval"); int from = iv.getInt(0); int to = iv.getInt(1); PointList instPL = new PointList(to - from, is3D); for (int j = from; j <= to; j++) { instPL.add(pointList, j); } // TODO way and payment type Instruction instr = new Instruction(sign, text, -1, -1, instPL).setDistance(instDist).setTime(instTime); il.add(instr); } res.setInstructions(il); } return res.setPoints(pointList).setDistance(distance).setMillis(time); } catch (Exception ex) { throw new RuntimeException( "Problem while fetching path " + request.getFrom() + "->" + request.getTo(), ex); } finally { logger.debug("Full request took:" + sw.stop().getSeconds() + ", API took:" + took); } }