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(); }
/** * 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; }
/** * 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; }