private int drawEdge(Edge e) {
   if (e.getGeometry() == null) return 0; // do not attempt to draw geometry-less edges
   Coordinate[] coords = e.getGeometry().getCoordinates();
   beginShape();
   for (int i = 0; i < coords.length; i++)
     vertex((float) toScreenX(coords[i].x), (float) toScreenY(coords[i].y));
   endShape();
   return coords.length; // should be used to count segments, not edges drawn
 }
  private boolean testForUshape(
      Edge edge,
      long maxTime,
      long fromTime,
      long toTime,
      double angleLimit,
      double distanceTolerance,
      double userSpeed,
      boolean hasCar,
      boolean performSpeedTest) {

    LineString ls = (LineString) edge.getGeometry();
    if (ls.getNumPoints() <= 3) { // first filter since u-shapes need at least 4 pts
      // this is the normal case
      return false;
    } else {
      // try to identify u-shapes by checking if the angle EndPoint-StartPoint-StartPoint+1
      // is about 90 degrees (using Azimuths on the sphere)
      double diffTo90Azimuths = 360;
      if (edge instanceof PlainStreetEdge) {
        double firstSegmentAngle = DirectionUtils.getFirstAngle(edge.getGeometry());
        if (firstSegmentAngle < 0) firstSegmentAngle = firstSegmentAngle + Math.PI;
        double firstToLastSegmentAngle = getFirstToLastSegmentAngle(edge.getGeometry());
        if (firstToLastSegmentAngle < 0)
          firstToLastSegmentAngle = firstToLastSegmentAngle + Math.PI;
        double diffAzimuths = Math.abs(firstToLastSegmentAngle - firstSegmentAngle);
        diffTo90Azimuths = Math.abs(diffAzimuths - (Math.PI / 2.0));
      } else {
        // this will happen in particular for transit routes
        // LOG.debug("Edge is not a PlainStreetEdge");
      }
      if (diffTo90Azimuths < angleLimit) {
        // no need to test further if we know its a u-shape
        // System.out.println("u-shape found, (spherical) angle: " + diffTo90Azimuths* 180/Math.PI);
        return true;
      } else {
        if (performSpeedTest) {
          // Use also a distance based criteria since the angle criteria may fail.
          // However a distance based one may fail as well for steep terrain.
          long dt = Math.abs(toTime - fromTime);
          double lineDist = edge.getDistance();
          double distanceToWalkInTimeMissing =
              distanceToMoveInRemainingTime(maxTime, fromTime, dt, userSpeed, edge, hasCar, false);
          double approxWalkableDistanceInTime = distanceToWalkInTimeMissing * distanceTolerance;
          if ((approxWalkableDistanceInTime < lineDist)) {
            return true;
          }
        }
        return false;
      }
    }
  }
 public synchronized void initIndexes() {
   if (vertexIndex != null) {
     return;
   }
   graphService.setLoadLevel(LoadLevel.DEBUG);
   Graph graph = graphService.getGraph();
   vertexIndex = new STRtree();
   edgeIndex = new STRtree();
   for (Vertex v : graph.getVertices()) {
     Envelope vertexEnvelope = new Envelope(v.getCoordinate());
     vertexIndex.insert(vertexEnvelope, v);
     for (Edge e : v.getOutgoing()) {
       Envelope envelope;
       Geometry geometry = e.getGeometry();
       if (geometry == null) {
         envelope = vertexEnvelope;
       } else {
         envelope = geometry.getEnvelopeInternal();
       }
       edgeIndex.insert(envelope, e);
     }
   }
   vertexIndex.build();
   edgeIndex.build();
 }
  /**
   * Get edges inside a bbox.
   *
   * @return
   */
  @Secured({"ROLE_USER"})
  @GET
  @Path("/edges")
  @Produces({MediaType.APPLICATION_JSON})
  public Object getEdges(
      @QueryParam("lowerLeft") String lowerLeft,
      @QueryParam("upperRight") String upperRight,
      @QueryParam("exactClass") String className,
      @QueryParam("skipTransit") boolean skipTransit,
      @QueryParam("skipStreets") boolean skipStreets,
      @QueryParam("skipNoGeometry") boolean skipNoGeometry) {

    initIndexes();

    Envelope envelope = getEnvelope(lowerLeft, upperRight);

    EdgeSet out = new EdgeSet();
    Graph graph = graphService.getGraph();

    @SuppressWarnings("unchecked")
    List<Edge> query = edgeIndex.query(envelope);
    out.edges = new ArrayList<WrappedEdge>();
    for (Edge e : query) {
      if (skipStreets && (e instanceof StreetEdge)) continue;
      if (skipTransit && !(e instanceof StreetEdge)) continue;
      if (skipNoGeometry && e.getGeometry() == null) continue;
      if (className != null && !e.getClass().getName().endsWith("." + className)) continue;
      out.edges.add(new WrappedEdge(e, graph.getIdForEdge(e)));
    }
    return out.withGraph(graph);
  }
Example #5
0
 protected List<Edge> getOutgoingMatchableEdges(Vertex vertex) {
   List<Edge> edges = new ArrayList<Edge>();
   for (Edge e : vertex.getOutgoing()) {
     if (!(e instanceof StreetEdge)) continue;
     if (e.getGeometry() == null) continue;
     edges.add(e);
   }
   return edges;
 }
 /* use endpoints instead of geometry for quick updating */
 private void drawEdgeFast(Edge e) {
   Coordinate[] coords = e.getGeometry().getCoordinates();
   Coordinate c0 = coords[0];
   Coordinate c1 = coords[coords.length - 1];
   line(
       (float) toScreenX(c0.x),
       (float) toScreenY(c0.y),
       (float) toScreenX(c1.x),
       (float) toScreenY(c1.y));
 }
 /*
  * Iterate through all vertices and their (outgoing) edges. If they are of 'interesting' types, add them to the corresponding spatial index.
  */
 public synchronized void buildSpatialIndex() {
   vertexIndex = new STRtree();
   edgeIndex = new STRtree();
   Envelope env;
   // int xminx, xmax, ymin, ymax;
   for (Vertex v : graph.getVertices()) {
     Coordinate c = v.getCoordinate();
     env = new Envelope(c);
     vertexIndex.insert(env, v);
     for (Edge e : v.getOutgoing()) {
       if (e.getGeometry() == null) continue;
       if (e instanceof PatternEdge || e instanceof StreetTransitLink || e instanceof StreetEdge) {
         env = e.getGeometry().getEnvelopeInternal();
         edgeIndex.insert(env, e);
       }
     }
   }
   vertexIndex.build();
   edgeIndex.build();
 }
  public InferredEdge(@Nonnull Edge edge, @Nonnull Integer edgeId, @Nonnull OtpGraph graph) {
    Preconditions.checkNotNull(edge);
    Preconditions.checkNotNull(graph);
    Preconditions.checkNotNull(edgeId);

    assert !(edge instanceof TurnEdge || edge == null);

    this.graph = graph;
    this.edgeId = edgeId;
    this.edge = edge;

    /*
     * Warning: this geometry is in lon/lat and may contain more than one
     * straight line.
     */
    this.geometry = edge.getGeometry();

    this.locationIndexedLine = new LocationIndexedLine(geometry);
    this.lengthIndexedLine = new LengthIndexedLine(geometry);
    this.lengthLocationMap = new LengthLocationMap(geometry);

    this.startVertex = edge.getFromVertex();
    this.endVertex = edge.getToVertex();

    final Coordinate startPointCoord =
        this.locationIndexedLine.extractPoint(this.locationIndexedLine.getStartIndex());

    this.startPoint =
        VectorFactory.getDefault().createVector2D(startPointCoord.x, startPointCoord.y);

    final Coordinate endPointCoord =
        this.locationIndexedLine.extractPoint(this.locationIndexedLine.getEndIndex());
    this.endPoint = VectorFactory.getDefault().createVector2D(endPointCoord.x, endPointCoord.y);

    this.velocityPrecisionDist =
        // ~4.4 m/s, std. dev ~ 30 m/s, Gamma with exp. value = 30 m/s
        // TODO perhaps variance of velocity should be in m/s^2. yeah...
        new NormalInverseGammaDistribution(
            4.4d, 1d / Math.pow(30d, 2d), 1d / Math.pow(30d, 2d) + 1d, Math.pow(30d, 2d));
    this.velocityEstimator =
        new UnivariateGaussianMeanVarianceBayesianEstimator(velocityPrecisionDist);
  }
  @Test
  public final void testOnBoardDepartureTime() {
    Coordinate[] coordinates = new Coordinate[5];
    coordinates[0] = new Coordinate(0.0, 0.0);
    coordinates[1] = new Coordinate(0.0, 1.0);
    coordinates[2] = new Coordinate(2.0, 1.0);
    coordinates[3] = new Coordinate(5.0, 1.0);
    coordinates[4] = new Coordinate(5.0, 5.0);

    PatternDepartVertex depart = mock(PatternDepartVertex.class);
    PatternArriveVertex dwell = mock(PatternArriveVertex.class);
    PatternArriveVertex arrive = mock(PatternArriveVertex.class);
    Graph graph = mock(Graph.class);
    RoutingRequest routingRequest = mock(RoutingRequest.class);
    ServiceDay serviceDay = mock(ServiceDay.class);

    when(graph.getTimeZone()).thenReturn(TimeZone.getTimeZone("GMT"));

    GeometryFactory geometryFactory = GeometryUtils.getGeometryFactory();
    CoordinateSequenceFactory coordinateSequenceFactory =
        geometryFactory.getCoordinateSequenceFactory();
    CoordinateSequence coordinateSequence = coordinateSequenceFactory.create(coordinates);
    LineString geometry = new LineString(coordinateSequence, geometryFactory);
    ArrayList<Edge> hops = new ArrayList<Edge>(2);
    RoutingContext routingContext = new RoutingContext(routingRequest, graph, null, arrive);
    AgencyAndId agencyAndId = new AgencyAndId("Agency", "ID");
    Route route = new Route();
    ArrayList<StopTime> stopTimes = new ArrayList<StopTime>(3);
    StopTime stopDepartTime = new StopTime();
    StopTime stopDwellTime = new StopTime();
    StopTime stopArriveTime = new StopTime();
    Stop stopDepart = new Stop();
    Stop stopDwell = new Stop();
    Stop stopArrive = new Stop();
    Trip trip = new Trip();

    routingContext.serviceDays = new ArrayList<ServiceDay>(Collections.singletonList(serviceDay));
    route.setId(agencyAndId);
    stopDepart.setId(agencyAndId);
    stopDwell.setId(agencyAndId);
    stopArrive.setId(agencyAndId);
    stopDepartTime.setStop(stopDepart);
    stopDepartTime.setDepartureTime(0);
    stopDwellTime.setArrivalTime(20);
    stopDwellTime.setStop(stopDwell);
    stopDwellTime.setDepartureTime(40);
    stopArriveTime.setArrivalTime(60);
    stopArriveTime.setStop(stopArrive);
    stopTimes.add(stopDepartTime);
    stopTimes.add(stopDwellTime);
    stopTimes.add(stopArriveTime);
    trip.setId(agencyAndId);
    trip.setTripHeadsign("The right");

    TripTimes tripTimes = new TripTimes(trip, stopTimes, new Deduplicator());
    StopPattern stopPattern = new StopPattern(stopTimes);
    TripPattern tripPattern = new TripPattern(route, stopPattern);

    when(depart.getTripPattern()).thenReturn(tripPattern);
    when(dwell.getTripPattern()).thenReturn(tripPattern);

    PatternHop patternHop0 = new PatternHop(depart, dwell, stopDepart, stopDwell, 0);
    PatternHop patternHop1 = new PatternHop(dwell, arrive, stopDwell, stopArrive, 1);

    hops.add(patternHop0);
    hops.add(patternHop1);

    when(graph.getEdges()).thenReturn(hops);
    when(depart.getCoordinate()).thenReturn(new Coordinate(0, 0));
    when(dwell.getCoordinate()).thenReturn(new Coordinate(0, 0));
    when(arrive.getCoordinate()).thenReturn(new Coordinate(0, 0));
    when(routingRequest.getFrom()).thenReturn(new GenericLocation());
    when(routingRequest.getStartingTransitTripId()).thenReturn(agencyAndId);
    when(serviceDay.secondsSinceMidnight(anyInt())).thenReturn(9);

    patternHop0.setGeometry(geometry);
    tripPattern.add(tripTimes);
    graph.index = new GraphIndex(graph);

    coordinates = new Coordinate[3];
    coordinates[0] = new Coordinate(3.5, 1.0);
    coordinates[1] = new Coordinate(5.0, 1.0);
    coordinates[2] = new Coordinate(5.0, 5.0);

    coordinateSequence = coordinateSequenceFactory.create(coordinates);
    geometry = new LineString(coordinateSequence, geometryFactory);

    Vertex vertex = onBoardDepartServiceImpl.setupDepartOnBoard(routingContext);
    Edge edge = vertex.getOutgoing().toArray(new Edge[1])[0];

    assertEquals(vertex, edge.getFromVertex());
    assertEquals(dwell, edge.getToVertex());
    assertEquals("The right", edge.getDirection());
    assertEquals(geometry, edge.getGeometry());

    assertEquals(coordinates[0].x, vertex.getX(), 0.0);
    assertEquals(coordinates[0].y, vertex.getY(), 0.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;
  }
  /**
   * Filters all input edges and returns all those as LineString geometries, that have at least one
   * end point within the time limits. If they have only one end point inside, then the sub-edge is
   * returned.
   *
   * @param maxTime the time limit in seconds that defines the size of the walkshed
   * @param allConnectingStateEdges all Edges that have been found to connect all states < maxTime
   * @param spt the ShortestPathTree generated for the pushpin drop point as origin
   * @param angleLimit the angle tolerance to detect roads with u-shapes, i.e. Pi/2 angles, in
   *     Radiant.
   * @param distanceTolerance in percent (e.g. 1.1 = 110%) for u-shape detection based on distance
   *     criteria
   * @param hasCar is travel mode by CAR?
   * @param performSpeedTest if true applies a test to each edge to check if the edge can be
   *     traversed in time. The test can detect u-shaped roads.
   * @return
   */
  ArrayList<LineString> getLinesAndSubEdgesWithinMaxTime(
      long maxTime,
      ArrayList<Edge> allConnectingStateEdges,
      ShortestPathTree spt,
      double angleLimit,
      double distanceTolerance,
      double userSpeed,
      boolean hasCar,
      boolean performSpeedTest) {

    LOG.debug("maximal userSpeed set to: " + userSpeed + " m/sec ");
    if (hasCar) {
      LOG.debug("travel mode is set to CAR, hence the given speed may be adjusted for each edge");
    }

    ArrayList<LineString> walkShedEdges = new ArrayList<LineString>();
    ArrayList<LineString> otherEdges = new ArrayList<LineString>();
    ArrayList<LineString> borderEdges = new ArrayList<LineString>();
    ArrayList<LineString> uShapes = new ArrayList<LineString>();
    int countEdgesOutside = 0;
    // -- determination of walkshed edges via edge states
    for (Iterator iterator = allConnectingStateEdges.iterator(); iterator.hasNext(); ) {
      Edge edge = (Edge) iterator.next();
      State sFrom = spt.getState(edge.getFromVertex());
      State sTo = spt.getState(edge.getToVertex());
      if ((sFrom != null) && (sTo != null)) {
        long fromTime = sFrom.getElapsedTimeSeconds();
        long toTime = sTo.getElapsedTimeSeconds();
        long dt = Math.abs(toTime - fromTime);
        Geometry edgeGeom = edge.getGeometry();
        if ((edgeGeom != null) && (edgeGeom instanceof LineString)) {
          LineString ls = (LineString) edgeGeom;
          // detect u-shape roads/crescents - they need to be treated separately
          boolean uShapeOrLonger =
              testForUshape(
                  edge,
                  maxTime,
                  fromTime,
                  toTime,
                  angleLimit,
                  distanceTolerance,
                  userSpeed,
                  hasCar,
                  performSpeedTest);
          if (uShapeOrLonger) {
            uShapes.add(ls);
          }

          // evaluate if an edge is completely within the time or only with one end
          if ((fromTime < maxTime) && (toTime < maxTime)) {
            // this one is within the time limit on both ends, however we need to do
            // a second test if we have a u-shaped road.
            if (uShapeOrLonger) {
              treatAndAddUshapeWithinTimeLimits(
                  maxTime, userSpeed, walkShedEdges, edge, fromTime, toTime, ls, hasCar);
            } else {
              walkShedEdges.add(ls);
            }
          } // end if:fromTime & toTime < maxTime
          else {
            // check if at least one end is inside, because then we need to
            // create the sub edge
            if ((fromTime < maxTime) || (toTime < maxTime)) {
              double lineDist = edge.getDistance();
              LineString inputLS = ls;
              double fraction = 1.0;
              if (fromTime < toTime) {
                double distanceToWalkInTimeMissing =
                    distanceToMoveInRemainingTime(
                        maxTime, fromTime, dt, userSpeed, edge, hasCar, uShapeOrLonger);
                fraction = (double) distanceToWalkInTimeMissing / (double) lineDist;
              } else {
                // toTime < fromTime : invert the edge direction
                inputLS = (LineString) ls.reverse();
                double distanceToWalkInTimeMissing =
                    distanceToMoveInRemainingTime(
                        maxTime, toTime, dt, userSpeed, edge, hasCar, uShapeOrLonger);
                fraction = (double) distanceToWalkInTimeMissing / (double) lineDist;
              }
              // get the subedge
              LineString subLine = this.getSubLineString(inputLS, fraction);
              borderEdges.add(subLine);
            } else {
              // this edge is completely outside - this should actually not happen
              // we will not do anything, just count
              countEdgesOutside++;
            }
          } // end else: fromTime & toTime < maxTime
        } // end if: edge instance of LineString
        else {
          // edge is not instance of LineString
          LOG.debug("edge not instance of LineString");
        }
      } // end if(sFrom && sTo != null) start Else
      else {
        // LOG.debug("could not retrieve state for edge-endpoint"); //for a 6min car ride, there can
        // be (too) many of such messages
        Geometry edgeGeom = edge.getGeometry();
        if ((edgeGeom != null) && (edgeGeom instanceof LineString)) {
          otherEdges.add((LineString) edgeGeom);
        }
      } // end else: sFrom && sTo != null
    } // end for loop over edges
    walkShedEdges.addAll(borderEdges);
    this.debugGeoms.addAll(uShapes);
    LOG.debug("number of detected u-shapes/crescents: " + uShapes.size());
    return walkShedEdges;
  }
  /**
   * Calculates walksheds for a given location, based on time given to walk and the walk speed.
   *
   * <p>Depending on the value for the "output" parameter (i.e. "POINTS", "SHED" or "EDGES"), a
   * different type of GeoJSON geometry is returned. If a SHED is requested, then a ConcaveHull of
   * the EDGES/roads is returned. If that fails, a ConvexHull will be returned.
   *
   * <p>The ConcaveHull parameter is set to 0.005 degrees. The offroad walkspeed is assumed to be
   * 0.83333 m/sec (= 3km/h) until a road is hit.
   *
   * <p>Note that the set of EDGES/roads returned as well as POINTS returned may contain duplicates.
   * If POINTS are requested, then not the end-points are returned at which the max time is reached,
   * but instead all the graph nodes/crossings that are within the time limits.
   *
   * <p>In case there is no road near by within the given time, then a circle for the walktime limit
   * is created and returned for the SHED parameter. Otherwise the edge with the direction towards
   * the closest road. Note that the circle is calculated in Euclidian 2D coordinates, and
   * distortions towards an ellipse will appear if it is transformed/projected to the user location.
   *
   * <p>An example request may look like this:
   * localhost:8080/otp-rest-servlet/ws/iso?layers=traveltime&styles=mask&batch=true&fromPlace=51.040193121307176
   * %2C-114.04471635818481&toPlace
   * =51.09098935%2C-113.95179705&time=2012-06-06T08%3A00%3A00&mode=WALK&maxWalkDistance=10000&walkSpeed=1.38&walkTime=10.7&output=EDGES
   * Though the first parameters (i) layer, (ii) styles and (iii) batch could be discarded.
   *
   * @param walkmins Maximum number of minutes to walk.
   * @param output Can be set to "POINTS", "SHED" or "EDGES" to return different types of GeoJSON
   *     geometry. SHED returns a ConcaveHull or ConvexHull of the edges/roads. POINTS returns all
   *     graph nodes that are within the time limit.
   * @return a JSON document containing geometries (either points, lineStrings or a polygon).
   * @throws Exception
   * @author sstein---geo.uzh.ch
   */
  @GET
  @Produces({MediaType.APPLICATION_JSON})
  public String getIsochrone(
      @QueryParam("walkTime") @DefaultValue("15") double walkmins,
      @QueryParam("output") @DefaultValue("POINTS") String output)
      throws Exception {

    this.debugGeoms = new ArrayList();
    this.tooFastTraversedEdgeGeoms = new ArrayList();

    RoutingRequest sptRequestA = buildRequest(0);
    String from = sptRequestA.getFrom().toString();
    int pos = 1;
    float lat = 0;
    float lon = 0;
    for (String s : from.split(",")) {
      if (s.isEmpty()) {
        // no location
        Response.status(Status.BAD_REQUEST).entity("no position").build();
        return null;
      }
      try {
        float num = Float.parseFloat(s);
        if (pos == 1) {
          lat = num;
        }
        if (pos == 2) {
          lon = num;
        }
      } catch (Exception e) {
        throw new WebApplicationException(
            Response.status(Status.BAD_REQUEST)
                .entity(
                    "Could not parse position string to number. Require numerical lat & long coords.")
                .build());
      }
      pos++;
    }

    GeometryFactory gf = new GeometryFactory();

    Coordinate dropPoint = new Coordinate(lon, lat);

    int walkInMin = (int) Math.floor(walkmins);
    double walkInSec = walkmins * 60;
    LOG.debug(
        "given travel time: " + walkInMin + " mins + " + (walkInSec - (60 * walkInMin)) + " sec");
    // restrict the evaluated SPT size to 30mins for requests with walking < 30min
    // if larger walking times are requested we adjust the evaluated
    // graph dynamically by 1.3 * min -> this should save processing time
    if (walkInMin < 30) {
      sptRequestA.worstTime = sptRequestA.dateTime + (30 * 60);
    } else {
      sptRequestA.worstTime = sptRequestA.dateTime + Math.round(walkInMin * 1.3 * 60);
    }
    // set the switch-time for shed/area calculation, i.e. to decide if the hull is calculated based
    // on points or on edges
    TraverseModeSet modes = sptRequestA.modes;
    LOG.debug("mode(s): " + modes);
    if ((modes.contains(TraverseMode.TRANSIT))
        || (modes.contains(TraverseMode.BUSISH))
        || (modes.contains(TraverseMode.TRAINISH))) {
      shedCalcMethodSwitchTimeInSec =
          60 * 20; // 20min (use 20min for transit, since buses may not come all the time)
    } else if (modes.contains(TraverseMode.CAR)) {
      shedCalcMethodSwitchTimeInSec = 60 * 10; // 10min
    } else if (modes.contains(TraverseMode.BICYCLE)) {
      shedCalcMethodSwitchTimeInSec = 60 * 10; // 10min
    } else {
      shedCalcMethodSwitchTimeInSec = 60 * 20; // 20min
    }
    // set the maxUserSpeed, which is used later to check for u-type streets/crescents when
    // calculating sub-edges;
    // Note, that the car speed depends on the edge itself, so this value may be replaced later
    this.usesCar = false;
    int numberOfModes = modes.getModes().size();
    if (numberOfModes == 1) {
      if (modes.getWalk()) {
        this.maxUserSpeed = sptRequestA.getWalkSpeed();
      } else if (modes.getBicycle()) {
        this.maxUserSpeed = sptRequestA.getBikeSpeed();
      } else if (modes.getDriving()) {
        this.maxUserSpeed = sptRequestA.getCarSpeed();
        this.usesCar = true;
      }
    } else { // for all other cases (multiple-modes)
      // sstein: I thought I may set it to 36.111 m/sec = 130 km/h,
      // but maybe it is better to assume walk speed for transit, i.e. treat it like if the
      // person gets off the bus on the last crossing and walks the "last mile".
      this.maxUserSpeed = sptRequestA.getWalkSpeed();
    }

    if (doSpeedTest) {
      LOG.debug("performing angle and speed based test to detect u-shapes");
    } else {
      LOG.debug("performing only angle based test to detect u-shapes");
    }

    // TODO: OTP prefers to snap to car-roads/ways, which is not so nice, when walking,
    // and a footpath is closer by. So far there is no option to switch that off

    // create the ShortestPathTree
    try {
      sptRequestA.setRoutingContext(graphService.getGraph());
    } catch (Exception e) {
      // if we get an exception here, and in particular a VertexNotFoundException,
      // then it is likely that we chose a (transit) mode without having that (transit) modes data
      LOG.debug("cannot set RoutingContext: " + e.toString());
      LOG.debug("cannot set RoutingContext: setting mode=WALK");
      sptRequestA.setMode(TraverseMode.WALK); // fall back to walk mode
      sptRequestA.setRoutingContext(graphService.getGraph());
    }
    ShortestPathTree sptA = sptService.getShortestPathTree(sptRequestA);
    StreetLocation origin = (StreetLocation) sptRequestA.rctx.fromVertex;
    sptRequestA.cleanup(); // remove inserted points

    // create a LineString for display
    Coordinate pathToStreetCoords[] = new Coordinate[2];
    pathToStreetCoords[0] = dropPoint;
    pathToStreetCoords[1] = origin.getCoordinate();
    LineString pathToStreet = gf.createLineString(pathToStreetCoords);

    // get distance between origin and drop point for time correction
    double distanceToRoad =
        this.distanceLibrary.distance(origin.getY(), origin.getX(), dropPoint.y, dropPoint.x);
    long offRoadTimeCorrection = (long) (distanceToRoad / this.offRoadWalkspeed);

    //
    // --- filter the states ---
    //
    Set<Coordinate> visitedCoords = new HashSet<Coordinate>();
    ArrayList<Edge> allConnectingEdges = new ArrayList<Edge>();
    Coordinate coords[] = null;
    long maxTime = (long) walkInSec - offRoadTimeCorrection;
    // System.out.println("Reducing walktime from: " + (int)(walkmins * 60) + "sec to " + maxTime +
    // "sec due to initial walk of " + distanceToRoad
    // + "m");

    // if the initial walk is already to long, there is no need to parse...
    if (maxTime <= 0) {
      noRoadNearBy = true;
      long timeToWalk = (long) walkInSec;
      long timeBetweenStates = offRoadTimeCorrection;
      long timeMissing = timeToWalk;
      double fraction = (double) timeMissing / (double) timeBetweenStates;
      pathToStreet = getSubLineString(pathToStreet, fraction);
      LOG.debug(
          "no street found within giving travel time (for off-road walkspeed: {} m/sec)",
          this.offRoadWalkspeed);
    } else {
      noRoadNearBy = false;
      Map<ReversibleLineStringWrapper, Edge> connectingEdgesMap = Maps.newHashMap();
      for (State state : sptA.getAllStates()) {
        long et = state.getElapsedTimeSeconds();
        if (et <= maxTime) {
          // -- filter points, as the same coordinate may be passed several times due to the graph
          // structure
          // in a Calgary suburb family homes neighborhood with a 15min walkshed it filtered about
          // 250 points away (while 145 were finally displayed)
          if (visitedCoords.contains(state.getVertex().getCoordinate())) {
            continue;
          } else {
            visitedCoords.add(state.getVertex().getCoordinate());
          }
          // -- get all Edges needed later for the edge representation
          // and to calculate an edge-based walkshed
          // Note, it can happen that we get a null geometry here, e.g. for hop-edges!
          Collection<Edge> vertexEdgesIn = state.getVertex().getIncoming();
          for (Iterator<Edge> iterator = vertexEdgesIn.iterator(); iterator.hasNext(); ) {
            Edge edge = (Edge) iterator.next();
            Geometry edgeGeom = edge.getGeometry();
            if (edgeGeom != null) { // make sure we get only real edges
              if (edgeGeom instanceof LineString) {
                // allConnectingEdges.add(edge); // instead of this, use a map now, so we don't have
                // similar edge many times
                connectingEdgesMap.put(
                    new ReversibleLineStringWrapper((LineString) edgeGeom), edge);
              }
            }
          }
          Collection<Edge> vertexEdgesOut = state.getVertex().getOutgoing();
          for (Iterator<Edge> iterator = vertexEdgesOut.iterator(); iterator.hasNext(); ) {
            Edge edge = (Edge) iterator.next();
            Geometry edgeGeom = edge.getGeometry();
            if (edgeGeom != null) {
              if (edgeGeom instanceof LineString) {
                // allConnectingEdges.add(edge); // instead of this, use a map now, so we don't
                // similar edge many times
                connectingEdgesMap.put(
                    new ReversibleLineStringWrapper((LineString) edgeGeom), edge);
              }
            }
          }
        } // end : if(et < maxTime)
      }
      // --
      // points from list to array, for later
      coords = new Coordinate[visitedCoords.size()];
      int i = 0;
      for (Coordinate c : visitedCoords) coords[i++] = c;

      // connection edges from Map to List
      allConnectingEdges.clear();
      for (Edge tedge : connectingEdgesMap.values()) allConnectingEdges.add(tedge);
    }
    StringWriter sw = new StringWriter();
    GeoJSONBuilder json = new GeoJSONBuilder(sw);
    //
    // -- create the different outputs ---
    //
    try {
      if (output.equals(IsoChrone.RESULT_TYPE_POINTS)) {
        // in case there was no road we create a circle and
        // and return those points
        if (noRoadNearBy) {
          Geometry circleShape = createCirle(dropPoint, pathToStreet);
          coords = circleShape.getCoordinates();
        }
        // -- the states/nodes with time elapsed <= X min.
        LOG.debug("write multipoint geom with {} points", coords.length);
        json.writeGeom(gf.createMultiPoint(coords));
        LOG.debug("done");
      } else if (output.equals(IsoChrone.RESULT_TYPE_SHED)) {

        Geometry geomsArray[] = null;
        // in case there was no road we create a circle
        if (noRoadNearBy) {
          Geometry circleShape = createCirle(dropPoint, pathToStreet);
          json.writeGeom(circleShape);
        } else {
          if (maxTime > shedCalcMethodSwitchTimeInSec) { // eg., walkshed > 20 min
            // -- create a point-based walkshed
            // less exact and should be used for large walksheds with many edges
            LOG.debug("create point-based shed (not from edges)");
            geomsArray = new Geometry[coords.length];
            for (int j = 0; j < geomsArray.length; j++) {
              geomsArray[j] = gf.createPoint(coords[j]);
            }
          } else {
            // -- create an edge-based walkshed
            // it is more exact and should be used for short walks
            LOG.debug("create edge-based shed (not from points)");
            Map<ReversibleLineStringWrapper, LineString> walkShedEdges = Maps.newHashMap();
            // add the walk from the pushpin to closest street point
            walkShedEdges.put(new ReversibleLineStringWrapper(pathToStreet), pathToStreet);
            // get the edges and edge parts within time limits
            ArrayList<LineString> withinTimeEdges =
                this.getLinesAndSubEdgesWithinMaxTime(
                    maxTime,
                    allConnectingEdges,
                    sptA,
                    angleLimitForUShapeDetection,
                    distanceToleranceForUShapeDetection,
                    maxUserSpeed,
                    usesCar,
                    doSpeedTest);
            for (LineString ls : withinTimeEdges) {
              walkShedEdges.put(new ReversibleLineStringWrapper(ls), ls);
            }
            geomsArray = new Geometry[walkShedEdges.size()];
            int k = 0;
            for (LineString ls : walkShedEdges.values()) geomsArray[k++] = ls;
          } // end if-else: maxTime condition
          GeometryCollection gc = gf.createGeometryCollection(geomsArray);
          // create the concave hull, but in case it fails we just return the convex hull
          Geometry outputHull = null;
          LOG.debug(
              "create concave hull from {} geoms with edge length limit of about {} m (distance on meridian)",
              geomsArray.length,
              concaveHullAlpha * 111132);
          // 1deg at Latitude phi = 45deg is about 111.132km
          // (see wikipedia:
          // http://en.wikipedia.org/wiki/Latitude#The_length_of_a_degree_of_latitude)
          try {
            ConcaveHull hull = new ConcaveHull(gc, concaveHullAlpha);
            outputHull = hull.getConcaveHull();
          } catch (Exception e) {
            outputHull = gc.convexHull();
            LOG.debug("Could not generate ConcaveHull for WalkShed, using ConvexHull instead.");
          }
          LOG.debug("write shed geom");
          json.writeGeom(outputHull);
          LOG.debug("done");
        }
      } else if (output.equals(IsoChrone.RESULT_TYPE_EDGES)) {
        // in case there was no road we return only the suggested path to the street
        if (noRoadNearBy) {
          json.writeGeom(pathToStreet);
        } else {
          // -- if we would use only the edges from the paths to the origin we will miss
          // some edges that will be never on the shortest path (e.g. loops/crescents).
          // However, we can retrieve all edges by checking the times for each
          // edge end-point
          Map<ReversibleLineStringWrapper, LineString> walkShedEdges = Maps.newHashMap();
          // add the walk from the pushpin to closest street point
          walkShedEdges.put(new ReversibleLineStringWrapper(pathToStreet), pathToStreet);
          // get the edges and edge parts within time limits
          ArrayList<LineString> withinTimeEdges =
              this.getLinesAndSubEdgesWithinMaxTime(
                  maxTime,
                  allConnectingEdges,
                  sptA,
                  angleLimitForUShapeDetection,
                  distanceToleranceForUShapeDetection,
                  maxUserSpeed,
                  usesCar,
                  doSpeedTest);
          for (LineString ls : withinTimeEdges) {
            walkShedEdges.put(new ReversibleLineStringWrapper(ls), ls);
          }
          Geometry mls = null;
          LineString edges[] = new LineString[walkShedEdges.size()];
          int k = 0;
          for (LineString ls : walkShedEdges.values()) edges[k++] = ls;
          LOG.debug("create multilinestring from {} geoms", edges.length);
          mls = gf.createMultiLineString(edges);
          LOG.debug("write geom");
          json.writeGeom(mls);
          LOG.debug("done");
        }
      } else if (output.equals("DEBUGEDGES")) {
        // -- for debugging, i.e. display of detected u-shapes/crescents
        ArrayList<LineString> withinTimeEdges =
            this.getLinesAndSubEdgesWithinMaxTime(
                maxTime,
                allConnectingEdges,
                sptA,
                angleLimitForUShapeDetection,
                distanceToleranceForUShapeDetection,
                maxUserSpeed,
                usesCar,
                doSpeedTest);
        if (this.showTooFastEdgesAsDebugGeomsANDnotUShapes) {
          LOG.debug("displaying edges that are traversed too fast");
          this.debugGeoms = this.tooFastTraversedEdgeGeoms;
        } else {
          LOG.debug("displaying detected u-shaped roads/crescents");
        }
        LineString edges[] = new LineString[this.debugGeoms.size()];
        int k = 0;
        for (Iterator iterator = debugGeoms.iterator(); iterator.hasNext(); ) {
          LineString ls = (LineString) iterator.next();
          edges[k] = ls;
          k++;
        }
        Geometry mls = gf.createMultiLineString(edges);
        LOG.debug("write debug geom");
        json.writeGeom(mls);
        LOG.debug("done");
      }
    } catch (org.codehaus.jettison.json.JSONException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    return sw.toString();
  }
 public synchronized void draw() {
   final int BLOCK_SIZE = 1000;
   final long DECIMATE = 100;
   final int FRAME_TIME = 800 / FRAME_RATE;
   int startMillis = millis();
   if (drawLevel == DRAW_PARTIAL) {
     background(15);
     stroke(30, 128, 30);
     strokeWeight(1);
     noFill();
     // noSmooth();
     int drawIndex = 0;
     int drawStart = 0;
     int drawCount = 0;
     while (drawStart < visibleStreetEdges.size()) {
       if (drawFast) drawEdgeFast(visibleStreetEdges.get(drawIndex));
       else drawEdge(visibleStreetEdges.get(drawIndex));
       drawIndex += DECIMATE;
       drawCount += 1;
       if (drawCount % BLOCK_SIZE == 0 && millis() - startMillis > FRAME_TIME) {
         drawFast = drawCount < visibleStreetEdges.size() / 4;
         break;
       }
       if (drawIndex >= visibleStreetEdges.size()) {
         drawStart += 1;
         drawIndex = drawStart;
       }
     }
   } else if (drawLevel == DRAW_ALL) {
     // } else if (drawLevel == DRAW_STREETS) {
     // smooth();
     if (drawOffset == 0) {
       findVisibleElements();
       background(15);
     }
     if (drawStreetEdges) {
       stroke(30, 128, 30); // dark green
       strokeWeight(1);
       noFill();
       // for (Edge e : visibleStreetEdges) drawEdge(e);
       while (drawOffset < visibleStreetEdges.size()) {
         drawEdge(visibleStreetEdges.get(drawOffset));
         drawOffset += 1;
         // if (drawOffset % FRAME_SIZE == 0) return;
         if (drawOffset % BLOCK_SIZE == 0) {
           if (millis() - startMillis > FRAME_TIME) return;
         }
       }
     }
   } else if (drawLevel == DRAW_TRANSIT) {
     if (drawTransitEdges) {
       stroke(40, 40, 128, 30); // transparent blue
       strokeWeight(4);
       noFill();
       // for (Edge e : visibleTransitEdges) {
       while (drawOffset < visibleTransitEdges.size()) {
         Edge e = visibleTransitEdges.get(drawOffset);
         drawEdge(e);
         drawOffset += 1;
         if (drawOffset % BLOCK_SIZE == 0) {
           if (millis() - startMillis > FRAME_TIME) return;
         }
       }
     }
   } else if (drawLevel == DRAW_VERTICES) {
     /* turn off vertex display when zoomed out */
     final double METERS_PER_DEGREE_LAT = 111111.111111;
     drawTransitStopVertices = (modelBounds.getHeight() * METERS_PER_DEGREE_LAT / this.width < 4);
     /* Draw selected visible vertices */
     fill(60, 60, 200);
     for (Vertex v : visibleVertices) {
       if (drawTransitStopVertices && v instanceof TransitStop) {
         drawVertex(v, 5);
       }
     }
     /* Draw highlighted edges in another color */
     noFill();
     stroke(200, 200, 000, 16); // yellow transparent edge highlight
     strokeWeight(8);
     if (highlightedEdges != null) {
       for (Edge e : highlightedEdges) {
         drawEdge(e);
       }
     }
     /* Draw highlighted graph path in another color */
     if (highlightedGraphPath != null) {
       drawGraphPath(highlightedGraphPath);
     }
     /* Draw (single) highlighted edge in highlight color */
     if (highlightedEdge != null && highlightedEdge.getGeometry() != null) {
       stroke(200, 10, 10, 128);
       strokeWeight(8);
       drawEdge(highlightedEdge);
     }
     /* Draw highlighted vertices */
     fill(255, 127, 0); // orange fill
     noStroke();
     if (highlightedVertices != null) {
       for (Vertex v : highlightedVertices) {
         drawVertex(v, 8);
       }
     }
     /* Draw (single) highlighed vertex in a different color */
     if (highlightedVertex != null) {
       fill(255, 255, 30);
       drawVertex(highlightedVertex, 7);
     }
     noFill();
   } else if (drawLevel == DRAW_MINIMAL) {
     if (!newHighlightedEdges.isEmpty()) handleNewHighlights();
     // Black background box
     fill(0, 0, 0);
     stroke(30, 128, 30);
     // noStroke();
     strokeWeight(1);
     rect(3, 3, 303, textAscent() + textDescent() + 6);
     // Print lat & lon coordinates
     fill(128, 128, 256);
     // noStroke();
     String output = lonFormatter.format(mouseModelX) + " " + latFormatter.format(mouseModelY);
     textAlign(LEFT, TOP);
     text(output, 6, 6);
   }
   drawOffset = 0;
   if (drawLevel > DRAW_MINIMAL) drawLevel -= 1; // move to next layer
 }
  /**
   * 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;
  }
  @Override
  public void buildGraph(Graph graph, HashMap<Class<?>, Object> extra) {
    final Parser parser[] = new Parser[] {new Parser()};
    GeometryFactory geometryFactory = GeometryUtils.getGeometryFactory();
    EarliestArrivalSPTService earliestArrivalSPTService = new EarliestArrivalSPTService();
    earliestArrivalSPTService.maxDuration = (maxDuration);

    for (TransitStop ts : Iterables.filter(graph.getVertices(), TransitStop.class)) {
      // Only link street linkable stops
      if (!ts.isStreetLinkable()) continue;
      LOG.trace("linking stop '{}' {}", ts.getStop(), ts);

      // Determine the set of pathway/transfer destinations
      Set<TransitStop> pathwayDestinations = new HashSet<TransitStop>();
      for (Edge e : ts.getOutgoing()) {
        if (e instanceof PathwayEdge || e instanceof SimpleTransfer) {
          if (e.getToVertex() instanceof TransitStop) {
            TransitStop to = (TransitStop) e.getToVertex();
            pathwayDestinations.add(to);
          }
        }
      }

      int n = 0;
      RoutingRequest routingRequest = new RoutingRequest(TraverseMode.WALK);
      routingRequest.clampInitialWait = (0L);
      routingRequest.setRoutingContext(graph, ts, null);
      routingRequest.rctx.pathParsers = parser;
      ShortestPathTree spt = earliestArrivalSPTService.getShortestPathTree(routingRequest);

      if (spt != null) {
        for (State state : spt.getAllStates()) {
          Vertex vertex = state.getVertex();
          if (ts == vertex) continue;

          if (vertex instanceof TransitStop) {
            TransitStop other = (TransitStop) vertex;
            if (!other.isStreetLinkable()) continue;
            if (pathwayDestinations.contains(other)) {
              LOG.trace("Skipping '{}', {}, already connected.", other.getStop(), other);
              continue;
            }
            double distance = 0.0;
            GraphPath graphPath = new GraphPath(state, false);
            CoordinateArrayListSequence coordinates = new CoordinateArrayListSequence();

            for (Edge edge : graphPath.edges) {
              if (edge instanceof StreetEdge) {
                LineString geometry = edge.getGeometry();

                if (geometry != null) {
                  if (coordinates.size() == 0) {
                    coordinates.extend(geometry.getCoordinates());
                  } else {
                    coordinates.extend(geometry.getCoordinates(), 1);
                  }
                }

                distance += edge.getDistance();
              }
            }

            if (coordinates.size() < 2) { // Otherwise the walk step generator breaks.
              ArrayList<Coordinate> coordinateList = new ArrayList<Coordinate>(2);
              coordinateList.add(graphPath.states.get(1).getVertex().getCoordinate());
              State lastState = graphPath.states.getLast().getBackState();
              coordinateList.add(lastState.getVertex().getCoordinate());
              coordinates = new CoordinateArrayListSequence(coordinateList);
            }

            LineString geometry =
                geometryFactory.createLineString(
                    new PackedCoordinateSequence.Double(coordinates.toCoordinateArray()));
            LOG.trace("  to stop: '{}' {} ({}m) [{}]", other.getStop(), other, distance, geometry);
            new SimpleTransfer(ts, other, distance, geometry);
            n++;
          }
        }
      }

      LOG.trace("linked to {} others.", n);
      if (n == 0) {
        LOG.warn(graph.addBuilderAnnotation(new StopNotLinkedForTransfers(ts)));
      }
    }
  }