/** Generates a TripPlan from a Request */ public TripPlan generate(RoutingRequest options) { // TODO: this seems to only check the endpoints, which are usually auto-generated // if ( ! options.isAccessible()) // throw new LocationNotAccessible(); /* try to plan the trip */ List<GraphPath> paths = null; boolean tooSloped = false; try { paths = pathService.getPaths(options); if (paths == null && options.getWheelchairAccessible()) { // There are no paths that meet the user's slope restrictions. // Try again without slope restrictions (and warn user). options.maxSlope = Double.MAX_VALUE; paths = pathService.getPaths(options); tooSloped = true; } } catch (VertexNotFoundException e) { LOG.info("Vertex not found: " + options.getFrom() + " : " + options.getTo(), e); throw e; } if (paths == null || paths.size() == 0) { LOG.info("Path not found: " + options.getFrom() + " : " + options.getTo()); throw new PathNotFoundException(); } TripPlan plan = generatePlan(paths, options); if (plan != null) { for (Itinerary i : plan.itinerary) { i.tooSloped = tooSloped; /* fix up from/to on first/last legs */ if (i.legs.size() == 0) { LOG.warn("itinerary has no legs"); continue; } Leg firstLeg = i.legs.get(0); firstLeg.from.orig = options.getFromName(); Leg lastLeg = i.legs.get(i.legs.size() - 1); lastLeg.to.orig = options.getToName(); } } return plan; }
/** * Makes a new empty leg from a starting edge * * @param itinerary */ private Leg makeLeg(Itinerary itinerary, State s) { Leg leg = new Leg(); itinerary.addLeg(leg); leg.startTime = makeCalendar(s.getBackState()); EdgeNarrative en = s.getBackEdgeNarrative(); leg.distance = 0.0; leg.from = makePlace(s.getBackState(), false); leg.mode = en.getMode().toString(); return leg; }
/** * Adjusts an Itinerary's elevation fields from an elevation profile * * @return the elevation at the end of the profile */ private double applyElevation( PackedCoordinateSequence profile, Itinerary itinerary, double previousElevation) { if (profile != null) { for (Coordinate coordinate : profile.toCoordinateArray()) { if (previousElevation == Double.MAX_VALUE) { previousElevation = coordinate.y; continue; } double elevationChange = previousElevation - coordinate.y; if (elevationChange > 0) { itinerary.elevationGained += elevationChange; } else { itinerary.elevationLost -= elevationChange; } previousElevation = coordinate.y; } } return previousElevation; }
/** * Makes a new empty leg from a starting edge * * @param itinerary */ private Leg makeLeg(Itinerary itinerary, State s) { Leg leg = new Leg(); itinerary.addLeg(leg); leg.startTime = makeCalendar(s.getBackState()); leg.distance = 0.0; String name; Edge backEdge = s.getBackEdge(); if (backEdge instanceof StreetEdge) { name = backEdge.getName(); } else { name = s.getVertex().getName(); } leg.from = makePlace(s.getBackState(), name, false); leg.mode = s.getBackMode().toString(); if (s.isBikeRenting()) { leg.rentedBike = true; } return leg; }
/** * Makes a new empty Itinerary for a given path. * * @return */ private Itinerary makeEmptyItinerary(GraphPath path) { Itinerary itinerary = new Itinerary(); State startState = path.states.getFirst(); State endState = path.states.getLast(); itinerary.startTime = makeCalendar(startState); itinerary.endTime = makeCalendar(endState); itinerary.duration = endState.getTimeInMillis() - startState.getTimeInMillis(); Graph graph = path.getRoutingContext().graph; FareService fareService = graph.getService(FareService.class); if (fareService != null) { itinerary.fare = fareService.getCost(path); } itinerary.transfers = -1; return itinerary; }
/** * Generate an itinerary from a @{link GraphPath}. The algorithm here is to walk over each state * in the graph path, accumulating geometry, time, and length data from the incoming edge. When * the incoming edge and outgoing edge have different modes (or when a vehicle changes names due * to interlining) a new leg is generated. Street legs undergo an additional processing step to * generate turn-by-turn directions. * * @param path * @param showIntermediateStops whether intermediate stops are included in the generated itinerary * @return itinerary */ private Itinerary generateItinerary(GraphPath path, boolean showIntermediateStops) { Graph graph = path.getRoutingContext().graph; TransitIndexService transitIndex = graph.getService(TransitIndexService.class); Itinerary itinerary = makeEmptyItinerary(path); EdgeNarrative postponedAlerts = null; Leg leg = null; CoordinateArrayListSequence coordinates = new CoordinateArrayListSequence(); double previousElevation = Double.MAX_VALUE; int startWalk = -1; int i = -1; PlanGenState pgstate = PlanGenState.START; String nextName = null; for (State state : path.states) { i += 1; Edge backEdge = state.getBackEdge(); EdgeNarrative backEdgeNarrative = state.getBackEdgeNarrative(); if (backEdge == null) { continue; } TraverseMode mode = backEdgeNarrative.getMode(); if (mode != null) { long dt = state.getAbsTimeDeltaSec(); if (mode == TraverseMode.BOARDING || mode == TraverseMode.ALIGHTING || mode == TraverseMode.STL) { itinerary.waitingTime += dt; } else if (mode.isOnStreetNonTransit()) { itinerary.walkDistance += backEdgeNarrative.getDistance(); itinerary.walkTime += dt; } else if (mode.isTransit()) { itinerary.transitTime += dt; } } if (backEdge instanceof FreeEdge) { if (backEdge instanceof PreBoardEdge) { // Add boarding alerts to the next leg postponedAlerts = backEdgeNarrative; } else if (backEdge instanceof PreAlightEdge) { // Add alighting alerts to the previous leg addNotesToLeg(itinerary.legs.get(itinerary.legs.size() - 1), backEdgeNarrative); } continue; } if (backEdge instanceof EdgeWithElevation) { PackedCoordinateSequence profile = ((EdgeWithElevation) backEdge).getElevationProfile(); previousElevation = applyElevation(profile, itinerary, previousElevation); } switch (pgstate) { case START: if (mode == TraverseMode.WALK) { pgstate = PlanGenState.WALK; leg = makeLeg(itinerary, state); leg.from.orig = nextName; startWalk = i; } else if (mode == TraverseMode.BICYCLE) { pgstate = PlanGenState.BICYCLE; leg = makeLeg(itinerary, state); leg.from.orig = nextName; startWalk = i; } else if (mode == TraverseMode.CAR) { pgstate = PlanGenState.CAR; leg = makeLeg(itinerary, state); leg.from.orig = nextName; startWalk = i; } else if (mode == TraverseMode.BOARDING) { // this itinerary starts with transit pgstate = PlanGenState.PRETRANSIT; leg = makeLeg(itinerary, state); leg.from.orig = nextName; startWalk = -1; } else if (mode == TraverseMode.STL) { // this comes after an alight; do nothing } else if (mode == TraverseMode.TRANSFER) { // handle the whole thing in one step leg = makeLeg(itinerary, state); coordinates = new CoordinateArrayListSequence(); coordinates.add(state.getBackState().getVertex().getCoordinate()); coordinates.add(state.getVertex().getCoordinate()); finalizeLeg(leg, state, path.states, i, i, coordinates); coordinates.clear(); } else { LOG.error("Unexpected state (in START): " + mode); } break; case WALK: if (leg == null) { leg = makeLeg(itinerary, state); } if (mode == TraverseMode.WALK) { // do nothing } else if (mode == TraverseMode.BICYCLE) { finalizeLeg(leg, state, path.states, startWalk, i, coordinates); startWalk = i; leg = makeLeg(itinerary, state); if (backEdge instanceof RentABikeOnEdge) { leg.rentedBike = true; } pgstate = PlanGenState.BICYCLE; } else if (mode == TraverseMode.STL) { finalizeLeg(leg, state, path.states, startWalk, i, coordinates); leg = null; pgstate = PlanGenState.PRETRANSIT; } else if (mode == TraverseMode.BOARDING) { // this only happens in case of a timed transfer. pgstate = PlanGenState.PRETRANSIT; finalizeLeg(leg, state, path.states, startWalk, i, coordinates); leg = makeLeg(itinerary, state); itinerary.transfers++; } else if (backEdgeNarrative instanceof LegSwitchingEdge) { nextName = state.getBackState().getBackState().getBackState().getVertex().getName(); finalizeLeg(leg, state, path.states, startWalk, i - 1, coordinates); leg = null; pgstate = PlanGenState.START; } else { LOG.error("Unexpected state (in WALK): " + mode); } break; case BICYCLE: if (leg == null) { leg = makeLeg(itinerary, state); } if (mode == TraverseMode.BICYCLE) { // do nothing } else if (mode == TraverseMode.WALK) { finalizeLeg(leg, state, path.states, startWalk, i, coordinates); leg = makeLeg(itinerary, state); startWalk = i; pgstate = PlanGenState.WALK; } else if (mode == TraverseMode.STL) { finalizeLeg(leg, state, path.states, startWalk, i, coordinates); leg = null; pgstate = PlanGenState.PRETRANSIT; } else if (backEdgeNarrative instanceof LegSwitchingEdge) { finalizeLeg(leg, state, path.states, startWalk, i - 1, coordinates); leg = null; pgstate = PlanGenState.START; } else { LOG.error("Unexpected state (in BICYCLE): " + mode); } break; case CAR: if (leg == null) { leg = makeLeg(itinerary, state); } if (mode == TraverseMode.CAR) { // do nothing } else if (mode == TraverseMode.STL) { finalizeLeg(leg, state, path.states, startWalk, i, coordinates); leg = null; pgstate = PlanGenState.PRETRANSIT; } else if (backEdgeNarrative instanceof LegSwitchingEdge) { finalizeLeg(leg, state, path.states, startWalk, i - 1, coordinates); leg = null; pgstate = PlanGenState.START; } else { LOG.error("Unexpected state (in CAR): " + mode); } break; case PRETRANSIT: if (mode == TraverseMode.BOARDING) { if (leg != null) { LOG.error("leg unexpectedly not null (boarding loop)"); } else { leg = makeLeg(itinerary, state); leg.stop = new ArrayList<Place>(); itinerary.transfers++; leg.boardRule = (String) state.getExtension("boardAlightRule"); } } else if (backEdge instanceof HopEdge) { pgstate = PlanGenState.TRANSIT; fixupTransitLeg(leg, state, transitIndex); leg.stop = new ArrayList<Place>(); } else { LOG.error("Unexpected state (in PRETRANSIT): " + mode); } break; case TRANSIT: String route = backEdgeNarrative.getName(); if (mode == TraverseMode.ALIGHTING) { if (showIntermediateStops && leg.stop != null && leg.stop.size() > 0) { if (leg.stop.isEmpty()) { leg.stop = null; } } leg.alightRule = (String) state.getExtension("boardAlightRule"); finalizeLeg(leg, state, null, -1, -1, coordinates); leg = null; pgstate = PlanGenState.START; } else if (mode.toString().equals(leg.mode)) { // no mode change, handle intermediate stops if (showIntermediateStops) { /* * any further transit edge, add "from" vertex to intermediate stops */ if (!(backEdge instanceof DwellEdge)) { Place stop = makePlace(state.getBackState(), true); leg.stop.add(stop); } else if (leg.stop.size() > 0) { leg.stop.get(leg.stop.size() - 1).departure = makeCalendar(state); } } if (!route.equals(leg.route)) { // interline dwell finalizeLeg(leg, state, null, -1, -1, coordinates); leg = makeLeg(itinerary, state); leg.stop = new ArrayList<Place>(); fixupTransitLeg(leg, state, transitIndex); leg.startTime = makeCalendar(state); leg.interlineWithPreviousLeg = true; } } else { LOG.error("Unexpected state (in TRANSIT): " + mode); } break; } if (leg != null) { leg.distance += backEdgeNarrative.getDistance(); Geometry edgeGeometry = backEdgeNarrative.getGeometry(); if (edgeGeometry != null) { Coordinate[] edgeCoordinates = edgeGeometry.getCoordinates(); if (coordinates.size() > 0 && coordinates.getCoordinate(coordinates.size() - 1).equals(edgeCoordinates[0])) { coordinates.extend(edgeCoordinates, 1); } else { coordinates.extend(edgeCoordinates); } } if (postponedAlerts != null) { addNotesToLeg(leg, postponedAlerts); postponedAlerts = null; } addNotesToLeg(leg, backEdgeNarrative); } } /* end loop over graphPath edge list */ if (leg != null) { finalizeLeg(leg, path.states.getLast(), path.states, startWalk, i, coordinates); } itinerary.removeBogusLegs(); itinerary.fixupDates(graph.getService(CalendarServiceData.class)); if (itinerary.legs.size() == 0) throw new TrivialPathException(); return itinerary; }
/** * Generate an itinerary from a @{link GraphPath}. The algorithm here is to walk over each state * in the graph path, accumulating geometry, time, and length data from the incoming edge. When * the incoming edge and outgoing edge have different modes (or when a vehicle changes names due * to interlining) a new leg is generated. Street legs undergo an additional processing step to * generate turn-by-turn directions. * * @param path * @param showIntermediateStops whether intermediate stops are included in the generated itinerary * @return itinerary */ private Itinerary generateItinerary(GraphPath path, boolean showIntermediateStops) { Graph graph = path.getRoutingContext().graph; TransitIndexService transitIndex = graph.getService(TransitIndexService.class); Itinerary itinerary = makeEmptyItinerary(path); Set<Alert> postponedAlerts = null; Leg leg = null; CoordinateArrayListSequence coordinates = new CoordinateArrayListSequence(); double previousElevation = Double.MAX_VALUE; int startWalk = -1; int i = -1; boolean foldingElevatorLegIntoCycleLeg = false; PlanGenState pgstate = PlanGenState.START; String nextName = null; for (State state : path.states) { i += 1; Edge backEdge = state.getBackEdge(); if (backEdge == null) { continue; } // debug: push vehicle late status out to UI // if (backEdge instanceof PatternHop) { // TripTimes tt = state.getTripTimes(); // int hop = ((PatternHop)backEdge).stopIndex; // LOG.info("{} {}", tt.getTrip().toString(), hop); // if ( ! tt.isScheduled()) { // int delay = tt.getDepartureDelay(hop); // String d = "on time"; // if (Math.abs(delay) > 10) { // d = String.format("%2.1f min %s", delay / 60.0, // (delay < 0) ? "early" : "late"); // } // d = "Using real-time delay information: ".concat(d); // leg.addAlert(Alert.createSimpleAlerts(d)); // LOG.info(d); // } // else { // leg.addAlert(Alert.createSimpleAlerts("Using published timetables.")); // LOG.info("sched"); // } // } TraverseMode mode = state.getBackMode(); if (mode != null) { long dt = state.getAbsTimeDeltaSec(); if (mode == TraverseMode.BOARDING || mode == TraverseMode.ALIGHTING || mode == TraverseMode.STL) { itinerary.waitingTime += dt; } else if (mode.isOnStreetNonTransit()) { itinerary.walkDistance += backEdge.getDistance(); itinerary.walkTime += dt; } else if (mode.isTransit()) { itinerary.transitTime += dt; } } if (backEdge instanceof FreeEdge) { if (backEdge instanceof PreBoardEdge) { // Add boarding alerts to the next leg postponedAlerts = state.getBackAlerts(); } else if (backEdge instanceof PreAlightEdge) { // Add alighting alerts to the previous leg addNotesToLeg(itinerary.legs.get(itinerary.legs.size() - 1), state.getBackAlerts()); } continue; } if (backEdge instanceof EdgeWithElevation) { PackedCoordinateSequence profile = ((EdgeWithElevation) backEdge).getElevationProfile(); previousElevation = applyElevation(profile, itinerary, previousElevation); } switch (pgstate) { case START: if (mode == TraverseMode.WALK) { pgstate = PlanGenState.WALK; leg = makeLeg(itinerary, state); leg.from.orig = nextName; startWalk = i; } else if (mode == TraverseMode.BICYCLE) { pgstate = PlanGenState.BICYCLE; leg = makeLeg(itinerary, state); leg.from.orig = nextName; startWalk = i; } else if (mode == TraverseMode.CAR) { pgstate = PlanGenState.CAR; leg = makeLeg(itinerary, state); leg.from.orig = nextName; startWalk = i; } else if (mode == TraverseMode.BOARDING) { // this itinerary starts with transit pgstate = PlanGenState.PRETRANSIT; leg = makeLeg(itinerary, state); leg.from.orig = nextName; itinerary.transfers++; startWalk = -1; } else if (mode == TraverseMode.STL) { // this comes after an alight; do nothing } else if (mode == TraverseMode.TRANSFER) { // handle the whole thing in one step leg = makeLeg(itinerary, state); coordinates = new CoordinateArrayListSequence(); coordinates.add(state.getBackState().getVertex().getCoordinate()); coordinates.add(state.getVertex().getCoordinate()); finalizeLeg(leg, state, path.states, i, i, coordinates, itinerary); coordinates.clear(); } else { LOG.error("Unexpected state (in START): " + mode); } break; case WALK: if (leg == null) { leg = makeLeg(itinerary, state); } if (mode == TraverseMode.WALK) { // do nothing } else if (mode == TraverseMode.BICYCLE) { finalizeLeg(leg, state, path.states, startWalk, i, coordinates, itinerary); startWalk = i; leg = makeLeg(itinerary, state); pgstate = PlanGenState.BICYCLE; } else if (mode == TraverseMode.STL) { finalizeLeg(leg, state, path.states, startWalk, i, coordinates, itinerary); leg = null; pgstate = PlanGenState.PRETRANSIT; } else if (mode == TraverseMode.BOARDING) { // this only happens in case of a timed transfer. pgstate = PlanGenState.PRETRANSIT; finalizeLeg(leg, state, path.states, startWalk, i, coordinates, itinerary); leg = makeLeg(itinerary, state); itinerary.transfers++; } else if (backEdge instanceof LegSwitchingEdge) { nextName = state.getBackState().getBackState().getBackState().getVertex().getName(); finalizeLeg(leg, state, path.states, startWalk, i - 1, coordinates, itinerary); leg = null; pgstate = PlanGenState.START; } else { LOG.error("Unexpected state (in WALK): " + mode); } break; case BICYCLE: if (leg == null) { leg = makeLeg(itinerary, state); } // If there are elevator edges that have mode == BICYCLE on both sides, they should // be folded into the bicycle leg. But ones with walk on one side or the other should // not if (state.getBackEdge() instanceof ElevatorBoardEdge) { int j = i + 1; // proceed forward from the current state until we find one that isn't on an // elevator, and check the traverse mode while (path.states.get(j).getBackEdge() instanceof ElevatorEdge) j++; // path.states[j] is not an elevator edge if (path.states.get(j).getBackMode() == TraverseMode.BICYCLE) foldingElevatorLegIntoCycleLeg = true; } if (foldingElevatorLegIntoCycleLeg) { if (state.getBackEdge() instanceof ElevatorEdge) { break; // from the case } else { foldingElevatorLegIntoCycleLeg = false; // do not break but allow it to be processed below (which will do nothing) } } if (mode == TraverseMode.BICYCLE) { // do nothing } else if (mode == TraverseMode.WALK) { finalizeLeg(leg, state, path.states, startWalk, i, coordinates, itinerary); leg = makeLeg(itinerary, state); startWalk = i; pgstate = PlanGenState.WALK; } else if (mode == TraverseMode.STL) { finalizeLeg(leg, state, path.states, startWalk, i, coordinates, itinerary); startWalk = i; leg = null; pgstate = PlanGenState.PRETRANSIT; } else if (backEdge instanceof LegSwitchingEdge) { finalizeLeg(leg, state, path.states, startWalk, i - 1, coordinates, itinerary); leg = null; pgstate = PlanGenState.START; } else { LOG.error("Unexpected state (in BICYCLE): " + mode); } break; case CAR: if (leg == null) { leg = makeLeg(itinerary, state); } if (mode == TraverseMode.CAR) { // do nothing } else if (mode == TraverseMode.STL) { finalizeLeg(leg, state, path.states, startWalk, i, coordinates, itinerary); leg = null; pgstate = PlanGenState.PRETRANSIT; } else if (backEdge instanceof LegSwitchingEdge) { finalizeLeg(leg, state, path.states, startWalk, i - 1, coordinates, itinerary); leg = null; pgstate = PlanGenState.START; } else { LOG.error("Unexpected state (in CAR): " + mode); } break; case PRETRANSIT: if (mode == TraverseMode.BOARDING) { if (leg != null) { LOG.error("leg unexpectedly not null (boarding loop)"); } else { leg = makeLeg(itinerary, state); leg.from.stopIndex = ((OnBoardForwardEdge) backEdge).getStopIndex(); leg.stop = new ArrayList<Place>(); itinerary.transfers++; leg.boardRule = (String) state.getExtension("boardAlightRule"); } } else if (backEdge instanceof HopEdge) { pgstate = PlanGenState.TRANSIT; fixupTransitLeg(leg, state, transitIndex); leg.stop = new ArrayList<Place>(); } else { LOG.error("Unexpected state (in PRETRANSIT): " + mode); } break; case TRANSIT: String route = backEdge.getName(); if (mode == TraverseMode.ALIGHTING) { if (showIntermediateStops && leg.stop != null && leg.stop.size() > 0) { if (leg.stop.isEmpty()) { leg.stop = null; } } leg.alightRule = (String) state.getExtension("boardAlightRule"); finalizeLeg(leg, state, null, -1, -1, coordinates, itinerary); leg = null; pgstate = PlanGenState.START; } else if (mode.toString().equals(leg.mode)) { // no mode change, handle intermediate stops if (showIntermediateStops) { /* * any further transit edge, add "from" vertex to intermediate stops */ if (!(backEdge instanceof DwellEdge)) { Place stop = makePlace( state.getBackState(), state.getBackState().getVertex().getName(), true); leg.stop.add(stop); } else if (leg.stop.size() > 0) { leg.stop.get(leg.stop.size() - 1).departure = makeCalendar(state); } } if (!route.equals(leg.route)) { // interline dwell finalizeLeg(leg, state, null, -1, -1, coordinates, itinerary); leg = makeLeg(itinerary, state); leg.stop = new ArrayList<Place>(); fixupTransitLeg(leg, state, transitIndex); leg.startTime = makeCalendar(state); leg.interlineWithPreviousLeg = true; } } else { LOG.error("Unexpected state (in TRANSIT): " + mode); } break; } if (leg != null) { leg.distance += backEdge.getDistance(); Geometry edgeGeometry = backEdge.getGeometry(); if (edgeGeometry != null) { Coordinate[] edgeCoordinates = edgeGeometry.getCoordinates(); if (coordinates.size() > 0 && coordinates.getCoordinate(coordinates.size() - 1).equals(edgeCoordinates[0])) { coordinates.extend(edgeCoordinates, 1); } else { coordinates.extend(edgeCoordinates); } } if (postponedAlerts != null) { addNotesToLeg(leg, postponedAlerts); postponedAlerts = null; } addNotesToLeg(leg, state.getBackAlerts()); } } /* end loop over graphPath edge list */ if (leg != null) { finalizeLeg(leg, path.states.getLast(), path.states, startWalk, i, coordinates, itinerary); } itinerary.removeBogusLegs(); itinerary.fixupDates(graph.getService(CalendarServiceData.class)); if (itinerary.legs.size() == 0) throw new TrivialPathException(); return itinerary; }