@Override public List<GraphPath> plan( String fromPlace, String toPlace, List<String> intermediates, Date targetTime, TraverseOptions options) { if (options.getModes().contains(TraverseMode.TRANSIT)) { throw new UnsupportedOperationException("TSP is not supported for transit trips"); } ArrayList<String> notFound = new ArrayList<String>(); Vertex fromVertex = getVertexForPlace(fromPlace, options); if (fromVertex == null) { notFound.add("from"); } Vertex toVertex = getVertexForPlace(toPlace, options); if (toVertex == null) { notFound.add("to"); } ArrayList<Vertex> intermediateVertices = new ArrayList<Vertex>(); int i = 0; for (String intermediate : intermediates) { Vertex vertex = getVertexForPlace(intermediate, options); if (vertex == null) { notFound.add("intermediate." + i); } else { intermediateVertices.add(vertex); } i += 1; } if (notFound.size() > 0) { throw new VertexNotFoundException(notFound); } if (_graphService.getCalendarService() != null) options.setCalendarService(_graphService.getCalendarService()); options.setTransferTable(_graphService.getGraph().getTransferTable()); GraphPath path = _routingService.route( fromVertex, toVertex, intermediateVertices, (int) (targetTime.getTime() / 1000), options); return Arrays.asList(path); }
/** Build the weight table, parallelized according to the number of processors */ public void buildTable() { ArrayList<TransitStop> stopVertices; LOG.debug("Number of vertices: " + g.getVertices().size()); stopVertices = new ArrayList<TransitStop>(); for (Vertex gv : g.getVertices()) if (gv instanceof TransitStop) stopVertices.add((TransitStop) gv); int nStops = stopVertices.size(); stopIndices = new IdentityHashMap<Vertex, Integer>(nStops); for (int i = 0; i < nStops; i++) stopIndices.put(stopVertices.get(i), i); LOG.debug("Number of stops: " + nStops); table = new float[nStops][nStops]; for (float[] row : table) Arrays.fill(row, Float.POSITIVE_INFINITY); LOG.debug("Performing search at each transit stop."); int nThreads = Runtime.getRuntime().availableProcessors(); LOG.debug("number of threads: " + nThreads); ArrayBlockingQueue<Runnable> taskQueue = new ArrayBlockingQueue<Runnable>(nStops); ThreadPoolExecutor threadPool = new ThreadPoolExecutor(nThreads, nThreads, 10, TimeUnit.SECONDS, taskQueue); GenericObjectPool heapPool = new GenericObjectPool(new PoolableBinHeapFactory<State>(g.getVertices().size()), nThreads); // make one heap and recycle it TraverseOptions options = new TraverseOptions(); options.speed = maxWalkSpeed; final double MAX_WEIGHT = 60 * 60 * options.walkReluctance; final double OPTIMISTIC_BOARD_COST = options.boardCost; // create a task for each transit stop in the graph ArrayList<Callable<Void>> tasks = new ArrayList<Callable<Void>>(); for (TransitStop origin : stopVertices) { SPTComputer task = new SPTComputer(heapPool, options, MAX_WEIGHT, OPTIMISTIC_BOARD_COST, origin); tasks.add(task); } try { // invoke all of tasks. threadPool.invokeAll(tasks); threadPool.shutdown(); } catch (InterruptedException e) { throw new RuntimeException(e); } floyd(); }
/** * Get polygons covering the components of the graph. The largest component (in terms of number of * nodes) will not overlap any other components (it will have holes); the others may overlap each * other. * * @param modes * @return */ @Secured({"ROLE_USER"}) @GET @Path("/polygons") @Produces({MediaType.APPLICATION_JSON}) public GraphComponentPolygons getComponentPolygons( @DefaultValue("TRANSIT,WALK") @QueryParam("modes") TraverseModeSet modes, @QueryParam(RequestInf.DATE) String date, @QueryParam(RequestInf.TIME) String time, @DefaultValue("") @QueryParam(RequestInf.BANNED_ROUTES) String bannedRoutes) { TraverseOptions options = new TraverseOptions(modes); options.bannedRoutes = new HashSet<RouteSpec>(); if (bannedRoutes.length() > 0) { for (String element : bannedRoutes.split(",")) { String[] routeSpec = element.split("_", 2); if (routeSpec.length != 2) { throw new IllegalArgumentException("AgencyId or routeId not set in bannedRoutes list"); } options.bannedRoutes.add(new RouteSpec(routeSpec[0], routeSpec[1])); } } long dateTime = DateUtils.toDate(date, time).getTime(); if (cachedPolygons == null || dateTime != cachedDateTime || !options.equals(cachedOptions)) { cachedOptions = options; cachedDateTime = dateTime; Graph graph = graphService.getGraph(); if (graphService.getCalendarService() != null) { options.setCalendarService(graphService.getCalendarService()); } options.setServiceDays(dateTime, graph.getAgencyIds()); cachedPolygons = AnalysisUtils.getComponentPolygons(graph, options, dateTime); } GraphComponentPolygons out = new GraphComponentPolygons(); out.components = new ArrayList<GraphComponent>(); for (Geometry geometry : cachedPolygons) { GraphComponent component = new GraphComponent(); component.polygon = geometry; out.components.add(component); } return out; }
@Override public List<GraphPath> plan( String fromPlace, String toPlace, Date targetTime, TraverseOptions options, int nItineraries) { ArrayList<String> notFound = new ArrayList<String>(); Vertex fromVertex = getVertexForPlace(fromPlace, options); if (fromVertex == null) { notFound.add("from"); } Vertex toVertex = getVertexForPlace(toPlace, options); if (toVertex == null) { notFound.add("to"); } if (notFound.size() > 0) { throw new VertexNotFoundException(notFound); } Vertex origin = null; Vertex target = null; if (options.isArriveBy()) { origin = toVertex; target = fromVertex; } else { origin = fromVertex; target = toVertex; } State state = new State((int) (targetTime.getTime() / 1000), origin, options); return plan(state, target, nItineraries); }
private TraverseResult tryWalkBike(State s0, TraverseOptions options, boolean back) { if (options.getModes().contains(TraverseMode.BICYCLE)) { return doTraverse(s0, options.getWalkingOptions(), back); } return null; }
@Override public List<GraphPath> plan(State origin, Vertex target, int nItineraries) { Date targetTime = new Date(origin.getTime() * 1000); TraverseOptions options = origin.getOptions(); if (_graphService.getCalendarService() != null) options.setCalendarService(_graphService.getCalendarService()); options.setTransferTable(_graphService.getGraph().getTransferTable()); options.setServiceDays(targetTime.getTime() / 1000); if (options.getModes().getTransit() && !_graphService.getGraph().transitFeedCovers(targetTime)) { // user wants a path through the transit network, // but the date provided is outside those covered by the transit feed. throw new TransitTimesException(); } // decide which A* heuristic to use options.remainingWeightHeuristic = _remainingWeightHeuristicFactory.getInstanceForSearch(options, target); LOG.debug("Applied A* heuristic: {}", options.remainingWeightHeuristic); // If transit is not to be used, disable walk limit and only search for one itinerary. if (!options.getModes().getTransit()) { nItineraries = 1; options.setMaxWalkDistance(Double.MAX_VALUE); } ArrayList<GraphPath> paths = new ArrayList<GraphPath>(); // The list of options specifying various modes, banned routes, etc to try for multiple // itineraries Queue<TraverseOptions> optionQueue = new LinkedList<TraverseOptions>(); optionQueue.add(options); /* if the user wants to travel by transit, create a bus-only set of options */ if (options.getModes().getTrainish() && options.getModes().contains(TraverseMode.BUS)) { TraverseOptions busOnly = options.clone(); busOnly.setModes(options.getModes().clone()); busOnly.getModes().setTrainish(false); // Moved inside block to avoid double insertion in list ? (AMB) // optionQueue.add(busOnly); } double maxWeight = Double.MAX_VALUE; long maxTime = options.isArriveBy() ? 0 : Long.MAX_VALUE; while (paths.size() < nItineraries) { options = optionQueue.poll(); if (options == null) { break; } StateEditor editor = new StateEditor(origin, null); editor.setTraverseOptions(options); origin = editor.makeState(); // options.worstTime = maxTime; // options.maxWeight = maxWeight; long searchBeginTime = System.currentTimeMillis(); LOG.debug("BEGIN SEARCH"); List<GraphPath> somePaths = _routingService.route(origin, target); LOG.debug("END SEARCH {} msec", System.currentTimeMillis() - searchBeginTime); if (maxWeight == Double.MAX_VALUE) { /* * the worst trip we are willing to accept is at most twice as bad or twice as long. */ if (somePaths.isEmpty()) { // if there is no first path, there won't be any other paths return null; } GraphPath path = somePaths.get(0); long duration = path.getDuration(); LOG.debug("Setting max time and weight for subsequent searches."); LOG.debug("First path start time: {}", path.getStartTime()); maxTime = path.getStartTime() + MAX_TIME_FACTOR * (options.isArriveBy() ? -duration : duration); LOG.debug("First path duration: {}", duration); LOG.debug("Max time set to: {}", maxTime); maxWeight = path.getWeight() * MAX_WEIGHT_FACTOR; LOG.debug("Max weight set to: {}", maxWeight); } if (somePaths.isEmpty()) { LOG.debug("NO PATHS FOUND"); continue; } for (GraphPath path : somePaths) { if (!paths.contains(path)) { // DEBUG // path.dump(); paths.add(path); // now, create a list of options, one with each trip in this journey banned. LOG.debug("New trips: {}", path.getTrips()); TraverseOptions newOptions = options.clone(); for (AgencyAndId trip : path.getTrips()) { newOptions.bannedTrips.add(trip); } if (!optionQueue.contains(newOptions)) { optionQueue.add(newOptions); } /* * // now, create a list of options, one with each route in this trip banned. // * the HashSet banned is not strictly necessary as the optionsQueue will // * already remove duplicate options, but it might be slightly faster as // * hashing TraverseOptions is slow. LOG.debug("New routespecs: {}", * path.getRouteSpecs()); for (RouteSpec spec : path.getRouteSpecs()) { * TraverseOptions newOptions = options.clone(); * newOptions.bannedRoutes.add(spec); if (!optionQueue.contains(newOptions)) { * optionQueue.add(newOptions); } } */ } } LOG.debug("{} / {} itineraries", paths.size(), nItineraries); } if (paths.size() == 0) { return null; } // We order the list of returned paths by the time of arrival or departure (not path duration) Collections.sort(paths, new PathComparator(origin.getOptions().isArriveBy())); return paths; }
public void testTriangle() { Coordinate c1 = new Coordinate(-122.575033, 45.456773); Coordinate c2 = new Coordinate(-122.576668, 45.451426); Vertex v1 = new Vertex("v1", c1, null); Vertex v2 = new Vertex("v2", c2, null); GeometryFactory factory = new GeometryFactory(); LineString geometry = factory.createLineString(new Coordinate[] {c1, c2}); double length = 650.0; PlainStreetEdge testStreet = new PlainStreetEdge( v1, v2, geometry, "Test Lane", length, StreetTraversalPermission.ALL, false); testStreet.setBicycleSafetyEffectiveLength(length * 0.74); // a safe street Coordinate[] profile = new Coordinate[] { new Coordinate(0, 0), // slope = 0.1 new Coordinate(length / 2, length / 20.0), new Coordinate(length, 0) // slope = -0.1 }; PackedCoordinateSequence elev = new PackedCoordinateSequence.Double(profile); testStreet.setElevationProfile(elev); double trueLength = ElevationUtils.getLengthMultiplierFromElevation(elev) * length; testStreet.setSlopeSpeedEffectiveLength(trueLength); // normalize length SlopeCosts costs = ElevationUtils.getSlopeCosts(elev, "test"); TraverseOptions options = new TraverseOptions(TraverseMode.BICYCLE); options.optimizeFor = OptimizeType.TRIANGLE; options.speed = 6.0; options.walkReluctance = 1; options.setTriangleSafetyFactor(0); options.setTriangleSlopeFactor(0); options.setTriangleTimeFactor(1); State startState = new State(v1, options); State result = testStreet.traverse(startState); double timeWeight = result.getWeight(); double expectedSpeedWeight = trueLength / options.speed; assertEquals(expectedSpeedWeight, timeWeight); options.setTriangleSafetyFactor(0); options.setTriangleSlopeFactor(1); options.setTriangleTimeFactor(0); startState = new State(v1, options); result = testStreet.traverse(startState); double slopeWeight = result.getWeight(); assertTrue(length * 1.5 / options.speed < slopeWeight); assertTrue(length * 1.5 * 10 / options.speed > slopeWeight); options.setTriangleSafetyFactor(1); options.setTriangleSlopeFactor(0); options.setTriangleTimeFactor(0); startState = new State(v1, options); result = testStreet.traverse(startState); double safetyWeight = result.getWeight(); double slopeSafety = costs.slopeSafetyCost; double expectedSafetyWeight = (trueLength * 0.74 + slopeSafety) / options.speed; assertTrue(expectedSafetyWeight - safetyWeight < 0.00001); final double ONE_THIRD = 1 / 3.0; options.setTriangleSafetyFactor(ONE_THIRD); options.setTriangleSlopeFactor(ONE_THIRD); options.setTriangleTimeFactor(ONE_THIRD); startState = new State(v1, options); result = testStreet.traverse(startState); double averageWeight = result.getWeight(); assertTrue( Math.abs( safetyWeight * ONE_THIRD + slopeWeight * ONE_THIRD + timeWeight * ONE_THIRD - averageWeight) < 0.00000001); }
@Override public List<GraphPath> plan(State origin, Vertex target, int nItineraries) { TraverseOptions options = origin.getOptions(); if (_graphService.getCalendarService() != null) options.setCalendarService(_graphService.getCalendarService()); options.setTransferTable(_graphService.getGraph().getTransferTable()); options.setServiceDays(origin.getTime(), _graphService.getGraph().getAgencyIds()); if (options.getModes().getTransit() && !_graphService.getGraph().transitFeedCovers(new Date(origin.getTime() * 1000))) { // user wants a path through the transit network, // but the date provided is outside those covered by the transit feed. throw new TransitTimesException(); } // always use the bidirectional heuristic because the others are not precise enough RemainingWeightHeuristic heuristic = new BidirectionalRemainingWeightHeuristic(_graphService.getGraph()); // the states that will eventually be turned into paths and returned List<State> returnStates = new LinkedList<State>(); // Populate any extra edges final ExtraEdgesStrategy extraEdgesStrategy = options.extraEdgesStrategy; OverlayGraph extraEdges = new OverlayGraph(); extraEdgesStrategy.addEdgesFor(extraEdges, origin.getVertex()); extraEdgesStrategy.addEdgesFor(extraEdges, target); BinHeap<State> pq = new BinHeap<State>(); // List<State> boundingStates = new ArrayList<State>(); // initialize heuristic outside loop so table can be reused heuristic.computeInitialWeight(origin, target); // increase maxWalk repeatedly in case hard limiting is in use WALK: for (double maxWalk = options.getMaxWalkDistance(); maxWalk < 100000 && returnStates.isEmpty(); maxWalk *= 2) { LOG.debug("try search with max walk {}", maxWalk); // increase maxWalk if settings make trip impossible if (maxWalk < Math.min( origin.getVertex().distance(target), origin.getVertex().getDistanceToNearestTransitStop() + target.getDistanceToNearestTransitStop())) continue WALK; options.setMaxWalkDistance(maxWalk); // reinitialize states for each retry HashMap<Vertex, List<State>> states = new HashMap<Vertex, List<State>>(); pq.reset(); pq.insert(origin, 0); long startTime = System.currentTimeMillis(); long endTime = startTime + (int) (_timeouts[0] * 1000); LOG.debug("starttime {} endtime {}", startTime, endTime); QUEUE: while (!pq.empty()) { if (System.currentTimeMillis() > endTime) { LOG.debug("timeout at {} msec", System.currentTimeMillis() - startTime); if (returnStates.isEmpty()) continue WALK; else { storeMemory(); break WALK; } } State su = pq.extract_min(); // for (State bs : boundingStates) { // if (eDominates(bs, su)) { // continue QUEUE; // } // } Vertex u = su.getVertex(); if (traverseVisitor != null) { traverseVisitor.visitVertex(su); } if (u.equals(target)) { // boundingStates.add(su); returnStates.add(su); if (!options.getModes().getTransit()) break QUEUE; // options should contain max itineraries if (returnStates.size() >= _maxPaths) break QUEUE; if (returnStates.size() < _timeouts.length) { endTime = startTime + (int) (_timeouts[returnStates.size()] * 1000); LOG.debug( "{} path, set timeout to {}", returnStates.size(), _timeouts[returnStates.size()] * 1000); } continue QUEUE; } for (Edge e : u.getEdges(extraEdges, null, options.isArriveBy())) { STATE: for (State new_sv = e.traverse(su); new_sv != null; new_sv = new_sv.getNextResult()) { if (traverseVisitor != null) { traverseVisitor.visitEdge(e, new_sv); } double h = heuristic.computeForwardWeight(new_sv, target); // for (State bs : boundingStates) { // if (eDominates(bs, new_sv)) { // continue STATE; // } // } Vertex v = new_sv.getVertex(); List<State> old_states = states.get(v); if (old_states == null) { old_states = new LinkedList<State>(); states.put(v, old_states); } else { for (State old_sv : old_states) { if (eDominates(old_sv, new_sv)) { continue STATE; } } Iterator<State> iter = old_states.iterator(); while (iter.hasNext()) { State old_sv = iter.next(); if (eDominates(new_sv, old_sv)) { iter.remove(); } } } if (traverseVisitor != null) traverseVisitor.visitEnqueue(new_sv); old_states.add(new_sv); pq.insert(new_sv, new_sv.getWeight() + h); } } } } storeMemory(); // Make the states into paths and return them List<GraphPath> paths = new LinkedList<GraphPath>(); for (State s : returnStates) { LOG.debug(s.toStringVerbose()); paths.add(new GraphPath(s, true)); } // sort by arrival time, though paths are already in order of increasing difficulty // Collections.sort(paths, new PathComparator(origin.getOptions().isArriveBy())); return paths; }
public void testHalfEdges() { // the shortest half-edge from the start vertex takes you down, but the shortest total path // is up and over TraverseOptions options = new TraverseOptions(); HashSet<Edge> turns = new HashSet<Edge>(graph.getOutgoing(left)); turns.addAll(graph.getOutgoing(leftBack)); StreetLocation start = StreetLocation.createStreetLocation( "start", "start", cast(turns, StreetEdge.class), new LinearLocation(0, 0.4).getCoordinate(left.getGeometry())); HashSet<Edge> endTurns = new HashSet<Edge>(graph.getOutgoing(right)); endTurns.addAll(graph.getOutgoing(rightBack)); StreetLocation end = StreetLocation.createStreetLocation( "end", "end", cast(endTurns, StreetEdge.class), new LinearLocation(0, 0.8).getCoordinate(right.getGeometry())); assertTrue(start.getX() < end.getX()); assertTrue(start.getY() < end.getY()); List<DirectEdge> extra = end.getExtra(); assertEquals(12, extra.size()); GregorianCalendar startTime = new GregorianCalendar(2009, 11, 1, 12, 34, 25); ShortestPathTree spt1 = AStar.getShortestPathTree(graph, brOut, end, TestUtils.toSeconds(startTime), options); GraphPath pathBr = spt1.getPath(end, false); assertNotNull("There must be a path from br to end", pathBr); ShortestPathTree spt2 = AStar.getShortestPathTree(graph, trOut, end, TestUtils.toSeconds(startTime), options); GraphPath pathTr = spt2.getPath(end, false); assertNotNull("There must be a path from tr to end", pathTr); assertTrue( "path from bottom to end must be longer than path from top to end", pathBr.getWeight() > pathTr.getWeight()); ShortestPathTree spt = AStar.getShortestPathTree(graph, start, end, TestUtils.toSeconds(startTime), options); GraphPath path = spt.getPath(end, false); assertNotNull("There must be a path from start to end", path); // the bottom is not part of the shortest path for (State s : path.states) { assertNotSame(s.getVertex(), graph.getVertex("bottom")); assertNotSame(s.getVertex(), graph.getVertex("bottomBack")); } startTime = new GregorianCalendar(2009, 11, 1, 12, 34, 25); options.setArriveBy(true); spt = AStar.getShortestPathTree(graph, start, end, TestUtils.toSeconds(startTime), options); path = spt.getPath(start, false); assertNotNull("There must be a path from start to end (looking back)", path); // the bottom edge is not part of the shortest path for (State s : path.states) { assertNotSame(s.getVertex(), graph.getVertex("bottom")); assertNotSame(s.getVertex(), graph.getVertex("bottomBack")); } /* Now, the right edge is not bikeable. But the user can walk their bike. So here are some tests * that prove (a) that walking bikes works, but that (b) it is not preferred to riding a tiny bit longer. */ options = new TraverseOptions(new TraverseModeSet(TraverseMode.BICYCLE)); start = StreetLocation.createStreetLocation( "start1", "start1", cast(turns, StreetEdge.class), new LinearLocation(0, 0.95).getCoordinate(top.getGeometry())); end = StreetLocation.createStreetLocation( "end1", "end1", cast(turns, StreetEdge.class), new LinearLocation(0, 0.95).getCoordinate(bottom.getGeometry())); spt = AStar.getShortestPathTree(graph, start, end, TestUtils.toSeconds(startTime), options); path = spt.getPath(start, false); assertNotNull("There must be a path from top to bottom along the right", path); // the left edge is not part of the shortest path (even though the bike must be walked along the // right) for (State s : path.states) { assertNotSame(s.getVertex(), graph.getVertex("left")); assertNotSame(s.getVertex(), graph.getVertex("leftBack")); } start = StreetLocation.createStreetLocation( "start2", "start2", cast(turns, StreetEdge.class), new LinearLocation(0, 0.55).getCoordinate(top.getGeometry())); end = StreetLocation.createStreetLocation( "end2", "end2", cast(turns, StreetEdge.class), new LinearLocation(0, 0.55).getCoordinate(bottom.getGeometry())); spt = AStar.getShortestPathTree(graph, start, end, TestUtils.toSeconds(startTime), options); path = spt.getPath(start, false); assertNotNull("There must be a path from top to bottom", path); // the right edge is not part of the shortest path, e for (State s : path.states) { assertNotSame(s.getVertex(), graph.getVertex("right")); assertNotSame(s.getVertex(), graph.getVertex("rightBack")); } }