All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opentripplanner.raptor.path.Path Maven / Gradle / Ivy

The newest version!
package org.opentripplanner.raptor.path;

import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import org.opentripplanner.raptor.api.model.RaptorConstants;
import org.opentripplanner.raptor.api.model.RaptorTransferConstraint;
import org.opentripplanner.raptor.api.model.RaptorTripSchedule;
import org.opentripplanner.raptor.api.path.AccessPathLeg;
import org.opentripplanner.raptor.api.path.EgressPathLeg;
import org.opentripplanner.raptor.api.path.PathLeg;
import org.opentripplanner.raptor.api.path.PathStringBuilder;
import org.opentripplanner.raptor.api.path.RaptorPath;
import org.opentripplanner.raptor.api.path.RaptorStopNameResolver;
import org.opentripplanner.raptor.api.path.TransitPathLeg;

/**
 * The result of a Raptor search is a path describing the one possible journey. The path is the
 * main DTO part of the Raptor result, but it is also used internally in Raptor. Hence, it is a bit
 * more complex, and it has more responsiblilities than it should.
 * 

* To improve the design, Raptor should not use the path internally. Instead, there should * be a special destination arrival that could take over the Raptor responsibilities. The * path would still need to be constructed at the time of arrival and then become a part of the * destination arrival. The reason for this is that the data necessary to create a path is not * kept in the Raptor state between rounds. * * @param The TripSchedule type defined by the user of the raptor API. */ public class Path implements RaptorPath { private final int iterationDepartureTime; private final int startTime; private final int startTimeInclusivePenalty; private final int endTime; private final int endTimeInclusivePenalty; private final int numberOfTransfers; private final int c1; private final int c2; private final AccessPathLeg accessLeg; private final EgressPathLeg egressLeg; /** @see #dummyPath(int, int, int, int, int) */ private Path( int iterationDepartureTime, int startTime, int endTime, int numberOfTransfers, int c1 ) { this.iterationDepartureTime = iterationDepartureTime; this.startTime = startTime; this.startTimeInclusivePenalty = startTime; this.endTime = endTime; this.endTimeInclusivePenalty = endTime; this.numberOfTransfers = numberOfTransfers; this.c1 = c1; this.accessLeg = null; this.egressLeg = null; this.c2 = RaptorConstants.NOT_SET; } public Path(int iterationDepartureTime, AccessPathLeg accessLeg, int c1, int c2) { this.iterationDepartureTime = iterationDepartureTime; this.startTime = accessLeg.fromTime(); var access = accessLeg.access(); this.startTimeInclusivePenalty = access.hasTimePenalty() ? startTime - access.timePenalty() : startTime; this.c1 = c1; this.accessLeg = accessLeg; this.egressLeg = findEgressLeg(accessLeg); this.numberOfTransfers = countNumberOfTransfers(accessLeg, egressLeg); this.endTime = egressLeg.toTime(); var egress = egressLeg.egress(); this.endTimeInclusivePenalty = egress.hasTimePenalty() ? endTime + egress.timePenalty() : endTime; this.c2 = c2; } public Path(int iterationDepartureTime, AccessPathLeg accessLeg, int c1) { this(iterationDepartureTime, accessLeg, c1, RaptorConstants.NOT_SET); } /** Copy constructor */ protected Path(RaptorPath original) { this( original.rangeRaptorIterationDepartureTime(), original.accessLeg(), original.c1(), original.c2() ); } /** * Create a "dummy" path without legs. Can be used to test if a path is pareto optimal without * creating the hole path. */ public static RaptorPath dummyPath( int iteration, int startTime, int endTime, int numberOfTransfers, int cost ) { return new Path<>(iteration, startTime, endTime, numberOfTransfers, cost); } @Override public final int rangeRaptorIterationDepartureTime() { return iterationDepartureTime; } @Override public final int startTime() { return startTime; } @Override public int startTimeInclusivePenalty() { return startTimeInclusivePenalty; } @Override public final int endTime() { return endTime; } @Override public int endTimeInclusivePenalty() { return endTimeInclusivePenalty; } @Override public final int numberOfTransfers() { return numberOfTransfers; } @Override public final int numberOfTransfersExAccessEgress() { return Math.max(0, (int) transitLegs().count() - 1); } @Override public final int c1() { return c1; } @Override public int c2() { return c2; } @Override public final AccessPathLeg accessLeg() { return accessLeg; } @Override public final EgressPathLeg egressLeg() { return egressLeg; } @Override public List listStops() { return accessLeg.nextLeg().stream().map(PathLeg::fromStop).collect(Collectors.toList()); } @Override public int waitTime() { // Get the total duration for all legs exclusive slack/wait time. int legsTotalDuration = legStream().mapToInt(PathLeg::duration).sum(); return durationInSeconds() - legsTotalDuration; } @Override public Stream> legStream() { return accessLeg.stream(); } @Override public Stream> transitLegs() { return legStream().filter(PathLeg::isTransitLeg).map(PathLeg::asTransitLeg); } @Override public String toStringDetailed(RaptorStopNameResolver stopNameResolver) { return buildString(true, stopNameResolver, null); } @Override public String toString(RaptorStopNameResolver stopNameTranslator) { return buildString(false, stopNameTranslator, null); } @Override public int hashCode() { return Objects.hash(startTime, endTime, numberOfTransfers, accessLeg); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Path path = (Path) o; return ( startTime == path.startTime && endTime == path.endTime && numberOfTransfers == path.numberOfTransfers && Objects.equals(accessLeg, path.accessLeg) ); } @Override public String toString() { return buildString(false, null, null); } protected String toString(boolean detailed, RaptorStopNameResolver stopNameResolver) { return buildString(detailed, stopNameResolver, null); } protected String buildString( boolean detailed, @Nullable RaptorStopNameResolver stopNameResolver, @Nullable Consumer appendToSummary ) { RaptorTransferConstraint constraintPrevLeg = null; var buf = new PathStringBuilder(stopNameResolver); if (accessLeg != null) { int prevToTime = 0; for (PathLeg leg : accessLeg.iterator()) { if (leg == accessLeg) { if (!accessLeg.access().isFree()) { buf.accessEgress(accessLeg.access()); addWalkDetails(detailed, buf, leg); } } else { buf.stop(leg.fromStop()); if (detailed) { buf.duration(leg.fromTime() - prevToTime); // Add Transfer constraints info from the previous transit lag if (constraintPrevLeg != null) { buf.text(constraintPrevLeg.toString()); constraintPrevLeg = null; } } if (leg.isTransitLeg()) { TransitPathLeg transitLeg = leg.asTransitLeg(); buf.transit( transitLeg.trip().pattern().debugInfo(), transitLeg.fromTime(), transitLeg.toTime() ); if (detailed) { buf.duration(leg.duration()); buf.c1(leg.c1()); } if (transitLeg.getConstrainedTransferAfterLeg() != null) { constraintPrevLeg = transitLeg.getConstrainedTransferAfterLeg().getTransferConstraint(); } } else if (leg.isTransferLeg()) { buf.walk(leg.duration()); addWalkDetails(detailed, buf, leg); } // Access and Egress else if (leg.isEgressLeg()) { var egress = leg.asEgressLeg().egress(); if (!egress.isFree()) { buf.accessEgress(egress); addWalkDetails(detailed, buf, leg); } } } prevToTime = leg.toTime(); } } // Add summary info buf.summary(startTime, endTime, numberOfTransfers, c1, c2, appendToSummary); return buf.toString(); } private static EgressPathLeg findEgressLeg(PathLeg leg) { return (EgressPathLeg) leg.stream().reduce((a, b) -> b).orElseThrow(); } /* private methods */ private static int countNumberOfTransfers( AccessPathLeg accessLeg, EgressPathLeg egressPathLeg ) { int nAccessRides = accessLeg.access().numberOfRides(); int nTransitRides = (int) accessLeg .stream() .filter(PathLeg::isTransitLeg) .map(PathLeg::asTransitLeg) .filter(Predicate.not(TransitPathLeg::isStaySeatedOntoNextLeg)) .count(); int nEgressRides = egressPathLeg.egress().numberOfRides(); // Remove one boarding to get the count of transfers only. return nAccessRides + nTransitRides + nEgressRides - 1; } private void addWalkDetails(boolean detailed, PathStringBuilder buf, PathLeg leg) { if (detailed) { int fromTime = leg.fromTime(); int toTime = leg.toTime(); int cost = leg.c1(); buf.time(fromTime, toTime).c1(cost); } } }