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; }