private void fixupTransitLeg(Leg leg, State state, TransitIndexService transitIndex) {
    Edge en = state.getBackEdge();
    leg.route = en.getName();
    Trip trip = state.getBackTrip();
    leg.headsign = state.getBackDirection();
    if (trip != null) {
      // this is the stop headsign
      // leg.headsign = "This is the headsign";
      // handle no stop headsign
      if (leg.headsign == null) leg.headsign = trip.getTripHeadsign();

      leg.tripId = trip.getId().getId();
      leg.agencyId = trip.getId().getAgencyId();
      leg.tripShortName = trip.getTripShortName();
      leg.routeShortName = trip.getRoute().getShortName();
      leg.routeLongName = trip.getRoute().getLongName();
      leg.routeColor = trip.getRoute().getColor();
      leg.routeTextColor = trip.getRoute().getTextColor();
      leg.routeId = trip.getRoute().getId().getId();
      if (transitIndex != null) {
        Agency agency = transitIndex.getAgency(leg.agencyId);
        leg.agencyName = agency.getName();
        leg.agencyUrl = agency.getUrl();
      }
    }
    leg.mode = state.getBackMode().toString();
    leg.startTime = makeCalendar(state.getBackState());
  }
 private WalkStep createWalkStep(State s) {
   Edge en = s.getBackEdge();
   WalkStep step;
   step = new WalkStep();
   step.streetName = en.getName();
   step.lon = en.getFromVertex().getX();
   step.lat = en.getFromVertex().getY();
   step.elevation = encodeElevationProfile(s.getBackEdge(), 0);
   step.bogusName = en.hasBogusName();
   step.addAlerts(s.getBackAlerts());
   step.angle = DirectionUtils.getFirstAngle(s.getBackEdge().getGeometry());
   if (s.getBackEdge() instanceof AreaEdge) {
     step.area = true;
   }
   return step;
 }
 /**
  * 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;
 }
  private void finalizeLeg(
      Leg leg,
      State state,
      List<State> states,
      int start,
      int end,
      CoordinateArrayListSequence coordinates,
      Itinerary itinerary) {

    // this leg has already been added to the itinerary, so we actually want the penultimate leg, if
    // any
    if (states != null) {
      int extra = 0;
      WalkStep continuation = null;
      if (itinerary.legs.size() >= 2) {
        Leg previousLeg = itinerary.legs.get(itinerary.legs.size() - 2);
        if (previousLeg.walkSteps != null) {
          continuation = previousLeg.walkSteps.get(previousLeg.walkSteps.size() - 1);
          extra = 1;
        }
      }
      if (end == states.size() - 1) {
        extra = 1;
      }

      leg.walkSteps = getWalkSteps(states.subList(start, end + extra), continuation);
    }
    leg.endTime = makeCalendar(state.getBackState());
    Geometry geometry = GeometryUtils.getGeometryFactory().createLineString(coordinates);
    leg.legGeometry = PolylineEncoder.createEncodings(geometry);
    Edge backEdge = state.getBackEdge();
    String name;
    if (backEdge instanceof StreetEdge) {
      name = backEdge.getName();
    } else {
      name = state.getVertex().getName();
    }
    leg.to = makePlace(state, name, true);
    coordinates.clear();
  }
Пример #5
0
  /**
   * Calculates what distance can be traveled with the remaining time and given speeds. For car use
   * the speed limit is taken from the edge itself. Slopes are accounted for when walking and
   * biking. A minimal slope of 0.06 (6m/100m) is necessary.
   *
   * @param maxTime in sec, the time we have left
   * @param fromTime in sec, the time when we enter the edge
   * @param traverseTime in sec, original edge traverse time needed to adjust the speed based
   *     calculation to slope effects
   * @param userSpeed in m/sec, dependent on traversal mode
   * @param edge the edge itself (used to the get the speed in car mode)
   * @param usesCar if we traverse the edge in car mode
   * @param hasUshape if know, indicate if the edge has a u-shape
   * @return the distance in meter that can be moved until maxTime
   */
  double distanceToMoveInRemainingTime(
      long maxTime,
      long fromTime,
      double traverseTime,
      double userSpeed,
      Edge edge,
      boolean usesCar,
      boolean hasUshape) {

    boolean isTooFast = false;
    String msg = "";

    double originalTravelSpeed =
        edge.getDistance() / traverseTime; // this may be wrong for u-shapes

    if (originalTravelSpeed < userSpeed) {
      // we may have slope effects
      if (edge instanceof PlainStreetEdge) {
        PlainStreetEdge pe = (PlainStreetEdge) edge;
        double maxSlope = pe.getElevationProfileSegment().getMaxSlope();
        // if we are over the slope limit, then we should use the slower speed
        if (maxSlope > 0.06) { // limit 6m/100m = 3.4 degree
          userSpeed = originalTravelSpeed;
        }
      }
    } else {
      // in this case we may have a u-shape, or the user speeds are too small, or something else.
      double vdiff = Math.abs(originalTravelSpeed - userSpeed);
      double vDiffPercent = vdiff / (userSpeed / 100.0);
      if (vDiffPercent > 20) {
        isTooFast = true;
        // [sstein Dec 2012]: Note, it seems like most of these edges are indeed of u-shape type,
        // i.e. small roads that come from and return from (the same) main road
        msg =
            "v_traversed is much faster than (allowed) v_user, edgeName: "
                + edge.getName()
                + ", >>> (in m/s): v_traversed="
                + (int) Math.floor(originalTravelSpeed)
                + ", v_maxUser="******", known u-shape, ";
        }
        if ((usesCar == false) && (hasUshape == false)) {
          this.tooFastTraversedEdgeGeoms.add(edge.getGeometry());
          LOG.debug(msg);
        } // otherwise we print msg below
      }
    }
    // correct speed for car use, as each road has its speed limits
    if (usesCar) {
      if (edge instanceof PlainStreetEdge) {
        PlainStreetEdge pe = (PlainStreetEdge) edge;
        userSpeed = pe.getCarSpeed();
        // we need to check again if the originalTravelSpeed is faster
        if ((isTooFast == true) && (originalTravelSpeed > userSpeed) && (hasUshape == false)) {
          this.tooFastTraversedEdgeGeoms.add(edge.getGeometry());
          LOG.debug(msg + "; setting v_PlainStreetEdge=" + (int) Math.floor(userSpeed));
        }
      }
    }
    // finally calculate how far we can travel with the remaining time
    long timeMissing = maxTime - fromTime;
    double distanceToWalkInTimeMissing = timeMissing * userSpeed;
    return distanceToWalkInTimeMissing;
  }
Пример #6
0
  /**
   * Converts a list of street edges to a list of turn-by-turn directions.
   *
   * @param edges : A list of street edges
   * @return
   */
  private List<WalkStep> getWalkSteps(List<State> states) {
    List<WalkStep> steps = new ArrayList<WalkStep>();
    WalkStep step = null;
    double lastAngle = 0, distance = 0; // distance used for appending elevation profiles
    int roundaboutExit = 0; // track whether we are in a roundabout, and if so the exit number
    String roundaboutPreviousStreet = null;

    for (State currState : states) {
      State backState = currState.getBackState();
      Edge edge = currState.getBackEdge();
      EdgeNarrative edgeNarrative = currState.getBackEdgeNarrative();
      boolean createdNewStep = false;
      if (edge instanceof FreeEdge) {
        continue;
      }
      if (!edgeNarrative.getMode().isOnStreetNonTransit()) {
        continue; // ignore STLs and the like
      }
      Geometry geom = edgeNarrative.getGeometry();
      if (geom == null) {
        continue;
      }

      // generate a step for getting off an elevator (all
      // elevator narrative generation occurs when alighting). We don't need to know what came
      // before or will come after
      if (edge instanceof ElevatorAlightEdge) {
        // don't care what came before or comes after
        step = createWalkStep(currState);

        // tell the user where to get off the elevator using the exit notation, so the
        // i18n interface will say 'Elevator to <exit>'
        // what happens is that the webapp sees name == null and ignores that, and it sees
        // exit != null and uses to <exit>
        // the floor name is the AlightEdge name
        // reset to avoid confusion with 'Elevator on floor 1 to floor 1'
        step.streetName = ((ElevatorAlightEdge) edge).getName();

        step.relativeDirection = RelativeDirection.ELEVATOR;

        steps.add(step);
        continue;
      }

      String streetName = edgeNarrative.getName();
      int idx = streetName.indexOf('(');
      String streetNameNoParens;
      if (idx > 0) streetNameNoParens = streetName.substring(0, idx - 1);
      else streetNameNoParens = streetName;

      if (step == null) {
        // first step
        step = createWalkStep(currState);
        createdNewStep = true;

        steps.add(step);
        double thisAngle = DirectionUtils.getFirstAngle(geom);
        step.setAbsoluteDirection(thisAngle);
        // new step, set distance to length of first edge
        distance = edgeNarrative.getDistance();
      } else if ((step.streetName != null && !step.streetNameNoParens().equals(streetNameNoParens))
          && (!step.bogusName || !edgeNarrative.hasBogusName())) {
        /* street name has changed */
        if (roundaboutExit > 0) {
          // if we were just on a roundabout,
          // make note of which exit was taken in the existing step
          step.exit = Integer.toString(roundaboutExit); // ordinal numbers from
          if (streetNameNoParens.equals(roundaboutPreviousStreet)) {
            step.stayOn = true;
          }
          // localization
          roundaboutExit = 0;
        }
        /* start a new step */
        step = createWalkStep(currState);
        createdNewStep = true;

        steps.add(step);
        if (edgeNarrative.isRoundabout()) {
          // indicate that we are now on a roundabout
          // and use one-based exit numbering
          roundaboutExit = 1;
          roundaboutPreviousStreet = backState.getBackEdgeNarrative().getName();
          idx = roundaboutPreviousStreet.indexOf('(');
          if (idx > 0) roundaboutPreviousStreet = roundaboutPreviousStreet.substring(0, idx - 1);
        }
        double thisAngle = DirectionUtils.getFirstAngle(geom);
        step.setDirections(lastAngle, thisAngle, edgeNarrative.isRoundabout());
        // new step, set distance to length of first edge
        distance = edgeNarrative.getDistance();
      } else {
        /* street name has not changed */
        double thisAngle = DirectionUtils.getFirstAngle(geom);
        RelativeDirection direction =
            WalkStep.getRelativeDirection(lastAngle, thisAngle, edgeNarrative.isRoundabout());
        boolean optionsBefore = backState.multipleOptionsBefore();
        if (edgeNarrative.isRoundabout()) {
          // we are on a roundabout, and have already traversed at least one edge of it.
          if (optionsBefore) {
            // increment exit count if we passed one.
            roundaboutExit += 1;
          }
        }
        if (edgeNarrative.isRoundabout() || direction == RelativeDirection.CONTINUE) {
          // we are continuing almost straight, or continuing along a roundabout.
          // just append elevation info onto the existing step.

        } else {
          // we are not on a roundabout, and not continuing straight through.

          // figure out if there were other plausible turn options at the last
          // intersection
          // to see if we should generate a "left to continue" instruction.
          boolean shouldGenerateContinue = false;
          if (edge instanceof PlainStreetEdge) {
            // the next edges will be TinyTurnEdges or PlainStreetEdges, we hope
            double angleDiff = getAbsoluteAngleDiff(thisAngle, lastAngle);
            for (Edge alternative : backState.getVertex().getOutgoingStreetEdges()) {
              if (alternative instanceof TinyTurnEdge) {
                // a tiny turn edge has no geometry, but the next
                // edge will be a TurnEdge or PSE and will have direction
                alternative = alternative.getToVertex().getOutgoingStreetEdges().get(0);
              }
              if (alternative.getName().equals(streetName)) {
                // alternatives that have the same name
                // are usually caused by street splits
                continue;
              }
              double altAngle = DirectionUtils.getFirstAngle(alternative.getGeometry());
              double altAngleDiff = getAbsoluteAngleDiff(altAngle, lastAngle);
              if (angleDiff > Math.PI / 4 || altAngleDiff - angleDiff < Math.PI / 16) {
                shouldGenerateContinue = true;
                break;
              }
            }
          } else if (edge instanceof TinyTurnEdge) {
            // do nothing as this will be handled in other cases
          } else {
            double angleDiff = getAbsoluteAngleDiff(lastAngle, thisAngle);
            // in the case of a turn edge, we actually have to go back two steps to see
            // where
            // else we might be, as once we are on the streetvertex leading into this
            // edge,
            // we are stuck
            State twoStatesBack = backState.getBackState();
            Vertex backVertex = twoStatesBack.getVertex();
            for (Edge alternative : backVertex.getOutgoingStreetEdges()) {
              List<Edge> alternatives = alternative.getToVertex().getOutgoingStreetEdges();
              if (alternatives.size() == 0) {
                continue; // this is not an alternative
              }
              alternative = alternatives.get(0);
              if (alternative.getName().equals(streetName)) {
                // alternatives that have the same name
                // are usually caused by street splits
                continue;
              }
              double altAngle = DirectionUtils.getFirstAngle(alternative.getGeometry());
              double altAngleDiff = getAbsoluteAngleDiff(altAngle, lastAngle);
              if (angleDiff > Math.PI / 4 || altAngleDiff - angleDiff < Math.PI / 16) {
                shouldGenerateContinue = true;
                break;
              }
            }
          }

          if (shouldGenerateContinue) {
            // turn to stay on same-named street
            step = createWalkStep(currState);
            createdNewStep = true;
            steps.add(step);
            step.setDirections(lastAngle, thisAngle, false);
            step.stayOn = true;
            // new step, set distance to length of first edge
            distance = edgeNarrative.getDistance();
          }
        }
      }

      if (createdNewStep) {
        // check last three steps for zag
        int last = steps.size() - 1;
        if (last >= 2) {
          WalkStep threeBack = steps.get(last - 2);
          WalkStep twoBack = steps.get(last - 1);
          WalkStep lastStep = steps.get(last);

          if (twoBack.distance < MAX_ZAG_DISTANCE
              && lastStep.streetNameNoParens().equals(threeBack.streetNameNoParens())) {
            // total hack to remove zags.
            steps.remove(last);
            steps.remove(last - 1);
            step = threeBack;
            step.distance += twoBack.distance;
            if (twoBack.elevation != null) {
              if (step.elevation == null) {
                step.elevation = twoBack.elevation;
              } else {
                for (P2<Double> d : twoBack.elevation) {
                  step.elevation.add(new P2<Double>(d.getFirst() + step.distance, d.getSecond()));
                }
              }
            }
          }
        }
      } else {
        if (step.elevation != null) {
          List<P2<Double>> s = encodeElevationProfile(edge, distance);
          if (step.elevation != null && step.elevation.size() > 0) {
            step.elevation.addAll(s);
          } else {
            step.elevation = s;
          }
        }
        distance += edgeNarrative.getDistance();
      }

      // increment the total length for this step
      step.distance += edgeNarrative.getDistance();
      step.addAlerts(edgeNarrative.getNotes());
      lastAngle = DirectionUtils.getLastAngle(geom);
    }
    return steps;
  }
  /**
   * 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;
  }