コード例 #1
0
  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;
  }
コード例 #2
0
  /**
   * Prune raptor data to include only routes and boardings which have trips today. Doesn't actually
   * improve speed
   */
  @SuppressWarnings("unchecked")
  private RaptorData pruneDataForServiceDays(Graph graph, ArrayList<ServiceDay> serviceDays) {

    if (serviceDays.equals(cachedServiceDays)) return cachedRaptorData;
    RaptorData data = graph.getService(RaptorDataService.class).getData();
    RaptorData pruned = new RaptorData();
    pruned.raptorStopsForStopId = data.raptorStopsForStopId;
    pruned.stops = data.stops;
    pruned.routes = new ArrayList<RaptorRoute>();
    pruned.routesForStop = new List[pruned.stops.length];

    for (RaptorRoute route : data.routes) {
      ArrayList<Integer> keep = new ArrayList<Integer>();

      for (int i = 0; i < route.boards[0].length; ++i) {
        Edge board = route.boards[0][i];
        int serviceId;
        if (board instanceof TransitBoardAlight) {
          serviceId = ((TransitBoardAlight) board).getPattern().getServiceId();
        } else {
          log.debug("Unexpected nonboard among boards");
          continue;
        }
        for (ServiceDay day : serviceDays) {
          if (day.serviceIdRunning(serviceId)) {
            keep.add(i);
            break;
          }
        }
      }
      if (keep.isEmpty()) continue;
      int nPatterns = keep.size();
      RaptorRoute prunedRoute = new RaptorRoute(route.getNStops(), nPatterns);
      for (int stop = 0; stop < route.getNStops() - 1; ++stop) {
        for (int pattern = 0; pattern < nPatterns; ++pattern) {
          prunedRoute.boards[stop][pattern] = route.boards[stop][keep.get(pattern)];
        }
      }
      pruned.routes.add(route);
      for (RaptorStop stop : route.stops) {
        List<RaptorRoute> routes = pruned.routesForStop[stop.index];
        if (routes == null) {
          routes = new ArrayList<RaptorRoute>();
          pruned.routesForStop[stop.index] = routes;
        }
        routes.add(route);
      }
    }
    for (RaptorStop stop : data.stops) {
      if (pruned.routesForStop[stop.index] == null) {
        pruned.routesForStop[stop.index] = Collections.emptyList();
      }
    }
    cachedServiceDays = serviceDays;
    cachedRaptorData = pruned;
    return pruned;
  }
コード例 #3
0
  /**
   * 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;
  }
コード例 #4
0
  /** Returns the first trip of the service day. */
  public TripPlan generateFirstTrip(RoutingRequest request) {
    Graph graph = graphService.getGraph(request.getRouterId());

    TransitIndexService transitIndex = graph.getService(TransitIndexService.class);
    transitIndexWithBreakRequired(transitIndex);

    request.setArriveBy(false);

    TimeZone tz = graph.getTimeZone();

    GregorianCalendar calendar = new GregorianCalendar(tz);
    calendar.setTimeInMillis(request.dateTime * 1000);
    calendar.set(Calendar.HOUR, 0);
    calendar.set(Calendar.MINUTE, 0);
    calendar.set(Calendar.AM_PM, 0);
    calendar.set(Calendar.SECOND, transitIndex.getOvernightBreak());

    request.dateTime = calendar.getTimeInMillis() / 1000;
    return generate(request);
  }
コード例 #5
0
 public RoutingContext(
     RoutingRequest traverseOptions, Graph graph, Vertex from, Vertex to, boolean findPlaces) {
   this.opt = traverseOptions;
   this.graph = graph;
   if (findPlaces) {
     // normal mode, search for vertices based on fromPlace and toPlace
     fromVertex = graph.streetIndex.getVertexForPlace(opt.getFromPlace(), opt);
     toVertex = graph.streetIndex.getVertexForPlace(opt.getToPlace(), opt, fromVertex);
     if (opt.intermediatePlaces != null) {
       for (NamedPlace intermediate : opt.intermediatePlaces) {
         Vertex vertex = graph.streetIndex.getVertexForPlace(intermediate, opt);
         intermediateVertices.add(vertex);
       }
     }
   } else {
     // debug mode, force endpoint vertices to those specified rather than searching
     fromVertex = from;
     toVertex = to;
   }
   if (opt.getStartingTransitStopId() != null) {
     TransitIndexService tis = graph.getService(TransitIndexService.class);
     if (tis == null) {
       throw new RuntimeException(
           "Next/Previous/First/Last trip "
               + "functionality depends on the transit index. Rebuild "
               + "the graph with TransitIndexBuilder");
     }
     AgencyAndId stopId = opt.getStartingTransitStopId();
     startingStop = tis.getPreBoardEdge(stopId).getToVertex();
   }
   origin = opt.arriveBy ? toVertex : fromVertex;
   target = opt.arriveBy ? fromVertex : toVertex;
   calendarService = graph.getCalendarService();
   transferTable = graph.getTransferTable();
   timetableSnapshot = null;
   setServiceDays();
   if (opt.batch) remainingWeightHeuristic = new TrivialRemainingWeightHeuristic();
   else remainingWeightHeuristic = heuristicFactory.getInstanceForSearch(opt);
 }
コード例 #6
0
  @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;
  }
コード例 #7
0
  /**
   * 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;
  }
コード例 #8
0
  /**
   * 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;
  }