Skip to content

Commit

Permalink
Merge pull request #3330 from matsim-org/transit-schedule-enhancements
Browse files Browse the repository at this point in the history
Support TransitRouteStops where boarding or alighting is not allowed
  • Loading branch information
mrieser authored Jun 22, 2024
2 parents 5960389 + 2c5374a commit 1ac6e04
Show file tree
Hide file tree
Showing 9 changed files with 536 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,10 @@ public RaptorRoute calcLeastCostRoute(double depTime, Facility fromFacility, Fac
// if it's intermodal, we still start here, as we might transfer to another close-by but non-intermodal stop.
continue;
}
if (!routeStop.routeStop.isAllowBoarding()) {
continue;
}

RRoute route = this.data.routes[routeStop.transitRouteIndex];
int depOffset = routeStop.departureOffset;

Expand Down Expand Up @@ -318,6 +322,9 @@ public List<RaptorRoute> calcRoutes(double earliestDepTime, double desiredDepTim
// this is the last stop of a route
continue;
}
if (!routeStop.routeStop.isAllowBoarding()) {
continue;
}
RRoute route = this.data.routes[routeStop.transitRouteIndex];
int depOffset = routeStop.departureOffset;
for (int depIndex = route.indexFirstDeparture; depIndex < route.indexFirstDeparture + route.countDepartures; depIndex++) {
Expand Down Expand Up @@ -502,13 +509,15 @@ public Map<Id<TransitStopFacility>, TravelInfo> calcLeastCostTree(double depTime
int[] routeStopIndices = this.data.routeStopsPerStopFacility.get(stop.stop);
for (int routeStopIndex : routeStopIndices) {
boolean useStop = true;
if (parameters.isExactDeparturesOnly()) {
RRouteStop routeStop = this.data.routeStops[routeStopIndex];
RRouteStop routeStop = this.data.routeStops[routeStopIndex];
if (!routeStop.routeStop.isAllowBoarding()) {
useStop = false;
}
if (useStop && parameters.isExactDeparturesOnly()) {
int routeIndex = routeStop.transitRouteIndex;
RRoute route = this.data.routes[routeIndex];
int currentDepartureIndex = findNextDepartureIndex(route, routeStop, (int) depTime);
if (currentDepartureIndex >= 0) {
Vehicle currentVehicle = this.data.departureVehicles[currentDepartureIndex];
int firstDepartureTime = this.data.departures[currentDepartureIndex];
int stopDepartureTime = firstDepartureTime + routeStop.departureOffset;
useStop = Math.abs(depTime - stopDepartureTime) < 1e-5;
Expand Down Expand Up @@ -692,8 +701,11 @@ private void exploreRoutes(RaptorParameters parameters, Person person, CachingTr
transferProvider.reset(boardingPE.transfer);

for (int toRouteStopIndex = firstRouteStopIndex + 1; toRouteStopIndex < route.indexFirstRouteStop + route.countRouteStops; toRouteStopIndex++) {
routeSegmentIterator.reset(currentDepartureIndex, currentAgentBoardingTime, currentBoardingRouteStopIndex, toRouteStopIndex);
RRouteStop toRouteStop = this.data.routeStops[toRouteStopIndex];
if (!toRouteStop.routeStop.isAllowAlighting()) {
continue;
}
this.routeSegmentIterator.reset(currentDepartureIndex, currentAgentBoardingTime, currentBoardingRouteStopIndex, toRouteStopIndex);
int arrivalTime = currentDepartureTime + toRouteStop.arrivalOffset;
int inVehicleTime = arrivalTime - currentAgentBoardingTime;
double inVehicleCost = this.inVehicleCostCalculator.getInVehicleCost(inVehicleTime, marginalUtilityOfTravelTime_utl_s, person, currentVehicle, parameters, routeSegmentIterator);
Expand Down Expand Up @@ -814,17 +826,17 @@ private void handleTransfers(boolean strict, RaptorParameters raptorParams, Cach
continue;
}
RRouteStop fromRouteStop = fromPE.toRouteStop; // this is the route stop we arrive with least cost at stop

// obtain on-demand transfers if applicable (will return null if transfers are calculated initially)
RTransfer[] transfers = this.data.calculateTransfers(fromRouteStop);

int firstTransferIndex = transfers == null ? fromRouteStop.indexFirstTransfer : 0;
int lastTransferIndex = transfers == null ? firstTransferIndex + fromRouteStop.countTransfers : transfers.length;
transfers = transfers == null ? this.data.transfers : transfers;

for (int transferIndex = firstTransferIndex; transferIndex < lastTransferIndex; transferIndex++) {
RTransfer transfer = transfers[transferIndex];

int toRouteStopIndex = transfer.toRouteStop;
transferProvider.reset(transfer);
int newArrivalTime = arrivalTime + transfer.transferTime;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public class SwissRailRaptorData {
final QuadTree<TransitStopFacility> stopsQT;
final Map<String, Map<String, QuadTree<TransitStopFacility>>> stopFilterAttribute2Value2StopsQT;
final OccupancyData occupancyData;

// data needed if cached transfer construction is activated
final IdMap<TransitStopFacility, Map<TransitStopFacility, Double>> staticTransferTimes;
final RTransfer[][] transferCache;
Expand All @@ -105,7 +105,7 @@ private SwissRailRaptorData(RaptorStaticConfig config, int countStops,
this.stopsQT = stopsQT;
this.stopFilterAttribute2Value2StopsQT = new HashMap<>();
this.occupancyData = occupancyData;

// data needed if cached transfer construction is activated
this.staticTransferTimes = staticTransferTimes;
this.transferCache = new RTransfer[routeStops.length][];
Expand Down Expand Up @@ -216,7 +216,7 @@ public static SwissRailRaptorData create(TransitSchedule schedule, @Nullable Veh

// if cached transfer calculation is active, don't generate any transfers here
final Map<Integer, RTransfer[]> allTransfers;

if (staticConfig.getTransferCalculation().equals(RaptorTransferCalculation.Initial)) {
allTransfers = calculateRouteStopTransfers(schedule, stopsQT, routeStopsPerStopFacility, routeStops,
staticConfig);
Expand Down Expand Up @@ -244,7 +244,7 @@ public static SwissRailRaptorData create(TransitSchedule schedule, @Nullable Veh
indexTransfer += transferCount;
}
}

// if cached transfer calculation is used, build a map for quick lookup of minimal transfer times
IdMap<TransitStopFacility, Map<TransitStopFacility, Double>> staticTransferTimes = null;
if (staticConfig.getTransferCalculation().equals(RaptorTransferCalculation.Cached)) {
Expand All @@ -258,7 +258,7 @@ public static SwissRailRaptorData create(TransitSchedule schedule, @Nullable Veh
.put(schedule.getFacilities().get(iterator.getToStopId()), iterator.getSeconds());
}
}

SwissRailRaptorData data = new SwissRailRaptorData(staticConfig, countStopFacilities, routes, departures, departureVehicles, departureIds, routeStops, transfers, stopFacilityIndices, routeStopsPerStopFacility, stopsQT, occupancyData, staticTransferTimes);

long endMillis = System.currentTimeMillis();
Expand Down Expand Up @@ -327,7 +327,9 @@ private static Map<Integer, RTransfer[]> calculateRouteStopTransfers(TransitSche
stopTransfers.clear();
for (int toRouteStopIndex : toRouteStopIndices) {
RRouteStop toRouteStop = routeStops[toRouteStopIndex];
if (isUsefulTransfer(fromRouteStop, toRouteStop, maxBeelineWalkConnectionDistance, config.getOptimization())) {
if (isUsefulTransfer(fromRouteStop, toRouteStop, maxBeelineWalkConnectionDistance, config.getOptimization())
&& isTransferAllowed(fromRouteStop, toRouteStop)
) {
RTransfer newTransfer = new RTransfer(fromRouteStopIndex, toRouteStopIndex, fixedTransferTime, beelineDistance * beelineDistanceFactor);
stopTransfers.add(newTransfer);
}
Expand Down Expand Up @@ -388,6 +390,10 @@ private static boolean isUsefulTransfer(RRouteStop fromRouteStop, RRouteStop toR
return true;
}

private static boolean isTransferAllowed(RRouteStop fromRouteStop, RRouteStop toRouteStop) {
return fromRouteStop.routeStop.isAllowAlighting() && toRouteStop.routeStop.isAllowBoarding();
}

private static boolean isFirstStopInRoute(RRouteStop routeStop) {
TransitRouteStop firstRouteStop = routeStop.route.getStops().get(0);
return routeStop.routeStop == firstRouteStop;
Expand Down Expand Up @@ -641,12 +647,12 @@ public Transfer get() {
return this.transfer;
}
}

RTransfer[] calculateTransfers(RRouteStop fromRouteStop) {
if (config.getTransferCalculation().equals(RaptorTransferCalculation.Initial)) {
return null;
}

// We tested this in a parallel set-up and things seem to work as they are
// implemented. The routing threads will access the cache as read-only an
// retrieve the cached stop connections. It can happen that two of them try to
Expand All @@ -661,41 +667,41 @@ RTransfer[] calculateTransfers(RRouteStop fromRouteStop) {

RTransfer[] cache = transferCache[fromRouteStop.index];
if (cache != null) return cache; // we had a cache hit

// setting up useful constants
final double minimalTransferTime = config.getMinimalTransferTime();
final double beelineWalkConnectionDistance = config.getBeelineWalkConnectionDistance();
final double beelineDistanceFactor = config.getBeelineWalkDistanceFactor();
final double beelineWalkSpeed = config.getBeelineWalkSpeed();
final RaptorOptimization optimization = config.getOptimization();

// the facility from which we want to transfer
TransitStopFacility fromRouteFacility = fromRouteStop.routeStop.getStopFacility();
Collection<TransitStopFacility> transferCandidates = new LinkedList<>();

// find transfer candidates by distance
transferCandidates.addAll(stopsQT.getDisk(fromRouteFacility.getCoord().getX(), fromRouteFacility.getCoord().getY(), config.getBeelineWalkConnectionDistance()));

// find transfer candidates with predefined transfer time
Map<TransitStopFacility, Double> transferTimes = staticTransferTimes.get(fromRouteFacility.getId());

if (transferTimes != null) { // some transfer times are predefined
transferCandidates.addAll(transferTimes.keySet());
}

// now evaluate whether transfers are useful, distance, and travel time
List<RTransfer> transfers = new LinkedList<>();
for (TransitStopFacility toRouteFacility : transferCandidates) {
for (int toRouteStopIndex : routeStopsPerStopFacility.get(toRouteFacility)) {
RRouteStop toRouteStop = routeStops[toRouteStopIndex];

double beelineDistance = CoordUtils.calcEuclideanDistance(fromRouteFacility.getCoord(), toRouteFacility.getCoord());
double transferTime = beelineDistance / beelineWalkSpeed;

if (transferTime < minimalTransferTime) {
transferTime = minimalTransferTime;
}

if (transferTimes != null) {
// check if we find a predefined transfer time
transferTime = transferTimes.getOrDefault(toRouteFacility, transferTime);
Expand All @@ -706,10 +712,10 @@ RTransfer[] calculateTransfers(RRouteStop fromRouteStop) {
}
}
}

// convert to array
RTransfer[] stopTransfers = transfers.toArray(new RTransfer[transfers.size()]);

// save to cache (no issue regarding parallel execution because we simply set an element)
transferCache[fromRouteStop.index] = stopTransfers;
return stopTransfers;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ abstract class Constants {
static final String VEHICLE_REF_ID = "vehicleRefId";
static final String DEPARTURE_OFFSET = "departureOffset";
static final String ARRIVAL_OFFSET = "arrivalOffset";
static final String ALLOW_BOARDING = "allowBoarding";
static final String ALLOW_ALIGHTING = "allowAlighting";
static final String AWAIT_DEPARTURE = "awaitDeparture";
static final String IS_BLOCKING = "isBlocking";
static final String STOP_AREA_ID = "stopAreaId";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,15 @@ public class TransitRouteStopImpl implements TransitRouteStop {
private final OptionalTime departureOffset;
private final OptionalTime arrivalOffset;
private boolean awaitDepartureTime = false;
private boolean allowBoarding = true;
private boolean allowAlighting = true;

private TransitRouteStopImpl(Builder builder) {
stop = builder.stop;
departureOffset = builder.departureOffset;
arrivalOffset = builder.arrivalOffset;
this.stop = builder.stop;
this.departureOffset = builder.departureOffset;
this.arrivalOffset = builder.arrivalOffset;
this.allowBoarding = builder.allowBoarding;
this.allowAlighting = builder.allowAlighting;
setAwaitDepartureTime(builder.awaitDepartureTime);
}

Expand All @@ -65,6 +69,26 @@ public OptionalTime getArrivalOffset() {
return this.arrivalOffset;
}

@Override
public boolean isAllowBoarding() {
return this.allowBoarding;
}

@Override
public void setAllowBoarding(boolean allowBoarding) {
this.allowBoarding = allowBoarding;
}

@Override
public boolean isAllowAlighting() {
return this.allowAlighting;
}

@Override
public void setAllowAlighting(boolean allowAlighting) {
this.allowAlighting = allowAlighting;
}

@Override
public boolean isAwaitDepartureTime() {
return this.awaitDepartureTime;
Expand All @@ -77,14 +101,13 @@ public void setAwaitDepartureTime(final boolean awaitDepartureTime) {

/**
* TransitRouteStops are typical Value Objects, so we consider two stops equal if they are equal field-wise.
*
*
*/
@Override
public boolean equals(Object obj) {
if (!(obj instanceof TransitRouteStopImpl)) {
if (!(obj instanceof TransitRouteStopImpl other)) {
return false;
}
TransitRouteStopImpl other = (TransitRouteStopImpl) obj;
if (this.stop == null) {
if (other.getStopFacility() != null) {
return false;
Expand All @@ -96,10 +119,16 @@ public boolean equals(Object obj) {
}
if (!this.departureOffset.equals(other.getDepartureOffset())) {
return false;
}
}
if (!this.arrivalOffset.equals(other.getArrivalOffset())) {
return false;
}
if (this.allowBoarding != other.allowBoarding) {
return false;
}
if (this.allowAlighting != other.allowAlighting) {
return false;
}
if (this.awaitDepartureTime != other.isAwaitDepartureTime()) {
return false;
}
Expand All @@ -110,7 +139,7 @@ public boolean equals(Object obj) {
public int hashCode() {
return stop.hashCode();
}

@Override
public String toString() {
return "[TransitRouteStop stop=" + this.stop.getId() + " offset=" + this.departureOffset +" ]";
Expand All @@ -120,6 +149,8 @@ public static final class Builder implements TransitRouteStop.Builder<Builder> {
private TransitStopFacility stop;
private OptionalTime departureOffset = OptionalTime.undefined();
private OptionalTime arrivalOffset = OptionalTime.undefined();
private boolean allowBoarding = true;
private boolean allowAlighting = true;
private boolean awaitDepartureTime;

public Builder() {
Expand Down Expand Up @@ -157,6 +188,16 @@ public Builder arrivalOffset(OptionalTime val) {
return this;
}

public Builder allowBoarding(boolean allowBoarding) {
this.allowBoarding = allowBoarding;
return this;
}

public Builder allowAlighting(boolean allowAlighting) {
this.allowAlighting = allowAlighting;
return this;
}

public Builder awaitDepartureTime(boolean val) {
awaitDepartureTime = val;
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ public void startTag(final String name, final Attributes atts, final Stack<Strin
if (departure != null) {
Time.parseOptionalTime(departure).ifDefined(stopBuilder::departureOffset);
}
stopBuilder.allowBoarding(Boolean.parseBoolean(atts.getValue(Constants.ALLOW_BOARDING)));
stopBuilder.allowAlighting(Boolean.parseBoolean(atts.getValue(Constants.ALLOW_ALIGHTING)));
stopBuilder.awaitDepartureTime(Boolean.parseBoolean(atts.getValue(Constants.AWAIT_DEPARTURE)));
this.currentTransitRoute.stopBuilders.add(stopBuilder);
} else if (Constants.RELATION.equals(name)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,13 @@ private void writeRouteProfile(final List<TransitRouteStop> stops) throws Unchec
.ifDefined(offset -> attributes.add(createTimeTuple(Constants.ARRIVAL_OFFSET, offset)));
stop.getDepartureOffset().ifDefined(offset->
attributes.add(createTimeTuple(Constants.DEPARTURE_OFFSET, offset)));
// do not write out if it is true ==> the default value
if (!stop.isAllowBoarding()) {
attributes.add(createTuple(Constants.ALLOW_BOARDING, String.valueOf(stop.isAllowBoarding())));
}
if (!stop.isAllowAlighting()) {
attributes.add(createTuple(Constants.ALLOW_ALIGHTING, String.valueOf(stop.isAllowAlighting())));
}
attributes.add(createTuple(Constants.AWAIT_DEPARTURE, String.valueOf(stop.isAwaitDepartureTime())));
this.writeStartTag(Constants.STOP, attributes, true);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ public interface TransitRouteStop {

public abstract OptionalTime getArrivalOffset();

/** @return <code>true</code> if agents are allowed to board the transit vehicle at this route stop. */
boolean isAllowBoarding();

void setAllowBoarding(boolean allowBoarding);

/** @return <code>true</code> if agents are allowed to exit the transit vehicle at this route stop. */
boolean isAllowAlighting();

void setAllowAlighting(boolean allowAlighting);

/**
* Specifies if a driver should wait until the specified departure time
* has come before departing, especially if the driver is too early at
Expand Down Expand Up @@ -69,6 +79,10 @@ interface Builder<B extends Builder<B>> {

Builder<B> awaitDepartureTime(boolean val);

Builder<B> allowBoarding(boolean val);

Builder<B> allowAlighting(boolean val);

TransitRouteStop build();
}
}
}
Loading

0 comments on commit 1ac6e04

Please sign in to comment.