/** * Whether this leg is a transit leg or not. * * @return Boolean true if the leg is a transit leg */ public Boolean isTransitLeg() { if (mode == null) return null; else if (mode.equals(TraverseMode.WALK.toString())) return false; else if (mode.equals(TraverseMode.CAR.toString())) return false; else if (mode.equals(TraverseMode.BICYCLE.toString())) return false; else if (mode.equals(TraverseMode.CUSTOM_MOTOR_VEHICLE.toString())) return false; else return true; }
/** * One leg of a trip -- that is, a temporally continuous piece of the journey that takes place on a * particular vehicle (or on foot). */ public class Leg { /** The date and time this leg begins. */ public Calendar startTime = null; /** The date and time this leg ends. */ public Calendar endTime = null; /** * For transit leg, the offset from the scheduled departure-time of the boarding stop in this leg. * "scheduled time of departure at boarding stop" = startTime - departureDelay */ public int departureDelay = 0; /** * For transit leg, the offset from the scheduled arrival-time of the alighting stop in this leg. * "scheduled time of arrival at alighting stop" = endTime - arrivalDelay */ public int arrivalDelay = 0; /** Whether there is real-time data about this Leg */ public Boolean realTime = false; /** Is this a frequency-based trip with non-strict departure times? */ public Boolean isNonExactFrequency = null; /** * The best estimate of the time between two arriving vehicles. This is particularly important for * non-strict frequency trips, but could become important for real-time trips, strict frequency * trips, and scheduled trips with empirical headways. */ public Integer headway = null; /** The distance traveled while traversing the leg in meters. */ public Double distance = null; /** The mode (e.g., <code>Walk</code>) used when traversing this leg. */ @XmlAttribute @JsonSerialize public String mode = TraverseMode.WALK.toString(); /** * For transit legs, the route of the bus or train being used. For non-transit legs, the name of * the street being traversed. */ @XmlAttribute @JsonSerialize public String route = ""; @XmlAttribute @JsonSerialize public String agencyName; @XmlAttribute @JsonSerialize public String agencyUrl; @XmlAttribute @JsonSerialize public int agencyTimeZoneOffset; /** * For transit leg, the route's (background) color (if one exists). For non-transit legs, null. */ @XmlAttribute @JsonSerialize public String routeColor = null; /** * For transit legs, the type of the route. Non transit -1 When 0-7: 0 Tram, 1 Subway, 2 Train, 3 * Bus, 4 Ferry, 5 Cable Car, 6 Gondola, 7 Funicular When equal or highter than 100, it is coded * using the Hierarchical Vehicle Type (HVT) codes from the European TPEG standard Also see * http://groups.google.com/group/gtfs-changes/msg/ed917a69cf8c5bef */ @XmlAttribute @JsonSerialize public Integer routeType = null; /** For transit legs, the ID of the route. For non-transit legs, null. */ @XmlAttribute @JsonSerialize public String routeId = null; /** For transit leg, the route's text color (if one exists). For non-transit legs, null. */ @XmlAttribute @JsonSerialize public String routeTextColor = null; /** For transit legs, if the rider should stay on the vehicle as it changes route names. */ @XmlAttribute @JsonSerialize public Boolean interlineWithPreviousLeg; /** For transit leg, the trip's short name (if one exists). For non-transit legs, null. */ @XmlAttribute @JsonSerialize public String tripShortName = null; /** For transit leg, the trip's block ID (if one exists). For non-transit legs, null. */ @XmlAttribute @JsonSerialize public String tripBlockId = null; /** For transit legs, the headsign of the bus or train being used. For non-transit legs, null. */ @XmlAttribute @JsonSerialize public String headsign = null; /** * For transit legs, the ID of the transit agency that operates the service used for this leg. For * non-transit legs, null. */ @XmlAttribute @JsonSerialize public String agencyId = null; /** For transit legs, the ID of the trip. For non-transit legs, null. */ @XmlAttribute @JsonSerialize public String tripId = null; /** For transit legs, the service date of the trip. For non-transit legs, null. */ @XmlAttribute @JsonSerialize public String serviceDate = null; /** The Place where the leg originates. */ public Place from = null; /** The Place where the leg begins. */ public Place to = null; /** * For transit legs, intermediate stops between the Place where the leg originates and the Place * where the leg ends. For non-transit legs, null. This field is optional i.e. it is always null * unless "showIntermediateStops" parameter is set to "true" in the planner request. */ @XmlElementWrapper(name = "intermediateStops") @JsonProperty(value = "intermediateStops") public List<Place> stop; /** The leg's geometry. */ public EncodedPolylineBean legGeometry; /** A series of turn by turn instructions used for walking, biking and driving. */ @XmlElementWrapper(name = "steps") @JsonProperty(value = "steps") public List<WalkStep> walkSteps; /** Deprecated field formerly used for notes -- will be removed. See alerts */ @XmlElement @JsonSerialize public List<Note> notes; @XmlElement @JsonSerialize public List<Alert> alerts; @XmlAttribute @JsonSerialize public String routeShortName; @XmlAttribute @JsonSerialize public String routeLongName; @XmlAttribute @JsonSerialize public String boardRule; @XmlAttribute @JsonSerialize public String alightRule; @XmlAttribute @JsonSerialize public Boolean rentedBike; /** * Whether this leg is a transit leg or not. * * @return Boolean true if the leg is a transit leg */ public Boolean isTransitLeg() { if (mode == null) return null; else if (mode.equals(TraverseMode.WALK.toString())) return false; else if (mode.equals(TraverseMode.CAR.toString())) return false; else if (mode.equals(TraverseMode.BICYCLE.toString())) return false; else if (mode.equals(TraverseMode.CUSTOM_MOTOR_VEHICLE.toString())) return false; else return true; } /** The leg's duration in seconds */ @XmlElement @JsonSerialize public double getDuration() { return endTime.getTimeInMillis() / 1000.0 - startTime.getTimeInMillis() / 1000.0; } public void addAlert(Alert alert) { if (notes == null) { notes = new ArrayList<Note>(); } if (alerts == null) { alerts = new ArrayList<Alert>(); } String text = alert.alertHeaderText.getSomeTranslation(); if (text == null) { text = alert.alertDescriptionText.getSomeTranslation(); } if (text == null) { text = alert.alertUrl.getSomeTranslation(); } Note note = new Note(text); if (!notes.contains(note)) { notes.add(note); } if (!alerts.contains(alert)) { alerts.add(alert); } } public void setTimeZone(TimeZone timeZone) { Calendar calendar = Calendar.getInstance(timeZone); calendar.setTime(startTime.getTime()); startTime = calendar; calendar = Calendar.getInstance(timeZone); calendar.setTime(endTime.getTime()); endTime = calendar; agencyTimeZoneOffset = timeZone.getOffset(startTime.getTimeInMillis()); } }
/** * return a StateEditor rather than a State so that we can make parking/mode switch modifications * for kiss-and-ride. */ private StateEditor doTraverse(State s0, RoutingRequest options, TraverseMode traverseMode) { boolean walkingBike = options.walkingBike; boolean backWalkingBike = s0.isBackWalkingBike(); TraverseMode backMode = s0.getBackMode(); Edge backEdge = s0.getBackEdge(); if (backEdge != null) { // No illegal U-turns. // NOTE(flamholz): we check both directions because both edges get a chance to decide // if they are the reverse of the other. Also, because it doesn't matter which direction // we are searching in - these traversals are always disallowed (they are U-turns in one // direction // or the other). // TODO profiling indicates that this is a hot spot. if (this.isReverseOf(backEdge) || backEdge.isReverseOf(this)) { return null; } } // Ensure we are actually walking, when walking a bike backWalkingBike &= TraverseMode.WALK.equals(backMode); walkingBike &= TraverseMode.WALK.equals(traverseMode); /* Check whether this street allows the current mode. If not and we are biking, attempt to walk the bike. */ if (!canTraverse(options, traverseMode)) { if (traverseMode == TraverseMode.BICYCLE) { return doTraverse(s0, options.bikeWalkingOptions, TraverseMode.WALK); } return null; } // Automobiles have variable speeds depending on the edge type double speed = calculateSpeed(options, traverseMode, s0.getTimeInMillis()); double time = getDistance() / speed; double weight; // TODO(flamholz): factor out this bike, wheelchair and walking specific logic to somewhere // central. if (options.wheelchairAccessible) { weight = getSlopeSpeedEffectiveLength() / speed; } else if (traverseMode.equals(TraverseMode.BICYCLE)) { time = getSlopeSpeedEffectiveLength() / speed; switch (options.optimize) { case SAFE: weight = bicycleSafetyFactor * getDistance() / speed; break; case GREENWAYS: weight = bicycleSafetyFactor * getDistance() / speed; if (bicycleSafetyFactor <= GREENWAY_SAFETY_FACTOR) { // greenways are treated as even safer than they really are weight *= 0.66; } break; case FLAT: /* see notes in StreetVertex on speed overhead */ weight = getDistance() / speed + getSlopeWorkCostEffectiveLength(); break; case QUICK: weight = getSlopeSpeedEffectiveLength() / speed; break; case TRIANGLE: double quick = getSlopeSpeedEffectiveLength(); double safety = bicycleSafetyFactor * getDistance(); // TODO This computation is not coherent with the one for FLAT double slope = getSlopeWorkCostEffectiveLength(); weight = quick * options.triangleTimeFactor + slope * options.triangleSlopeFactor + safety * options.triangleSafetyFactor; weight /= speed; break; default: weight = getDistance() / speed; } } else { if (walkingBike) { // take slopes into account when walking bikes time = getSlopeSpeedEffectiveLength() / speed; } weight = time; if (traverseMode.equals(TraverseMode.WALK)) { // take slopes into account when walking // FIXME: this causes steep stairs to be avoided. see #1297. double costs = ElevationUtils.getWalkCostsForSlope(getDistance(), getMaxSlope()); // as the cost walkspeed is assumed to be for 4.8km/h (= 1.333 m/sec) we need to adjust // for the walkspeed set by the user double elevationUtilsSpeed = 4.0 / 3.0; weight = costs * (elevationUtilsSpeed / speed); time = weight; // treat cost as time, as in the current model it actually is the same (this can // be checked for maxSlope == 0) /* // debug code if(weight > 100){ double timeflat = length / speed; System.out.format("line length: %.1f m, slope: %.3f ---> slope costs: %.1f , weight: %.1f , time (flat): %.1f %n", length, elevationProfile.getMaxSlope(), costs, weight, timeflat); } */ } } if (isStairs()) { weight *= options.stairsReluctance; } else { // TODO: this is being applied even when biking or driving. weight *= options.walkReluctance; } StateEditor s1 = s0.edit(this); s1.setBackMode(traverseMode); s1.setBackWalkingBike(walkingBike); /* Handle no through traffic areas. */ if (this.isNoThruTraffic()) { // Record transition into no-through-traffic area. if (backEdge instanceof StreetEdge && !((StreetEdge) backEdge).isNoThruTraffic()) { s1.setEnteredNoThroughTrafficArea(); } // If we transitioned into a no-through-traffic area at some point, check if we are exiting // it. if (s1.hasEnteredNoThroughTrafficArea()) { // Only Edges are marked as no-thru, but really we need to avoid creating dominant, pruned // states // on thru _Vertices_. This could certainly be improved somehow. for (StreetEdge se : Iterables.filter(s1.getVertex().getOutgoing(), StreetEdge.class)) { if (!se.isNoThruTraffic()) { // This vertex has at least one through-traffic edge. We can't dominate it with a // no-thru state. return null; } } } } /* Compute turn cost. */ StreetEdge backPSE; if (backEdge != null && backEdge instanceof StreetEdge) { backPSE = (StreetEdge) backEdge; RoutingRequest backOptions = backWalkingBike ? s0.getOptions().bikeWalkingOptions : s0.getOptions(); double backSpeed = backPSE.calculateSpeed(backOptions, backMode, s0.getTimeInMillis()); final double realTurnCost; // Units are seconds. // Apply turn restrictions if (options.arriveBy && !canTurnOnto(backPSE, s0, backMode)) { return null; } else if (!options.arriveBy && !backPSE.canTurnOnto(this, s0, traverseMode)) { return null; } /* * This is a subtle piece of code. Turn costs are evaluated differently during * forward and reverse traversal. During forward traversal of an edge, the turn * *into* that edge is used, while during reverse traversal, the turn *out of* * the edge is used. * * However, over a set of edges, the turn costs must add up the same (for * general correctness and specifically for reverse optimization). This means * that during reverse traversal, we must also use the speed for the mode of * the backEdge, rather than of the current edge. */ if (options.arriveBy && tov instanceof IntersectionVertex) { // arrive-by search IntersectionVertex traversedVertex = ((IntersectionVertex) tov); realTurnCost = backOptions .getIntersectionTraversalCostModel() .computeTraversalCost( traversedVertex, this, backPSE, backMode, backOptions, (float) speed, (float) backSpeed); } else if (!options.arriveBy && fromv instanceof IntersectionVertex) { // depart-after search IntersectionVertex traversedVertex = ((IntersectionVertex) fromv); realTurnCost = options .getIntersectionTraversalCostModel() .computeTraversalCost( traversedVertex, backPSE, this, traverseMode, options, (float) backSpeed, (float) speed); } else { // In case this is a temporary edge not connected to an IntersectionVertex LOG.debug("Not computing turn cost for edge {}", this); realTurnCost = 0; } if (!traverseMode.isDriving()) { s1.incrementWalkDistance(realTurnCost / 100); // just a tie-breaker } long turnTime = (long) Math.ceil(realTurnCost); time += turnTime; weight += options.turnReluctance * realTurnCost; } if (walkingBike || TraverseMode.BICYCLE.equals(traverseMode)) { if (!(backWalkingBike || TraverseMode.BICYCLE.equals(backMode))) { s1.incrementTimeInSeconds(options.bikeSwitchTime); s1.incrementWeight(options.bikeSwitchCost); } } if (!traverseMode.isDriving()) { s1.incrementWalkDistance(getDistance()); } /* On the pre-kiss/pre-park leg, limit both walking and driving, either soft or hard. */ int roundedTime = (int) Math.ceil(time); if (options.kissAndRide || options.parkAndRide) { if (options.arriveBy) { if (!s0.isCarParked()) s1.incrementPreTransitTime(roundedTime); } else { if (!s0.isEverBoarded()) s1.incrementPreTransitTime(roundedTime); } if (s1.isMaxPreTransitTimeExceeded(options)) { if (options.softPreTransitLimiting) { weight += calculateOverageWeight( s0.getPreTransitTime(), s1.getPreTransitTime(), options.maxPreTransitTime, options.preTransitPenalty, options.preTransitOverageRate); } else return null; } } /* Apply a strategy for avoiding walking too far, either soft (weight increases) or hard limiting (pruning). */ if (s1.weHaveWalkedTooFar(options)) { // if we're using a soft walk-limit if (options.softWalkLimiting) { // just slap a penalty for the overage onto s1 weight += calculateOverageWeight( s0.getWalkDistance(), s1.getWalkDistance(), options.getMaxWalkDistance(), options.softWalkPenalty, options.softWalkOverageRate); } else { // else, it's a hard limit; bail LOG.debug("Too much walking. Bailing."); return null; } } s1.incrementTimeInSeconds(roundedTime); s1.incrementWeight(weight); return s1; }