package org.opentripplanner.profile;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import org.opentripplanner.index.model.RouteShort;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* This is a response model class which holds data that will be serialized and returned to the client.
* It is not used internally in routing.
*/
public class Option {
private static final Logger LOG = LoggerFactory.getLogger(Option.class);
public List<Segment> transit;
public List<StreetSegment> access;
public List<StreetSegment> egress;
public Stats stats = new Stats();
public String summary;
public List<DCFareCalculator.Fare> fares;
// The fares are outside the transit segments because a fare can apply to multiple segments so there is no one-to-one
// correspondance. For example, when you transfer from one subway to another and pay one fare for the two segments.
public Option (Ride tail, Collection<StopAtDistance> accessPaths, Collection<StopAtDistance> egressPaths) {
access = StreetSegment.list(accessPaths);
egress = StreetSegment.list(egressPaths);
// Include access and egress times across all modes in the overall travel time statistics for this option.
// FIXME In the event that there is only access, N will still be 1 which is strange.
stats.add(access);
stats.add(egress);
List<Ride> rides = Lists.newArrayList();
// Chase back-pointers to get a reversed sequence of rides
for (Ride ride = tail; ride != null; ride = ride.previous) {
rides.add(ride);
}
if ( ! rides.isEmpty()) {
Collections.reverse(rides);
// The access times have already been calculated separately, avoid double-inclusion by zeroing them out here
rides.get(0).accessStats = new Stats();
rides.get(0).accessDist = 0;
// Make a transit segment for each ride in order
transit = Lists.newArrayList();
for (Ride ride : rides) {
Segment segment = new Segment(ride);
transit.add(segment);
stats.add(segment.walkTime);
if(segment.waitStats != null) stats.add(segment.waitStats);
stats.add(segment.rideStats);
}
}
// Really should be one per segment, with transfers to the same operator having a price of 0.
fares = DCFareCalculator.calculateFares(rides);
summary = generateSummary();
}
/** Make a human readable text summary of this option. */
public String generateSummary() {
if (transit == null || transit.isEmpty()) {
return "Non-transit options";
}
List<String> vias = Lists.newArrayList();
List<String> routes = Lists.newArrayList();
for (Segment segment : transit) {
List<String> routeShortNames = Lists.newArrayList();
for (RouteShort rs : segment.routes) {
String routeName = rs.shortName == null ? rs.longName : rs.shortName;
routeShortNames.add(routeName);
}
routes.add(Joiner.on("/").join(routeShortNames));
vias.add(segment.toName);
}
StringBuilder sb = new StringBuilder();
sb.append("routes ");
sb.append(Joiner.on(", ").join(routes));
if (!vias.isEmpty()) vias.remove(vias.size() - 1);
if (!vias.isEmpty()) {
sb.append(" via ");
sb.append(Joiner.on(", ").join(vias));
}
return sb.toString();
}
public static enum SortOrder {
MIN, AVG, MAX;
}
public static class MinComparator implements Comparator<Option> {
@Override
public int compare(Option one, Option two) {
return one.stats.min - two.stats.min;
}
}
public static class AvgComparator implements Comparator<Option> {
@Override
public int compare(Option one, Option two) {
return one.stats.avg - two.stats.avg;
}
}
public static class MaxComparator implements Comparator<Option> {
@Override
public int compare(Option one, Option two) {
return one.stats.max - two.stats.max;
}
}
/**
* Rides or transfers may contain no patterns after applying time window.
* Return true if this Option contains any transit rides that contain zero active patterns.
*/
public boolean hasEmptyRides() {
for (Segment seg : transit) {
if (seg.rideStats.num == 0 || seg.waitStats.num == 0) {
return true;
}
}
return false;
}
}