diff --git a/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/SwissRailRaptorCore.java b/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/SwissRailRaptorCore.java index 4a84ee50c21..3778eebfaea 100644 --- a/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/SwissRailRaptorCore.java +++ b/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/SwissRailRaptorCore.java @@ -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; @@ -318,6 +322,9 @@ public List 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++) { @@ -502,13 +509,15 @@ public Map, 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; @@ -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); @@ -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; diff --git a/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/SwissRailRaptorData.java b/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/SwissRailRaptorData.java index 4813fafd95d..7ec137e6096 100644 --- a/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/SwissRailRaptorData.java +++ b/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/SwissRailRaptorData.java @@ -81,7 +81,7 @@ public class SwissRailRaptorData { final QuadTree stopsQT; final Map>> stopFilterAttribute2Value2StopsQT; final OccupancyData occupancyData; - + // data needed if cached transfer construction is activated final IdMap> staticTransferTimes; final RTransfer[][] transferCache; @@ -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][]; @@ -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 allTransfers; - + if (staticConfig.getTransferCalculation().equals(RaptorTransferCalculation.Initial)) { allTransfers = calculateRouteStopTransfers(schedule, stopsQT, routeStopsPerStopFacility, routeStops, staticConfig); @@ -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> staticTransferTimes = null; if (staticConfig.getTransferCalculation().equals(RaptorTransferCalculation.Cached)) { @@ -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(); @@ -327,7 +327,9 @@ private static Map 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); } @@ -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; @@ -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 @@ -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 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 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 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); @@ -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; diff --git a/matsim/src/main/java/org/matsim/pt/transitSchedule/Constants.java b/matsim/src/main/java/org/matsim/pt/transitSchedule/Constants.java index 88a55b790f0..8045d019c53 100644 --- a/matsim/src/main/java/org/matsim/pt/transitSchedule/Constants.java +++ b/matsim/src/main/java/org/matsim/pt/transitSchedule/Constants.java @@ -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"; diff --git a/matsim/src/main/java/org/matsim/pt/transitSchedule/TransitRouteStopImpl.java b/matsim/src/main/java/org/matsim/pt/transitSchedule/TransitRouteStopImpl.java index e9123814f35..4193e872511 100644 --- a/matsim/src/main/java/org/matsim/pt/transitSchedule/TransitRouteStopImpl.java +++ b/matsim/src/main/java/org/matsim/pt/transitSchedule/TransitRouteStopImpl.java @@ -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); } @@ -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; @@ -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; @@ -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; } @@ -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 +" ]"; @@ -120,6 +149,8 @@ public static final class Builder implements TransitRouteStop.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() { @@ -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; diff --git a/matsim/src/main/java/org/matsim/pt/transitSchedule/TransitScheduleReaderV2.java b/matsim/src/main/java/org/matsim/pt/transitSchedule/TransitScheduleReaderV2.java index 4c364538f3b..2ef65e449d3 100644 --- a/matsim/src/main/java/org/matsim/pt/transitSchedule/TransitScheduleReaderV2.java +++ b/matsim/src/main/java/org/matsim/pt/transitSchedule/TransitScheduleReaderV2.java @@ -175,6 +175,8 @@ public void startTag(final String name, final Attributes atts, final Stack 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); } diff --git a/matsim/src/main/java/org/matsim/pt/transitSchedule/api/TransitRouteStop.java b/matsim/src/main/java/org/matsim/pt/transitSchedule/api/TransitRouteStop.java index 108e52cb6db..dcc0e51d9cd 100644 --- a/matsim/src/main/java/org/matsim/pt/transitSchedule/api/TransitRouteStop.java +++ b/matsim/src/main/java/org/matsim/pt/transitSchedule/api/TransitRouteStop.java @@ -38,6 +38,16 @@ public interface TransitRouteStop { public abstract OptionalTime getArrivalOffset(); + /** @return true if agents are allowed to board the transit vehicle at this route stop. */ + boolean isAllowBoarding(); + + void setAllowBoarding(boolean allowBoarding); + + /** @return true 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 @@ -69,6 +79,10 @@ interface Builder> { Builder awaitDepartureTime(boolean val); + Builder allowBoarding(boolean val); + + Builder allowAlighting(boolean val); + TransitRouteStop build(); } -} \ No newline at end of file +} diff --git a/matsim/src/main/resources/dtd/transitSchedule_v2.dtd b/matsim/src/main/resources/dtd/transitSchedule_v2.dtd index cf3665e0679..5c030f2279c 100644 --- a/matsim/src/main/resources/dtd/transitSchedule_v2.dtd +++ b/matsim/src/main/resources/dtd/transitSchedule_v2.dtd @@ -54,6 +54,8 @@ refId CDATA #REQUIRED departureOffset CDATA #IMPLIED arrivalOffset CDATA #IMPLIED + allowBoarding (true|false) "true" + allowAlighting (true|false) "true" awaitDeparture (true|false) "false">