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

org.opentripplanner.transit.raptor.api.path.PathBuilderLeg Maven / Gradle / Ivy

There is a newer version: 2.5.0
Show newest version
package org.opentripplanner.transit.raptor.api.path;

import static org.opentripplanner.transit.raptor.api.transit.CostCalculator.ZERO_COST;

import java.util.function.Predicate;
import javax.annotation.Nullable;
import org.opentripplanner.transit.raptor.api.transit.CostCalculator;
import org.opentripplanner.transit.raptor.api.transit.RaptorConstrainedTransfer;
import org.opentripplanner.transit.raptor.api.transit.RaptorSlackProvider;
import org.opentripplanner.transit.raptor.api.transit.RaptorTransfer;
import org.opentripplanner.transit.raptor.api.transit.RaptorTransferConstraint;
import org.opentripplanner.transit.raptor.api.transit.RaptorTripSchedule;
import org.opentripplanner.transit.raptor.api.view.BoardAndAlightTime;
import org.opentripplanner.transit.raptor.util.PathStringBuilder;

/**
 * This is the leg implementation for the {@link PathBuilder}. It Private inner class which help cashing and calculating values before constructing a path.
 */
public class PathBuilderLeg {
    private static final int NOT_SET = -999_999_999;

    /** Immutable data for the current leg. */
    private MyLeg leg;

    private int fromTime = NOT_SET;
    private int toTime = NOT_SET;

    private PathBuilderLeg prev = null;
    private PathBuilderLeg next = null;

    /**
     * Copy-constructor - do a deep copy with the exception of immutable types.
     * Always start with the desired head. The constructor will recursively copy the entire
     * path (all legs until {@code next} do not exist).
     */
    private PathBuilderLeg(PathBuilderLeg other) {
        // Immutable fields
        this.fromTime = other.fromTime;
        this.toTime = other.toTime;
        this.leg = other.leg;

        // Mutable fields
        if(other.next != null) {
            this.next = new PathBuilderLeg<>(other.next);
            this.next.prev = this;
        }
    }

    private PathBuilderLeg(MyLeg leg) {
        this.leg = leg;
        if(leg.isTransit()) {
            @SuppressWarnings("unchecked") var transit = (MyTransitLeg)leg;
            this.fromTime = transit.fromTime();
            this.toTime = transit.toTime();
        }
    }


    /* factory methods */

    static  PathBuilderLeg accessLeg(RaptorTransfer access) {
        return new PathBuilderLeg<>(new MyAccessLeg(access));
    }

    /**
     * Create a transfer leg. The given {@code toStop} is the stop you arrive on when
     * traveling in the direction from origin to destination. In a forward search the
     * given {@code transfer.stop()} and the {@code toStop} is the same, but in a reverse
     * search, the {@code transfer.stop()} is the fromStop; hence the {@code toStop} must be
     * passed in.
     */
    static  PathBuilderLeg transferLeg(
            RaptorTransfer transfer, int toStop
    ) {
        return new PathBuilderLeg<>(new MyTransferLeg(transfer, toStop));
    }

    static  PathBuilderLeg transitLeg(
            T trip, BoardAndAlightTime boardAndAlightTime
    ) {
        return transitLeg(trip, boardAndAlightTime, null);
    }

    static  PathBuilderLeg transitLeg(
            T trip,
            BoardAndAlightTime boardAndAlightTime,
            @Nullable RaptorConstrainedTransfer txConstrainedAfterLeg
    ) {
        return new PathBuilderLeg<>(
                new MyTransitLeg<>(trip, boardAndAlightTime, txConstrainedAfterLeg)
        );
    }

    static  PathBuilderLeg egress(RaptorTransfer egress) {
      return new PathBuilderLeg<>(new MyEgressLeg(egress));
    }

    /* accessors */

    public int fromTime() { return fromTime; }

    public int fromStop() { return prev.toStop(); }

    public int fromStopPos() {
        return asTransitLeg().fromStopPos();
    }

    public int toTime() { return toTime; }

    public int toStop() { return leg.toStop(); }

    public int toStopPos() {
        return asTransitLeg().toStopPos();
    }

    private void setTime(int fromTime, int toTime) {
        this.fromTime = fromTime;
        this.toTime = toTime;
    }

    public int durationInSec() { return toTime - fromTime; }

    @Nullable
    public RaptorConstrainedTransfer constrainedTransferAfterLeg() {
        return isTransit() ? asTransitLeg().constrainedTransferAfterLeg : null;
    }

    public void setConstrainedTransferAfterLeg(
            @Nullable RaptorConstrainedTransfer constrainedTransferAfterLeg
    ) {
        var old = asTransitLeg();
        this.leg = new MyTransitLeg<>(old.trip, old.boardAndAlightTime, constrainedTransferAfterLeg);
    }

    public boolean isAccess() { return leg.isAccess(); }

    private boolean isAccessWithoutRides() { return isAccess() && hasNoRides(); }

    /** This leg is a transit leg or access/transfer/egress leg with rides (FLEX) */
    public boolean hasRides() {
        return isTransit() || ((MyStreetLeg)leg).streetPath.hasRides();
    }

    /** This leg is an access/transfer/egress leg without any rides. */
    public boolean hasNoRides() {
        return !hasRides();
    }

    public boolean isTransit() { return leg.isTransit(); }

    public boolean isTransfer() { return leg.isTransfer(); }

    public boolean isEgress() { return leg.isEgress(); }

    private MyAccessLeg asAccessLeg() { return (MyAccessLeg) leg; }

    private MyTransferLeg asTransferLeg() { return (MyTransferLeg) leg; }

    private MyTransitLeg asTransitLeg() {
        //noinspection unchecked
        return (MyTransitLeg) leg;
    }

    private MyEgressLeg asEgressLeg() { return (MyEgressLeg) leg; }

    public T trip() { return asTransitLeg().trip; }

    PathBuilderLeg prev() { return prev; }

    void setPrev(PathBuilderLeg prev) { this.prev = prev; }

    public PathBuilderLeg next() { return next; }

    void setNext(PathBuilderLeg next) {
        this.next = next;
    }

    @Nullable
    PathBuilderLeg prevTransitLeg() {
        var it = prev();
        if (it == null) { return null; }
        if (it.isTransfer()) { it = it.prev(); }
        if (it == null) { return null; }
        // Check transfer, it can be a FLEX access
        return it.isTransit() ? it : null;
    }

    @Nullable
    public PathBuilderLeg nextTransitLeg() {  return next(PathBuilderLeg::isTransit); }

    @Nullable
    PathBuilderLeg next(Predicate> test) {
        var it = next();
        while (it != null && !test.test(it)) {
            it = it.next();
        }
        return it;
    }

    @Override
    public String toString() {
        return leg.toString();
    }

    public void toString(PathStringBuilder builder) {
        leg.addToString(builder);
    }


    /* Build helper methods, package local */

    PathBuilderLeg mutate() {
        return new PathBuilderLeg<>(this);
    }

    /**
     * Access, transfers and egress legs must be time-shifted towards the appropriate
     * transit leg. The access is moved to arrive just in time to board the next transit, and
     * transfers and egress are moved in time to start when the previous transit arrive, maximising
     * the wait time after the transfer. Slacks are respected.
     * 

* This method operate on the current leg for access legs and the NEXT leg for transfer and * egress. So, if the NEXT leg is a transit or egress leg it is time-shifted. This make it safe * to call this method on any leg - just make sure the legs are linked first. */ public void timeShiftThisAndNextLeg(RaptorSlackProvider slackProvider) { if(isAccess()) { timeShiftAccessTime(slackProvider); } if(next != null) { if (next.isTransfer()) { next.timeShiftTransferTime(slackProvider); } else if (next.isEgress()) { next.timeShiftEgressTime(slackProvider); } } } /** * Calculate the cost of this leg including wait time [if previous is set]. *

* This method is safe to use event as long as the next leg is set. */ public int generalizedCost(CostCalculator costCalculator, RaptorSlackProvider slackProvider) { if(costCalculator == null) { return ZERO_COST; } if(isAccess()) { return asAccessLeg().streetPath.generalizedCost(); } if(isTransfer()) { return asTransferLeg().streetPath.generalizedCost(); } if(isTransit()) { return transitCost(costCalculator, slackProvider); } if(isEgress()) { return egressCost(costCalculator, slackProvider); } throw new IllegalStateException("Unknown leg type: " + this); } public void changeBoardingPosition(int stopPosition) { if(stopPosition == fromStopPos()) { return; } var old = asTransitLeg(); var boardAndAlightTime = new BoardAndAlightTime(old.trip, stopPosition, old.boardAndAlightTime.alightStopPos()); this.leg = new MyTransitLeg<>(old.trip, boardAndAlightTime, old.constrainedTransferAfterLeg); this.fromTime = boardAndAlightTime.boardTime(); } /* Factory methods, create new path */ AccessPathLeg createAccessPathLeg( CostCalculator costCalculator, RaptorSlackProvider slackProvider ) { PathLeg nextLeg = next.createPathLeg(costCalculator, slackProvider); var accessPath = asAccessLeg().streetPath; int cost = cost(costCalculator, accessPath); return new AccessPathLeg<>(accessPath, fromTime, toTime, cost, nextLeg); } private PathLeg createPathLeg(CostCalculator costCalculator, RaptorSlackProvider slackProvider) { if(isAccess()) { return createAccessPathLeg(costCalculator, slackProvider); } if(isTransit()) { return createTransitPathLeg(costCalculator, slackProvider); } if(isTransfer()) { return createTransferPathLeg(costCalculator, slackProvider); } if(isEgress()) { return createEgressPathLeg(costCalculator, slackProvider); } throw new IllegalStateException("Unknown leg type: " + this); } private TransferPathLeg createTransferPathLeg( CostCalculator costCalculator, RaptorSlackProvider slackProvider ) { PathLeg nextLeg = next.createPathLeg(costCalculator, slackProvider); var streetPath = asTransferLeg().streetPath; int cost = cost(costCalculator, streetPath); return new TransferPathLeg<>(fromStop(), fromTime, toTime, cost, streetPath, nextLeg); } private TransitPathLeg createTransitPathLeg( CostCalculator costCalculator, RaptorSlackProvider slackProvider ) { PathLeg nextLeg = next.createPathLeg(costCalculator, slackProvider); var leg = asTransitLeg(); int cost = transitCost(costCalculator, slackProvider); return new TransitPathLeg<>( leg.trip, leg.boardAndAlightTime, leg.constrainedTransferAfterLeg, cost, nextLeg ); } private EgressPathLeg createEgressPathLeg( CostCalculator costCalculator, RaptorSlackProvider slackProvider ) { int cost = egressCost(costCalculator, slackProvider); return new EgressPathLeg<>(asEgressLeg().streetPath, fromTime, toTime, cost); } /* private methods */ /** * Find the stop arrival time for this leg, this include alight-slack for transit legs. */ private int stopArrivalTime(RaptorSlackProvider slackProvider) { if(!isTransit()) { return toTime; } return toTime + slackProvider.alightSlack(asTransitLeg().trip.pattern()); } /** * The wait-time between this TRANSIT leg and the next transit leg including any slack. * If there is a transfer leg between the two transit legs, the transfer time is excluded from * the wait time. *

* {@code -1} is returned: *

    *
  • if this leg is not a transit leg
  • *
  • no transit leg exist after this leg
  • *
      */ public int waitTimeBeforeNextTransitIncludingSlack() { if(next.hasRides()) { return waitTimeBeforeNextLegIncludingSlack(); } // Add wait-time before and after transfer(the next leg) return waitTimeBeforeNextLegIncludingSlack() + next.waitTimeBeforeNextLegIncludingSlack(); } private int waitTimeAfterPrevStopArrival(RaptorSlackProvider slackProvider) { return fromTime - prev.stopArrivalTime(slackProvider); } private int waitTimeBeforeNextLegIncludingSlack() { return next.fromTime - toTime; } /** * Return the stop-arrival-time for the leg in front of this transit leg. */ private int transitStopArrivalTimeBefore( RaptorSlackProvider slackProvider, boolean withTransferSlack ) { var leg = asTransitLeg(); int slack = slackProvider.boardSlack(leg.trip.pattern()) + (withTransferSlack ? slackProvider.transferSlack() : 0); return leg.fromTime() - slack; } /** * We need to calculate the access-arrival-time. There are 3 cases: *
        *
      1. Normal case: Walk ~ boardSlack ~ transit (access can be time-shifted)
      2. *
      3. Flex and transit: Flex ~ (transferSlack + boardSlack) ~ transit
      4. *
      5. Flex, walk and transit: Flex ~ Walk ~ (transferSlack + boardSlack) ~ transit
      6. *
      * Flex access may or may not be time-shifted. */ private void timeShiftAccessTime(RaptorSlackProvider slackProvider) { var accessPath = asAccessLeg().streetPath; var nextTransitLeg = nextTransitLeg(); @SuppressWarnings("ConstantConditions") int newToTime = nextTransitLeg.transitStopArrivalTimeBefore(slackProvider, hasRides()); if(next.isTransfer()) { newToTime -= next.asTransferLeg().streetPath.durationInSeconds(); } newToTime = accessPath.latestArrivalTime(newToTime); setTime(newToTime - accessPath.durationInSeconds(), newToTime); } private void timeShiftTransferTime(RaptorSlackProvider slackProvider) { int newFromTime; if(prev.isTransit()) { newFromTime = prev.toTime() + slackProvider.alightSlack(prev.asTransitLeg().trip.pattern()); } else if(prev.isAccess()) { newFromTime = prev.toTime(); } else { throw new IllegalStateException("Unexpected leg type before TransferLeg: " + this); } setTime(newFromTime, newFromTime + asTransferLeg().streetPath.durationInSeconds()); } private void timeShiftEgressTime(RaptorSlackProvider slackProvider) { var egressPath = asEgressLeg().streetPath; int newFromTime = prev.stopArrivalTime(slackProvider); if (egressPath.hasRides()) { newFromTime += slackProvider.transferSlack(); } newFromTime = egressPath.earliestDepartureTime(newFromTime); setTime(newFromTime, newFromTime + egressPath.durationInSeconds()); } private int transitCost(CostCalculator costCalculator, RaptorSlackProvider slackProvider) { if(costCalculator == null) { return ZERO_COST; } var leg = asTransitLeg(); // Need to check prev, since this method should return a value for partially constructed // paths (a tail of a path only). int prevStopArrivalTime; // If path is not fully constructed yet, then prev leg might be null. if(prev == null) { prevStopArrivalTime = fromTime; } else { prevStopArrivalTime = prev.stopArrivalTime(slackProvider); } var prevTransit = prevTransitLeg(); var txBeforeLeg = prevTransit == null ? null : prevTransit.constrainedTransferAfterLeg(); var transferConstraint = txBeforeLeg == null ? RaptorTransferConstraint.REGULAR_TRANSFER : txBeforeLeg.getTransferConstraint(); boolean firstBoarding = prev != null && prev.isAccessWithoutRides(); int boardCost = costCalculator.boardingCost( firstBoarding, prevStopArrivalTime, leg.fromStop(), fromTime, trip(), transferConstraint ); return costCalculator.transitArrivalCost( boardCost, slackProvider.alightSlack(leg.trip.pattern()), durationInSec(), leg.trip.transitReluctanceFactorIndex(), toStop() ); } private int egressCost(CostCalculator costCalculator, RaptorSlackProvider slackProvider) { if(costCalculator == null) { return ZERO_COST; } var egressPath = asEgressLeg().streetPath; final int egressCost = costCalculator.costEgress(egressPath); if(prev == null) { return egressCost; } final int waitCost = costCalculator.waitCost(waitTimeAfterPrevStopArrival(slackProvider)); return waitCost + egressCost; } private static int cost(CostCalculator costCalculator, RaptorTransfer streetPath) { return costCalculator != null ? streetPath.generalizedCost() : ZERO_COST; } /* PRIVATE INTERFACES */ /** This is the common interface for all immutable leg structures. */ private interface MyLeg { default boolean isAccess() { return false; } default boolean isTransit() { return false; } default boolean isTransfer() { return false; } default boolean isEgress() { return false; } int toStop(); PathStringBuilder addToString(PathStringBuilder builder); } /* PRIVATE CLASSES */ /** Abstract access/transfer/egress leg */ private static abstract class MyStreetLeg implements MyLeg { final RaptorTransfer streetPath; MyStreetLeg(RaptorTransfer streetPath) { this.streetPath = streetPath; } final @Override public String toString() { return addToString(new PathStringBuilder(null)).toString(); } } private static class MyAccessLeg extends MyStreetLeg { private MyAccessLeg(RaptorTransfer streetPath) { super(streetPath); } @Override public boolean isAccess() { return true; } @Override public int toStop() { return streetPath.stop(); } @Override public PathStringBuilder addToString(PathStringBuilder builder) { return builder.accessEgress(streetPath).sep().stop(toStop()).sep(); } } private static class MyTransferLeg extends MyStreetLeg { final int toStop; MyTransferLeg(RaptorTransfer streetPath, int toStop) { super(streetPath); this.toStop = toStop; } @Override public boolean isTransfer() { return true; } @Override public int toStop() { return toStop; } @Override public PathStringBuilder addToString(PathStringBuilder builder) { return builder.walk(streetPath.durationInSeconds()).sep().stop(toStop()).sep(); } } private static class MyTransitLeg implements MyLeg { final T trip; final BoardAndAlightTime boardAndAlightTime; /** * Transfer constraints are attached to the transit leg BEFORE the transit, this * simplifies the code a bit. We do not always have transfers (same stop) between * to transits, so it is can not be attached to the transfer. Also, attaching it to the * transit leg BEFORE make the transit leg after more reusable when mutating the builder. */ @Nullable private final RaptorConstrainedTransfer constrainedTransferAfterLeg; private MyTransitLeg( T trip, BoardAndAlightTime boardAndAlightTime, RaptorConstrainedTransfer constrainedTransferAfterLeg ) { this.trip = trip; this.boardAndAlightTime = boardAndAlightTime; this.constrainedTransferAfterLeg = constrainedTransferAfterLeg; } @Override public boolean isTransit() { return true; } public int fromStop() { return trip.pattern().stopIndex(boardAndAlightTime.boardStopPos()); } public int fromStopPos() { return boardAndAlightTime.boardStopPos(); } public int fromTime() { return boardAndAlightTime.boardTime(); } @Override public int toStop() { return trip.pattern().stopIndex(boardAndAlightTime.alightStopPos()); } public int toStopPos() { return boardAndAlightTime.alightStopPos(); } public int toTime() { return boardAndAlightTime.alightTime(); } final @Override public String toString() { return addToString(new PathStringBuilder(null)).toString(); } @Override public PathStringBuilder addToString(PathStringBuilder builder) { return builder.transit(trip.pattern().debugInfo(), fromTime(), toTime()).sep() .stop(toStop()).sep(); } } private static class MyEgressLeg extends MyStreetLeg { MyEgressLeg(RaptorTransfer streetPath) { super(streetPath); } @Override public boolean isEgress() { return true; } @Override public int toStop() { throw new IllegalStateException("Egress leg have no toStop"); } @Override public PathStringBuilder addToString(PathStringBuilder builder) { return builder.accessEgress(streetPath); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy