
org.opentripplanner.raptor.path.PathBuilder Maven / Gradle / Ivy
Show all versions of otp Show documentation
package org.opentripplanner.raptor.path;
import java.util.Objects;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import org.opentripplanner.raptor.api.model.RaptorAccessEgress;
import org.opentripplanner.raptor.api.model.RaptorConstants;
import org.opentripplanner.raptor.api.model.RaptorConstrainedTransfer;
import org.opentripplanner.raptor.api.model.RaptorTransfer;
import org.opentripplanner.raptor.api.model.RaptorTripSchedule;
import org.opentripplanner.raptor.api.path.AccessPathLeg;
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.spi.BoardAndAlightTime;
import org.opentripplanner.raptor.spi.RaptorCostCalculator;
import org.opentripplanner.raptor.spi.RaptorPathConstrainedTransferSearch;
import org.opentripplanner.raptor.spi.RaptorSlackProvider;
/**
* The path builder is a utility to build paths. The path builder is responsible for reconstructing
* information that was used in decision-making inside Raptor, but not kept due to performance
* reasons. For example information about the transfer like transfer constraints.
*
* The path builder enforces the same logic as Raptor and generates information like the
* generalized-cost instead of getting it from the stop-arrivals. This is convenient if a path is
* created OUTSIDE Raptor, which is the case in the {@link
* org.opentripplanner.routing.algorithm.transferoptimization.OptimizeTransferService}.
*
* The path builder comes in two versions. One which adds new legs to the tail of the path, allowing
* us to add legs starting with the access leg and ending with the egress leg. The other adds legs
* in the opposite order, from egress to access. Hence, the forward and reverse mappers are
* simplified using the head and tail builder respectively. See {@link #headPathBuilder(
* RaptorSlackProvider, int, RaptorCostCalculator, RaptorStopNameResolver,
* RaptorPathConstrainedTransferSearch)} and {@link #tailPathBuilder(RaptorSlackProvider, int,
* RaptorCostCalculator, RaptorStopNameResolver, RaptorPathConstrainedTransferSearch)}.
*
* The builder is also used for creating test data in unit test.
*
* The {@code PathBuilder} can be extended to override specific things. The {@link
* org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizedPathTail} does this to
* be able to create {@link org.opentripplanner.routing.algorithm.transferoptimization.api.OptimizedPath}
* instead of regular {@link RaptorPath} objects.
*/
public abstract class PathBuilder {
private final RaptorSlackProvider slackProvider;
@Nullable
private final RaptorCostCalculator costCalculator;
@Nullable
private final RaptorStopNameResolver stopNameResolver;
@Nullable
private final RaptorPathConstrainedTransferSearch transferConstraintsSearch;
private int c2 = RaptorConstants.NOT_SET;
// Path leg elements as a double linked list. This makes it easy to look at
// legs before and after in the logic and easy to fork, building alternative
// paths with the same path tail.
private PathBuilderLeg head = null;
private PathBuilderLeg tail = null;
protected final int iterationDepartureTime;
protected PathBuilder(PathBuilder other) {
this(
other.slackProvider,
other.iterationDepartureTime,
other.costCalculator,
other.stopNameResolver,
other.transferConstraintsSearch
);
this.head = other.head == null ? null : other.head.mutate();
this.tail = this.head == null ? null : last(this.head);
}
protected PathBuilder(
RaptorSlackProvider slackProvider,
int iterationDepartureTime,
@Nullable RaptorCostCalculator costCalculator,
@Nullable RaptorStopNameResolver stopNameResolver,
@Nullable RaptorPathConstrainedTransferSearch transferConstraintsSearch
) {
this.slackProvider = slackProvider;
this.costCalculator = costCalculator;
this.stopNameResolver = stopNameResolver;
this.transferConstraintsSearch = transferConstraintsSearch;
this.iterationDepartureTime = iterationDepartureTime;
}
/**
* Create a new path builder to build path starting from the access and add elements in forward
* order until the last egress leg is added.
*
* This builder inserts transferConstraints, time-shifts access/transfers/egress and calculates
* generalized-cost in the build phase. (Insert new tail)
*/
public static PathBuilder headPathBuilder(
RaptorSlackProvider slackProvider,
int iterationDepartureTime,
@Nullable RaptorCostCalculator costCalculator,
@Nullable RaptorStopNameResolver stopNameResolver,
@Nullable RaptorPathConstrainedTransferSearch transferConstraintsSearch
) {
return new HeadPathBuilder<>(
slackProvider,
costCalculator,
iterationDepartureTime,
stopNameResolver,
transferConstraintsSearch
);
}
/**
* Create a new path builder to build path starting from the egress and add elements in reverse
* order until the access leg is added last. (Insert new head)
*
* This builder inserts transferConstraints, time-shifts access/transfers/egress and calculates
* generalized-cost in the build phase.
*/
public static PathBuilder tailPathBuilder(
RaptorSlackProvider slackProvider,
int iterationDepartureTime,
@Nullable RaptorCostCalculator costCalculator,
@Nullable RaptorStopNameResolver stopNameResolver,
@Nullable RaptorPathConstrainedTransferSearch transferConstraintsSearch
) {
return new TailPathBuilder<>(
slackProvider,
costCalculator,
iterationDepartureTime,
stopNameResolver,
transferConstraintsSearch
);
}
protected RaptorSlackProvider slackProvider() {
return slackProvider;
}
@Nullable
protected RaptorCostCalculator costCalculator() {
return costCalculator;
}
@Nullable
protected RaptorStopNameResolver stopNameResolver() {
return stopNameResolver;
}
public void access(RaptorAccessEgress access) {
add(PathBuilderLeg.accessLeg(access));
}
public void transit(T trip, BoardAndAlightTime times) {
add(PathBuilderLeg.transitLeg(trip, times));
}
public void transit(
T trip,
BoardAndAlightTime times,
RaptorConstrainedTransfer txConstrainedTransferAfter
) {
add(PathBuilderLeg.transitLeg(trip, times, txConstrainedTransferAfter));
}
public void transfer(RaptorTransfer transfer, int toStop) {
add(PathBuilderLeg.transferLeg(transfer, toStop));
}
public void egress(RaptorAccessEgress egress) {
add(PathBuilderLeg.egress(egress));
}
public void c2(int c2) {
this.c2 = c2;
}
public int c2() {
return tail.isC2Set() ? tail.c2() : c2;
}
public boolean isC2Set() {
return tail.isC2Set() || c2 != RaptorConstants.NOT_SET;
}
public RaptorPath build() {
updateAggregatedFields();
var pathLegs = createPathLegs(costCalculator, slackProvider);
return new Path<>(iterationDepartureTime, pathLegs, pathLegs.c1Total(), c2());
}
@Override
public String toString() {
final PathStringBuilder builder = new PathStringBuilder(stopNameResolver);
legsAsStream().forEach(it -> it.toString(builder));
return builder.toString();
}
public Stream> legsAsStream() {
return Stream.iterate(head, Objects::nonNull, PathBuilderLeg::next);
}
public PathBuilderLeg head() {
return head;
}
public PathBuilderLeg tail() {
return tail;
}
/* package local methods, accessible by private sub-classes */
protected abstract void add(PathBuilderLeg newLeg);
protected void updateAggregatedFields() {
timeShiftAllStreetLegs();
insertConstrainedTransfers();
}
protected void addTail(PathBuilderLeg newLeg) {
if (head == null) {
head = tail = newLeg;
} else {
tail.setNext(newLeg);
newLeg.setPrev(tail);
tail = newLeg;
}
}
protected void addHead(PathBuilderLeg newLeg) {
if (head == null) {
head = tail = newLeg;
} else {
newLeg.setNext(head);
head.setPrev(newLeg);
head = newLeg;
}
}
/* private methods */
protected AccessPathLeg createPathLegs(
RaptorCostCalculator costCalculator,
RaptorSlackProvider slackProvider
) {
return head.createAccessPathLeg(costCalculator, slackProvider);
}
protected boolean skipCostCalc() {
return costCalculator == null;
}
private void timeShiftAllStreetLegs() {
legsAsStream()
.forEach(leg -> leg.timeShiftThisAndNextLeg(slackProvider, iterationDepartureTime));
}
private void insertConstrainedTransfers() {
// If constrained transfer is not in use
if (transferConstraintsSearch == null) {
return;
}
var prev = head.nextTransitLeg();
if (prev == null) {
return;
}
var curr = prev.nextTransitLeg();
while (curr != null) {
addTransferConstraints(prev, curr);
prev = curr;
curr = curr.nextTransitLeg();
}
}
private void addTransferConstraints(PathBuilderLeg from, PathBuilderLeg to) {
@SuppressWarnings("ConstantConditions")
var tx = transferConstraintsSearch.findConstrainedTransfer(
from.trip(),
from.toStopPos(),
to.trip(),
to.fromStopPos()
);
if (tx != null) {
from.setConstrainedTransferAfterLeg(tx);
}
}
private PathBuilderLeg last(PathBuilderLeg head) {
return head.next() == null ? head : last(head.next());
}
private static class HeadPathBuilder extends PathBuilder {
private HeadPathBuilder(
RaptorSlackProvider slackProvider,
RaptorCostCalculator costCalculator,
int iterationDepartureTime,
@Nullable RaptorStopNameResolver stopNameResolver,
@Nullable RaptorPathConstrainedTransferSearch transferConstraintsSearch
) {
super(
slackProvider,
iterationDepartureTime,
costCalculator,
stopNameResolver,
transferConstraintsSearch
);
}
@Override
protected void add(PathBuilderLeg newLeg) {
addHead(newLeg);
}
}
private static class TailPathBuilder extends PathBuilder {
private TailPathBuilder(
RaptorSlackProvider slackProvider,
RaptorCostCalculator costCalculator,
int iterationDepartureTime,
@Nullable RaptorStopNameResolver stopNameResolver,
@Nullable RaptorPathConstrainedTransferSearch transferConstraintsSearch
) {
super(
slackProvider,
iterationDepartureTime,
costCalculator,
stopNameResolver,
transferConstraintsSearch
);
}
@Override
protected void add(PathBuilderLeg newLeg) {
addTail(newLeg);
}
}
}