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;
 }
  /**
   * Converts a list of street edges to a list of turn-by-turn directions.
   *
   * @param previous a non-transit leg that immediately precedes this one (bike-walking, say), or
   *     null
   * @param edges : A list of street edges
   * @return
   */
  private List<WalkStep> getWalkSteps(List<State> states, WalkStep previous) {
    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();
      boolean createdNewStep = false, disableZagRemovalForThisStep = false;
      if (edge instanceof FreeEdge) {
        continue;
      }
      if (currState.getBackMode() == null || !currState.getBackMode().isOnStreetNonTransit()) {
        continue; // ignore STLs and the like
      }
      Geometry geom = edge.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);
        createdNewStep = true;
        disableZagRemovalForThisStep = true;

        // 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 = edge.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);
        if (previous == null) {
          step.setAbsoluteDirection(thisAngle);
        } else {
          step.setDirections(previous.angle, thisAngle, false);
        }
        // new step, set distance to length of first edge
        distance = edge.getDistance();
      } else if (((step.streetName != null && !step.streetNameNoParens().equals(streetNameNoParens))
              && (!step.bogusName || !edge.hasBogusName()))
          ||
          // if we are on a roundabout now and weren't before, start a new step
          edge.isRoundabout() != (roundaboutExit > 0)
          || isLink(edge) && !isLink(backState.getBackEdge())) {
        /* street name has changed, or we've changed state from a roundabout to a street */
        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;
        }
        if (backState.getVertex() instanceof ExitVertex) {
          step.exit = ((ExitVertex) backState.getVertex()).getExitName();
        }
        /* start a new step */
        step = createWalkStep(currState);
        createdNewStep = true;

        steps.add(step);
        if (edge.isRoundabout()) {
          // indicate that we are now on a roundabout
          // and use one-based exit numbering
          roundaboutExit = 1;
          roundaboutPreviousStreet = backState.getBackEdge().getName();
          idx = roundaboutPreviousStreet.indexOf('(');
          if (idx > 0) roundaboutPreviousStreet = roundaboutPreviousStreet.substring(0, idx - 1);
        }
        double thisAngle = DirectionUtils.getFirstAngle(geom);
        step.setDirections(lastAngle, thisAngle, edge.isRoundabout());
        // new step, set distance to length of first edge
        distance = edge.getDistance();
      } else {
        /* street name has not changed */
        double thisAngle = DirectionUtils.getFirstAngle(geom);
        RelativeDirection direction =
            WalkStep.getRelativeDirection(lastAngle, thisAngle, edge.isRoundabout());
        boolean optionsBefore = backState.multipleOptionsBefore();
        if (edge.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 (edge.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 PlainStreetEdges, we hope
            double angleDiff = getAbsoluteAngleDiff(thisAngle, lastAngle);
            for (Edge alternative : backState.getVertex().getOutgoingStreetEdges()) {
              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 {
            double angleDiff = getAbsoluteAngleDiff(lastAngle, thisAngle);
            // FIXME: this code might be wrong with the removal of the edge-based graph
            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 = edge.getDistance();
          }
        }
      }

      if (createdNewStep
          && !disableZagRemovalForThisStep
          && currState.getBackMode() == backState.getBackMode()) {
        // 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())) {

            if (((lastStep.relativeDirection == RelativeDirection.RIGHT
                        || lastStep.relativeDirection == RelativeDirection.HARD_RIGHT)
                    && (twoBack.relativeDirection == RelativeDirection.RIGHT
                        || twoBack.relativeDirection == RelativeDirection.HARD_RIGHT))
                || ((lastStep.relativeDirection == RelativeDirection.LEFT
                        || lastStep.relativeDirection == RelativeDirection.HARD_LEFT)
                    && (twoBack.relativeDirection == RelativeDirection.LEFT
                        || twoBack.relativeDirection == RelativeDirection.HARD_LEFT))) {
              // in this case, we have two left turns or two right turns in quick
              // succession; this is probably a U-turn.

              steps.remove(last - 1);

              lastStep.distance += twoBack.distance;

              // A U-turn to the left, typical in the US.
              if (lastStep.relativeDirection == RelativeDirection.LEFT
                  || lastStep.relativeDirection == RelativeDirection.HARD_LEFT)
                lastStep.relativeDirection = RelativeDirection.UTURN_LEFT;
              else lastStep.relativeDirection = RelativeDirection.UTURN_RIGHT;

              // in this case, we're definitely staying on the same street
              // (since it's zag removal, the street names are the same)
              lastStep.stayOn = true;
            } else {
              // total hack to remove zags.
              steps.remove(last);
              steps.remove(last - 1);
              step = threeBack;
              step.distance += twoBack.distance;
              distance += step.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 (!createdNewStep && 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 += edge.getDistance();
      }

      // increment the total length for this step
      step.distance += edge.getDistance();
      step.addAlerts(currState.getBackAlerts());
      lastAngle = DirectionUtils.getLastAngle(geom);
    }
    return steps;
  }