@Override public int terminalFor(State state) { Vertex v = state.getVertex(); if (v instanceof StreetVertex || v instanceof StreetLocation) { TraverseModeSet modes = state.getOptions().modes; if (modes.contains(TraverseMode.BICYCLE) && (!modes.contains(TraverseMode.WALK) || !state.isBikeRenting())) { Edge edge = state.getBackEdge(); if (edge instanceof StreetEdge) { int cls = ((StreetEdge) edge).getStreetClass(); return cls & StreetEdge.CROSSING_CLASS_MASK; } else { return StreetEdge.CLASS_OTHERPATH; } } else { return StreetEdge.CLASS_OTHERPATH; } } if (v instanceof OnboardVertex) return TRANSIT; if (v instanceof OffboardVertex) return STATION; if (v instanceof BikeRentalStationVertex || v instanceof ParkAndRideVertex || v instanceof BikeParkVertex) return StreetEdge.CLASS_OTHERPATH; else throw new RuntimeException("failed to tokenize path"); }
public RaptorStateSet getStateSet(RoutingRequest options) { final Graph graph; if (options.rctx == null) { graph = graphService.getGraph(options.getRouterId()); options.setRoutingContext(graph); options.rctx.pathParsers = new PathParser[] {new BasicPathParser(), new NoThruTrafficPathParser()}; } else { graph = options.rctx.graph; } RaptorData data = graph.getService(RaptorDataService.class).getData(); // we multiply the initial walk distance to account for epsilon dominance. options.setMaxWalkDistance(options.getMaxWalkDistance() * WALK_EPSILON); RoutingRequest walkOptions = options.clone(); walkOptions.rctx.pathParsers = new PathParser[0]; TraverseModeSet modes = options.getModes().clone(); modes.setTransit(false); walkOptions.setModes(modes); RaptorSearch search = new RaptorSearch(data, options); for (int i = 0; i < options.getMaxTransfers() + 2; ++i) { if (!round(data, options, walkOptions, search, i)) break; } RaptorStateSet result = new RaptorStateSet(); result.statesByStop = search.statesByStop; return result; }
/** * Get the transit stop icon for the given mode * * @param mode * @return the transit stop icon for the given mode */ public static int getStopIcon(TraverseModeSet mode) { if (mode.contains(TraverseMode.BUSISH) && mode.contains(TraverseMode.TRAINISH)) { return R.drawable.ri_flag_triangle; } else if (mode.contains(TraverseMode.BUSISH)) { return R.drawable.ri_flag_triangle; } else if (mode.contains(TraverseMode.TRAINISH)) { return R.drawable.ri_flag_triangle; } // Just use the mode icon return getModeIcon(mode); }
/** * Gets the mode icon for the given mode * * @return the mode icon for the given mode */ public static int getModeIcon(TraverseModeSet mode) { if (mode.contains(TraverseMode.BUSISH) && mode.contains(TraverseMode.TRAINISH)) { return R.drawable.ic_maps_directions_bus; } else if (mode.contains(TraverseMode.BUSISH)) { return R.drawable.ic_maps_directions_bus; } else if (mode.contains(TraverseMode.TRAINISH)) { return R.drawable.ic_directions_railway; } else if (mode.contains(TraverseMode.FERRY)) { return R.drawable.ic_directions_boat; } else if (mode.contains(TraverseMode.GONDOLA)) { return R.drawable.ic_directions_boat; } else if (mode.contains(TraverseMode.SUBWAY)) { return R.drawable.ic_directions_subway; } else if (mode.contains(TraverseMode.TRAM)) { return R.drawable.ic_directions_railway; } else if (mode.contains(TraverseMode.WALK)) { return R.drawable.ic_directions_walk; } else if (mode.contains(TraverseMode.BICYCLE)) { return R.drawable.ic_directions_bike; } else { Log.d(TAG, "No icon for mode set: " + mode); return -1; } }
/** * Calculates walksheds for a given location, based on time given to walk and the walk speed. * * <p>Depending on the value for the "output" parameter (i.e. "POINTS", "SHED" or "EDGES"), a * different type of GeoJSON geometry is returned. If a SHED is requested, then a ConcaveHull of * the EDGES/roads is returned. If that fails, a ConvexHull will be returned. * * <p>The ConcaveHull parameter is set to 0.005 degrees. The offroad walkspeed is assumed to be * 0.83333 m/sec (= 3km/h) until a road is hit. * * <p>Note that the set of EDGES/roads returned as well as POINTS returned may contain duplicates. * If POINTS are requested, then not the end-points are returned at which the max time is reached, * but instead all the graph nodes/crossings that are within the time limits. * * <p>In case there is no road near by within the given time, then a circle for the walktime limit * is created and returned for the SHED parameter. Otherwise the edge with the direction towards * the closest road. Note that the circle is calculated in Euclidian 2D coordinates, and * distortions towards an ellipse will appear if it is transformed/projected to the user location. * * <p>An example request may look like this: * localhost:8080/otp-rest-servlet/ws/iso?layers=traveltime&styles=mask&batch=true&fromPlace=51.040193121307176 * %2C-114.04471635818481&toPlace * =51.09098935%2C-113.95179705&time=2012-06-06T08%3A00%3A00&mode=WALK&maxWalkDistance=10000&walkSpeed=1.38&walkTime=10.7&output=EDGES * Though the first parameters (i) layer, (ii) styles and (iii) batch could be discarded. * * @param walkmins Maximum number of minutes to walk. * @param output Can be set to "POINTS", "SHED" or "EDGES" to return different types of GeoJSON * geometry. SHED returns a ConcaveHull or ConvexHull of the edges/roads. POINTS returns all * graph nodes that are within the time limit. * @return a JSON document containing geometries (either points, lineStrings or a polygon). * @throws Exception * @author sstein---geo.uzh.ch */ @GET @Produces({MediaType.APPLICATION_JSON}) public String getIsochrone( @QueryParam("walkTime") @DefaultValue("15") double walkmins, @QueryParam("output") @DefaultValue("POINTS") String output) throws Exception { this.debugGeoms = new ArrayList(); this.tooFastTraversedEdgeGeoms = new ArrayList(); RoutingRequest sptRequestA = buildRequest(0); String from = sptRequestA.getFrom().toString(); int pos = 1; float lat = 0; float lon = 0; for (String s : from.split(",")) { if (s.isEmpty()) { // no location Response.status(Status.BAD_REQUEST).entity("no position").build(); return null; } try { float num = Float.parseFloat(s); if (pos == 1) { lat = num; } if (pos == 2) { lon = num; } } catch (Exception e) { throw new WebApplicationException( Response.status(Status.BAD_REQUEST) .entity( "Could not parse position string to number. Require numerical lat & long coords.") .build()); } pos++; } GeometryFactory gf = new GeometryFactory(); Coordinate dropPoint = new Coordinate(lon, lat); int walkInMin = (int) Math.floor(walkmins); double walkInSec = walkmins * 60; LOG.debug( "given travel time: " + walkInMin + " mins + " + (walkInSec - (60 * walkInMin)) + " sec"); // restrict the evaluated SPT size to 30mins for requests with walking < 30min // if larger walking times are requested we adjust the evaluated // graph dynamically by 1.3 * min -> this should save processing time if (walkInMin < 30) { sptRequestA.worstTime = sptRequestA.dateTime + (30 * 60); } else { sptRequestA.worstTime = sptRequestA.dateTime + Math.round(walkInMin * 1.3 * 60); } // set the switch-time for shed/area calculation, i.e. to decide if the hull is calculated based // on points or on edges TraverseModeSet modes = sptRequestA.modes; LOG.debug("mode(s): " + modes); if ((modes.contains(TraverseMode.TRANSIT)) || (modes.contains(TraverseMode.BUSISH)) || (modes.contains(TraverseMode.TRAINISH))) { shedCalcMethodSwitchTimeInSec = 60 * 20; // 20min (use 20min for transit, since buses may not come all the time) } else if (modes.contains(TraverseMode.CAR)) { shedCalcMethodSwitchTimeInSec = 60 * 10; // 10min } else if (modes.contains(TraverseMode.BICYCLE)) { shedCalcMethodSwitchTimeInSec = 60 * 10; // 10min } else { shedCalcMethodSwitchTimeInSec = 60 * 20; // 20min } // set the maxUserSpeed, which is used later to check for u-type streets/crescents when // calculating sub-edges; // Note, that the car speed depends on the edge itself, so this value may be replaced later this.usesCar = false; int numberOfModes = modes.getModes().size(); if (numberOfModes == 1) { if (modes.getWalk()) { this.maxUserSpeed = sptRequestA.getWalkSpeed(); } else if (modes.getBicycle()) { this.maxUserSpeed = sptRequestA.getBikeSpeed(); } else if (modes.getDriving()) { this.maxUserSpeed = sptRequestA.getCarSpeed(); this.usesCar = true; } } else { // for all other cases (multiple-modes) // sstein: I thought I may set it to 36.111 m/sec = 130 km/h, // but maybe it is better to assume walk speed for transit, i.e. treat it like if the // person gets off the bus on the last crossing and walks the "last mile". this.maxUserSpeed = sptRequestA.getWalkSpeed(); } if (doSpeedTest) { LOG.debug("performing angle and speed based test to detect u-shapes"); } else { LOG.debug("performing only angle based test to detect u-shapes"); } // TODO: OTP prefers to snap to car-roads/ways, which is not so nice, when walking, // and a footpath is closer by. So far there is no option to switch that off // create the ShortestPathTree try { sptRequestA.setRoutingContext(graphService.getGraph()); } catch (Exception e) { // if we get an exception here, and in particular a VertexNotFoundException, // then it is likely that we chose a (transit) mode without having that (transit) modes data LOG.debug("cannot set RoutingContext: " + e.toString()); LOG.debug("cannot set RoutingContext: setting mode=WALK"); sptRequestA.setMode(TraverseMode.WALK); // fall back to walk mode sptRequestA.setRoutingContext(graphService.getGraph()); } ShortestPathTree sptA = sptService.getShortestPathTree(sptRequestA); StreetLocation origin = (StreetLocation) sptRequestA.rctx.fromVertex; sptRequestA.cleanup(); // remove inserted points // create a LineString for display Coordinate pathToStreetCoords[] = new Coordinate[2]; pathToStreetCoords[0] = dropPoint; pathToStreetCoords[1] = origin.getCoordinate(); LineString pathToStreet = gf.createLineString(pathToStreetCoords); // get distance between origin and drop point for time correction double distanceToRoad = this.distanceLibrary.distance(origin.getY(), origin.getX(), dropPoint.y, dropPoint.x); long offRoadTimeCorrection = (long) (distanceToRoad / this.offRoadWalkspeed); // // --- filter the states --- // Set<Coordinate> visitedCoords = new HashSet<Coordinate>(); ArrayList<Edge> allConnectingEdges = new ArrayList<Edge>(); Coordinate coords[] = null; long maxTime = (long) walkInSec - offRoadTimeCorrection; // System.out.println("Reducing walktime from: " + (int)(walkmins * 60) + "sec to " + maxTime + // "sec due to initial walk of " + distanceToRoad // + "m"); // if the initial walk is already to long, there is no need to parse... if (maxTime <= 0) { noRoadNearBy = true; long timeToWalk = (long) walkInSec; long timeBetweenStates = offRoadTimeCorrection; long timeMissing = timeToWalk; double fraction = (double) timeMissing / (double) timeBetweenStates; pathToStreet = getSubLineString(pathToStreet, fraction); LOG.debug( "no street found within giving travel time (for off-road walkspeed: {} m/sec)", this.offRoadWalkspeed); } else { noRoadNearBy = false; Map<ReversibleLineStringWrapper, Edge> connectingEdgesMap = Maps.newHashMap(); for (State state : sptA.getAllStates()) { long et = state.getElapsedTimeSeconds(); if (et <= maxTime) { // -- filter points, as the same coordinate may be passed several times due to the graph // structure // in a Calgary suburb family homes neighborhood with a 15min walkshed it filtered about // 250 points away (while 145 were finally displayed) if (visitedCoords.contains(state.getVertex().getCoordinate())) { continue; } else { visitedCoords.add(state.getVertex().getCoordinate()); } // -- get all Edges needed later for the edge representation // and to calculate an edge-based walkshed // Note, it can happen that we get a null geometry here, e.g. for hop-edges! Collection<Edge> vertexEdgesIn = state.getVertex().getIncoming(); for (Iterator<Edge> iterator = vertexEdgesIn.iterator(); iterator.hasNext(); ) { Edge edge = (Edge) iterator.next(); Geometry edgeGeom = edge.getGeometry(); if (edgeGeom != null) { // make sure we get only real edges if (edgeGeom instanceof LineString) { // allConnectingEdges.add(edge); // instead of this, use a map now, so we don't have // similar edge many times connectingEdgesMap.put( new ReversibleLineStringWrapper((LineString) edgeGeom), edge); } } } Collection<Edge> vertexEdgesOut = state.getVertex().getOutgoing(); for (Iterator<Edge> iterator = vertexEdgesOut.iterator(); iterator.hasNext(); ) { Edge edge = (Edge) iterator.next(); Geometry edgeGeom = edge.getGeometry(); if (edgeGeom != null) { if (edgeGeom instanceof LineString) { // allConnectingEdges.add(edge); // instead of this, use a map now, so we don't // similar edge many times connectingEdgesMap.put( new ReversibleLineStringWrapper((LineString) edgeGeom), edge); } } } } // end : if(et < maxTime) } // -- // points from list to array, for later coords = new Coordinate[visitedCoords.size()]; int i = 0; for (Coordinate c : visitedCoords) coords[i++] = c; // connection edges from Map to List allConnectingEdges.clear(); for (Edge tedge : connectingEdgesMap.values()) allConnectingEdges.add(tedge); } StringWriter sw = new StringWriter(); GeoJSONBuilder json = new GeoJSONBuilder(sw); // // -- create the different outputs --- // try { if (output.equals(IsoChrone.RESULT_TYPE_POINTS)) { // in case there was no road we create a circle and // and return those points if (noRoadNearBy) { Geometry circleShape = createCirle(dropPoint, pathToStreet); coords = circleShape.getCoordinates(); } // -- the states/nodes with time elapsed <= X min. LOG.debug("write multipoint geom with {} points", coords.length); json.writeGeom(gf.createMultiPoint(coords)); LOG.debug("done"); } else if (output.equals(IsoChrone.RESULT_TYPE_SHED)) { Geometry geomsArray[] = null; // in case there was no road we create a circle if (noRoadNearBy) { Geometry circleShape = createCirle(dropPoint, pathToStreet); json.writeGeom(circleShape); } else { if (maxTime > shedCalcMethodSwitchTimeInSec) { // eg., walkshed > 20 min // -- create a point-based walkshed // less exact and should be used for large walksheds with many edges LOG.debug("create point-based shed (not from edges)"); geomsArray = new Geometry[coords.length]; for (int j = 0; j < geomsArray.length; j++) { geomsArray[j] = gf.createPoint(coords[j]); } } else { // -- create an edge-based walkshed // it is more exact and should be used for short walks LOG.debug("create edge-based shed (not from points)"); Map<ReversibleLineStringWrapper, LineString> walkShedEdges = Maps.newHashMap(); // add the walk from the pushpin to closest street point walkShedEdges.put(new ReversibleLineStringWrapper(pathToStreet), pathToStreet); // get the edges and edge parts within time limits ArrayList<LineString> withinTimeEdges = this.getLinesAndSubEdgesWithinMaxTime( maxTime, allConnectingEdges, sptA, angleLimitForUShapeDetection, distanceToleranceForUShapeDetection, maxUserSpeed, usesCar, doSpeedTest); for (LineString ls : withinTimeEdges) { walkShedEdges.put(new ReversibleLineStringWrapper(ls), ls); } geomsArray = new Geometry[walkShedEdges.size()]; int k = 0; for (LineString ls : walkShedEdges.values()) geomsArray[k++] = ls; } // end if-else: maxTime condition GeometryCollection gc = gf.createGeometryCollection(geomsArray); // create the concave hull, but in case it fails we just return the convex hull Geometry outputHull = null; LOG.debug( "create concave hull from {} geoms with edge length limit of about {} m (distance on meridian)", geomsArray.length, concaveHullAlpha * 111132); // 1deg at Latitude phi = 45deg is about 111.132km // (see wikipedia: // http://en.wikipedia.org/wiki/Latitude#The_length_of_a_degree_of_latitude) try { ConcaveHull hull = new ConcaveHull(gc, concaveHullAlpha); outputHull = hull.getConcaveHull(); } catch (Exception e) { outputHull = gc.convexHull(); LOG.debug("Could not generate ConcaveHull for WalkShed, using ConvexHull instead."); } LOG.debug("write shed geom"); json.writeGeom(outputHull); LOG.debug("done"); } } else if (output.equals(IsoChrone.RESULT_TYPE_EDGES)) { // in case there was no road we return only the suggested path to the street if (noRoadNearBy) { json.writeGeom(pathToStreet); } else { // -- if we would use only the edges from the paths to the origin we will miss // some edges that will be never on the shortest path (e.g. loops/crescents). // However, we can retrieve all edges by checking the times for each // edge end-point Map<ReversibleLineStringWrapper, LineString> walkShedEdges = Maps.newHashMap(); // add the walk from the pushpin to closest street point walkShedEdges.put(new ReversibleLineStringWrapper(pathToStreet), pathToStreet); // get the edges and edge parts within time limits ArrayList<LineString> withinTimeEdges = this.getLinesAndSubEdgesWithinMaxTime( maxTime, allConnectingEdges, sptA, angleLimitForUShapeDetection, distanceToleranceForUShapeDetection, maxUserSpeed, usesCar, doSpeedTest); for (LineString ls : withinTimeEdges) { walkShedEdges.put(new ReversibleLineStringWrapper(ls), ls); } Geometry mls = null; LineString edges[] = new LineString[walkShedEdges.size()]; int k = 0; for (LineString ls : walkShedEdges.values()) edges[k++] = ls; LOG.debug("create multilinestring from {} geoms", edges.length); mls = gf.createMultiLineString(edges); LOG.debug("write geom"); json.writeGeom(mls); LOG.debug("done"); } } else if (output.equals("DEBUGEDGES")) { // -- for debugging, i.e. display of detected u-shapes/crescents ArrayList<LineString> withinTimeEdges = this.getLinesAndSubEdgesWithinMaxTime( maxTime, allConnectingEdges, sptA, angleLimitForUShapeDetection, distanceToleranceForUShapeDetection, maxUserSpeed, usesCar, doSpeedTest); if (this.showTooFastEdgesAsDebugGeomsANDnotUShapes) { LOG.debug("displaying edges that are traversed too fast"); this.debugGeoms = this.tooFastTraversedEdgeGeoms; } else { LOG.debug("displaying detected u-shaped roads/crescents"); } LineString edges[] = new LineString[this.debugGeoms.size()]; int k = 0; for (Iterator iterator = debugGeoms.iterator(); iterator.hasNext(); ) { LineString ls = (LineString) iterator.next(); edges[k] = ls; k++; } Geometry mls = gf.createMultiLineString(edges); LOG.debug("write debug geom"); json.writeGeom(mls); LOG.debug("done"); } } catch (org.codehaus.jettison.json.JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } return sw.toString(); }
@Override public List<GraphPath> getPaths(RoutingRequest options) { final Graph graph = graphService.getGraph(options.getRouterId()); if (options.rctx == null) { options.setRoutingContext(graph); options.rctx.pathParsers = new PathParser[] {new BasicPathParser(), new NoThruTrafficPathParser()}; } if (!options.getModes().isTransit()) { return sptService.getShortestPathTree(options).getPaths(); } // also fall back to A* for short trips double distance = distanceLibrary.distance( options.rctx.origin.getCoordinate(), options.rctx.target.getCoordinate()); if (distance < shortPathCutoff) { log.debug("Falling back to A* for very short path"); return shortPathService.getPaths(options); } RaptorDataService service = graph.getService(RaptorDataService.class); if (service == null) { log.warn("No raptor data. Rebuild with RaptorDataBuilder"); return Collections.emptyList(); } RaptorData data = service.getData(); // we multiply the initial walk distance to account for epsilon dominance. double initialWalk = options.getMaxWalkDistance() * WALK_EPSILON; options.setMaxWalkDistance(initialWalk); // do not even bother with obviously impossible walks double minWalk = options.rctx.origin.getDistanceToNearestTransitStop() + options.rctx.target.getDistanceToNearestTransitStop(); if (options.getMaxWalkDistance() < minWalk) { options.setMaxWalkDistance(minWalk); } RoutingRequest walkOptions = options.clone(); walkOptions.rctx.pathParsers = new PathParser[0]; TraverseModeSet modes = options.getModes().clone(); modes.setTransit(false); walkOptions.setModes(modes); RaptorSearch search = new RaptorSearch(data, options); if (data.maxTransitRegions != null) { Calendar tripDate = Calendar.getInstance(graph.getTimeZone()); tripDate.setTime(new Date(1000L * options.dateTime)); Calendar maxTransitStart = Calendar.getInstance(graph.getTimeZone()); maxTransitStart.set(Calendar.YEAR, data.maxTransitRegions.startYear); maxTransitStart.set(Calendar.MONTH, data.maxTransitRegions.startMonth); maxTransitStart.set(Calendar.DAY_OF_MONTH, data.maxTransitRegions.startDay); int day = 0; while (tripDate.after(maxTransitStart)) { day++; tripDate.add(Calendar.DAY_OF_MONTH, -1); } if (day > data.maxTransitRegions.maxTransit.length || options.isWheelchairAccessible()) { day = -1; } search.maxTimeDayIndex = day; } int rushAheadRound = preliminaryRaptorSearch(data, options, walkOptions, search); long searchBeginTime = System.currentTimeMillis(); double expectedWorstTime = 1.5 * distanceLibrary.distance( options.rctx.origin.getCoordinate(), options.rctx.target.getCoordinate()) / options.getWalkSpeed(); int foundSoFar = 0; double firstWalkDistance = 0; List<RaptorState> targetStates = new ArrayList<RaptorState>(); do { int bestElapsedTime = Integer.MAX_VALUE; RETRY: do { for (int round = 0; round < options.getMaxTransfers() + 2; ++round) { if (!round(data, options, walkOptions, search, round)) break; long elapsed = System.currentTimeMillis() - searchBeginTime; if (elapsed > multiPathTimeout * 1000 && multiPathTimeout > 0 && targetStates.size() > 0) break RETRY; ArrayList<RaptorState> toRemove = new ArrayList<RaptorState>(); for (RaptorState state : search.getTargetStates()) { if (state.nBoardings == 0 && options.getMaxWalkDistance() > initialWalk) { toRemove.add(state); } } if (search.getTargetStates().size() > 0) { if (firstWalkDistance == 0) { firstWalkDistance = options.getMaxWalkDistance(); } for (RaptorState state : toRemove) { search.removeTargetState(state.walkPath); } } if (targetStates.size() >= options.getNumItineraries() && round >= rushAheadRound) { int oldBest = bestElapsedTime; for (RaptorState state : search.getTargetStates()) { final int elapsedTime = (int) Math.abs(state.arrivalTime - options.dateTime); if (elapsedTime < bestElapsedTime) { bestElapsedTime = elapsedTime; } } int improvement = oldBest - bestElapsedTime; if (improvement < 600 && bestElapsedTime < expectedWorstTime) break RETRY; } } if (foundSoFar < search.getTargetStates().size()) { foundSoFar = search.getTargetStates().size(); } else if (foundSoFar > 0) { // we didn't find anything new in this round, and we already have // some paths, so bail out break; } options = options.clone(); walkOptions = walkOptions.clone(); if (search.getTargetStates().size() > 0 && bestElapsedTime < expectedWorstTime) { // we have found some paths so we no longer want to expand the max walk distance break RETRY; } else { options.setMaxWalkDistance(options.getMaxWalkDistance() * 2); walkOptions.setMaxWalkDistance(options.getMaxWalkDistance()); options.setWalkReluctance(options.getWalkReluctance() * 2); walkOptions.setWalkReluctance(options.getWalkReluctance()); } search.reset(options); } while (options.getMaxWalkDistance() < initialWalk * MAX_WALK_MULTIPLE && initialWalk < Double.MAX_VALUE); options = options.clone(); walkOptions = walkOptions.clone(); for (RaptorState state : search.getTargetStates()) { for (AgencyAndId trip : state.getTrips()) { options.bannedTrips.add(trip); } } if (search.getTargetStates().size() == 0) break; // no paths found; searching more won't help options.setMaxWalkDistance(firstWalkDistance); walkOptions.setMaxWalkDistance(firstWalkDistance); targetStates.addAll(search.getTargetStates()); search = new RaptorSearch(data, options); } while (targetStates.size() < options.getNumItineraries()); collectRoutesUsed(data, options, targetStates); if (targetStates.isEmpty()) { log.info("RAPTOR found no paths"); } Collections.sort(targetStates); if (targetStates.size() > options.getNumItineraries()) targetStates = targetStates.subList(0, options.getNumItineraries()); List<GraphPath> paths = new ArrayList<GraphPath>(); for (RaptorState targetState : targetStates) { // reconstruct path ArrayList<RaptorState> states = new ArrayList<RaptorState>(); RaptorState cur = targetState; while (cur != null) { states.add(cur); cur = cur.getParent(); } // states is in reverse order of time State state = getState(targetState.getRequest(), data, states); paths.add(new GraphPath(state, true)); } return paths; }