getSkipEdgeStrategy(
if (maxStopCount > 0) {
var strategy = new MaxCountSkipEdgeStrategy<>(
maxStopCount,
- NearbyStopFinder::isTransitVertex
+ NearbyStopFinder::hasReachedStop
);
return new ComposingSkipEdgeStrategy<>(strategy, durationSkipEdgeStrategy);
}
@@ -360,15 +353,23 @@ private boolean canBoardFlex(State state, boolean reverse) {
return edges
.stream()
- .anyMatch(e ->
- e instanceof StreetEdge && ((StreetEdge) e).getPermission().allows(TraverseMode.CAR)
- );
+ .anyMatch(e -> e instanceof StreetEdge se && se.getPermission().allows(TraverseMode.CAR));
}
/**
- * Checks if the {@code state} as at a transit vertex.
+ * Checks if the {@code state} is at a transit vertex and if it's final, which means that the state
+ * can actually board a vehicle.
+ *
+ * This is important because there can be cases where states that cannot actually board the vehicle
+ * can dominate those that can thereby leading to zero found stops when this predicate is used with
+ * the {@link MaxCountSkipEdgeStrategy}.
+ *
+ * An example of this would be an egress/reverse search with a very high walk reluctance where
+ * the states that speculatively rent a vehicle move the walk states down the A* priority queue
+ * until the required number of stops are reached to abort the search, leading to zero egress
+ * results.
*/
- public static boolean isTransitVertex(State state) {
- return state.getVertex() instanceof TransitStopVertex;
+ public static boolean hasReachedStop(State state) {
+ return state.getVertex() instanceof TransitStopVertex && state.isFinal();
}
}
diff --git a/src/main/java/org/opentripplanner/graph_builder/module/islandpruning/PruneIslands.java b/src/main/java/org/opentripplanner/graph_builder/module/islandpruning/PruneIslands.java
index d3024a144f5..00404845349 100644
--- a/src/main/java/org/opentripplanner/graph_builder/module/islandpruning/PruneIslands.java
+++ b/src/main/java/org/opentripplanner/graph_builder/module/islandpruning/PruneIslands.java
@@ -25,10 +25,7 @@
import org.opentripplanner.street.model.edge.AreaEdge;
import org.opentripplanner.street.model.edge.AreaEdgeList;
import org.opentripplanner.street.model.edge.Edge;
-import org.opentripplanner.street.model.edge.ElevatorEdge;
-import org.opentripplanner.street.model.edge.FreeEdge;
import org.opentripplanner.street.model.edge.StreetEdge;
-import org.opentripplanner.street.model.edge.StreetTransitEntityLink;
import org.opentripplanner.street.model.vertex.StreetVertex;
import org.opentripplanner.street.model.vertex.TransitStopVertex;
import org.opentripplanner.street.model.vertex.Vertex;
@@ -338,16 +335,6 @@ private void collectNeighbourVertices(
}
State s0 = new State(gv, request);
for (Edge e : gv.getOutgoing()) {
- if (
- !(
- e instanceof StreetEdge ||
- e instanceof ElevatorEdge ||
- e instanceof FreeEdge ||
- e instanceof StreetTransitEntityLink
- )
- ) {
- continue;
- }
if (
e instanceof StreetEdge &&
shouldMatchNoThruType != ((StreetEdge) e).isNoThruTraffic(traverseMode)
diff --git a/src/main/java/org/opentripplanner/inspector/vector/AreaStopsLayerBuilder.java b/src/main/java/org/opentripplanner/inspector/vector/AreaStopsLayerBuilder.java
index 1604cf7d8d1..5e4539e1f5c 100644
--- a/src/main/java/org/opentripplanner/inspector/vector/AreaStopsLayerBuilder.java
+++ b/src/main/java/org/opentripplanner/inspector/vector/AreaStopsLayerBuilder.java
@@ -7,7 +7,7 @@
import java.util.function.Function;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
-import org.opentripplanner.api.mapping.PropertyMapper;
+import org.opentripplanner.apis.support.mapping.PropertyMapper;
import org.opentripplanner.transit.model.site.AreaStop;
import org.opentripplanner.transit.service.TransitService;
diff --git a/src/main/java/org/opentripplanner/inspector/vector/DebugClientAreaStopPropertyMapper.java b/src/main/java/org/opentripplanner/inspector/vector/DebugClientAreaStopPropertyMapper.java
index 88f7a17385b..63c58dd9b05 100644
--- a/src/main/java/org/opentripplanner/inspector/vector/DebugClientAreaStopPropertyMapper.java
+++ b/src/main/java/org/opentripplanner/inspector/vector/DebugClientAreaStopPropertyMapper.java
@@ -3,8 +3,8 @@
import java.util.Collection;
import java.util.List;
import java.util.Locale;
-import org.opentripplanner.api.mapping.I18NStringMapper;
-import org.opentripplanner.api.mapping.PropertyMapper;
+import org.opentripplanner.apis.support.mapping.PropertyMapper;
+import org.opentripplanner.framework.i18n.I18NStringMapper;
import org.opentripplanner.transit.model.site.AreaStop;
import org.opentripplanner.transit.service.TransitService;
diff --git a/src/main/java/org/opentripplanner/inspector/vector/LayerBuilder.java b/src/main/java/org/opentripplanner/inspector/vector/LayerBuilder.java
index 3e796487271..949bbef0a94 100644
--- a/src/main/java/org/opentripplanner/inspector/vector/LayerBuilder.java
+++ b/src/main/java/org/opentripplanner/inspector/vector/LayerBuilder.java
@@ -10,7 +10,7 @@
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
-import org.opentripplanner.api.mapping.PropertyMapper;
+import org.opentripplanner.apis.support.mapping.PropertyMapper;
import org.opentripplanner.framework.geometry.GeometryUtils;
/**
diff --git a/src/main/java/org/opentripplanner/inspector/vector/LayerParameters.java b/src/main/java/org/opentripplanner/inspector/vector/LayerParameters.java
index 2718c649797..ca4a6a64c76 100644
--- a/src/main/java/org/opentripplanner/inspector/vector/LayerParameters.java
+++ b/src/main/java/org/opentripplanner/inspector/vector/LayerParameters.java
@@ -1,6 +1,6 @@
package org.opentripplanner.inspector.vector;
-import org.opentripplanner.api.mapping.PropertyMapper;
+import org.opentripplanner.apis.support.mapping.PropertyMapper;
/**
* Configuration options for a single vector tile layer.
diff --git a/src/main/java/org/opentripplanner/inspector/vector/geofencing/GeofencingZonesLayerBuilder.java b/src/main/java/org/opentripplanner/inspector/vector/geofencing/GeofencingZonesLayerBuilder.java
index 8a77b8502ea..1764451cc89 100644
--- a/src/main/java/org/opentripplanner/inspector/vector/geofencing/GeofencingZonesLayerBuilder.java
+++ b/src/main/java/org/opentripplanner/inspector/vector/geofencing/GeofencingZonesLayerBuilder.java
@@ -4,7 +4,7 @@
import java.util.Map;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
-import org.opentripplanner.api.mapping.PropertyMapper;
+import org.opentripplanner.apis.support.mapping.PropertyMapper;
import org.opentripplanner.framework.geometry.GeometryUtils;
import org.opentripplanner.inspector.vector.LayerBuilder;
import org.opentripplanner.inspector.vector.LayerParameters;
diff --git a/src/main/java/org/opentripplanner/inspector/vector/geofencing/GeofencingZonesPropertyMapper.java b/src/main/java/org/opentripplanner/inspector/vector/geofencing/GeofencingZonesPropertyMapper.java
index 0d0f7b44fe9..98c9cf23eca 100644
--- a/src/main/java/org/opentripplanner/inspector/vector/geofencing/GeofencingZonesPropertyMapper.java
+++ b/src/main/java/org/opentripplanner/inspector/vector/geofencing/GeofencingZonesPropertyMapper.java
@@ -6,7 +6,7 @@
import java.util.Collection;
import java.util.List;
-import org.opentripplanner.api.mapping.PropertyMapper;
+import org.opentripplanner.apis.support.mapping.PropertyMapper;
import org.opentripplanner.inspector.vector.KeyValue;
import org.opentripplanner.street.model.vertex.Vertex;
diff --git a/src/main/java/org/opentripplanner/model/plan/Itinerary.java b/src/main/java/org/opentripplanner/model/plan/Itinerary.java
index 58320bf1652..d1252c45f2d 100644
--- a/src/main/java/org/opentripplanner/model/plan/Itinerary.java
+++ b/src/main/java/org/opentripplanner/model/plan/Itinerary.java
@@ -17,6 +17,7 @@
import org.opentripplanner.framework.tostring.ToStringBuilder;
import org.opentripplanner.model.SystemNotice;
import org.opentripplanner.model.fare.ItineraryFares;
+import org.opentripplanner.raptor.api.model.RaptorConstants;
import org.opentripplanner.raptor.api.path.PathStringBuilder;
import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.RaptorCostConverter;
import org.opentripplanner.routing.api.request.RouteRequest;
@@ -43,6 +44,7 @@ public class Itinerary implements ItinerarySortKey {
private Double elevationLost = 0.0;
private Double elevationGained = 0.0;
private int generalizedCost = UNKNOWN;
+ private Integer generalizedCost2 = null;
private TimeAndCost accessPenalty = null;
private TimeAndCost egressPenalty = null;
private int waitTimeOptimizedCost = UNKNOWN;
@@ -260,6 +262,7 @@ public String toString() {
.addDuration("transitTime", transitDuration)
.addDuration("waitingTime", waitingDuration)
.addNum("generalizedCost", generalizedCost, UNKNOWN)
+ .addNum("generalizedCost2", generalizedCost2)
.addNum("waitTimeOptimizedCost", waitTimeOptimizedCost, UNKNOWN)
.addNum("transferPriorityCost", transferPriorityCost, UNKNOWN)
.addNum("nonTransitDistance", nonTransitDistanceMeters, "m")
@@ -306,7 +309,12 @@ public String toStr() {
buf.stop(leg.getTo().name.toString());
}
- buf.summary(RaptorCostConverter.toRaptorCost(generalizedCost));
+ // The generalizedCost2 is printed as is, it is a special cost and the scale depends on the
+ // use-case.
+ buf.summary(
+ RaptorCostConverter.toRaptorCost(generalizedCost),
+ getGeneralizedCost2().orElse(RaptorConstants.NOT_SET)
+ );
return buf.toString();
}
@@ -495,6 +503,24 @@ public void setGeneralizedCost(int generalizedCost) {
this.generalizedCost = generalizedCost;
}
+ /**
+ * The transit router allows the usage of a second generalized-cost parameter to be used in
+ * routing. In Raptor this is called c2, but in OTP it is generalized-cost-2. What this cost
+ * represents depends on the use-case and the unit and scale is also given by the use-case.
+ *
+ * Currently, the pass-through search and the transit-priority uses this. This is relevant for
+ * anyone who wants to debug a search and tune the system.
+ *
+ * {@link RaptorConstants#NOT_SET} indicate that the cost is not set/computed.
+ */
+ public Optional getGeneralizedCost2() {
+ return Optional.ofNullable(generalizedCost2);
+ }
+
+ public void setGeneralizedCost2(Integer generalizedCost2) {
+ this.generalizedCost2 = generalizedCost2;
+ }
+
@Nullable
public TimeAndCost getAccessPenalty() {
return accessPenalty;
diff --git a/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java b/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java
index 26016947c09..8fc30ae166a 100644
--- a/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java
+++ b/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java
@@ -11,6 +11,7 @@
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
+import java.util.stream.Collectors;
import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore;
import org.opentripplanner.model.StopTime;
import org.opentripplanner.model.calendar.ServiceCalendar;
@@ -460,19 +461,36 @@ private void mapTripPatterns(Map serviceIds) {
for (JourneyPattern_VersionStructure journeyPattern : currentNetexIndex
.getJourneyPatternsById()
.localValues()) {
- TripPatternMapperResult result = tripPatternMapper.mapTripPattern(journeyPattern);
+ tripPatternMapper
+ .mapTripPattern(journeyPattern)
+ .ifPresent(this::applyTripPatternMapperResult);
+ }
+ }
- for (Map.Entry> it : result.tripStopTimes.entrySet()) {
- transitBuilder.getStopTimesSortedByTrip().put(it.getKey(), it.getValue());
- transitBuilder.getTripsById().add(it.getKey());
- }
- for (var it : result.tripPatterns.entries()) {
- transitBuilder.getTripPatterns().put(it.getKey(), it.getValue());
- }
- currentMapperIndexes.addStopTimesByNetexId(result.stopTimeByNetexId);
- groupMapper.scheduledStopPointsIndex.putAll(Multimaps.asMap(result.scheduledStopPointsIndex));
- transitBuilder.getTripOnServiceDates().addAll(result.tripOnServiceDates);
+ private void applyTripPatternMapperResult(TripPatternMapperResult result) {
+ var stopPattern = result.tripPattern().getStopPattern();
+ var journeyPatternExists = transitBuilder
+ .getTripPatterns()
+ .get(stopPattern)
+ .stream()
+ .anyMatch(tripPattern -> result.tripPattern().getId().equals(tripPattern.getId()));
+ if (journeyPatternExists) {
+ issueStore.add(
+ "DuplicateJourneyPattern",
+ "Duplicate of JourneyPattern %s found",
+ result.tripPattern().getId().getId()
+ );
+ }
+
+ for (Map.Entry> it : result.tripStopTimes().entrySet()) {
+ transitBuilder.getStopTimesSortedByTrip().put(it.getKey(), it.getValue());
+ transitBuilder.getTripsById().add(it.getKey());
}
+
+ transitBuilder.getTripPatterns().put(stopPattern, result.tripPattern());
+ currentMapperIndexes.addStopTimesByNetexId(result.stopTimeByNetexId());
+ groupMapper.scheduledStopPointsIndex.putAll(Multimaps.asMap(result.scheduledStopPointsIndex()));
+ transitBuilder.getTripOnServiceDates().addAll(result.tripOnServiceDates());
}
private void mapNoticeAssignments() {
diff --git a/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java b/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java
index 6fa000c1049..f0d06b8871d 100644
--- a/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java
+++ b/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java
@@ -5,10 +5,12 @@
import jakarta.xml.bind.JAXBElement;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.stream.Collectors;
import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore;
import org.opentripplanner.model.StopTime;
@@ -84,8 +86,6 @@ class TripPatternMapper {
private final Deduplicator deduplicator;
- private TripPatternMapperResult result;
-
TripPatternMapper(
DataImportIssueStore issueStore,
FeedScopedIdFactory idFactory,
@@ -157,9 +157,7 @@ class TripPatternMapper {
}
}
- TripPatternMapperResult mapTripPattern(JourneyPattern_VersionStructure journeyPattern) {
- // Make sure the result is clean, by creating a new object.
- result = new TripPatternMapperResult();
+ Optional mapTripPattern(JourneyPattern_VersionStructure journeyPattern) {
Collection serviceJourneys = serviceJourniesByPatternId.get(
journeyPattern.getId()
);
@@ -170,10 +168,14 @@ TripPatternMapperResult mapTripPattern(JourneyPattern_VersionStructure journeyPa
"ServiceJourneyPattern %s does not contain any serviceJourneys.",
journeyPattern.getId()
);
- return result;
+ return Optional.empty();
}
List trips = new ArrayList<>();
+ ArrayListMultimap scheduledStopPointsIndex = ArrayListMultimap.create();
+ HashMap> tripStopTimes = new HashMap<>();
+ Map stopTimeByNetexId = new HashMap<>();
+ ArrayList tripOnServiceDates = new ArrayList<>();
for (ServiceJourney serviceJourney : serviceJourneys) {
Trip trip = mapTrip(journeyPattern, serviceJourney);
@@ -184,7 +186,7 @@ TripPatternMapperResult mapTripPattern(JourneyPattern_VersionStructure journeyPa
}
// Add the dated service journey to the model for this trip [if it exists]
- mapDatedServiceJourney(journeyPattern, serviceJourney, trip);
+ tripOnServiceDates.addAll(mapDatedServiceJourney(journeyPattern, serviceJourney, trip));
StopTimesMapperResult stopTimes = stopTimesMapper.mapToStopTimes(
journeyPattern,
@@ -198,25 +200,22 @@ TripPatternMapperResult mapTripPattern(JourneyPattern_VersionStructure journeyPa
continue;
}
- result.scheduledStopPointsIndex.putAll(
- serviceJourney.getId(),
- stopTimes.scheduledStopPointIds
- );
- result.tripStopTimes.put(trip, stopTimes.stopTimes);
- result.stopTimeByNetexId.putAll(stopTimes.stopTimeByNetexId);
+ scheduledStopPointsIndex.putAll(serviceJourney.getId(), stopTimes.scheduledStopPointIds);
+ tripStopTimes.put(trip, stopTimes.stopTimes);
+ stopTimeByNetexId.putAll(stopTimes.stopTimeByNetexId);
trips.add(trip);
}
// No trips successfully mapped
if (trips.isEmpty()) {
- return result;
+ return Optional.empty();
}
// Create StopPattern from any trip (since they are part of the same JourneyPattern)
StopPattern stopPattern = deduplicator.deduplicateObject(
StopPattern.class,
- new StopPattern(result.tripStopTimes.get(trips.get(0)))
+ new StopPattern(tripStopTimes.get(trips.get(0)))
);
var tripPatternModes = new HashSet();
@@ -246,7 +245,7 @@ TripPatternMapperResult mapTripPattern(JourneyPattern_VersionStructure journeyPa
);
}
- TripPatternBuilder tripPatternBuilder = TripPattern
+ var tripPattern = TripPattern
.of(idFactory.createId(journeyPattern.getId()))
.withRoute(lookupRoute(journeyPattern))
.withStopPattern(stopPattern)
@@ -256,30 +255,35 @@ TripPatternMapperResult mapTripPattern(JourneyPattern_VersionStructure journeyPa
.withName(journeyPattern.getName() == null ? "" : journeyPattern.getName().getValue())
.withHopGeometries(
serviceLinkMapper.getGeometriesByJourneyPattern(journeyPattern, stopPattern)
- );
-
- TripPattern tripPattern = tripPatternBuilder.build();
- createTripTimes(trips, tripPattern);
-
- result.tripPatterns.put(stopPattern, tripPattern);
-
- return result;
+ )
+ .build();
+ createTripTimes(trips, tripStopTimes).forEach(tripPattern::add);
+
+ return Optional.of(
+ new TripPatternMapperResult(
+ tripPattern,
+ scheduledStopPointsIndex,
+ tripStopTimes,
+ stopTimeByNetexId,
+ tripOnServiceDates
+ )
+ );
}
- private void mapDatedServiceJourney(
+ private ArrayList mapDatedServiceJourney(
JourneyPattern_VersionStructure journeyPattern,
ServiceJourney serviceJourney,
Trip trip
) {
+ var tripsOnServiceDates = new ArrayList();
if (datedServiceJourneysBySJId.containsKey(serviceJourney.getId())) {
for (DatedServiceJourney datedServiceJourney : datedServiceJourneysBySJId.get(
serviceJourney.getId()
)) {
- result.tripOnServiceDates.add(
- mapDatedServiceJourney(journeyPattern, trip, datedServiceJourney)
- );
+ tripsOnServiceDates.add(mapDatedServiceJourney(journeyPattern, trip, datedServiceJourney));
}
}
+ return tripsOnServiceDates;
}
private TripOnServiceDate mapDatedServiceJourney(
@@ -360,9 +364,13 @@ private org.opentripplanner.transit.model.network.Route lookupRoute(
return otpRouteById.get(idFactory.createId(lineId));
}
- private void createTripTimes(List trips, TripPattern tripPattern) {
+ private List createTripTimes(
+ List trips,
+ Map> tripStopTimes
+ ) {
+ var tripTimesResult = new ArrayList();
for (Trip trip : trips) {
- List stopTimes = result.tripStopTimes.get(trip);
+ List stopTimes = tripStopTimes.get(trip);
if (stopTimes.isEmpty()) {
issueStore.add(
"TripWithoutTripTimes",
@@ -372,12 +380,13 @@ private void createTripTimes(List trips, TripPattern tripPattern) {
} else {
try {
TripTimes tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, deduplicator);
- tripPattern.add(tripTimes);
+ tripTimesResult.add(tripTimes);
} catch (DataValidationException e) {
issueStore.add(e.error());
}
}
}
+ return tripTimesResult;
}
private Trip mapTrip(
diff --git a/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapperResult.java b/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapperResult.java
index 6b14fea5d31..f2f75cd2561 100644
--- a/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapperResult.java
+++ b/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapperResult.java
@@ -13,24 +13,15 @@
import org.opentripplanner.transit.model.timetable.TripOnServiceDate;
/**
- * This mapper returnes two collections, so we need to use a simple wraper to be able to return the
- * result from the mapping method.
+ * Wrapper class for the result of TripPatternMapper
+ *
+ * @param scheduledStopPointsIndex A map from trip/serviceJourney id to an ordered list of scheduled stop point ids.
+ * @param stopTimeByNetexId stopTimes by the timetabled-passing-time id
*/
-class TripPatternMapperResult {
-
- /**
- * A map from trip/serviceJourney id to an ordered list of scheduled stop point ids.
- */
- final ArrayListMultimap scheduledStopPointsIndex = ArrayListMultimap.create();
-
- final Map> tripStopTimes = new HashMap<>();
-
- final Multimap tripPatterns = ArrayListMultimap.create();
-
- /**
- * stopTimes by the timetabled-passing-time id
- */
- final Map stopTimeByNetexId = new HashMap<>();
-
- final ArrayList tripOnServiceDates = new ArrayList<>();
-}
+record TripPatternMapperResult(
+ TripPattern tripPattern,
+ ArrayListMultimap scheduledStopPointsIndex,
+ Map> tripStopTimes,
+ Map stopTimeByNetexId,
+ ArrayList tripOnServiceDates
+) {}
diff --git a/src/main/java/org/opentripplanner/raptor/RaptorService.java b/src/main/java/org/opentripplanner/raptor/RaptorService.java
index 450f993de1c..d8e7fcd3dcd 100644
--- a/src/main/java/org/opentripplanner/raptor/RaptorService.java
+++ b/src/main/java/org/opentripplanner/raptor/RaptorService.java
@@ -71,7 +71,7 @@ private RaptorResponse routeUsingStdWorker(
var worker = config.createStdWorker(transitData, request);
var result = worker.route();
var arrivals = new DefaultStopArrivals(result);
- return new RaptorResponse<>(result.extractPaths(), arrivals, request, request);
+ return new RaptorResponse<>(result.extractPaths(), arrivals, request, false);
}
private static void logRequest(RaptorRequest request) {
diff --git a/src/main/java/org/opentripplanner/raptor/api/path/PathStringBuilder.java b/src/main/java/org/opentripplanner/raptor/api/path/PathStringBuilder.java
index 4cf13e362fd..b96d1a96f14 100644
--- a/src/main/java/org/opentripplanner/raptor/api/path/PathStringBuilder.java
+++ b/src/main/java/org/opentripplanner/raptor/api/path/PathStringBuilder.java
@@ -119,8 +119,8 @@ public PathStringBuilder numberOfTransfers(int nTransfers) {
: this;
}
- public PathStringBuilder summary(int c1) {
- return summaryStart().c1(c1).summaryEnd();
+ public PathStringBuilder summary(int c1, int c2) {
+ return summaryStart().c1(c1).c2(c2).summaryEnd();
}
public PathStringBuilder summary(int startTime, int endTime, int nTransfers, int c1, int c2) {
diff --git a/src/main/java/org/opentripplanner/raptor/api/path/RaptorPath.java b/src/main/java/org/opentripplanner/raptor/api/path/RaptorPath.java
index 36caaf856e8..ca67599e262 100644
--- a/src/main/java/org/opentripplanner/raptor/api/path/RaptorPath.java
+++ b/src/main/java/org/opentripplanner/raptor/api/path/RaptorPath.java
@@ -3,6 +3,7 @@
import java.util.List;
import java.util.stream.Stream;
import javax.annotation.Nullable;
+import org.opentripplanner.raptor.api.model.RaptorConstants;
import org.opentripplanner.raptor.api.model.RaptorTripSchedule;
import org.opentripplanner.raptor.api.model.RelaxFunction;
@@ -56,8 +57,15 @@ public interface RaptorPath extends Comparable transitPriorityCalculator() {
return Optional.ofNullable(transitPriorityCalculator);
}
@@ -85,8 +76,8 @@ public List passThroughPoints() {
}
/**
- * Whether to accept non-optimal trips if they are close enough - if and only if they represent an
- * optimal path for their given iteration. In other words this slack only relaxes the pareto
+ * Whether to accept non-optimal trips if they are close enough - if and only if they represent
+ * an optimal path for their given iteration. In other words this slack only relaxes the pareto
* comparison at the destination.
*
* Let {@code c} be the existing minimum pareto optimal cost to beat. Then a trip with cost
@@ -102,8 +93,9 @@ public List passThroughPoints() {
* is replaced by {@link #relaxC1()}. This parameter is ignored if {@link #relaxC1()} exist.
*/
@Deprecated
- public Optional relaxCostAtDestination() {
- return Optional.ofNullable(relaxCostAtDestination);
+ @Nullable
+ public Double relaxCostAtDestination() {
+ return relaxCostAtDestination;
}
@Override
diff --git a/src/main/java/org/opentripplanner/raptor/api/response/RaptorResponse.java b/src/main/java/org/opentripplanner/raptor/api/response/RaptorResponse.java
index 9cee0357d9d..82c0c06d0cd 100644
--- a/src/main/java/org/opentripplanner/raptor/api/response/RaptorResponse.java
+++ b/src/main/java/org/opentripplanner/raptor/api/response/RaptorResponse.java
@@ -17,20 +17,20 @@
public class RaptorResponse {
private final Collection> paths;
- private final RaptorRequest requestOriginal;
private final RaptorRequest requestUsed;
private final StopArrivals arrivals;
+ private final boolean heuristicPathExist;
public RaptorResponse(
Collection> paths,
StopArrivals arrivals,
- RaptorRequest requestOriginal,
- RaptorRequest requestUsed
+ RaptorRequest requestUsed,
+ boolean heuristicPathExist
) {
this.paths = paths;
this.arrivals = arrivals;
- this.requestOriginal = requestOriginal;
this.requestUsed = requestUsed;
+ this.heuristicPathExist = heuristicPathExist;
}
/**
@@ -52,13 +52,6 @@ public StopArrivals getArrivals() {
return arrivals;
}
- /**
- * The original request issued to perform the travel search.
- */
- public RaptorRequest requestOriginal() {
- return requestOriginal;
- }
-
/**
* The actual request used to perform the travel search. In the case of a multi-criteria search,
* heuristics is used to optimize the search and the request is changed to account for this. Also,
@@ -69,12 +62,20 @@ public RaptorRequest requestUsed() {
return requestUsed;
}
+ /**
+ * Return {@code true} if the heuristic and the main search does not find any connections.
+ * Searching again with another time/search-window will not produce any results. There is no paths
+ * in the set of days provided in the transit data with the request usd.
+ */
+ public boolean noConnectionFound() {
+ return paths.isEmpty() && !heuristicPathExist;
+ }
+
@Override
public String toString() {
return ToStringBuilder
.of(RaptorResponse.class)
.addObj("paths", paths)
- .addObj("requestOriginal", requestOriginal)
.addObj("requestUsed", requestUsed)
.toString();
}
diff --git a/src/main/java/org/opentripplanner/raptor/path/PathBuilder.java b/src/main/java/org/opentripplanner/raptor/path/PathBuilder.java
index c046a8f240d..7612cc0b3ba 100644
--- a/src/main/java/org/opentripplanner/raptor/path/PathBuilder.java
+++ b/src/main/java/org/opentripplanner/raptor/path/PathBuilder.java
@@ -182,6 +182,10 @@ 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);
diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/SystemErrDebugLogger.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/SystemErrDebugLogger.java
index 8c35f103106..060d3a2e018 100644
--- a/src/main/java/org/opentripplanner/raptor/rangeraptor/SystemErrDebugLogger.java
+++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/SystemErrDebugLogger.java
@@ -219,7 +219,7 @@ private void print(PatternRideView, ?> p, String action) {
}
private String path(ArrivalView> a) {
- return path(a, new PathStringBuilder(null)).summary(a.c1()).toString();
+ return path(a, new PathStringBuilder(null)).summary(a.c1(), a.c2()).toString();
}
private PathStringBuilder path(ArrivalView> a, PathStringBuilder buf) {
diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContext.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContext.java
index eda2cabc0a1..1706c879a2c 100644
--- a/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContext.java
+++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContext.java
@@ -1,5 +1,9 @@
package org.opentripplanner.raptor.rangeraptor.context;
+import static org.opentripplanner.raptor.rangeraptor.internalapi.ParetoSetTime.USE_ARRIVAL_TIME;
+import static org.opentripplanner.raptor.rangeraptor.internalapi.ParetoSetTime.USE_DEPARTURE_TIME;
+import static org.opentripplanner.raptor.rangeraptor.internalapi.ParetoSetTime.USE_TIMETABLE;
+
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
@@ -20,6 +24,7 @@
import org.opentripplanner.raptor.api.request.RaptorTuningParameters;
import org.opentripplanner.raptor.api.request.SearchParams;
import org.opentripplanner.raptor.rangeraptor.debug.DebugHandlerFactory;
+import org.opentripplanner.raptor.rangeraptor.internalapi.ParetoSetTime;
import org.opentripplanner.raptor.rangeraptor.internalapi.RoundProvider;
import org.opentripplanner.raptor.rangeraptor.internalapi.SlackProvider;
import org.opentripplanner.raptor.rangeraptor.internalapi.WorkerLifeCycle;
@@ -218,6 +223,13 @@ public TimeBasedBoardingSupport createTimeBasedBoardingSupport() {
);
}
+ /**
+ * Resolve which pareto-set time config to use.
+ */
+ public ParetoSetTime paretoSetTimeConfig() {
+ return paretoSetTimeConfig(searchParams(), searchDirection());
+ }
+
/**
* The multi-criteria state can handle multiple access/egress paths to a single stop, but the
* Standard and BestTime states do not. To get a deterministic behaviour we filter the paths and
@@ -307,4 +319,17 @@ private static EgressPaths egressPaths(RaptorRequest> request) {
var paths = forward ? params.egressPaths() : params.accessPaths();
return EgressPaths.create(paths, request.profile());
}
+
+ static ParetoSetTime paretoSetTimeConfig(
+ SearchParams searchParams,
+ SearchDirection searchDirection
+ ) {
+ if (searchParams.timetable()) {
+ return USE_TIMETABLE;
+ }
+ boolean preferLatestDeparture =
+ searchParams.preferLateArrival() != searchDirection.isInReverse();
+
+ return preferLatestDeparture ? USE_DEPARTURE_TIME : USE_ARRIVAL_TIME;
+ }
}
diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/ParetoSetCost.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/ParetoSetCost.java
new file mode 100644
index 00000000000..7ae3706bb4e
--- /dev/null
+++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/ParetoSetCost.java
@@ -0,0 +1,48 @@
+package org.opentripplanner.raptor.rangeraptor.internalapi;
+
+/**
+ * These are the different cost configuration Raptor support. Each configuration will
+ * be used to change the pareto-function used to compare arrivals and paths. We add
+ * new values here when needed by a new use-case.
+ */
+public enum ParetoSetCost {
+ /**
+ * Cost is not used.
+ */
+ NONE,
+ /**
+ * One cost parameter is used. A small c1 value is better than a large value.
+ */
+ USE_C1,
+ /**
+ * Same as {@link #USE_C1}, but the relax function is used to relax the cost at the destination.
+ * DO not use this! This will be removed as soon as the Vy, Entur, Norway has migrated off
+ * this feature.
+ */
+ @Deprecated
+ USE_C1_RELAX_DESTINATION,
+ /**
+ * Use both c1 and c2 in the pareto function. A small value is better than a large one.
+ */
+ USE_C1_AND_C2,
+ /**
+ * Use c1 in the pareto function, but relax c1 is c2 is optimal. This allows slightly worse
+ * c1 values if a path is considered better based on the c2 value. Another way of looking at
+ * this, is that all paths are grouped by the c2 value. When two paths are compared inside a group
+ * the normal c1 comparison is used, and when comparing paths from different groups the relaxed
+ * c1 comparison is used.
+ */
+ USE_C1_RELAXED_IF_C2_IS_OPTIMAL;
+
+ public boolean includeC1() {
+ return this != NONE;
+ }
+
+ /**
+ * Use c2 as input to the pareto function. The c2 value is used as a criteria, or it is used
+ * to modify the function ({@link #USE_C1_RELAXED_IF_C2_IS_OPTIMAL}).
+ */
+ public boolean includeC2() {
+ return this == USE_C1_AND_C2 || this == USE_C1_RELAXED_IF_C2_IS_OPTIMAL;
+ }
+}
diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/ParetoSetTime.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/ParetoSetTime.java
new file mode 100644
index 00000000000..c0c6f7f08e1
--- /dev/null
+++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/ParetoSetTime.java
@@ -0,0 +1,21 @@
+package org.opentripplanner.raptor.rangeraptor.internalapi;
+
+/**
+ * These are the different time configurations Raptor supports. Each configuration will
+ * be used to change the pareto-function.
+ */
+public enum ParetoSetTime {
+ /**
+ * Uses iteration-departure-time and arrival-time as criteria in pareto function. Note!
+ * iteration-departure-time is slightly different from the more precise departure-time.
+ */
+ USE_TIMETABLE,
+ /**
+ * Uses arrival-time as criteria in pareto function.
+ */
+ USE_ARRIVAL_TIME,
+ /**
+ * Uses departure-time as criteria in pareto function.
+ */
+ USE_DEPARTURE_TIME,
+}
diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/configure/McRangeRaptorConfig.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/configure/McRangeRaptorConfig.java
index 649737bc42f..e7aa07fb914 100644
--- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/configure/McRangeRaptorConfig.java
+++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/configure/McRangeRaptorConfig.java
@@ -9,6 +9,7 @@
import org.opentripplanner.raptor.api.request.RaptorTransitPriorityGroupCalculator;
import org.opentripplanner.raptor.rangeraptor.context.SearchContext;
import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics;
+import org.opentripplanner.raptor.rangeraptor.internalapi.ParetoSetCost;
import org.opentripplanner.raptor.rangeraptor.internalapi.PassThroughPointsService;
import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorker;
import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerState;
@@ -157,7 +158,8 @@ private > ParetoSet createPatternRideParetoSet(
private DestinationArrivalPaths createDestinationArrivalPaths() {
if (paths == null) {
- paths = pathConfig.createDestArrivalPaths(true, includeC2() ? dominanceFunctionC2() : null);
+ var c2Comp = includeC2() ? dominanceFunctionC2() : null;
+ paths = pathConfig.createDestArrivalPaths(resolveCostConfig(), c2Comp);
}
return paths;
}
@@ -209,4 +211,17 @@ private boolean isPassThrough() {
private boolean isTransitPriority() {
return mcRequest().transitPriorityCalculator().isPresent();
}
+
+ private ParetoSetCost resolveCostConfig() {
+ if (isTransitPriority()) {
+ return ParetoSetCost.USE_C1_RELAXED_IF_C2_IS_OPTIMAL;
+ }
+ if (isPassThrough()) {
+ return ParetoSetCost.USE_C1_AND_C2;
+ }
+ if (context.multiCriteria().relaxCostAtDestination() != null) {
+ return ParetoSetCost.USE_C1_RELAX_DESTINATION;
+ }
+ return ParetoSetCost.USE_C1;
+ }
}
diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/path/PathParetoSetComparators.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/path/PathParetoSetComparators.java
index 220e014b836..00ea6d11fe5 100644
--- a/src/main/java/org/opentripplanner/raptor/rangeraptor/path/PathParetoSetComparators.java
+++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/path/PathParetoSetComparators.java
@@ -7,12 +7,15 @@
import static org.opentripplanner.raptor.api.path.RaptorPath.compareIterationDepartureTime;
import static org.opentripplanner.raptor.api.path.RaptorPath.compareNumberOfTransfers;
+import java.util.Objects;
import javax.annotation.Nonnull;
import org.opentripplanner.raptor.api.model.DominanceFunction;
import org.opentripplanner.raptor.api.model.RaptorTripSchedule;
import org.opentripplanner.raptor.api.model.RelaxFunction;
import org.opentripplanner.raptor.api.model.SearchDirection;
import org.opentripplanner.raptor.api.path.RaptorPath;
+import org.opentripplanner.raptor.rangeraptor.internalapi.ParetoSetCost;
+import org.opentripplanner.raptor.rangeraptor.internalapi.ParetoSetTime;
import org.opentripplanner.raptor.util.paretoset.ParetoComparator;
/**
@@ -25,6 +28,14 @@
* Number of transfers
* Total travel duration time
*
+ * Optional features are :
+ *
+ * - Prefer late arrival - arriveBy search
+ * - Include c1 - include c1 in pareto function (generalized-cost).
+ * - Include c2 - include c2 in pareto function (custom criteria).
+ * - Relax c1 - accept c1 values which is slightly worse than the best result.
+ * - Relax c1, if c2 is optimal
+ *
* The {@code travelDuration} is added as a criteria to the pareto comparator in addition to the
* parameters used for each stop arrivals. The {@code travelDuration} is only needed at the
* destination, because Range Raptor works in iterations backwards in time.
@@ -36,98 +47,52 @@ private PathParetoSetComparators() {}
/**
* Create pareto-set comparison function.
- * @param includeC1 Whether to include generalized cost as a criteria.
- * @param includeTimetable // TODO: 2023-07-31 What is this parameter doing exactly?
- * @param preferLateArrival // TODO: 2023-07-31 What is this parameter doing exactly?
- * @param relaxC1 Relax function for the generalized cost
- * @param c2Comp Dominance function for accumulated criteria TWO. If function is null, C2 will
- * not be included in the comparison.
+ *
+ * @param timeConfig Which time information (arrival-time, departure-time, or timetable) to include in comparator.
+ * @param costConfig Supported configurations of c1, c2 and relaxed cost(c1).
+ * @param relaxC1 Relax function for the generalized cost
+ * @param c2Comp Dominance function for accumulated criteria TWO. If function is null,
+ * C2 will not be included in the comparison.
*/
public static ParetoComparator> paretoComparator(
- final boolean includeC1,
- final boolean includeTimetable,
- final boolean preferLateArrival,
- final SearchDirection searchDirection,
- final RelaxFunction relaxC1,
- final DominanceFunction c2Comp
+ ParetoSetTime timeConfig,
+ ParetoSetCost costConfig,
+ RelaxFunction relaxC1,
+ DominanceFunction c2Comp
) {
- /*
- * TODO pass-through: I would like to see if we can refactor this with something like this, and
- * still get the same performance:
- *
- * if(c2Comp == null) {
- * return paretoComparator(...);
- * }
- * else {
- * return paretoComparator(...) || c2Comp.leftDominateRight(l.c2(), r.c2());
- * }
- */
- boolean includeRelaxedCost = includeC1 && !relaxC1.isNormal();
- boolean preferLatestDeparture = preferLateArrival != searchDirection.isInReverse();
-
- if (includeRelaxedCost) {
- if (includeTimetable) {
- if (c2Comp != null) {
- return comparatorTimetableAndRelaxedC1AndC2(relaxC1, c2Comp);
- } else {
- return comparatorTimetableAndRelaxedC1(relaxC1);
- }
- }
- if (preferLateArrival) {
- if (c2Comp != null) {
- return comparatorDepartureTimeAndRelaxedC1AndC2(relaxC1, c2Comp);
- } else {
- return comparatorDepartureTimeAndRelaxedC1(relaxC1);
- }
- } else {
- if (c2Comp != null) {
- return comparatorArrivalTimeAndRelaxedC1AndC2(relaxC1, c2Comp);
- } else {
- return comparatorArrivalTimeAndRelaxedC1(relaxC1);
- }
- }
- }
+ Objects.requireNonNull(timeConfig);
+ Objects.requireNonNull(costConfig);
- if (includeC1) {
- if (includeTimetable) {
- if (c2Comp != null) {
- return comparatorTimetableAndC1AndC2(c2Comp);
- } else {
- return comparatorTimetableAndC1();
- }
- }
- if (preferLatestDeparture) {
- if (c2Comp != null) {
- return comparatorDepartureTimeAndC1AndC2(c2Comp);
- } else {
- return comparatorDepartureTimeAndC1();
- }
- }
- if (c2Comp != null) {
- return comparatorWithC1AndC2(c2Comp);
- } else {
- return comparatorWithC1();
- }
- }
-
- if (includeTimetable) {
- if (c2Comp != null) {
- return comparatorTimetableAndC2(c2Comp);
- } else {
- return comparatorTimetable();
- }
- }
- if (preferLatestDeparture) {
- if (c2Comp != null) {
- return comparatorStandardDepartureTimeAndC2(c2Comp);
- } else {
- return comparatorStandardDepartureTime();
- }
- }
- if (c2Comp != null) {
- return comparatorStandardArrivalTimeAndC2(c2Comp);
- }
- return comparatorStandardArrivalTime();
+ return switch (costConfig) {
+ case NONE -> switch (timeConfig) {
+ case USE_TIMETABLE -> comparatorTimetable();
+ case USE_ARRIVAL_TIME -> comparatorStandardArrivalTime();
+ case USE_DEPARTURE_TIME -> comparatorStandardDepartureTime();
+ };
+ case USE_C1 -> switch (timeConfig) {
+ case USE_TIMETABLE -> comparatorTimetableAndC1();
+ case USE_ARRIVAL_TIME -> comparatorArrivalTimeAndC1();
+ case USE_DEPARTURE_TIME -> comparatorDepartureTimeAndC1();
+ };
+ case USE_C1_AND_C2 -> switch (timeConfig) {
+ case USE_TIMETABLE -> comparatorTimetableAndC1AndC2(c2Comp);
+ case USE_ARRIVAL_TIME -> comparatorWithC1AndC2(c2Comp);
+ case USE_DEPARTURE_TIME -> comparatorDepartureTimeAndC1AndC2(c2Comp);
+ };
+ case USE_C1_RELAXED_IF_C2_IS_OPTIMAL -> switch (timeConfig) {
+ case USE_TIMETABLE -> comparatorTimetableAndRelaxedC1IfC2IsOptimal(relaxC1, c2Comp);
+ case USE_ARRIVAL_TIME -> comparatorArrivalTimeAndRelaxedC1IfC2IsOptimal(relaxC1, c2Comp);
+ case USE_DEPARTURE_TIME -> comparatorDepartureTimeAndRelaxedC1IfC2IsOptimal(
+ relaxC1,
+ c2Comp
+ );
+ };
+ case USE_C1_RELAX_DESTINATION -> switch (timeConfig) {
+ case USE_TIMETABLE -> comparatorTimetableAndRelaxedC1(relaxC1);
+ case USE_ARRIVAL_TIME -> comparatorArrivalTimeAndRelaxedC1(relaxC1);
+ case USE_DEPARTURE_TIME -> comparatorDepartureTimeAndRelaxedC1(relaxC1);
+ };
+ };
}
private static <
@@ -136,34 +101,12 @@ > ParetoComparator> comparatorStandardArrivalTime() {
return (l, r) -> compareArrivalTime(l, r) || compareNumberOfTransfers(l, r);
}
- private static <
- T extends RaptorTripSchedule
- > ParetoComparator> comparatorStandardArrivalTimeAndC2(
- @Nonnull final DominanceFunction c2Comp
- ) {
- return (l, r) ->
- compareArrivalTime(l, r) ||
- compareNumberOfTransfers(l, r) ||
- c2Comp.leftDominateRight(l.c2(), r.c2());
- }
-
private static <
T extends RaptorTripSchedule
> ParetoComparator> comparatorStandardDepartureTime() {
return (l, r) -> compareDepartureTime(l, r) || compareNumberOfTransfers(l, r);
}
- private static <
- T extends RaptorTripSchedule
- > ParetoComparator> comparatorStandardDepartureTimeAndC2(
- @Nonnull final DominanceFunction c2Comp
- ) {
- return (l, r) ->
- compareDepartureTime(l, r) ||
- compareNumberOfTransfers(l, r) ||
- c2Comp.leftDominateRight(l.c2(), r.c2());
- }
-
private static <
T extends RaptorTripSchedule
> ParetoComparator> comparatorTimetable() {
@@ -173,18 +116,6 @@ > ParetoComparator> comparatorTimetable() {
compareNumberOfTransfers(l, r);
}
- private static <
- T extends RaptorTripSchedule
- > ParetoComparator> comparatorTimetableAndC2(
- @Nonnull final DominanceFunction c2Comp
- ) {
- return (l, r) ->
- compareIterationDepartureTime(l, r) ||
- compareArrivalTime(l, r) ||
- compareNumberOfTransfers(l, r) ||
- c2Comp.leftDominateRight(l.c2(), r.c2());
- }
-
private static <
T extends RaptorTripSchedule
> ParetoComparator> comparatorTimetableAndC1() {
@@ -209,7 +140,9 @@ > ParetoComparator> comparatorTimetableAndRelaxedC1(
compareC1(relaxCost, l, r);
}
- private static ParetoComparator> comparatorWithC1() {
+ private static <
+ T extends RaptorTripSchedule
+ > ParetoComparator> comparatorArrivalTimeAndC1() {
return (l, r) ->
compareArrivalTime(l, r) ||
compareNumberOfTransfers(l, r) ||
@@ -230,7 +163,7 @@ > ParetoComparator> comparatorDepartureTimeAndC1() {
private static <
T extends RaptorTripSchedule
> ParetoComparator> comparatorArrivalTimeAndRelaxedC1(
- @Nonnull final RelaxFunction relaxCost
+ @Nonnull RelaxFunction relaxCost
) {
return (l, r) ->
compareArrivalTime(l, r) ||
@@ -242,7 +175,7 @@ > ParetoComparator> comparatorArrivalTimeAndRelaxedC1(
private static <
T extends RaptorTripSchedule
> ParetoComparator> comparatorDepartureTimeAndRelaxedC1(
- @Nonnull final RelaxFunction relaxCost
+ @Nonnull RelaxFunction relaxCost
) {
return (l, r) ->
compareDepartureTime(l, r) ||
@@ -254,7 +187,7 @@ > ParetoComparator> comparatorDepartureTimeAndRelaxedC1(
private static <
T extends RaptorTripSchedule
> ParetoComparator> comparatorTimetableAndC1AndC2(
- @Nonnull final DominanceFunction c2Comp
+ @Nonnull DominanceFunction c2Comp
) {
return (l, r) ->
compareIterationDepartureTime(l, r) ||
@@ -267,22 +200,21 @@ > ParetoComparator> comparatorTimetableAndC1AndC2(
private static <
T extends RaptorTripSchedule
- > ParetoComparator> comparatorTimetableAndRelaxedC1AndC2(
- @Nonnull final RelaxFunction relaxCost,
- @Nonnull final DominanceFunction c2Comp
+ > ParetoComparator> comparatorTimetableAndRelaxedC1IfC2IsOptimal(
+ @Nonnull RelaxFunction relaxCost,
+ @Nonnull DominanceFunction c2Comp
) {
return (l, r) ->
compareIterationDepartureTime(l, r) ||
compareArrivalTime(l, r) ||
compareNumberOfTransfers(l, r) ||
compareDuration(l, r) ||
- compareC1(relaxCost, l, r) ||
- c2Comp.leftDominateRight(l.c2(), r.c2());
+ compareC1RelaxedIfC2IsOptimal(l, r, relaxCost, c2Comp);
}
private static <
T extends RaptorTripSchedule
- > ParetoComparator> comparatorWithC1AndC2(@Nonnull final DominanceFunction c2Comp) {
+ > ParetoComparator> comparatorWithC1AndC2(@Nonnull DominanceFunction c2Comp) {
return (l, r) ->
compareArrivalTime(l, r) ||
compareNumberOfTransfers(l, r) ||
@@ -294,7 +226,7 @@ > ParetoComparator> comparatorWithC1AndC2(@Nonnull final Dominance
private static <
T extends RaptorTripSchedule
> ParetoComparator> comparatorDepartureTimeAndC1AndC2(
- @Nonnull final DominanceFunction c2Comp
+ @Nonnull DominanceFunction c2Comp
) {
return (l, r) ->
compareDepartureTime(l, r) ||
@@ -306,29 +238,36 @@ > ParetoComparator> comparatorDepartureTimeAndC1AndC2(
private static <
T extends RaptorTripSchedule
- > ParetoComparator> comparatorArrivalTimeAndRelaxedC1AndC2(
- @Nonnull final RelaxFunction relaxCost,
- @Nonnull final DominanceFunction c2Comp
+ > ParetoComparator> comparatorArrivalTimeAndRelaxedC1IfC2IsOptimal(
+ @Nonnull RelaxFunction relaxCost,
+ @Nonnull DominanceFunction c2Comp
) {
return (l, r) ->
compareArrivalTime(l, r) ||
compareNumberOfTransfers(l, r) ||
compareDuration(l, r) ||
- compareC1(relaxCost, l, r) ||
- c2Comp.leftDominateRight(l.c2(), r.c2());
+ compareC1RelaxedIfC2IsOptimal(l, r, relaxCost, c2Comp);
}
private static <
T extends RaptorTripSchedule
- > ParetoComparator> comparatorDepartureTimeAndRelaxedC1AndC2(
- @Nonnull final RelaxFunction relaxCost,
- @Nonnull final DominanceFunction c2Comp
+ > ParetoComparator> comparatorDepartureTimeAndRelaxedC1IfC2IsOptimal(
+ @Nonnull RelaxFunction relaxCost,
+ @Nonnull DominanceFunction c2Comp
) {
return (l, r) ->
compareDepartureTime(l, r) ||
compareNumberOfTransfers(l, r) ||
compareDuration(l, r) ||
- compareC1(relaxCost, l, r) ||
- c2Comp.leftDominateRight(l.c2(), r.c2());
+ compareC1RelaxedIfC2IsOptimal(l, r, relaxCost, c2Comp);
+ }
+
+ private static boolean compareC1RelaxedIfC2IsOptimal(
+ @Nonnull RaptorPath l,
+ @Nonnull RaptorPath r,
+ @Nonnull RelaxFunction relaxCost,
+ @Nonnull DominanceFunction c2Comp
+ ) {
+ return c2Comp.leftDominateRight(l.c2(), r.c2()) ? compareC1(relaxCost, l, r) : compareC1(l, r);
}
}
diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/path/configure/PathConfig.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/path/configure/PathConfig.java
index 4403e72375c..89b2f447ca4 100644
--- a/src/main/java/org/opentripplanner/raptor/rangeraptor/path/configure/PathConfig.java
+++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/path/configure/PathConfig.java
@@ -3,12 +3,16 @@
import static org.opentripplanner.raptor.rangeraptor.path.PathParetoSetComparators.paretoComparator;
import org.opentripplanner.raptor.api.model.DominanceFunction;
+import org.opentripplanner.raptor.api.model.GeneralizedCostRelaxFunction;
import org.opentripplanner.raptor.api.model.RaptorTripSchedule;
+import org.opentripplanner.raptor.api.model.RelaxFunction;
import org.opentripplanner.raptor.api.model.SearchDirection;
import org.opentripplanner.raptor.api.path.RaptorPath;
import org.opentripplanner.raptor.api.path.RaptorStopNameResolver;
import org.opentripplanner.raptor.api.request.RaptorProfile;
import org.opentripplanner.raptor.rangeraptor.context.SearchContext;
+import org.opentripplanner.raptor.rangeraptor.internalapi.ParetoSetCost;
+import org.opentripplanner.raptor.rangeraptor.internalapi.ParetoSetTime;
import org.opentripplanner.raptor.rangeraptor.internalapi.WorkerLifeCycle;
import org.opentripplanner.raptor.rangeraptor.path.DestinationArrivalPaths;
import org.opentripplanner.raptor.rangeraptor.path.ForwardPathMapper;
@@ -35,23 +39,26 @@ public PathConfig(SearchContext context) {
this.ctx = context;
}
+ public DestinationArrivalPaths createDestArrivalPathsStdSearch() {
+ return createDestArrivalPaths(ParetoSetCost.NONE, DominanceFunction.noop());
+ }
+
/**
* Create a new {@link DestinationArrivalPaths}.
- * @param includeC1Cost whether to include generalized cost in the pareto set criteria.
- * It will be generated for each leg and a total for the path.
+ * @param costConfig Supported configurations of c1, c2 and relaxed cost(c1).
* @param c2Comp c2 comparator function to be used in the pareto set criteria. If c2 comparator is null
* then no c2 comparison will be used.
*/
public DestinationArrivalPaths createDestArrivalPaths(
- boolean includeC1Cost,
- final DominanceFunction c2Comp
+ ParetoSetCost costConfig,
+ DominanceFunction c2Comp
) {
return new DestinationArrivalPaths<>(
- createPathParetoComparator(includeC1Cost, c2Comp),
+ createPathParetoComparator(costConfig, c2Comp),
ctx.calculator(),
- includeC1Cost ? ctx.costCalculator() : null,
+ costConfig.includeC1() ? ctx.costCalculator() : null,
ctx.slackProvider(),
- createPathMapper(includeC1Cost),
+ createPathMapper(costConfig.includeC1()),
ctx.debugFactory(),
ctx.stopNameResolver(),
ctx.lifeCycle()
@@ -61,17 +68,30 @@ public DestinationArrivalPaths createDestArrivalPaths(
/* private members */
private ParetoComparator> createPathParetoComparator(
- boolean includeC1,
- final DominanceFunction c2Comp
+ ParetoSetCost costConfig,
+ DominanceFunction c2Comp
) {
- return paretoComparator(
- includeC1,
- ctx.searchParams().timetable(),
- ctx.searchParams().preferLateArrival(),
- ctx.searchDirection(),
- ctx.multiCriteria().relaxC1AtDestination(),
- c2Comp
- );
+ // This code goes away when the USE_C1_RELAX_DESTINATION is deleted
+ var relaxC1 =
+ switch (costConfig) {
+ case USE_C1_RELAXED_IF_C2_IS_OPTIMAL -> ctx.multiCriteria().relaxC1();
+ case USE_C1_RELAX_DESTINATION -> GeneralizedCostRelaxFunction.of(
+ ctx.multiCriteria().relaxCostAtDestination()
+ );
+ default -> RelaxFunction.NORMAL;
+ };
+
+ return paretoComparator(paretoSetTimeConfig(), costConfig, relaxC1, c2Comp);
+ }
+
+ private ParetoSetTime paretoSetTimeConfig() {
+ boolean preferLatestDeparture =
+ ctx.searchParams().preferLateArrival() != ctx.searchDirection().isInReverse();
+
+ ParetoSetTime timeConfig = ctx.searchParams().timetable()
+ ? ParetoSetTime.USE_TIMETABLE
+ : (preferLatestDeparture ? ParetoSetTime.USE_DEPARTURE_TIME : ParetoSetTime.USE_ARRIVAL_TIME);
+ return timeConfig;
}
private PathMapper createPathMapper(boolean includeCost) {
diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/configure/StdRangeRaptorConfig.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/configure/StdRangeRaptorConfig.java
index d3f57c9443f..6e0c3ee5afd 100644
--- a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/configure/StdRangeRaptorConfig.java
+++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/configure/StdRangeRaptorConfig.java
@@ -6,9 +6,9 @@
import java.util.HashSet;
import java.util.Set;
import org.opentripplanner.raptor.api.model.RaptorTripSchedule;
-import org.opentripplanner.raptor.api.model.RelaxFunction;
import org.opentripplanner.raptor.rangeraptor.context.SearchContext;
import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics;
+import org.opentripplanner.raptor.rangeraptor.internalapi.ParetoSetCost;
import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult;
import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerState;
import org.opentripplanner.raptor.rangeraptor.internalapi.RoutingStrategy;
@@ -174,7 +174,7 @@ private StopArrivalsState wrapStopArrivalsStateWithDebugger(StopArrivalsState
}
private DestinationArrivalPaths destinationArrivalPaths() {
- var destinationArrivalPaths = pathConfig.createDestArrivalPaths(false, null);
+ var destinationArrivalPaths = pathConfig.createDestArrivalPathsStdSearch();
// Add egressArrivals to stops and bind them to the destination arrival paths. The
// adapter notify the destination on each new egress stop arrival.
@@ -244,25 +244,15 @@ private BestNumberOfTransfers resolveBestNumberOfTransfers() {
}
private UnknownPathFactory unknownPathFactory() {
- return oneOf(
- new UnknownPathFactory<>(
- resolveBestTimes(),
- resolveBestNumberOfTransfers(),
- ctx.calculator(),
- ctx.slackProvider().transferSlack(),
- ctx.egressPaths(),
- MIN_TRAVEL_DURATION.is(ctx.profile()),
- paretoComparator(
- false,
- ctx.searchParams().timetable(),
- ctx.searchParams().preferLateArrival(),
- ctx.searchDirection(),
- RelaxFunction.NORMAL,
- null
- ),
- ctx.lifeCycle()
- ),
- UnknownPathFactory.class
+ return new UnknownPathFactory<>(
+ resolveBestTimes(),
+ resolveBestNumberOfTransfers(),
+ ctx.calculator(),
+ ctx.slackProvider().transferSlack(),
+ ctx.egressPaths(),
+ MIN_TRAVEL_DURATION.is(ctx.profile()),
+ paretoComparator(ctx.paretoSetTimeConfig(), ParetoSetCost.NONE, null, null),
+ ctx.lifeCycle()
);
}
diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/heuristics/HeuristicsAdapter.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/heuristics/HeuristicsAdapter.java
index ff38fff6d40..107d41208f4 100644
--- a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/heuristics/HeuristicsAdapter.java
+++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/heuristics/HeuristicsAdapter.java
@@ -159,7 +159,8 @@ private int[] toIntArray(int size, int unreached, IntUnaryOperator supplier) {
private record AggregatedResults(
int minJourneyTravelDuration,
int minJourneyNumOfTransfers,
- int earliestArrivalTime
+ int earliestArrivalTime,
+ boolean reached
) {
private static AggregatedResults create(
TransitCalculator> calculator,
@@ -204,7 +205,8 @@ private static AggregatedResults create(
return new AggregatedResults(
bestJourneyTravelDuration,
bestJourneyNumOfTransfers,
- bestArrivalTime
+ bestArrivalTime,
+ bestArrivalTime != NOT_SET
);
}
}
diff --git a/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java b/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java
index fe8bb884912..fb96b7a8724 100644
--- a/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java
+++ b/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java
@@ -79,11 +79,11 @@ public RaptorResponse route() {
return new RaptorResponse<>(
Collections.emptyList(),
null,
- originalRequest,
// If a trip exist(forward heuristics succeed), but is outside the calculated
// search-window, then set the search-window params as if the request was
- // performed. This enable the client to page to the next window
- requestWithDynamicSearchParams(originalRequest)
+ // performed. This enables the client to page to the next window
+ requestWithDynamicSearchParams(originalRequest),
+ false
);
}
}
@@ -146,8 +146,9 @@ private RaptorResponse createAndRunDynamicRRWorker(RaptorRequest request)
return new RaptorResponse<>(
result.extractPaths(),
new DefaultStopArrivals(result),
- originalRequest,
- request
+ request,
+ // This method is not run unless the heuristic reached the destination
+ true
);
}
diff --git a/src/main/java/org/opentripplanner/raptor/spi/UnknownPath.java b/src/main/java/org/opentripplanner/raptor/spi/UnknownPath.java
index b95417e86b6..de6a846f480 100644
--- a/src/main/java/org/opentripplanner/raptor/spi/UnknownPath.java
+++ b/src/main/java/org/opentripplanner/raptor/spi/UnknownPath.java
@@ -125,7 +125,7 @@ public String toString(RaptorStopNameResolver stopNameTranslator) {
public String toString() {
PathStringBuilder pathBuilder = new PathStringBuilder(null);
if (departureTime == 0 && arrivalTime == 0) {
- pathBuilder.summary(c1());
+ pathBuilder.summary(c1(), c2());
} else {
pathBuilder.summary(startTime(), endTime(), numberOfTransfers, c1(), c2());
}
diff --git a/src/main/java/org/opentripplanner/routing/alertpatch/TransitAlert.java b/src/main/java/org/opentripplanner/routing/alertpatch/TransitAlert.java
index 98127555e56..d5d260a8218 100644
--- a/src/main/java/org/opentripplanner/routing/alertpatch/TransitAlert.java
+++ b/src/main/java/org/opentripplanner/routing/alertpatch/TransitAlert.java
@@ -33,6 +33,7 @@ public class TransitAlert extends AbstractTransitEntity entities;
@@ -52,6 +53,7 @@ public class TransitAlert extends AbstractTransitEntitynull
+ */
+ @Nullable
+ public Integer version() {
+ return version;
+ }
+
public ZonedDateTime updatedTime() {
return updatedTime;
}
@@ -195,6 +207,7 @@ public boolean sameAs(@Nonnull TransitAlert other) {
Objects.equals(effect, other.effect) &&
Objects.equals(priority, other.priority) &&
Objects.equals(creationTime, other.creationTime) &&
+ Objects.equals(version, other.version) &&
Objects.equals(updatedTime, other.updatedTime) &&
Objects.equals(siriCodespace, other.siriCodespace) &&
Objects.equals(entities, other.entities) &&
diff --git a/src/main/java/org/opentripplanner/routing/alertpatch/TransitAlertBuilder.java b/src/main/java/org/opentripplanner/routing/alertpatch/TransitAlertBuilder.java
index fda6b571585..654a47fe12b 100644
--- a/src/main/java/org/opentripplanner/routing/alertpatch/TransitAlertBuilder.java
+++ b/src/main/java/org/opentripplanner/routing/alertpatch/TransitAlertBuilder.java
@@ -27,6 +27,7 @@ public class TransitAlertBuilder extends AbstractEntityBuilder entities = new HashSet<>();
private final List timePeriods = new ArrayList<>();
@@ -167,6 +168,15 @@ public TransitAlertBuilder withCreationTime(ZonedDateTime creationTime) {
return this;
}
+ public Integer version() {
+ return version;
+ }
+
+ public TransitAlertBuilder withVersion(Integer version) {
+ this.version = version;
+ return this;
+ }
+
public ZonedDateTime updatedTime() {
return updatedTime;
}
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java
index a9b042083bf..fe20592576e 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java
@@ -153,6 +153,9 @@ else if (pathLeg.isTransferLeg()) {
if (egressPathLeg.egress() instanceof DefaultAccessEgress ae) {
itinerary.setAccessPenalty(ae.penalty());
}
+ if (path.isC2Set()) {
+ itinerary.setGeneralizedCost2(path.c2());
+ }
return itinerary;
}
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java
index f18aa056504..1b5c836c832 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java
@@ -229,7 +229,10 @@ private Collection fetchAccessEgresses(AccessEgressType typ
RouteRequest accessRequest = request.clone();
if (type.isAccess()) {
- accessRequest.journey().rental().setAllowArrivingInRentedVehicleAtDestination(false);
+ accessRequest.withPreferences(p -> {
+ p.withBike(b -> b.withRental(r -> r.withAllowArrivingInRentedVehicleAtDestination(false)));
+ p.withCar(c -> c.withRental(r -> r.withAllowArrivingInRentedVehicleAtDestination(false)));
+ });
}
Duration durationLimit = accessRequest
@@ -340,8 +343,7 @@ private void verifyAccessEgress(Collection> access, Collection> egress) {
* origin and destination.
*/
private void checkIfTransitConnectionExists(RaptorResponse response) {
- int searchWindowUsed = response.requestUsed().searchParams().searchWindowInSeconds();
- if (searchWindowUsed <= 0 && response.paths().isEmpty()) {
+ if (response.noConnectionFound()) {
throw new RoutingValidationException(
List.of(new RoutingError(RoutingErrorCode.NO_TRANSIT_CONNECTION, null))
);
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/BinarySetOperator.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/BinarySetOperator.java
new file mode 100644
index 00000000000..35e5b8c0918
--- /dev/null
+++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/BinarySetOperator.java
@@ -0,0 +1,20 @@
+package org.opentripplanner.routing.algorithm.raptoradapter.transit.request;
+
+/**
+ * Used to concatenate matches with either the logical "AND" or "OR" operator.
+ */
+enum BinarySetOperator {
+ AND("&"),
+ OR("|");
+
+ private final String token;
+
+ BinarySetOperator(String token) {
+ this.token = token;
+ }
+
+ @Override
+ public String toString() {
+ return token;
+ }
+}
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfigurator.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfigurator.java
index ba9af45adba..25006af49d8 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfigurator.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfigurator.java
@@ -23,8 +23,29 @@
*/
public class PriorityGroupConfigurator {
- private static final int BASE_GROUP_ID = TransitPriorityGroup32n.groupId(0);
- private int groupIndexCounter = 0;
+ /**
+ * There are two ways we can treat the base (local-traffic) transit priority group:
+ *
+ * - We can assign group id 1 (one) to the base group and it will be treated as any other group.
+ *
- We can assign group id 0 (zero) to the base and it will not be added to the set of groups
+ * a given path has.
+ *
+ * When we compare paths we compare sets of group ids. A set is dominating another set if it is
+ * a smaller subset or different from the other set.
+ *
+ * Example - base-group-id = 0 (zero)
+ *
+ * Let B be the base and G be concrete group. Then: (B) dominates (G), (G) dominates (B), (B)
+ * dominates (BG), but (G) does not dominate (BG). In other words, paths with only agency
+ * X (group G) is not given an advantage in the routing over paths with a combination of agency
+ * X (group G) and local traffic (group B).
+ *
+ * TODO: Experiment with base-group-id=0 and make it configurable.
+ */
+ private static final int GROUP_INDEX_COUNTER_START = 1;
+
+ private final int baseGroupId = TransitPriorityGroup32n.groupId(GROUP_INDEX_COUNTER_START);
+ private int groupIndexCounter = GROUP_INDEX_COUNTER_START;
private final boolean enabled;
private final PriorityGroupMatcher[] agencyMatchers;
private final PriorityGroupMatcher[] globalMatchers;
@@ -75,7 +96,7 @@ public static PriorityGroupConfigurator of(
*/
public int lookupTransitPriorityGroupId(RoutingTripPattern tripPattern) {
if (!enabled || tripPattern == null) {
- return BASE_GROUP_ID;
+ return baseGroupId;
}
var p = tripPattern.getPattern();
@@ -99,7 +120,11 @@ public int lookupTransitPriorityGroupId(RoutingTripPattern tripPattern) {
}
}
// Fallback to base-group-id
- return BASE_GROUP_ID;
+ return baseGroupId;
+ }
+
+ public int baseGroupId() {
+ return baseGroupId;
}
private int nextGroupId() {
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcher.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcher.java
index dd7c1b46636..4d8a0475239 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcher.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcher.java
@@ -1,5 +1,8 @@
package org.opentripplanner.routing.algorithm.raptoradapter.transit.request;
+import static org.opentripplanner.routing.algorithm.raptoradapter.transit.request.BinarySetOperator.AND;
+import static org.opentripplanner.routing.algorithm.raptoradapter.transit.request.BinarySetOperator.OR;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -59,7 +62,7 @@ public static PriorityGroupMatcher of(TransitPriorityGroupSelect select) {
if (!select.routeIds().isEmpty()) {
list.add(new IdMatcher("Route", select.routeIds(), p -> p.getRoute().getId()));
}
- return compositeOf(list);
+ return andOf(list);
}
static PriorityGroupMatcher[] of(Collection selectors) {
@@ -70,15 +73,15 @@ static PriorityGroupMatcher[] of(Collection selector
.toArray(PriorityGroupMatcher[]::new);
}
- private static String arrayToString(T[] values) {
- return colToString(Arrays.asList(values));
+ private static String arrayToString(BinarySetOperator op, T[] values) {
+ return colToString(op, Arrays.asList(values));
}
- private static String colToString(Collection values) {
- return values.stream().map(Objects::toString).collect(Collectors.joining(" | "));
+ private static String colToString(BinarySetOperator op, Collection values) {
+ return values.stream().map(Objects::toString).collect(Collectors.joining(" " + op + " "));
}
- private static PriorityGroupMatcher compositeOf(List list) {
+ private static PriorityGroupMatcher andOf(List list) {
// Remove empty/noop matchers
list = list.stream().filter(Predicate.not(PriorityGroupMatcher::isEmpty)).toList();
@@ -88,7 +91,7 @@ private static PriorityGroupMatcher compositeOf(List list)
if (list.size() == 1) {
return list.get(0);
}
- return new CompositeMatcher(list);
+ return new AndMatcher(list);
}
abstract boolean match(TripPattern pattern);
@@ -112,7 +115,7 @@ boolean match(TripPattern pattern) {
@Override
public String toString() {
- return "Mode(" + colToString(modes) + ')';
+ return "Mode(" + colToString(OR, modes) + ')';
}
}
@@ -145,7 +148,7 @@ boolean match(TripPattern pattern) {
@Override
public String toString() {
- return typeName + "Regexp(" + arrayToString(subModeRegexp) + ')';
+ return typeName + "Regexp(" + arrayToString(OR, subModeRegexp) + ')';
}
}
@@ -172,35 +175,35 @@ boolean match(TripPattern pattern) {
@Override
public String toString() {
- return typeName + "Id(" + colToString(ids) + ')';
+ return typeName + "Id(" + colToString(OR, ids) + ')';
}
}
/**
- * Take a list of matchers and provide a single interface. At least one matcher in the
- * list must match for the composite matcher to return a match.
+ * Takes a list of matchers and provide a single interface. All matchers in the list must match
+ * for the composite matcher to return a match.
*/
- private static final class CompositeMatcher extends PriorityGroupMatcher {
+ private static final class AndMatcher extends PriorityGroupMatcher {
private final PriorityGroupMatcher[] matchers;
- public CompositeMatcher(List matchers) {
+ public AndMatcher(List matchers) {
this.matchers = matchers.toArray(PriorityGroupMatcher[]::new);
}
@Override
boolean match(TripPattern pattern) {
for (var m : matchers) {
- if (m.match(pattern)) {
- return true;
+ if (!m.match(pattern)) {
+ return false;
}
}
- return false;
+ return true;
}
@Override
public String toString() {
- return "(" + arrayToString(matchers) + ')';
+ return "(" + arrayToString(AND, matchers) + ')';
}
}
}
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java
index 91b08212d2f..bfe5c3de841 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java
@@ -8,6 +8,7 @@
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
+import org.opentripplanner.raptor.api.model.RaptorConstants;
import org.opentripplanner.raptor.api.model.RaptorTripSchedule;
import org.opentripplanner.raptor.api.path.RaptorPath;
import org.opentripplanner.raptor.api.path.RaptorStopNameResolver;
@@ -115,6 +116,8 @@ public Set> findBestTransitPath(RaptorPath originalPath) {
var filteredTails = filter.filterFinalResult(tails);
+ setC2IfNotSet(originalPath, filteredTails);
+
return filteredTails.stream().map(OptimizedPathTail::build).collect(toSet());
}
@@ -255,4 +258,20 @@ private List>> sortTransfersOnArrivalStopPosInDecOrde
)
.collect(Collectors.toList());
}
+
+ /**
+ * Copy over c2 value from origin to new path if the c2 value is not generated by this service.
+ */
+ private static void setC2IfNotSet(
+ RaptorPath originalPath,
+ Set> filteredTails
+ ) {
+ if (originalPath.isC2Set()) {
+ for (OptimizedPathTail tail : filteredTails) {
+ if (!tail.isC2Set()) {
+ tail.c2(originalPath.c2());
+ }
+ }
+ }
+ }
}
diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/BikePreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/BikePreferences.java
index 115e1264036..5717876bbfc 100644
--- a/src/main/java/org/opentripplanner/routing/api/request/preference/BikePreferences.java
+++ b/src/main/java/org/opentripplanner/routing/api/request/preference/BikePreferences.java
@@ -30,6 +30,7 @@ public final class BikePreferences implements Serializable {
private final int switchTime;
private final Cost switchCost;
private final VehicleParkingPreferences parking;
+ private final VehicleRentalPreferences rental;
private final double stairsReluctance;
private final BicycleOptimizeType optimizeType;
private final TimeSlopeSafetyTriangle optimizeTriangle;
@@ -43,6 +44,7 @@ private BikePreferences() {
this.switchTime = 0;
this.switchCost = Cost.ZERO;
this.parking = VehicleParkingPreferences.DEFAULT;
+ this.rental = VehicleRentalPreferences.DEFAULT;
this.optimizeType = BicycleOptimizeType.SAFE;
this.optimizeTriangle = TimeSlopeSafetyTriangle.DEFAULT;
// very high reluctance to carry the bike up/down a flight of stairs
@@ -58,6 +60,7 @@ private BikePreferences(Builder builder) {
this.switchTime = Units.duration(builder.switchTime);
this.switchCost = builder.switchCost;
this.parking = builder.parking;
+ this.rental = builder.rental;
this.optimizeType = Objects.requireNonNull(builder.optimizeType);
this.optimizeTriangle = Objects.requireNonNull(builder.optimizeTriangle);
this.stairsReluctance = Units.reluctance(builder.stairsReluctance);
@@ -126,6 +129,11 @@ public VehicleParkingPreferences parking() {
return parking;
}
+ /** Rental preferences that can be different per request */
+ public VehicleRentalPreferences rental() {
+ return rental;
+ }
+
/**
* The set of characteristics that the user wants to optimize for -- defaults to SAFE.
*/
@@ -155,6 +163,7 @@ public boolean equals(Object o) {
switchTime == that.switchTime &&
switchCost.equals(that.switchCost) &&
parking.equals(that.parking) &&
+ rental.equals(that.rental) &&
optimizeType == that.optimizeType &&
optimizeTriangle.equals(that.optimizeTriangle) &&
doubleEquals(stairsReluctance, that.stairsReluctance)
@@ -172,6 +181,7 @@ public int hashCode() {
switchTime,
switchCost,
parking,
+ rental,
optimizeType,
optimizeTriangle,
stairsReluctance
@@ -190,8 +200,10 @@ public String toString() {
.addDurationSec("switchTime", switchTime, DEFAULT.switchTime)
.addObj("switchCost", switchCost, DEFAULT.switchCost)
.addObj("parking", parking, DEFAULT.parking)
+ .addObj("rental", rental, DEFAULT.rental)
.addEnum("optimizeType", optimizeType, DEFAULT.optimizeType)
.addObj("optimizeTriangle", optimizeTriangle, DEFAULT.optimizeTriangle)
+ .addNum("stairsReluctance", stairsReluctance, DEFAULT.stairsReluctance)
.toString();
}
@@ -207,6 +219,7 @@ public static class Builder {
private int switchTime;
private Cost switchCost;
private VehicleParkingPreferences parking;
+ private VehicleRentalPreferences rental;
private BicycleOptimizeType optimizeType;
private TimeSlopeSafetyTriangle optimizeTriangle;
@@ -222,6 +235,7 @@ public Builder(BikePreferences original) {
this.switchTime = original.switchTime;
this.switchCost = original.switchCost;
this.parking = original.parking;
+ this.rental = original.rental;
this.optimizeType = original.optimizeType;
this.optimizeTriangle = original.optimizeTriangle;
this.stairsReluctance = original.stairsReluctance;
@@ -299,6 +313,11 @@ public Builder withParking(Consumer body) {
return this;
}
+ public Builder withRental(Consumer body) {
+ this.rental = ifNotNull(this.rental, original.rental).copyOf().apply(body).build();
+ return this;
+ }
+
public BicycleOptimizeType optimizeType() {
return optimizeType;
}
diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/CarPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/CarPreferences.java
index 523e19afb70..6b103e8f3b7 100644
--- a/src/main/java/org/opentripplanner/routing/api/request/preference/CarPreferences.java
+++ b/src/main/java/org/opentripplanner/routing/api/request/preference/CarPreferences.java
@@ -24,6 +24,7 @@ public final class CarPreferences implements Serializable {
private final double speed;
private final double reluctance;
private final VehicleParkingPreferences parking;
+ private final VehicleRentalPreferences rental;
private final int pickupTime;
private final Cost pickupCost;
private final int dropoffTime;
@@ -35,6 +36,7 @@ private CarPreferences() {
this.speed = 40.0;
this.reluctance = 2.0;
this.parking = VehicleParkingPreferences.DEFAULT;
+ this.rental = VehicleRentalPreferences.DEFAULT;
this.pickupTime = 60;
this.pickupCost = Cost.costOfMinutes(2);
this.dropoffTime = 120;
@@ -46,6 +48,7 @@ private CarPreferences(Builder builder) {
this.speed = Units.speed(builder.speed);
this.reluctance = Units.reluctance(builder.reluctance);
this.parking = builder.parking;
+ this.rental = builder.rental;
this.pickupTime = Units.duration(builder.pickupTime);
this.pickupCost = builder.pickupCost;
this.dropoffTime = Units.duration(builder.dropoffTime);
@@ -79,6 +82,11 @@ public VehicleParkingPreferences parking() {
return parking;
}
+ /** Rental preferences that can be different per request */
+ public VehicleRentalPreferences rental() {
+ return rental;
+ }
+
/** Time of getting in/out of a carPickup (taxi) */
public int pickupTime() {
return pickupTime;
@@ -122,6 +130,7 @@ public boolean equals(Object o) {
DoubleUtils.doubleEquals(that.speed, speed) &&
DoubleUtils.doubleEquals(that.reluctance, reluctance) &&
parking.equals(that.parking) &&
+ rental.equals(that.rental) &&
pickupTime == that.pickupTime &&
pickupCost.equals(that.pickupCost) &&
dropoffTime == that.dropoffTime &&
@@ -136,6 +145,7 @@ public int hashCode() {
speed,
reluctance,
parking,
+ rental,
pickupTime,
pickupCost,
dropoffTime,
@@ -151,6 +161,7 @@ public String toString() {
.addNum("speed", speed, DEFAULT.speed)
.addNum("reluctance", reluctance, DEFAULT.reluctance)
.addObj("parking", parking, DEFAULT.parking)
+ .addObj("rental", rental, DEFAULT.rental)
.addNum("pickupTime", pickupTime, DEFAULT.pickupTime)
.addObj("pickupCost", pickupCost, DEFAULT.pickupCost)
.addNum("dropoffTime", dropoffTime, DEFAULT.dropoffTime)
@@ -166,6 +177,7 @@ public static class Builder {
private double speed;
private double reluctance;
private VehicleParkingPreferences parking;
+ private VehicleRentalPreferences rental;
private int pickupTime;
private Cost pickupCost;
private int dropoffTime;
@@ -177,6 +189,7 @@ public Builder(CarPreferences original) {
this.speed = original.speed;
this.reluctance = original.reluctance;
this.parking = original.parking;
+ this.rental = original.rental;
this.pickupTime = original.pickupTime;
this.pickupCost = original.pickupCost;
this.dropoffTime = original.dropoffTime;
@@ -203,6 +216,11 @@ public Builder withParking(Consumer body) {
return this;
}
+ public Builder withRental(Consumer body) {
+ this.rental = ifNotNull(this.rental, original.rental).copyOf().apply(body).build();
+ return this;
+ }
+
public Builder withPickupTime(int pickupTime) {
this.pickupTime = pickupTime;
return this;
diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/RoutingPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/RoutingPreferences.java
index b728b99f8e3..3230bbf5968 100644
--- a/src/main/java/org/opentripplanner/routing/api/request/preference/RoutingPreferences.java
+++ b/src/main/java/org/opentripplanner/routing/api/request/preference/RoutingPreferences.java
@@ -7,6 +7,7 @@
import java.util.Objects;
import java.util.function.Consumer;
import javax.annotation.Nonnull;
+import org.opentripplanner.routing.api.request.StreetMode;
import org.opentripplanner.street.search.TraverseMode;
/** User/trip cost/time/slack/reluctance search config. */
@@ -22,7 +23,6 @@ public final class RoutingPreferences implements Serializable {
private final WheelchairPreferences wheelchair;
private final BikePreferences bike;
private final CarPreferences car;
- private final VehicleRentalPreferences rental;
private final SystemPreferences system;
private final ItineraryFilterPreferences itineraryFilter;
@@ -34,7 +34,6 @@ public RoutingPreferences() {
this.wheelchair = WheelchairPreferences.DEFAULT;
this.bike = BikePreferences.DEFAULT;
this.car = CarPreferences.DEFAULT;
- this.rental = VehicleRentalPreferences.DEFAULT;
this.system = SystemPreferences.DEFAULT;
this.itineraryFilter = ItineraryFilterPreferences.DEFAULT;
}
@@ -47,7 +46,6 @@ private RoutingPreferences(Builder builder) {
this.street = requireNonNull(builder.street());
this.bike = requireNonNull(builder.bike());
this.car = requireNonNull(builder.car());
- this.rental = requireNonNull(builder.rental());
this.system = requireNonNull(builder.system());
this.itineraryFilter = requireNonNull(builder.itineraryFilter());
}
@@ -92,10 +90,6 @@ public CarPreferences car() {
return car;
}
- public VehicleRentalPreferences rental() {
- return rental;
- }
-
/**
* Get parking preferences for the traverse mode. Note, only car and bike are supported.
*/
@@ -103,6 +97,24 @@ public VehicleParkingPreferences parking(TraverseMode mode) {
return mode == TraverseMode.CAR ? car.parking() : bike.parking();
}
+ /**
+ * Get rental preferences for the traverse mode. Note, only car, scooter and bike are supported.
+ *
+ * TODO make scooter preferences independent of bike
+ */
+ public VehicleRentalPreferences rental(TraverseMode mode) {
+ return mode == TraverseMode.CAR ? car.rental() : bike.rental();
+ }
+
+ /**
+ * Get rental preferences for the traverse mode. Note, only car, scooter and bike are supported.
+ *
+ * TODO make scooter preferences independent of bike
+ */
+ public VehicleRentalPreferences rental(StreetMode mode) {
+ return mode == StreetMode.CAR_RENTAL ? car.rental() : bike.rental();
+ }
+
@Nonnull
public ItineraryFilterPreferences itineraryFilter() {
return itineraryFilter;
@@ -137,7 +149,6 @@ public boolean equals(Object o) {
Objects.equals(wheelchair, that.wheelchair) &&
Objects.equals(bike, that.bike) &&
Objects.equals(car, that.car) &&
- Objects.equals(rental, that.rental) &&
Objects.equals(system, that.system) &&
Objects.equals(itineraryFilter, that.itineraryFilter)
);
@@ -153,7 +164,6 @@ public int hashCode() {
wheelchair,
bike,
car,
- rental,
system,
itineraryFilter
);
@@ -169,7 +179,6 @@ public static class Builder {
private WheelchairPreferences wheelchair = null;
private BikePreferences bike = null;
private CarPreferences car = null;
- private VehicleRentalPreferences rental = null;
private SystemPreferences system = null;
private ItineraryFilterPreferences itineraryFilter = null;
@@ -250,15 +259,6 @@ public Builder withCar(Consumer body) {
return this;
}
- public VehicleRentalPreferences rental() {
- return rental == null ? original.rental : rental;
- }
-
- public Builder withRental(Consumer body) {
- this.rental = ifNotNull(this.rental, original.rental).copyOf().apply(body).build();
- return this;
- }
-
public SystemPreferences system() {
return system == null ? original.system : system;
}
diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java
index da5c4aad60d..1ad6e8ddacd 100644
--- a/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java
+++ b/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java
@@ -174,7 +174,7 @@ public boolean equals(Object o) {
boardSlack.equals(that.boardSlack) &&
alightSlack.equals(that.alightSlack) &&
reluctanceForMode.equals(that.reluctanceForMode) &&
- otherThanPreferredRoutesPenalty == that.otherThanPreferredRoutesPenalty &&
+ Objects.equals(otherThanPreferredRoutesPenalty, that.otherThanPreferredRoutesPenalty) &&
unpreferredCost.equals(that.unpreferredCost) &&
Objects.equals(relaxTransitPriorityGroup, that.relaxTransitPriorityGroup) &&
ignoreRealtimeUpdates == that.ignoreRealtimeUpdates &&
diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/VehicleRentalPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/VehicleRentalPreferences.java
index 85740177659..8e18d2a81d5 100644
--- a/src/main/java/org/opentripplanner/routing/api/request/preference/VehicleRentalPreferences.java
+++ b/src/main/java/org/opentripplanner/routing/api/request/preference/VehicleRentalPreferences.java
@@ -2,12 +2,12 @@
import java.io.Serializable;
import java.util.Objects;
+import java.util.Set;
import java.util.function.Consumer;
import org.opentripplanner.framework.lang.DoubleUtils;
import org.opentripplanner.framework.model.Cost;
import org.opentripplanner.framework.model.Units;
import org.opentripplanner.framework.tostring.ToStringBuilder;
-import org.opentripplanner.routing.api.request.request.VehicleRentalRequest;
/**
* Preferences for renting a Bike, Car or other type of vehicle.
@@ -24,6 +24,10 @@ public final class VehicleRentalPreferences implements Serializable {
private final boolean useAvailabilityInformation;
private final double arrivingInRentalVehicleAtDestinationCost;
+ private final boolean allowArrivingInRentedVehicleAtDestination;
+
+ private final Set allowedNetworks;
+ private final Set bannedNetworks;
private VehicleRentalPreferences() {
this.pickupTime = 60;
@@ -32,6 +36,9 @@ private VehicleRentalPreferences() {
this.dropoffCost = Cost.costOfSeconds(30);
this.useAvailabilityInformation = false;
this.arrivingInRentalVehicleAtDestinationCost = 0;
+ this.allowArrivingInRentedVehicleAtDestination = false;
+ this.allowedNetworks = Set.of();
+ this.bannedNetworks = Set.of();
}
private VehicleRentalPreferences(Builder builder) {
@@ -42,6 +49,10 @@ private VehicleRentalPreferences(Builder builder) {
this.useAvailabilityInformation = builder.useAvailabilityInformation;
this.arrivingInRentalVehicleAtDestinationCost =
DoubleUtils.roundTo1Decimal(builder.arrivingInRentalVehicleAtDestinationCost);
+ this.allowArrivingInRentedVehicleAtDestination =
+ builder.allowArrivingInRentedVehicleAtDestination;
+ this.allowedNetworks = builder.allowedNetworks;
+ this.bannedNetworks = builder.bannedNetworks;
}
public static Builder of() {
@@ -78,8 +89,6 @@ public int dropoffCost() {
/**
* Whether or not vehicle rental availability information will be used to plan vehicle rental
* trips
- *
- * TODO: This belong in the request?
*/
public boolean useAvailabilityInformation() {
return useAvailabilityInformation;
@@ -87,13 +96,31 @@ public boolean useAvailabilityInformation() {
/**
* The cost of arriving at the destination with the rented vehicle, to discourage doing so.
- *
- * @see VehicleRentalRequest#allowArrivingInRentedVehicleAtDestination()
*/
public double arrivingInRentalVehicleAtDestinationCost() {
return arrivingInRentalVehicleAtDestinationCost;
}
+ /**
+ * Whether arriving at the destination with a rented (station) vehicle is allowed without dropping
+ * it off.
+ *
+ * @see VehicleRentalPreferences#arrivingInRentalVehicleAtDestinationCost()
+ */
+ public boolean allowArrivingInRentedVehicleAtDestination() {
+ return allowArrivingInRentedVehicleAtDestination;
+ }
+
+ /** The vehicle rental networks which may be used. If empty all networks may be used. */
+ public Set allowedNetworks() {
+ return allowedNetworks;
+ }
+
+ /** The vehicle rental networks which may not be used. If empty, no networks are banned. */
+ public Set bannedNetworks() {
+ return bannedNetworks;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -101,15 +128,18 @@ public boolean equals(Object o) {
VehicleRentalPreferences that = (VehicleRentalPreferences) o;
return (
pickupTime == that.pickupTime &&
- pickupCost == that.pickupCost &&
+ Objects.equals(pickupCost, that.pickupCost) &&
dropoffTime == that.dropoffTime &&
- dropoffCost == that.dropoffCost &&
+ Objects.equals(dropoffCost, that.dropoffCost) &&
useAvailabilityInformation == that.useAvailabilityInformation &&
Double.compare(
that.arrivingInRentalVehicleAtDestinationCost,
arrivingInRentalVehicleAtDestinationCost
) ==
- 0
+ 0 &&
+ allowArrivingInRentedVehicleAtDestination == that.allowArrivingInRentedVehicleAtDestination &&
+ allowedNetworks.equals(that.allowedNetworks) &&
+ bannedNetworks.equals(that.bannedNetworks)
);
}
@@ -121,7 +151,10 @@ public int hashCode() {
dropoffTime,
dropoffCost,
useAvailabilityInformation,
- arrivingInRentalVehicleAtDestinationCost
+ arrivingInRentalVehicleAtDestinationCost,
+ allowArrivingInRentedVehicleAtDestination,
+ allowedNetworks,
+ bannedNetworks
);
}
@@ -139,6 +172,12 @@ public String toString() {
arrivingInRentalVehicleAtDestinationCost,
DEFAULT.arrivingInRentalVehicleAtDestinationCost
)
+ .addBoolIfTrue(
+ "allowArrivingInRentedVehicleAtDestination",
+ allowArrivingInRentedVehicleAtDestination
+ )
+ .addCol("allowedNetworks", allowedNetworks, DEFAULT.allowedNetworks)
+ .addCol("bannedNetworks", bannedNetworks, DEFAULT.bannedNetworks)
.toString();
}
@@ -151,6 +190,9 @@ public static class Builder {
private Cost dropoffCost;
private boolean useAvailabilityInformation;
private double arrivingInRentalVehicleAtDestinationCost;
+ private boolean allowArrivingInRentedVehicleAtDestination;
+ private Set allowedNetworks;
+ private Set bannedNetworks;
private Builder(VehicleRentalPreferences original) {
this.original = original;
@@ -161,6 +203,10 @@ private Builder(VehicleRentalPreferences original) {
this.useAvailabilityInformation = original.useAvailabilityInformation;
this.arrivingInRentalVehicleAtDestinationCost =
original.arrivingInRentalVehicleAtDestinationCost;
+ this.allowArrivingInRentedVehicleAtDestination =
+ original.allowArrivingInRentedVehicleAtDestination;
+ this.allowedNetworks = original.allowedNetworks;
+ this.bannedNetworks = original.bannedNetworks;
}
public VehicleRentalPreferences original() {
@@ -199,6 +245,23 @@ public Builder withArrivingInRentalVehicleAtDestinationCost(
return this;
}
+ public Builder withAllowArrivingInRentedVehicleAtDestination(
+ boolean allowArrivingInRentedVehicleAtDestination
+ ) {
+ this.allowArrivingInRentedVehicleAtDestination = allowArrivingInRentedVehicleAtDestination;
+ return this;
+ }
+
+ public Builder withAllowedNetworks(Set allowedNetworks) {
+ this.allowedNetworks = allowedNetworks;
+ return this;
+ }
+
+ public Builder withBannedNetworks(Set bannedNetworks) {
+ this.bannedNetworks = bannedNetworks;
+ return this;
+ }
+
public Builder apply(Consumer body) {
body.accept(this);
return this;
diff --git a/src/main/java/org/opentripplanner/routing/api/request/request/JourneyRequest.java b/src/main/java/org/opentripplanner/routing/api/request/request/JourneyRequest.java
index 39a775bf7f5..50e802690b3 100644
--- a/src/main/java/org/opentripplanner/routing/api/request/request/JourneyRequest.java
+++ b/src/main/java/org/opentripplanner/routing/api/request/request/JourneyRequest.java
@@ -6,18 +6,12 @@
// TODO VIA: Javadoc
public class JourneyRequest implements Cloneable, Serializable {
- // TODO VIA (Hannes): Move the fields below into StreetRequest
- private VehicleRentalRequest rental = new VehicleRentalRequest();
private TransitRequest transit = new TransitRequest();
private StreetRequest access = new StreetRequest();
private StreetRequest egress = new StreetRequest();
private StreetRequest transfer = new StreetRequest();
private StreetRequest direct = new StreetRequest();
- public VehicleRentalRequest rental() {
- return rental;
- }
-
public TransitRequest transit() {
return transit;
}
@@ -58,7 +52,6 @@ public RequestModes modes() {
public JourneyRequest clone() {
try {
var clone = (JourneyRequest) super.clone();
- clone.rental = this.rental.clone();
clone.transit = this.transit.clone();
clone.access = this.access.clone();
clone.egress = this.egress.clone();
diff --git a/src/main/java/org/opentripplanner/routing/api/request/request/StreetRequest.java b/src/main/java/org/opentripplanner/routing/api/request/request/StreetRequest.java
index 587efda6e05..42928a0a70a 100644
--- a/src/main/java/org/opentripplanner/routing/api/request/request/StreetRequest.java
+++ b/src/main/java/org/opentripplanner/routing/api/request/request/StreetRequest.java
@@ -4,7 +4,6 @@
import org.opentripplanner.routing.api.request.StreetMode;
// TODO VIA: Javadoc
-// TODO VIA (Hannes): Missing VehicleRentalRequest and VehicleParkingRequest
public class StreetRequest implements Cloneable, Serializable {
private StreetMode mode;
diff --git a/src/main/java/org/opentripplanner/routing/api/request/request/VehicleRentalRequest.java b/src/main/java/org/opentripplanner/routing/api/request/request/VehicleRentalRequest.java
deleted file mode 100644
index 90e39b22ff7..00000000000
--- a/src/main/java/org/opentripplanner/routing/api/request/request/VehicleRentalRequest.java
+++ /dev/null
@@ -1,66 +0,0 @@
-package org.opentripplanner.routing.api.request.request;
-
-import java.io.Serializable;
-import java.util.HashSet;
-import java.util.Set;
-import org.opentripplanner.routing.api.request.preference.VehicleRentalPreferences;
-import org.opentripplanner.service.vehiclerental.model.VehicleRentalStation;
-
-// TODO VIA: Javadoc
-public class VehicleRentalRequest implements Cloneable, Serializable {
-
- private Set allowedNetworks = Set.of();
- private Set bannedNetworks = Set.of();
-
- private boolean allowArrivingInRentedVehicleAtDestination = false;
-
- // TODO VIA (Hannes): Move useAvailabilityInformation here
-
- public void setAllowedNetworks(Set allowedNetworks) {
- this.allowedNetworks = allowedNetworks;
- }
-
- /** The vehicle rental networks which may be used. If empty all networks may be used. */
- public Set allowedNetworks() {
- return allowedNetworks;
- }
-
- public void setBannedNetworks(Set bannedNetworks) {
- this.bannedNetworks = bannedNetworks;
- }
-
- /** The vehicle rental networks which may not be used. If empty, no networks are banned. */
- public Set bannedNetworks() {
- return bannedNetworks;
- }
-
- public void setAllowArrivingInRentedVehicleAtDestination(
- boolean allowArrivingInRentedVehicleAtDestination
- ) {
- this.allowArrivingInRentedVehicleAtDestination = allowArrivingInRentedVehicleAtDestination;
- }
-
- /**
- * Whether arriving at the destination with a rented (station) bicycle is allowed without dropping
- * it off.
- *
- * @see VehicleRentalPreferences#arrivingInRentalVehicleAtDestinationCost()
- * @see VehicleRentalStation#isArrivingInRentalVehicleAtDestinationAllowed
- */
- public boolean allowArrivingInRentedVehicleAtDestination() {
- return allowArrivingInRentedVehicleAtDestination;
- }
-
- public VehicleRentalRequest clone() {
- try {
- var clone = (VehicleRentalRequest) super.clone();
- clone.allowedNetworks = new HashSet<>(this.allowedNetworks);
- clone.bannedNetworks = new HashSet<>(this.bannedNetworks);
-
- return clone;
- } catch (CloneNotSupportedException e) {
- /* this will never happen since our super is the cloneable object */
- throw new RuntimeException(e);
- }
- }
-}
diff --git a/src/main/java/org/opentripplanner/routing/error/PathNotFoundException.java b/src/main/java/org/opentripplanner/routing/error/PathNotFoundException.java
index 18d94d23038..a4071f9911e 100644
--- a/src/main/java/org/opentripplanner/routing/error/PathNotFoundException.java
+++ b/src/main/java/org/opentripplanner/routing/error/PathNotFoundException.java
@@ -4,6 +4,5 @@
* Indicates that the call to org.opentripplanner.routing.services.PathService returned either null
* or ZERO paths.
*
- * @see org.opentripplanner.api.resource.PlannerResource for where this is (locally) thrown.
*/
public class PathNotFoundException extends RuntimeException {}
diff --git a/src/main/java/org/opentripplanner/routing/service/DefaultRoutingService.java b/src/main/java/org/opentripplanner/routing/service/DefaultRoutingService.java
index 320ffb2117d..9879b52c9c1 100644
--- a/src/main/java/org/opentripplanner/routing/service/DefaultRoutingService.java
+++ b/src/main/java/org/opentripplanner/routing/service/DefaultRoutingService.java
@@ -58,6 +58,12 @@ public ViaRoutingResponse route(RouteViaRequest request) {
}
private void logResponse(RoutingResponse response) {
+ if (response.getTripPlan().itineraries.isEmpty() && response.getRoutingErrors().isEmpty()) {
+ // We should provide an error if there is no results, this is important for the client so
+ // it knows if it can page or abort.
+ LOG.warn("The routing result is empty, but there is no errors...");
+ }
+
if (LOG.isDebugEnabled()) {
var m = response.getMetadata();
var text = MultiLineToStringBuilder
diff --git a/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalPlace.java b/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalPlace.java
index ee181f7b3c1..1725e7df2dd 100644
--- a/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalPlace.java
+++ b/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalPlace.java
@@ -2,7 +2,7 @@
import java.util.Set;
import org.opentripplanner.framework.i18n.I18NString;
-import org.opentripplanner.routing.api.request.request.VehicleRentalRequest;
+import org.opentripplanner.routing.api.request.preference.VehicleRentalPreferences;
import org.opentripplanner.street.model.RentalFormFactor;
import org.opentripplanner.transit.model.framework.FeedScopedId;
@@ -80,22 +80,22 @@ public interface VehicleRentalPlace {
/** Deep links for this rental station or individual vehicle */
VehicleRentalStationUris getRentalUris();
- default boolean networkIsNotAllowed(VehicleRentalRequest request) {
+ default boolean networkIsNotAllowed(VehicleRentalPreferences preferences) {
if (
getNetwork() == null &&
- (!request.allowedNetworks().isEmpty() || !request.bannedNetworks().isEmpty())
+ (!preferences.allowedNetworks().isEmpty() || !preferences.bannedNetworks().isEmpty())
) {
return false;
}
- if (request.bannedNetworks().contains(getNetwork())) {
+ if (preferences.bannedNetworks().contains(getNetwork())) {
return true;
}
- if (request.allowedNetworks().isEmpty()) {
+ if (preferences.allowedNetworks().isEmpty()) {
return false;
}
- return !request.allowedNetworks().contains(getNetwork());
+ return !preferences.allowedNetworks().contains(getNetwork());
}
}
diff --git a/src/main/java/org/opentripplanner/service/vehiclerental/street/StreetVehicleRentalLink.java b/src/main/java/org/opentripplanner/service/vehiclerental/street/StreetVehicleRentalLink.java
index 96bbdaf4c0d..4a18aae03c2 100644
--- a/src/main/java/org/opentripplanner/service/vehiclerental/street/StreetVehicleRentalLink.java
+++ b/src/main/java/org/opentripplanner/service/vehiclerental/street/StreetVehicleRentalLink.java
@@ -52,7 +52,8 @@ public State[] traverse(State s0) {
return State.empty();
}
- if (vehicleRentalPlaceVertex.getStation().networkIsNotAllowed(s0.getRequest().rental())) {
+ var preferences = s0.getPreferences().rental(s0.getRequest().mode());
+ if (vehicleRentalPlaceVertex.getStation().networkIsNotAllowed(preferences)) {
return State.empty();
}
diff --git a/src/main/java/org/opentripplanner/service/vehiclerental/street/VehicleRentalEdge.java b/src/main/java/org/opentripplanner/service/vehiclerental/street/VehicleRentalEdge.java
index 9ec6cf79003..66665f73b54 100644
--- a/src/main/java/org/opentripplanner/service/vehiclerental/street/VehicleRentalEdge.java
+++ b/src/main/java/org/opentripplanner/service/vehiclerental/street/VehicleRentalEdge.java
@@ -49,10 +49,10 @@ public State[] traverse(State s0) {
VehicleRentalPlaceVertex stationVertex = (VehicleRentalPlaceVertex) tov;
VehicleRentalPlace station = stationVertex.getStation();
String network = station.getNetwork();
- var preferences = s0.getPreferences();
- boolean realtimeAvailability = preferences.rental().useAvailabilityInformation();
+ var preferences = s0.getPreferences().rental(formFactor.traverseMode);
+ boolean realtimeAvailability = preferences.useAvailabilityInformation();
- if (station.networkIsNotAllowed(s0.getRequest().rental())) {
+ if (station.networkIsNotAllowed(preferences)) {
return State.empty();
}
@@ -128,7 +128,7 @@ public State[] traverse(State s0) {
s1.beginFloatingVehicleRenting(formFactor, network, false);
} else {
boolean mayKeep =
- s0.getRequest().rental().allowArrivingInRentedVehicleAtDestination() &&
+ preferences.allowArrivingInRentedVehicleAtDestination() &&
station.isArrivingInRentalVehicleAtDestinationAllowed();
s1.beginVehicleRentingAtStation(formFactor, network, mayKeep, false);
}
@@ -161,12 +161,8 @@ public State[] traverse(State s0) {
}
}
- s1.incrementWeight(
- pickedUp ? preferences.rental().pickupCost() : preferences.rental().dropoffCost()
- );
- s1.incrementTimeInSeconds(
- pickedUp ? preferences.rental().pickupTime() : preferences.rental().dropoffTime()
- );
+ s1.incrementWeight(pickedUp ? preferences.pickupCost() : preferences.dropoffCost());
+ s1.incrementTimeInSeconds(pickedUp ? preferences.pickupTime() : preferences.dropoffTime());
s1.setBackMode(null);
return s1.makeStateArray();
}
diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java
index f6a79ea9192..e600f1f8f12 100644
--- a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java
+++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java
@@ -170,22 +170,17 @@ cost function. The cost function (`unpreferredCost`) is defined as a linear func
TransitPriorityGroupConfig.mapTransitRequest(c, request.journey().transit());
// Map preferences
- request.withPreferences(preferences -> mapPreferences(c, request, preferences));
+ request.withPreferences(preferences -> mapPreferences(c, preferences));
return request;
}
- private static void mapPreferences(
- NodeAdapter c,
- RouteRequest request,
- RoutingPreferences.Builder preferences
- ) {
+ private static void mapPreferences(NodeAdapter c, RoutingPreferences.Builder preferences) {
preferences.withTransit(it -> mapTransitPreferences(c, it));
preferences.withBike(it -> mapBikePreferences(c, it));
preferences.withStreet(it -> mapStreetPreferences(c, it));
preferences.withCar(it -> mapCarPreferences(c, it));
preferences.withSystem(it -> mapSystemPreferences(c, it));
- preferences.withRental(it -> setVehicleRental(c, request, it));
preferences.withTransfer(it -> mapTransferPreferences(c, it));
preferences.withWalk(it -> mapWalkPreferences(c, it));
preferences.withWheelchair(it -> mapWheelchairPreferences(c, it, WHEELCHAIR_ACCESSIBILITY));
@@ -508,7 +503,8 @@ Vehicle parking tags can originate from different places depending on the origin
)
.asStringSet(List.of())
)
- );
+ )
+ .withRental(it -> setVehicleRental(c, it));
}
private static void mapStreetPreferences(NodeAdapter c, StreetPreferences.Builder builder) {
@@ -821,7 +817,8 @@ Vehicle parking tags can originate from different places depending on the origin
)
.asStringSet(List.of())
)
- );
+ )
+ .withRental(it -> setVehicleRental(c, it));
}
private static void mapSystemPreferences(NodeAdapter c, SystemPreferences.Builder builder) {
diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/VehicleRentalConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/VehicleRentalConfig.java
index 94f392cc313..057daa7001c 100644
--- a/src/main/java/org/opentripplanner/standalone/config/routerequest/VehicleRentalConfig.java
+++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/VehicleRentalConfig.java
@@ -5,10 +5,7 @@
import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_2;
import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_3;
-import org.opentripplanner.routing.api.request.RouteRequest;
-import org.opentripplanner.routing.api.request.preference.RoutingPreferences;
import org.opentripplanner.routing.api.request.preference.VehicleRentalPreferences;
-import org.opentripplanner.routing.api.request.request.VehicleRentalRequest;
import org.opentripplanner.standalone.config.framework.json.NodeAdapter;
public class VehicleRentalConfig {
@@ -53,56 +50,42 @@ static void mapRentalPreferences(NodeAdapter c, VehicleRentalPreferences.Builder
"The cost of arriving at the destination with the rented vehicle, to discourage doing so."
)
.asDouble(dft.arrivingInRentalVehicleAtDestinationCost())
- );
- }
-
- static void setVehicleRentalRequestOptions(NodeAdapter c, RouteRequest request) {
- VehicleRentalRequest vehicleRentalRequest = request.journey().rental();
-
- vehicleRentalRequest.setAllowedNetworks(
- c
- .of("allowedNetworks")
- .since(V2_1)
- .summary(
- "The vehicle rental networks which may be used. If empty all networks may be used."
- )
- .asStringSet(vehicleRentalRequest.allowedNetworks())
- );
- vehicleRentalRequest.setBannedNetworks(
- c
- .of("bannedNetworks")
- .since(V2_1)
- .summary(
- "The vehicle rental networks which may not be used. If empty, no networks are banned."
- )
- .asStringSet(vehicleRentalRequest.bannedNetworks())
- );
-
- request
- .journey()
- .rental()
- .setAllowArrivingInRentedVehicleAtDestination(
+ )
+ .withAllowArrivingInRentedVehicleAtDestination(
c
.of("allowKeepingAtDestination")
.since(V2_2)
.summary(
"If a vehicle should be allowed to be kept at the end of a station-based rental."
)
- .asBoolean(request.journey().rental().allowArrivingInRentedVehicleAtDestination())
+ .asBoolean(dft.allowArrivingInRentedVehicleAtDestination())
+ )
+ .withAllowedNetworks(
+ c
+ .of("allowedNetworks")
+ .since(V2_1)
+ .summary(
+ "The vehicle rental networks which may be used. If empty all networks may be used."
+ )
+ .asStringSet(dft.allowedNetworks())
+ )
+ .withBannedNetworks(
+ c
+ .of("bannedNetworks")
+ .since(V2_1)
+ .summary(
+ "The vehicle rental networks which may not be used. If empty, no networks are banned."
+ )
+ .asStringSet(dft.bannedNetworks())
);
}
- static void setVehicleRental(
- NodeAdapter c,
- RouteRequest request,
- VehicleRentalPreferences.Builder preferences
- ) {
+ static void setVehicleRental(NodeAdapter c, VehicleRentalPreferences.Builder preferences) {
var vehicleRental = c
.of("vehicleRental")
.since(V2_3)
.summary("Vehicle rental options")
.asObject();
mapRentalPreferences(vehicleRental, preferences);
- setVehicleRentalRequestOptions(vehicleRental, request);
}
}
diff --git a/src/main/java/org/opentripplanner/standalone/server/OTPWebApplication.java b/src/main/java/org/opentripplanner/standalone/server/OTPWebApplication.java
index 56aaa2d1361..b238a74c7d3 100644
--- a/src/main/java/org/opentripplanner/standalone/server/OTPWebApplication.java
+++ b/src/main/java/org/opentripplanner/standalone/server/OTPWebApplication.java
@@ -7,7 +7,6 @@
import io.micrometer.prometheus.PrometheusMeterRegistry;
import jakarta.ws.rs.container.ContainerResponseFilter;
import jakarta.ws.rs.core.Application;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -17,10 +16,9 @@
import org.glassfish.jersey.internal.inject.AbstractBinder;
import org.glassfish.jersey.internal.inject.Binder;
import org.glassfish.jersey.jackson.internal.jackson.jaxrs.json.JacksonJsonProvider;
-import org.glassfish.jersey.server.ServerProperties;
import org.opentripplanner.api.common.OTPExceptionMapper;
-import org.opentripplanner.api.configuration.APIEndpoints;
-import org.opentripplanner.api.json.JSONObjectMapperProvider;
+import org.opentripplanner.apis.APIEndpoints;
+import org.opentripplanner.ext.restapi.serialization.JSONObjectMapperProvider;
import org.opentripplanner.framework.application.OTPFeature;
import org.opentripplanner.standalone.api.OtpServerRequestContext;
import org.slf4j.bridge.SLF4JBridgeHandler;
diff --git a/src/main/java/org/opentripplanner/street/model/edge/StreetTransitEntityLink.java b/src/main/java/org/opentripplanner/street/model/edge/StreetTransitEntityLink.java
index 4fe66735532..926eebc31d9 100644
--- a/src/main/java/org/opentripplanner/street/model/edge/StreetTransitEntityLink.java
+++ b/src/main/java/org/opentripplanner/street/model/edge/StreetTransitEntityLink.java
@@ -98,7 +98,11 @@ else if (
s0.isRentingVehicleFromStation() &&
!(
s0.mayKeepRentedVehicleAtDestination() &&
- s0.getRequest().rental().allowArrivingInRentedVehicleAtDestination()
+ s0
+ .getRequest()
+ .preferences()
+ .rental(s0.getRequest().mode())
+ .allowArrivingInRentedVehicleAtDestination()
)
) {
yield State.empty();
@@ -133,12 +137,13 @@ else if (
@Nonnull
private State[] buildState(State s0, StateEditor s1, RoutingPreferences pref) {
+ var rentalPreferences = s0.getRequest().preferences().rental(s0.getRequest().mode());
if (
s0.isRentingVehicleFromStation() &&
s0.mayKeepRentedVehicleAtDestination() &&
- s0.getRequest().rental().allowArrivingInRentedVehicleAtDestination()
+ rentalPreferences.allowArrivingInRentedVehicleAtDestination()
) {
- s1.incrementWeight(pref.rental().arrivingInRentalVehicleAtDestinationCost());
+ s1.incrementWeight(rentalPreferences.arrivingInRentalVehicleAtDestinationCost());
}
s1.setBackMode(null);
diff --git a/src/main/java/org/opentripplanner/street/model/edge/TemporaryFreeEdge.java b/src/main/java/org/opentripplanner/street/model/edge/TemporaryFreeEdge.java
index 35045db848a..011d0fec7f1 100644
--- a/src/main/java/org/opentripplanner/street/model/edge/TemporaryFreeEdge.java
+++ b/src/main/java/org/opentripplanner/street/model/edge/TemporaryFreeEdge.java
@@ -42,12 +42,13 @@ public State[] traverse(State s0) {
s1.incrementWeight(1);
s1.setBackMode(null);
+ var rentalPreferences = s0.getPreferences().rental(s0.getRequest().mode());
if (
s0.isRentingVehicleFromStation() &&
s0.mayKeepRentedVehicleAtDestination() &&
- s0.getRequest().rental().allowArrivingInRentedVehicleAtDestination()
+ rentalPreferences.allowArrivingInRentedVehicleAtDestination()
) {
- s1.incrementWeight(s0.getPreferences().rental().arrivingInRentalVehicleAtDestinationCost());
+ s1.incrementWeight(rentalPreferences.arrivingInRentalVehicleAtDestinationCost());
}
return s1.makeStateArray();
diff --git a/src/main/java/org/opentripplanner/street/search/StreetSearchBuilder.java b/src/main/java/org/opentripplanner/street/search/StreetSearchBuilder.java
index b4ac98674d0..079cd29706f 100644
--- a/src/main/java/org/opentripplanner/street/search/StreetSearchBuilder.java
+++ b/src/main/java/org/opentripplanner/street/search/StreetSearchBuilder.java
@@ -102,7 +102,7 @@ protected void prepareInitialStates(Collection initialStates) {
@Override
protected void initializeHeuristic(
RemainingWeightHeuristic heuristic,
- Set origin,
+ Set ignored,
Set destination,
boolean arriveBy
) {
@@ -111,7 +111,6 @@ protected void initializeHeuristic(
} else if (heuristic instanceof EuclideanRemainingWeightHeuristic euclideanHeuristic) {
euclideanHeuristic.initialize(
streetRequest.mode(),
- origin,
destination,
arriveBy,
routeRequest.preferences()
diff --git a/src/main/java/org/opentripplanner/street/search/request/StreetSearchRequest.java b/src/main/java/org/opentripplanner/street/search/request/StreetSearchRequest.java
index 6d8bd5783f3..c33ed6a35e9 100644
--- a/src/main/java/org/opentripplanner/street/search/request/StreetSearchRequest.java
+++ b/src/main/java/org/opentripplanner/street/search/request/StreetSearchRequest.java
@@ -12,7 +12,6 @@
import org.opentripplanner.routing.api.request.RouteRequest;
import org.opentripplanner.routing.api.request.StreetMode;
import org.opentripplanner.routing.api.request.preference.RoutingPreferences;
-import org.opentripplanner.routing.api.request.request.VehicleRentalRequest;
import org.opentripplanner.street.model.vertex.Vertex;
import org.opentripplanner.street.search.intersection_model.IntersectionTraversalCalculator;
import org.opentripplanner.street.search.state.State;
@@ -39,7 +38,6 @@ public class StreetSearchRequest implements AStarRequest {
private final StreetMode mode;
private final boolean arriveBy;
private final boolean wheelchair;
- private final VehicleRentalRequest rental;
private final GenericLocation from;
private final Envelope fromEnvelope;
@@ -60,7 +58,6 @@ private StreetSearchRequest() {
this.mode = StreetMode.WALK;
this.arriveBy = false;
this.wheelchair = false;
- this.rental = new VehicleRentalRequest();
this.from = null;
this.fromEnvelope = null;
this.to = null;
@@ -73,7 +70,6 @@ private StreetSearchRequest() {
this.mode = builder.mode;
this.arriveBy = builder.arriveBy;
this.wheelchair = builder.wheelchair;
- this.rental = builder.rental;
this.from = builder.from;
this.fromEnvelope = createEnvelope(from);
this.to = builder.to;
@@ -115,10 +111,6 @@ public boolean wheelchair() {
return wheelchair;
}
- public VehicleRentalRequest rental() {
- return rental;
- }
-
public GenericLocation from() {
return from;
}
diff --git a/src/main/java/org/opentripplanner/street/search/request/StreetSearchRequestBuilder.java b/src/main/java/org/opentripplanner/street/search/request/StreetSearchRequestBuilder.java
index 439e65a3289..fd2eff30e7e 100644
--- a/src/main/java/org/opentripplanner/street/search/request/StreetSearchRequestBuilder.java
+++ b/src/main/java/org/opentripplanner/street/search/request/StreetSearchRequestBuilder.java
@@ -5,7 +5,6 @@
import org.opentripplanner.model.GenericLocation;
import org.opentripplanner.routing.api.request.StreetMode;
import org.opentripplanner.routing.api.request.preference.RoutingPreferences;
-import org.opentripplanner.routing.api.request.request.VehicleRentalRequest;
public class StreetSearchRequestBuilder {
@@ -14,7 +13,6 @@ public class StreetSearchRequestBuilder {
RoutingPreferences preferences;
boolean arriveBy;
boolean wheelchair;
- VehicleRentalRequest rental;
GenericLocation from;
GenericLocation to;
@@ -24,7 +22,6 @@ public class StreetSearchRequestBuilder {
this.preferences = original.preferences();
this.arriveBy = original.arriveBy();
this.wheelchair = original.wheelchair();
- this.rental = original.rental();
this.from = original.from();
this.to = original.to();
}
@@ -58,11 +55,6 @@ public StreetSearchRequestBuilder withWheelchair(boolean wheelchair) {
return this;
}
- public StreetSearchRequestBuilder withRental(VehicleRentalRequest rental) {
- this.rental = rental;
- return this;
- }
-
public StreetSearchRequestBuilder withFrom(GenericLocation from) {
this.from = from;
return this;
diff --git a/src/main/java/org/opentripplanner/street/search/request/StreetSearchRequestMapper.java b/src/main/java/org/opentripplanner/street/search/request/StreetSearchRequestMapper.java
index 9f1f3c567f8..b4193e709e3 100644
--- a/src/main/java/org/opentripplanner/street/search/request/StreetSearchRequestMapper.java
+++ b/src/main/java/org/opentripplanner/street/search/request/StreetSearchRequestMapper.java
@@ -11,7 +11,6 @@ public static StreetSearchRequestBuilder map(RouteRequest opt) {
.withStartTime(opt.dateTime())
.withPreferences(opt.preferences())
.withWheelchair(opt.wheelchair())
- .withRental(opt.journey().rental())
.withFrom(opt.from())
.withTo(opt.to());
}
@@ -22,7 +21,6 @@ public static StreetSearchRequestBuilder mapToTransferRequest(RouteRequest opt)
.withStartTime(Instant.ofEpochSecond(0))
.withPreferences(opt.preferences())
.withWheelchair(opt.wheelchair())
- .withRental(opt.journey().rental())
.withMode(opt.journey().transfer().mode());
}
}
diff --git a/src/main/java/org/opentripplanner/street/search/state/State.java b/src/main/java/org/opentripplanner/street/search/state/State.java
index 37c3e81c097..deef516a674 100644
--- a/src/main/java/org/opentripplanner/street/search/state/State.java
+++ b/src/main/java/org/opentripplanner/street/search/state/State.java
@@ -214,7 +214,10 @@ private boolean vehicleRentalIsFinished() {
!stateData.insideNoRentalDropOffArea
) ||
(
- getRequest().rental().allowArrivingInRentedVehicleAtDestination() &&
+ getRequest()
+ .preferences()
+ .rental(getRequest().mode())
+ .allowArrivingInRentedVehicleAtDestination() &&
stateData.mayKeepRentedVehicleAtDestination &&
stateData.vehicleRentalState == VehicleRentalState.RENTING_FROM_STATION
)
@@ -503,7 +506,10 @@ private double getWalkDistanceDelta() {
private State reversedClone() {
StreetSearchRequest reversedRequest = request
.copyOfReversed(getTime())
- .withPreferences(p -> p.withRental(r -> r.withUseAvailabilityInformation(false)))
+ .withPreferences(p -> {
+ p.withCar(c -> c.withRental(r -> r.withUseAvailabilityInformation(false)));
+ p.withBike(b -> b.withRental(r -> r.withUseAvailabilityInformation(false)));
+ })
.build();
StateData newStateData = stateData.clone();
newStateData.backMode = null;
diff --git a/src/main/java/org/opentripplanner/street/search/state/StateData.java b/src/main/java/org/opentripplanner/street/search/state/StateData.java
index 77d359248aa..cc2bfa5a57a 100644
--- a/src/main/java/org/opentripplanner/street/search/state/StateData.java
+++ b/src/main/java/org/opentripplanner/street/search/state/StateData.java
@@ -83,10 +83,11 @@ public static List getInitialStateDatas(
StreetSearchRequest request,
Function stateDataConstructor
) {
+ var rentalPreferences = request.preferences().rental(request.mode());
return getInitialStateDatas(
request.mode(),
request.arriveBy(),
- request.rental().allowArrivingInRentedVehicleAtDestination(),
+ rentalPreferences.allowArrivingInRentedVehicleAtDestination(),
stateDataConstructor
);
}
@@ -97,10 +98,11 @@ public static List getInitialStateDatas(
* the given {@code request}.
*/
public static StateData getBaseCaseStateData(StreetSearchRequest request) {
+ var rentalPreferences = request.preferences().rental(request.mode());
var stateDatas = getInitialStateDatas(
request.mode(),
request.arriveBy(),
- request.rental().allowArrivingInRentedVehicleAtDestination(),
+ rentalPreferences.allowArrivingInRentedVehicleAtDestination(),
StateData::new
);
diff --git a/src/main/java/org/opentripplanner/street/search/strategy/EuclideanRemainingWeightHeuristic.java b/src/main/java/org/opentripplanner/street/search/strategy/EuclideanRemainingWeightHeuristic.java
index 36e4a57f8ec..e43278901d4 100644
--- a/src/main/java/org/opentripplanner/street/search/strategy/EuclideanRemainingWeightHeuristic.java
+++ b/src/main/java/org/opentripplanner/street/search/strategy/EuclideanRemainingWeightHeuristic.java
@@ -26,7 +26,6 @@ public class EuclideanRemainingWeightHeuristic implements RemainingWeightHeurist
// not work correctly.
public void initialize(
StreetMode streetMode,
- Set fromVertices,
Set toVertices,
boolean arriveBy,
RoutingPreferences preferences
diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java
index f502a220073..ff3f61394ae 100644
--- a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java
+++ b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java
@@ -11,9 +11,11 @@
import java.util.OptionalInt;
import java.util.function.Supplier;
import javax.annotation.Nullable;
+import org.opentripplanner.framework.error.OtpError;
import org.opentripplanner.framework.i18n.I18NString;
import org.opentripplanner.framework.lang.IntUtils;
import org.opentripplanner.framework.time.DurationUtils;
+import org.opentripplanner.framework.time.TimeUtils;
import org.opentripplanner.model.BookingInfo;
import org.opentripplanner.transit.model.basic.Accessibility;
import org.opentripplanner.transit.model.framework.DataValidationException;
@@ -134,7 +136,7 @@ public int getServiceCode() {
@Override
public int getScheduledArrivalTime(final int stop) {
- return arrivalTimes[stop] + timeShift;
+ return timeShifted(arrivalTimes[stop]);
}
@Override
@@ -144,12 +146,12 @@ public int getArrivalTime(final int stop) {
@Override
public int getArrivalDelay(final int stop) {
- return getArrivalTime(stop) - (arrivalTimes[stop] + timeShift);
+ return getArrivalTime(stop) - timeShifted(arrivalTimes[stop]);
}
@Override
public int getScheduledDepartureTime(final int stop) {
- return departureTimes[stop] + timeShift;
+ return timeShifted(departureTimes[stop]);
}
@Override
@@ -159,7 +161,7 @@ public int getDepartureTime(final int stop) {
@Override
public int getDepartureDelay(final int stop) {
- return getDepartureTime(stop) - (departureTimes[stop] + timeShift);
+ return getDepartureTime(stop) - timeShifted(departureTimes[stop]);
}
@Override
@@ -314,9 +316,9 @@ I18NString[] copyHeadsigns(Supplier defaultValue) {
/* private methods */
private void validate() {
- int lastStop = departureTimes.length - 1;
- IntUtils.requireInRange(departureTimes[0], MIN_TIME, MAX_TIME);
- IntUtils.requireInRange(arrivalTimes[lastStop], MIN_TIME, MAX_TIME);
+ // Validate first departure time and last arrival time
+ validateTimeInRange("departureTime", departureTimes, 0);
+ validateTimeInRange("arrivalTime", arrivalTimes, arrivalTimes.length - 1);
// TODO: This class is used by FLEX, so we can not validate increasing TripTimes
// validateNonIncreasingTimes();
}
@@ -356,4 +358,27 @@ private void validateNonIncreasingTimes() {
prevDep = dep;
}
}
+
+ private int timeShifted(int time) {
+ return timeShift + time;
+ }
+
+ private void validateTimeInRange(String field, int[] times, int stopPos) {
+ int t = timeShifted(times[stopPos]);
+
+ if (t < MIN_TIME || t > MAX_TIME) {
+ throw new DataValidationException(
+ OtpError.of(
+ "TripTimeOutOfRange",
+ "The %s is not in range[%s, %s]. Time: %s, stop-pos: %d, trip: %s.",
+ field,
+ DurationUtils.durationToStr(MIN_TIME),
+ DurationUtils.durationToStr(MAX_TIME),
+ TimeUtils.timeToStrLong(t),
+ stopPos,
+ trip.getId()
+ )
+ );
+ }
+ }
}
diff --git a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql
index 2eff7d94020..07067acdb28 100644
--- a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql
+++ b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql
@@ -549,6 +549,8 @@ type PtSituationElement {
summary: [MultilingualString!]!
"Period this situation is in effect"
validityPeriod: ValidityPeriod
+ "Operator's version number for the situation element."
+ version: Int
"Timestamp when the situation element was updated."
versionedAtTime: DateTime
}
@@ -1286,6 +1288,8 @@ type TripPattern {
expectedStartTime: DateTime!
"Generalized cost or weight of the itinerary. Used for debugging."
generalizedCost: Int
+ "A second cost or weight of the itinerary. Some use-cases like pass-through and transit-priority-groups use a second cost during routing. This is used for debugging."
+ generalizedCost2: Int
"A list of legs. Each leg is either a walking (cycling, car) portion of the trip, or a ride leg on a particular vehicle. So a trip where the use walks to the Q train, transfers to the 6, then walks to their destination, has four legs."
legs: [Leg!]!
"Time that the trip departs."
@@ -1644,7 +1648,7 @@ enum RoutingErrorCode {
noStopsInRange
"No transit connection was found between the origin and destination withing the operating day or the next day"
noTransitConnection
- "Transit connection was found, but it was outside the search window, see metadata for the next search window"
+ "A transit connection was found, but it was outside the search window. Use paging to navigate to a result."
noTransitConnectionInSearchWindow
"The coordinates are outside the bounds of the data currently loaded into the system"
outsideBounds
diff --git a/src/test/java/org/opentripplanner/astar/strategy/MaxCountSkipEdgeStrategyTest.java b/src/test/java/org/opentripplanner/astar/strategy/MaxCountSkipEdgeStrategyTest.java
index 04844d65da2..17dc26a4c5e 100644
--- a/src/test/java/org/opentripplanner/astar/strategy/MaxCountSkipEdgeStrategyTest.java
+++ b/src/test/java/org/opentripplanner/astar/strategy/MaxCountSkipEdgeStrategyTest.java
@@ -12,7 +12,7 @@ class MaxCountSkipEdgeStrategyTest {
@Test
void countStops() {
var state = TestStateBuilder.ofWalking().stop().build();
- var strategy = new MaxCountSkipEdgeStrategy<>(1, NearbyStopFinder::isTransitVertex);
+ var strategy = new MaxCountSkipEdgeStrategy<>(1, NearbyStopFinder::hasReachedStop);
assertFalse(strategy.shouldSkipEdge(state, null));
assertTrue(strategy.shouldSkipEdge(state, null));
}
@@ -20,9 +20,17 @@ void countStops() {
@Test
void doNotCountStop() {
var state = TestStateBuilder.ofWalking().build();
- var strategy = new MaxCountSkipEdgeStrategy<>(1, NearbyStopFinder::isTransitVertex);
+ var strategy = new MaxCountSkipEdgeStrategy<>(1, NearbyStopFinder::hasReachedStop);
assertFalse(strategy.shouldSkipEdge(state, null));
assertFalse(strategy.shouldSkipEdge(state, null));
assertFalse(strategy.shouldSkipEdge(state, null));
}
+
+ @Test
+ void nonFinalState() {
+ var state = TestStateBuilder.ofScooterRentalArriveBy().stop().build();
+ assertFalse(state.isFinal());
+ var strategy = new MaxCountSkipEdgeStrategy<>(1, NearbyStopFinder::hasReachedStop);
+ assertFalse(strategy.shouldSkipEdge(state, null));
+ }
}
diff --git a/src/test/java/org/opentripplanner/api/common/PlannerResourceTest.java b/src/test/java/org/opentripplanner/ext/restapi/resources/PlannerResourceTest.java
similarity index 95%
rename from src/test/java/org/opentripplanner/api/common/PlannerResourceTest.java
rename to src/test/java/org/opentripplanner/ext/restapi/resources/PlannerResourceTest.java
index 36e0650b081..a86e270a2af 100644
--- a/src/test/java/org/opentripplanner/api/common/PlannerResourceTest.java
+++ b/src/test/java/org/opentripplanner/ext/restapi/resources/PlannerResourceTest.java
@@ -1,4 +1,4 @@
-package org.opentripplanner.api.common;
+package org.opentripplanner.ext.restapi.resources;
import static graphql.Assert.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -12,7 +12,6 @@
import org.opentripplanner.TestServerContext;
import org.opentripplanner._support.time.ZoneIds;
import org.opentripplanner.api.parameter.QualifiedModeSet;
-import org.opentripplanner.api.resource.PlannerResource;
import org.opentripplanner.routing.api.request.RequestModes;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.standalone.api.OtpServerRequestContext;
diff --git a/src/test/java/org/opentripplanner/graph_builder/module/islandpruning/AdaptivePruningTest.java b/src/test/java/org/opentripplanner/graph_builder/module/islandpruning/AdaptivePruningTest.java
index 832045b6469..90ffc33d848 100644
--- a/src/test/java/org/opentripplanner/graph_builder/module/islandpruning/AdaptivePruningTest.java
+++ b/src/test/java/org/opentripplanner/graph_builder/module/islandpruning/AdaptivePruningTest.java
@@ -1,37 +1,40 @@
package org.opentripplanner.graph_builder.module.islandpruning;
-import java.io.File;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.opentripplanner.graph_builder.module.islandpruning.IslandPruningUtils.buildOsmGraph;
+
import java.util.stream.Collectors;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
-import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore;
-import org.opentripplanner.graph_builder.module.osm.OsmModule;
-import org.opentripplanner.openstreetmap.OsmProvider;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.test.support.ResourceLoader;
-import org.opentripplanner.transit.model.framework.Deduplicator;
-import org.opentripplanner.transit.service.StopModel;
-import org.opentripplanner.transit.service.TransitModel;
-/* Test data consists of one bigger graph and two small sub graphs. These are totally disconnected.
- One small graphs is only at 5 meter distance from the big graph and another one 30 m away.
- Adaptive pruning retains the distant island but removes the closer one which appears to be
- disconnected part of the main graph.
+/**
+ * Test data consists of one bigger graph and two small sub graphs. These are totally disconnected.
+ * One small graphs is only at 5 meter distance from the big graph and another one 30 m away.
+ * Adaptive pruning retains the distant island but removes the closer one which appears to be
+ * disconnected part of the main graph.
*/
-
public class AdaptivePruningTest {
private static Graph graph;
@BeforeAll
static void setup() {
- graph = buildOsmGraph(ResourceLoader.of(AdaptivePruningTest.class).file("isoiiluoto.pbf"));
+ graph =
+ buildOsmGraph(
+ ResourceLoader.of(AdaptivePruningTest.class).file("isoiiluoto.pbf"),
+ 5,
+ 0,
+ 20,
+ 30
+ );
}
@Test
public void distantIslandIsRetained() {
- Assertions.assertTrue(
+ assertTrue(
graph
.getStreetEdges()
.stream()
@@ -55,7 +58,7 @@ public void nearIslandIsRemoved() {
@Test
public void mainGraphIsNotRemoved() {
- Assertions.assertTrue(
+ assertTrue(
graph
.getStreetEdges()
.stream()
@@ -64,42 +67,4 @@ public void mainGraphIsNotRemoved() {
.contains("73347312")
);
}
-
- private static Graph buildOsmGraph(File file) {
- try {
- var deduplicator = new Deduplicator();
- var graph = new Graph(deduplicator);
- var transitModel = new TransitModel(new StopModel(), deduplicator);
- // Add street data from OSM
- OsmProvider osmProvider = new OsmProvider(file, true);
- OsmModule osmModule = OsmModule.of(osmProvider, graph).withEdgeNamer(new TestNamer()).build();
-
- osmModule.buildGraph();
-
- transitModel.index();
- graph.index(transitModel.getStopModel());
-
- // Prune floating islands and set noThru where necessary
- PruneIslands pruneIslands = new PruneIslands(
- graph,
- transitModel,
- DataImportIssueStore.NOOP,
- null
- );
- // all 3 sub graphs are larger than 5 edges
- pruneIslands.setPruningThresholdIslandWithoutStops(5);
-
- // up to 5*20 = 100 edge graphs get pruned if they are too close
- pruneIslands.setAdaptivePruningFactor(20);
-
- // Distant island is 30 m away from main graph, let's keep it
- pruneIslands.setAdaptivePruningDistance(30);
-
- pruneIslands.buildGraph();
-
- return graph;
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
}
diff --git a/src/test/java/org/opentripplanner/graph_builder/module/islandpruning/EscalatorPruningTest.java b/src/test/java/org/opentripplanner/graph_builder/module/islandpruning/EscalatorPruningTest.java
new file mode 100644
index 00000000000..540befb07e0
--- /dev/null
+++ b/src/test/java/org/opentripplanner/graph_builder/module/islandpruning/EscalatorPruningTest.java
@@ -0,0 +1,30 @@
+package org.opentripplanner.graph_builder.module.islandpruning;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.opentripplanner.graph_builder.module.islandpruning.IslandPruningUtils.buildOsmGraph;
+
+import java.util.stream.Collectors;
+import org.junit.jupiter.api.Test;
+import org.opentripplanner.test.support.ResourceLoader;
+
+public class EscalatorPruningTest {
+
+ @Test
+ public void streetEdgesBetweenEscalatorEdgesRetained() {
+ var graph = buildOsmGraph(
+ ResourceLoader.of(EscalatorPruningTest.class).file("matinkyla-escalator.pbf"),
+ 10,
+ 2,
+ 50,
+ 250
+ );
+ assertTrue(
+ graph
+ .getStreetEdges()
+ .stream()
+ .map(streetEdge -> streetEdge.getName().toString())
+ .collect(Collectors.toSet())
+ .contains("490072445")
+ );
+ }
+}
diff --git a/src/test/java/org/opentripplanner/graph_builder/module/islandpruning/IslandPruningUtils.java b/src/test/java/org/opentripplanner/graph_builder/module/islandpruning/IslandPruningUtils.java
new file mode 100644
index 00000000000..59362472771
--- /dev/null
+++ b/src/test/java/org/opentripplanner/graph_builder/module/islandpruning/IslandPruningUtils.java
@@ -0,0 +1,52 @@
+package org.opentripplanner.graph_builder.module.islandpruning;
+
+import java.io.File;
+import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore;
+import org.opentripplanner.graph_builder.module.osm.OsmModule;
+import org.opentripplanner.openstreetmap.OsmProvider;
+import org.opentripplanner.routing.graph.Graph;
+import org.opentripplanner.transit.model.framework.Deduplicator;
+import org.opentripplanner.transit.service.StopModel;
+import org.opentripplanner.transit.service.TransitModel;
+
+class IslandPruningUtils {
+
+ static Graph buildOsmGraph(
+ File osmFile,
+ int thresholdIslandWithoutStops,
+ int thresholdIslandWithStops,
+ double adaptivePruningFactor,
+ int adaptivePruningDistance
+ ) {
+ try {
+ var deduplicator = new Deduplicator();
+ var graph = new Graph(deduplicator);
+ var transitModel = new TransitModel(new StopModel(), deduplicator);
+ // Add street data from OSM
+ OsmProvider osmProvider = new OsmProvider(osmFile, true);
+ OsmModule osmModule = OsmModule.of(osmProvider, graph).withEdgeNamer(new TestNamer()).build();
+
+ osmModule.buildGraph();
+
+ transitModel.index();
+ graph.index(transitModel.getStopModel());
+
+ // Prune floating islands and set noThru where necessary
+ PruneIslands pruneIslands = new PruneIslands(
+ graph,
+ transitModel,
+ DataImportIssueStore.NOOP,
+ null
+ );
+ pruneIslands.setPruningThresholdIslandWithoutStops(thresholdIslandWithoutStops);
+ pruneIslands.setPruningThresholdIslandWithStops(thresholdIslandWithStops);
+ pruneIslands.setAdaptivePruningFactor(adaptivePruningFactor);
+ pruneIslands.setAdaptivePruningDistance(adaptivePruningDistance);
+ pruneIslands.buildGraph();
+
+ return graph;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/src/test/java/org/opentripplanner/graph_builder/module/islandpruning/PruneNoThruIslandsTest.java b/src/test/java/org/opentripplanner/graph_builder/module/islandpruning/PruneNoThruIslandsTest.java
index 53a2d60fe0f..9070fb00f8f 100644
--- a/src/test/java/org/opentripplanner/graph_builder/module/islandpruning/PruneNoThruIslandsTest.java
+++ b/src/test/java/org/opentripplanner/graph_builder/module/islandpruning/PruneNoThruIslandsTest.java
@@ -1,20 +1,16 @@
package org.opentripplanner.graph_builder.module.islandpruning;
-import java.io.File;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.opentripplanner.graph_builder.module.islandpruning.IslandPruningUtils.buildOsmGraph;
+
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
-import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore;
-import org.opentripplanner.graph_builder.module.osm.OsmModule;
-import org.opentripplanner.openstreetmap.OsmProvider;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.street.model.edge.StreetEdge;
import org.opentripplanner.test.support.ResourceLoader;
-import org.opentripplanner.transit.model.framework.Deduplicator;
-import org.opentripplanner.transit.service.StopModel;
-import org.opentripplanner.transit.service.TransitModel;
public class PruneNoThruIslandsTest {
@@ -26,13 +22,17 @@ static void setup() {
buildOsmGraph(
ResourceLoader
.of(PruneNoThruIslandsTest.class)
- .file("herrenberg-island-prune-nothru.osm.pbf")
+ .file("herrenberg-island-prune-nothru.osm.pbf"),
+ 10,
+ 2,
+ 50,
+ 250
);
}
@Test
public void bicycleIslandsBecomeNoThru() {
- Assertions.assertTrue(
+ assertTrue(
graph
.getStreetEdges()
.stream()
@@ -45,7 +45,7 @@ public void bicycleIslandsBecomeNoThru() {
@Test
public void carIslandsBecomeNoThru() {
- Assertions.assertTrue(
+ assertTrue(
graph
.getStreetEdges()
.stream()
@@ -67,36 +67,4 @@ public void pruneFloatingBikeAndWalkIsland() {
.contains("159830257")
);
}
-
- private static Graph buildOsmGraph(File osmFile) {
- try {
- var deduplicator = new Deduplicator();
- var graph = new Graph(deduplicator);
- var transitModel = new TransitModel(new StopModel(), deduplicator);
- // Add street data from OSM
- OsmProvider osmProvider = new OsmProvider(osmFile, true);
- OsmModule osmModule = OsmModule.of(osmProvider, graph).withEdgeNamer(new TestNamer()).build();
-
- osmModule.buildGraph();
-
- transitModel.index();
- graph.index(transitModel.getStopModel());
-
- // Prune floating islands and set noThru where necessary
- PruneIslands pruneIslands = new PruneIslands(
- graph,
- transitModel,
- DataImportIssueStore.NOOP,
- null
- );
- pruneIslands.setPruningThresholdIslandWithoutStops(40);
- pruneIslands.setPruningThresholdIslandWithStops(5);
- pruneIslands.setAdaptivePruningFactor(1);
- pruneIslands.buildGraph();
-
- return graph;
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
}
diff --git a/src/test/java/org/opentripplanner/netex/mapping/TripPatternMapperTest.java b/src/test/java/org/opentripplanner/netex/mapping/TripPatternMapperTest.java
index f07a46ced2c..74448fb3638 100644
--- a/src/test/java/org/opentripplanner/netex/mapping/TripPatternMapperTest.java
+++ b/src/test/java/org/opentripplanner/netex/mapping/TripPatternMapperTest.java
@@ -1,9 +1,11 @@
package org.opentripplanner.netex.mapping;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import com.google.common.collect.ArrayListMultimap;
import java.util.Map;
+import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore;
import org.opentripplanner.netex.index.hierarchy.HierarchicalMap;
@@ -12,7 +14,6 @@
import org.opentripplanner.transit.model.framework.Deduplicator;
import org.opentripplanner.transit.model.framework.DefaultEntityById;
import org.opentripplanner.transit.model.framework.FeedScopedId;
-import org.opentripplanner.transit.model.network.TripPattern;
import org.opentripplanner.transit.model.timetable.Trip;
import org.opentripplanner.transit.model.timetable.TripAlteration;
import org.opentripplanner.transit.model.timetable.TripOnServiceDate;
@@ -55,26 +56,28 @@ public void testMapTripPattern() {
150
);
- TripPatternMapperResult r = tripPatternMapper.mapTripPattern(sample.getJourneyPattern());
+ Optional res = tripPatternMapper.mapTripPattern(
+ sample.getJourneyPattern()
+ );
- assertEquals(1, r.tripPatterns.size());
+ assertTrue(res.isPresent());
- TripPattern tripPattern = r.tripPatterns.values().stream().findFirst().orElseThrow();
+ TripPatternMapperResult r = res.get();
- assertEquals(4, tripPattern.numberOfStops());
- assertEquals(1, tripPattern.scheduledTripsAsStream().count());
+ assertEquals(4, r.tripPattern().numberOfStops());
+ assertEquals(1, r.tripPattern().scheduledTripsAsStream().count());
- Trip trip = tripPattern.scheduledTripsAsStream().findFirst().get();
+ Trip trip = r.tripPattern().scheduledTripsAsStream().findFirst().get();
assertEquals("RUT:ServiceJourney:1", trip.getId().getId());
- assertEquals("NSR:Quay:1", tripPattern.getStop(0).getId().getId());
- assertEquals("NSR:Quay:2", tripPattern.getStop(1).getId().getId());
- assertEquals("NSR:Quay:3", tripPattern.getStop(2).getId().getId());
- assertEquals("NSR:Quay:4", tripPattern.getStop(3).getId().getId());
+ assertEquals("NSR:Quay:1", r.tripPattern().getStop(0).getId().getId());
+ assertEquals("NSR:Quay:2", r.tripPattern().getStop(1).getId().getId());
+ assertEquals("NSR:Quay:3", r.tripPattern().getStop(2).getId().getId());
+ assertEquals("NSR:Quay:4", r.tripPattern().getStop(3).getId().getId());
- assertEquals(1, tripPattern.getScheduledTimetable().getTripTimes().size());
+ assertEquals(1, r.tripPattern().getScheduledTimetable().getTripTimes().size());
- TripTimes tripTimes = tripPattern.getScheduledTimetable().getTripTimes().get(0);
+ TripTimes tripTimes = r.tripPattern().getScheduledTimetable().getTripTimes().get(0);
assertEquals(4, tripTimes.getNumStops());
@@ -115,15 +118,19 @@ public void testMapTripPattern_datedServiceJourney() {
150
);
- TripPatternMapperResult r = tripPatternMapper.mapTripPattern(sample.getJourneyPattern());
+ Optional res = tripPatternMapper.mapTripPattern(
+ sample.getJourneyPattern()
+ );
+
+ assertTrue(res.isPresent());
+
+ var r = res.get();
- assertEquals(1, r.tripPatterns.size());
- assertEquals(2, r.tripOnServiceDates.size());
+ assertEquals(2, r.tripOnServiceDates().size());
- TripPattern tripPattern = r.tripPatterns.values().stream().findFirst().orElseThrow();
- Trip trip = tripPattern.scheduledTripsAsStream().findFirst().get();
+ Trip trip = r.tripPattern().scheduledTripsAsStream().findFirst().get();
- for (TripOnServiceDate tripOnServiceDate : r.tripOnServiceDates) {
+ for (TripOnServiceDate tripOnServiceDate : r.tripOnServiceDates()) {
assertEquals(trip, tripOnServiceDate.getTrip());
assertEquals(TripAlteration.PLANNED, tripOnServiceDate.getTripAlteration());
assertEquals(
diff --git a/src/test/java/org/opentripplanner/raptor/_data/api/TestPathBuilderTestRaptor.java b/src/test/java/org/opentripplanner/raptor/_data/api/TestPathBuilderTestRaptor.java
index bc11f32e4cc..de40c29d583 100644
--- a/src/test/java/org/opentripplanner/raptor/_data/api/TestPathBuilderTestRaptor.java
+++ b/src/test/java/org/opentripplanner/raptor/_data/api/TestPathBuilderTestRaptor.java
@@ -1,6 +1,7 @@
package org.opentripplanner.raptor._data.api;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.opentripplanner.framework.time.DurationUtils.durationInSeconds;
import static org.opentripplanner.framework.time.TimeUtils.time;
import static org.opentripplanner.model.transfer.TransferConstraint.REGULAR_TRANSFER;
@@ -84,14 +85,12 @@ public void testBasicPath() {
)
.egress(BasicPathTestCase.EGRESS_DURATION);
- Assertions.assertEquals(
- BasicPathTestCase.BASIC_PATH_AS_STRING,
- path.toString(this::stopIndexToName)
- );
- Assertions.assertEquals(
+ assertEquals(BasicPathTestCase.BASIC_PATH_AS_STRING, path.toString(this::stopIndexToName));
+ assertEquals(
BasicPathTestCase.BASIC_PATH_AS_DETAILED_STRING,
path.toStringDetailed(this::stopIndexToName)
);
- Assertions.assertEquals(BasicPathTestCase.TOTAL_C1, path.c1());
+ assertEquals(BasicPathTestCase.TOTAL_C1, path.c1());
+ assertTrue(path.isC2Set());
}
}
diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/K01_TransitPriorityTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/K01_TransitPriorityTest.java
index be544f26a11..c1db11be779 100644
--- a/src/test/java/org/opentripplanner/raptor/moduletests/K01_TransitPriorityTest.java
+++ b/src/test/java/org/opentripplanner/raptor/moduletests/K01_TransitPriorityTest.java
@@ -67,13 +67,20 @@ public DominanceFunction dominanceFunction() {
RaptorConfig.defaultConfigForTest()
);
+ /**
+ * Each pattern departs at the same time, but arrives at different times. They may belong to
+ * different groups. Line U1 is not optimal, because it slower than L1 and is in the same
+ * group as L1. Given a slack on the cost equals to ~90s makes both L1 and L2 optimal (since
+ * they are in different groups), but not L3 (which is in its own group, but its cost is
+ * outside the range allowed by the slack).
+ */
@BeforeEach
private void prepareRequest() {
- // Each pattern depart at the same time, but arrive with 60s between them.
- // Given a slack on the cost equals to ~90s make both L1 and L2 optimal, but no L3
data.withRoutes(
route(pattern("L1", STOP_B, STOP_C).withPriorityGroup(GROUP_A))
.withTimetable(schedule("00:02 00:12")),
+ route(pattern("U1", STOP_B, STOP_C).withPriorityGroup(GROUP_A))
+ .withTimetable(schedule("00:02 00:12:01")),
route(pattern("L2", STOP_B, STOP_C).withPriorityGroup(GROUP_B))
.withTimetable(schedule("00:02 00:13")),
route(pattern("L3", STOP_B, STOP_C).withPriorityGroup(GROUP_C))
diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/K02_TransitPriorityDestinationTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/K02_TransitPriorityDestinationTest.java
new file mode 100644
index 00000000000..c7b64cb5b9e
--- /dev/null
+++ b/src/test/java/org/opentripplanner/raptor/moduletests/K02_TransitPriorityDestinationTest.java
@@ -0,0 +1,151 @@
+package org.opentripplanner.raptor.moduletests;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.opentripplanner.raptor._data.RaptorTestConstants.STOP_B;
+import static org.opentripplanner.raptor._data.RaptorTestConstants.STOP_C;
+import static org.opentripplanner.raptor._data.RaptorTestConstants.STOP_D;
+import static org.opentripplanner.raptor._data.RaptorTestConstants.STOP_E;
+import static org.opentripplanner.raptor._data.RaptorTestConstants.STOP_F;
+import static org.opentripplanner.raptor._data.RaptorTestConstants.STOP_G;
+import static org.opentripplanner.raptor._data.RaptorTestConstants.T00_00;
+import static org.opentripplanner.raptor._data.RaptorTestConstants.T01_00;
+import static org.opentripplanner.raptor._data.api.PathUtils.pathsToString;
+import static org.opentripplanner.raptor._data.transit.TestAccessEgress.walk;
+import static org.opentripplanner.raptor._data.transit.TestRoute.route;
+import static org.opentripplanner.raptor._data.transit.TestTripPattern.pattern;
+import static org.opentripplanner.raptor._data.transit.TestTripSchedule.schedule;
+
+import java.time.Duration;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.opentripplanner.raptor.RaptorService;
+import org.opentripplanner.raptor._data.transit.TestTransitData;
+import org.opentripplanner.raptor._data.transit.TestTripSchedule;
+import org.opentripplanner.raptor.api.model.DominanceFunction;
+import org.opentripplanner.raptor.api.request.RaptorProfile;
+import org.opentripplanner.raptor.api.request.RaptorRequestBuilder;
+import org.opentripplanner.raptor.api.request.RaptorTransitPriorityGroupCalculator;
+import org.opentripplanner.raptor.configure.RaptorConfig;
+import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.RaptorCostConverter;
+
+/**
+ * FEATURE UNDER TEST
+ *
+ * Raptor should be able to handle route request with transit-priority.
+ */
+public class K02_TransitPriorityDestinationTest {
+
+ private static final RaptorTransitPriorityGroupCalculator PRIORITY_GROUP_CALCULATOR = new RaptorTransitPriorityGroupCalculator() {
+ @Override
+ public int mergeTransitPriorityGroupIds(int currentGroupIds, int boardingGroupId) {
+ return currentGroupIds | boardingGroupId;
+ }
+
+ /**
+ * Left dominate right, if right has at least one priority group not in left.
+ */
+ @Override
+ public DominanceFunction dominanceFunction() {
+ return (l, r) -> ((l ^ r) & r) != 0;
+ }
+ };
+
+ private static final int GROUP_A = 0x01;
+ private static final int GROUP_B = 0x02;
+ private static final int GROUP_C = 0x04;
+ private static final int GROUP_AB = PRIORITY_GROUP_CALCULATOR.mergeTransitPriorityGroupIds(
+ GROUP_A,
+ GROUP_B
+ );
+ private static final int GROUP_AC = PRIORITY_GROUP_CALCULATOR.mergeTransitPriorityGroupIds(
+ GROUP_A,
+ GROUP_C
+ );
+ private static final int C1_SLACK_90s = RaptorCostConverter.toRaptorCost(90);
+
+ private final TestTransitData data = new TestTransitData();
+ private final RaptorRequestBuilder requestBuilder = new RaptorRequestBuilder<>();
+ private final RaptorService raptorService = new RaptorService<>(
+ RaptorConfig.defaultConfigForTest()
+ );
+
+ @BeforeEach
+ private void prepareRequest() {
+ // Each pattern depart at the same time, but arrive at different times and they may
+ // belong to different groups.
+ // Line U1 is not optimal, because it slower than L1 and is in the same group.
+ // Given a slack on the cost equals to ~90s this makes both L1 and L2 optimal (since
+ // they are in different groups), but not L3 (which certainly is in its own group but
+ // its cost is outside the range allowed by the slack).
+ data.withRoutes(
+ route(pattern("L1", STOP_B, STOP_C).withPriorityGroup(GROUP_A))
+ .withTimetable(schedule("00:02 00:12")),
+ route(pattern("U1", STOP_B, STOP_D).withPriorityGroup(GROUP_A))
+ .withTimetable(schedule("00:02 00:12:01")),
+ route(pattern("L2", STOP_B, STOP_E).withPriorityGroup(GROUP_B))
+ .withTimetable(schedule("00:02 00:13")),
+ route(pattern("L3", STOP_B, STOP_F).withPriorityGroup(GROUP_C))
+ .withTimetable(schedule("00:02 00:14"))
+ );
+
+ requestBuilder
+ .profile(RaptorProfile.MULTI_CRITERIA)
+ // TODO: 2023-07-24 Currently heuristics does not work with pass-through so we
+ // have to turn them off. Make sure to re-enable optimization later when it's fixed
+ .clearOptimizations();
+
+ requestBuilder
+ .searchParams()
+ .earliestDepartureTime(T00_00)
+ .latestArrivalTime(T01_00)
+ .searchWindow(Duration.ofMinutes(2))
+ .timetable(true);
+
+ requestBuilder.withMultiCriteria(mc ->
+ // Raptor cost 9000 ~= 90 seconds slack
+ mc
+ .withRelaxC1(value -> value + C1_SLACK_90s)
+ .withTransitPriorityCalculator(PRIORITY_GROUP_CALCULATOR)
+ );
+ // Add 1 second access/egress paths
+ requestBuilder
+ .searchParams()
+ .addAccessPaths(walk(STOP_B, 1))
+ .addEgressPaths(walk(STOP_C, 1))
+ .addEgressPaths(walk(STOP_D, 1))
+ .addEgressPaths(walk(STOP_E, 1))
+ .addEgressPaths(walk(STOP_F, 1));
+ assetGroupCalculatorIsSetupCorrect();
+ }
+
+ @Test
+ public void transitPriority() {
+ // We expect L1 & L2 but not L3, since the cost of L3 is > $90.00.
+ assertEquals(
+ """
+ Walk 1s ~ B ~ BUS L1 0:02 0:12 ~ C ~ Walk 1s [0:01:59 0:12:01 10m2s Tₓ0 C₁1_204 C₂1]
+ Walk 1s ~ B ~ BUS L2 0:02 0:13 ~ E ~ Walk 1s [0:01:59 0:13:01 11m2s Tₓ0 C₁1_264 C₂2]
+ """.trim(),
+ pathsToString(raptorService.route(requestBuilder.build(), data))
+ );
+ }
+
+ /**
+ * Make sure the calculator and group setup is done correct.
+ */
+ void assetGroupCalculatorIsSetupCorrect() {
+ var d = PRIORITY_GROUP_CALCULATOR.dominanceFunction();
+
+ assertTrue(d.leftDominateRight(GROUP_A, GROUP_B));
+ assertTrue(d.leftDominateRight(GROUP_B, GROUP_A));
+ assertFalse(d.leftDominateRight(GROUP_A, GROUP_A));
+ // 3 = 1&2, 5 = 1&4
+ assertTrue(d.leftDominateRight(GROUP_A, GROUP_AB));
+ assertFalse(d.leftDominateRight(GROUP_AB, GROUP_A));
+ assertFalse(d.leftDominateRight(GROUP_AB, GROUP_AB));
+ assertTrue(d.leftDominateRight(GROUP_AB, GROUP_AC));
+ assertTrue(d.leftDominateRight(GROUP_AC, GROUP_AB));
+ }
+}
diff --git a/src/test/java/org/opentripplanner/raptor/rangeraptor/context/SearchContextTest.java b/src/test/java/org/opentripplanner/raptor/rangeraptor/context/SearchContextTest.java
index 8ac0af5b3a8..4ffa4ed1cd9 100644
--- a/src/test/java/org/opentripplanner/raptor/rangeraptor/context/SearchContextTest.java
+++ b/src/test/java/org/opentripplanner/raptor/rangeraptor/context/SearchContextTest.java
@@ -1,9 +1,14 @@
package org.opentripplanner.raptor.rangeraptor.context;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.opentripplanner.raptor.api.model.SearchDirection.FORWARD;
+import static org.opentripplanner.raptor.api.model.SearchDirection.REVERSE;
import static org.opentripplanner.raptor.api.request.RaptorProfile.MULTI_CRITERIA;
import static org.opentripplanner.raptor.api.request.RaptorProfile.STANDARD;
import static org.opentripplanner.raptor.rangeraptor.context.SearchContext.accessOrEgressPaths;
+import static org.opentripplanner.raptor.rangeraptor.internalapi.ParetoSetTime.USE_ARRIVAL_TIME;
+import static org.opentripplanner.raptor.rangeraptor.internalapi.ParetoSetTime.USE_DEPARTURE_TIME;
+import static org.opentripplanner.raptor.rangeraptor.internalapi.ParetoSetTime.USE_TIMETABLE;
import java.util.Collection;
import java.util.Comparator;
@@ -11,14 +16,20 @@
import java.util.stream.Collectors;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
import org.opentripplanner.raptor._data.RaptorTestConstants;
import org.opentripplanner.raptor._data.transit.TestAccessEgress;
import org.opentripplanner.raptor._data.transit.TestTripSchedule;
import org.opentripplanner.raptor.api.model.RaptorAccessEgress;
+import org.opentripplanner.raptor.api.model.SearchDirection;
import org.opentripplanner.raptor.api.request.RaptorRequestBuilder;
+import org.opentripplanner.raptor.rangeraptor.internalapi.ParetoSetTime;
class SearchContextTest implements RaptorTestConstants {
+ public static final TestAccessEgress ANY_WALK = TestAccessEgress.walk(1, 1);
private final boolean GET_ACCESS = true;
private final boolean GET_EGRESS = false;
private final RaptorAccessEgress PATH_A_10s = TestAccessEgress.walk(STOP_A, D10s);
@@ -82,4 +93,43 @@ private static List sort(Collection c) {
.sorted(Comparator.comparingInt(it -> it.stop() * 10_000 + it.durationInSeconds()))
.collect(Collectors.toList());
}
+
+ static List paretoSetTimeConfigTestCase() {
+ return List.of(
+ Arguments.of(USE_TIMETABLE, true, false, FORWARD),
+ Arguments.of(USE_TIMETABLE, true, false, REVERSE),
+ Arguments.of(USE_DEPARTURE_TIME, false, true, FORWARD),
+ Arguments.of(USE_DEPARTURE_TIME, false, false, REVERSE),
+ Arguments.of(USE_ARRIVAL_TIME, false, true, REVERSE),
+ Arguments.of(USE_ARRIVAL_TIME, false, false, FORWARD)
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("paretoSetTimeConfigTestCase")
+ void testParetoSetTimeConfig(
+ ParetoSetTime expected,
+ boolean timetable,
+ boolean prefLateArrival,
+ SearchDirection searchDirection
+ ) {
+ assertEquals(
+ expected,
+ SearchContext.paretoSetTimeConfig(
+ new RaptorRequestBuilder()
+ .searchParams()
+ // EDT, LAT, access and egress is required, but not used
+ .addAccessPaths(ANY_WALK)
+ .addEgressPaths(ANY_WALK)
+ .earliestDepartureTime(12)
+ .latestArrivalTime(120)
+ // Relevant parameters
+ .timetable(timetable)
+ .preferLateArrival(prefLateArrival)
+ .build()
+ .searchParams(),
+ searchDirection
+ )
+ );
+ }
}
diff --git a/src/test/java/org/opentripplanner/raptor/rangeraptor/path/PathParetoSetComparatorsTest.java b/src/test/java/org/opentripplanner/raptor/rangeraptor/path/PathParetoSetComparatorsTest.java
index d8d5ddfb472..296103499a1 100644
--- a/src/test/java/org/opentripplanner/raptor/rangeraptor/path/PathParetoSetComparatorsTest.java
+++ b/src/test/java/org/opentripplanner/raptor/rangeraptor/path/PathParetoSetComparatorsTest.java
@@ -2,8 +2,20 @@
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import org.junit.jupiter.api.Test;
+import static org.opentripplanner.raptor.rangeraptor.internalapi.ParetoSetCost.NONE;
+import static org.opentripplanner.raptor.rangeraptor.internalapi.ParetoSetCost.USE_C1;
+import static org.opentripplanner.raptor.rangeraptor.internalapi.ParetoSetCost.USE_C1_AND_C2;
+import static org.opentripplanner.raptor.rangeraptor.internalapi.ParetoSetCost.USE_C1_RELAXED_IF_C2_IS_OPTIMAL;
+import static org.opentripplanner.raptor.rangeraptor.internalapi.ParetoSetCost.USE_C1_RELAX_DESTINATION;
+import static org.opentripplanner.raptor.rangeraptor.internalapi.ParetoSetTime.USE_ARRIVAL_TIME;
+import static org.opentripplanner.raptor.rangeraptor.internalapi.ParetoSetTime.USE_DEPARTURE_TIME;
+import static org.opentripplanner.raptor.rangeraptor.internalapi.ParetoSetTime.USE_TIMETABLE;
+import static org.opentripplanner.raptor.rangeraptor.path.PathParetoSetComparators.paretoComparator;
+
+import java.util.List;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
import org.opentripplanner.raptor._data.api.TestRaptorPath;
import org.opentripplanner.raptor.api.model.DominanceFunction;
import org.opentripplanner.raptor.api.model.GeneralizedCostRelaxFunction;
@@ -11,325 +23,93 @@
import org.opentripplanner.raptor.api.model.RelaxFunction;
import org.opentripplanner.raptor.api.model.SearchDirection;
import org.opentripplanner.raptor.api.path.RaptorPath;
+import org.opentripplanner.raptor.rangeraptor.internalapi.ParetoSetCost;
+import org.opentripplanner.raptor.rangeraptor.internalapi.ParetoSetTime;
import org.opentripplanner.raptor.util.paretoset.ParetoComparator;
public class PathParetoSetComparatorsTest {
- final int BIG_VALUE = 999;
- final int SMALL_VALUE = 100;
- final int NO_VALUE = 0;
-
- private final DominanceFunction DOMINANCE_FUNCTION = (left, right) -> left > right;
- private final RelaxFunction RELAX_FUNCTION = GeneralizedCostRelaxFunction.of(1.5, 0);
-
- @Test
- public void testComparatorStandardArrivalTime() {
- var comparator = PathParetoSetComparators.paretoComparator(
- false,
- false,
- false,
- SearchDirection.FORWARD,
- RelaxFunction.NORMAL,
- null
- );
-
- verifyEndTimeComparator(comparator);
- verifyNumberOfTransfers(comparator);
- }
-
- @Test
- public void testComparatorStandardArrivalTimeAndC2() {
- var comparator = PathParetoSetComparators.paretoComparator(
- false,
- false,
- false,
- SearchDirection.FORWARD,
- RelaxFunction.NORMAL,
- DOMINANCE_FUNCTION
- );
-
- verifyEndTimeComparator(comparator);
- verifyNumberOfTransfers(comparator);
- verifyC2Comparator(comparator);
- }
-
- @Test
- public void testComparatorStandardDepartureTime() {
- var comparator = PathParetoSetComparators.paretoComparator(
- false,
- false,
- true,
- SearchDirection.FORWARD,
- RelaxFunction.NORMAL,
- null
- );
-
- verifyStartTimeComparator(comparator);
-
- verifyNumberOfTransfers(comparator);
- }
-
- @Test
- public void testComparatorStandardDepartureTimeAndC2() {
- var comparator = PathParetoSetComparators.paretoComparator(
- false,
- false,
- true,
- SearchDirection.FORWARD,
- RelaxFunction.NORMAL,
- DOMINANCE_FUNCTION
- );
-
- verifyStartTimeComparator(comparator);
- verifyNumberOfTransfers(comparator);
- verifyC2Comparator(comparator);
- }
-
- @Test
- public void testComparatorTimetable() {
- var comparator = PathParetoSetComparators.paretoComparator(
- false,
- true,
- false,
- SearchDirection.FORWARD,
- RelaxFunction.NORMAL,
- null
- );
-
- verifyRangeRaptorIterationDepartureTime(comparator);
- verifyEndTimeComparator(comparator);
- verifyNumberOfTransfers(comparator);
- }
-
- @Test
- public void testComparatorTimetableAndC2() {
- var comparator = PathParetoSetComparators.paretoComparator(
- false,
- true,
- false,
- SearchDirection.FORWARD,
- RelaxFunction.NORMAL,
- DOMINANCE_FUNCTION
- );
-
- verifyRangeRaptorIterationDepartureTime(comparator);
- verifyEndTimeComparator(comparator);
- verifyNumberOfTransfers(comparator);
- verifyC2Comparator(comparator);
- }
-
- @Test
- public void testComparatorTimetableAndC1() {
- var comparator = PathParetoSetComparators.paretoComparator(
- true,
- true,
- false,
- SearchDirection.FORWARD,
- RelaxFunction.NORMAL,
- null
- );
-
- verifyRangeRaptorIterationDepartureTime(comparator);
- verifyEndTimeComparator(comparator);
- verifyNumberOfTransfers(comparator);
- verifyDurationComparator(comparator);
- verifyC1Comparator(comparator);
- }
-
- @Test
- public void testComparatorTimetableAndRelaxedC1() {
- var comparator = PathParetoSetComparators.paretoComparator(
- true,
- true,
- false,
- SearchDirection.FORWARD,
- RELAX_FUNCTION,
- null
- );
-
- verifyRangeRaptorIterationDepartureTime(comparator);
- verifyEndTimeComparator(comparator);
- verifyNumberOfTransfers(comparator);
- verifyDurationComparator(comparator);
- verifyRelaxedC1Comparator(comparator);
- }
-
- @Test
- public void testComparatorWithC1() {
- var comparator = PathParetoSetComparators.paretoComparator(
- true,
- false,
- false,
- SearchDirection.FORWARD,
- RelaxFunction.NORMAL,
- null
- );
-
- verifyEndTimeComparator(comparator);
- verifyNumberOfTransfers(comparator);
- verifyDurationComparator(comparator);
- verifyC1Comparator(comparator);
- }
-
- @Test
- public void testComparatorDepartureTimeAndC1() {
- var comparator = PathParetoSetComparators.paretoComparator(
- true,
- false,
- true,
- SearchDirection.FORWARD,
- RelaxFunction.NORMAL,
- null
- );
-
- verifyStartTimeComparator(comparator);
- verifyNumberOfTransfers(comparator);
- verifyDurationComparator(comparator);
- verifyC1Comparator(comparator);
- }
-
- @Test
- public void comparatorArrivalTimeAndRelaxedC1() {
- var comparator = PathParetoSetComparators.paretoComparator(
- true,
- false,
- false,
- SearchDirection.FORWARD,
- RELAX_FUNCTION,
- null
- );
-
- verifyEndTimeComparator(comparator);
- verifyNumberOfTransfers(comparator);
- verifyDurationComparator(comparator);
- verifyRelaxedC1Comparator(comparator);
- }
-
- @Test
- public void testComparatorDepartureTimeAndRelaxedC1() {
- var comparator = PathParetoSetComparators.paretoComparator(
- true,
- false,
- true,
- SearchDirection.FORWARD,
- RELAX_FUNCTION,
- null
- );
-
- verifyStartTimeComparator(comparator);
- verifyNumberOfTransfers(comparator);
- verifyDurationComparator(comparator);
- verifyRelaxedC1Comparator(comparator);
- }
-
- @Test
- public void testComparatorTimetableAndC1AndC2() {
- var comparator = PathParetoSetComparators.paretoComparator(
- true,
- true,
- false,
- SearchDirection.FORWARD,
- RelaxFunction.NORMAL,
- DOMINANCE_FUNCTION
- );
-
- verifyRangeRaptorIterationDepartureTime(comparator);
- verifyEndTimeComparator(comparator);
- verifyNumberOfTransfers(comparator);
- verifyDurationComparator(comparator);
- verifyC1Comparator(comparator);
- verifyC2Comparator(comparator);
- }
-
- @Test
- public void testComparatorTimetableAndRelaxedC1AndC2() {
- var comparator = PathParetoSetComparators.paretoComparator(
- true,
- true,
- false,
- SearchDirection.FORWARD,
- RELAX_FUNCTION,
- DOMINANCE_FUNCTION
+ private static final int ANY = 0;
+ private static final int NORMAL = 100;
+ private static final int SMALL = 50;
+ private static final int LARGE = 999;
+ private static final int RELAXED = 140;
+ private static final int RELAXED_DELTA_LIMIT = 50;
+
+ private static final DominanceFunction DOMINANCE_FN = (left, right) -> left < right;
+ private static final DominanceFunction NO_COMP = null;
+
+ private static final RelaxFunction RELAX_FN = GeneralizedCostRelaxFunction.of(
+ 1.0,
+ RELAXED_DELTA_LIMIT
+ );
+ private static final RelaxFunction NO_RELAX_FN = RelaxFunction.NORMAL;
+
+ static List testCases() {
+ return List.of(
+ // Arrival time
+ Arguments.of(USE_ARRIVAL_TIME, NONE, NO_RELAX_FN, NO_COMP),
+ Arguments.of(USE_ARRIVAL_TIME, USE_C1, NO_RELAX_FN, NO_COMP),
+ Arguments.of(USE_ARRIVAL_TIME, USE_C1_AND_C2, NO_RELAX_FN, DOMINANCE_FN),
+ // TODO: This is failing, error in implementation
+ Arguments.of(USE_ARRIVAL_TIME, USE_C1_RELAXED_IF_C2_IS_OPTIMAL, RELAX_FN, DOMINANCE_FN),
+ Arguments.of(USE_ARRIVAL_TIME, USE_C1_RELAX_DESTINATION, RELAX_FN, NO_COMP),
+ // Departure time
+ Arguments.of(USE_DEPARTURE_TIME, NONE, NO_RELAX_FN, NO_COMP),
+ Arguments.of(USE_DEPARTURE_TIME, USE_C1_AND_C2, NO_RELAX_FN, DOMINANCE_FN),
+ // TODO: This is failing, error in implementation
+ Arguments.of(USE_DEPARTURE_TIME, USE_C1_RELAXED_IF_C2_IS_OPTIMAL, RELAX_FN, DOMINANCE_FN),
+ Arguments.of(USE_DEPARTURE_TIME, USE_C1_RELAX_DESTINATION, RELAX_FN, NO_COMP),
+ // Timetable
+ Arguments.of(USE_TIMETABLE, NONE, NO_RELAX_FN, NO_COMP),
+ Arguments.of(USE_TIMETABLE, USE_C1_AND_C2, NO_RELAX_FN, DOMINANCE_FN),
+ // TODO: This is failing, error in implementation
+ Arguments.of(USE_TIMETABLE, USE_C1_RELAXED_IF_C2_IS_OPTIMAL, RELAX_FN, DOMINANCE_FN),
+ Arguments.of(USE_TIMETABLE, USE_C1_RELAX_DESTINATION, RELAX_FN, NO_COMP)
);
-
- verifyRangeRaptorIterationDepartureTime(comparator);
- verifyEndTimeComparator(comparator);
- verifyNumberOfTransfers(comparator);
- verifyDurationComparator(comparator);
- verifyRelaxedC1Comparator(comparator);
- verifyC2Comparator(comparator);
- }
-
- @Test
- public void testComparatorWithC1AndC2() {
- var comparator = PathParetoSetComparators.paretoComparator(
- true,
- false,
- false,
- SearchDirection.FORWARD,
- RelaxFunction.NORMAL,
- DOMINANCE_FUNCTION
- );
-
- verifyEndTimeComparator(comparator);
- verifyNumberOfTransfers(comparator);
- verifyDurationComparator(comparator);
- verifyC1Comparator(comparator);
- verifyC2Comparator(comparator);
}
- @Test
- public void testComparatorDepartureTimeAndC1AndC2() {
- var comparator = PathParetoSetComparators.paretoComparator(
- true,
- false,
- true,
- SearchDirection.FORWARD,
- RelaxFunction.NORMAL,
- DOMINANCE_FUNCTION
- );
-
- verifyStartTimeComparator(comparator);
- verifyNumberOfTransfers(comparator);
- verifyDurationComparator(comparator);
- verifyC1Comparator(comparator);
- verifyC2Comparator(comparator);
- }
-
- @Test
- public void testComparatorArrivalTimeAndRelaxedC1AndC2() {
- var comparator = PathParetoSetComparators.paretoComparator(
- true,
- false,
- false,
- SearchDirection.FORWARD,
- RELAX_FUNCTION,
- DOMINANCE_FUNCTION
- );
-
- verifyEndTimeComparator(comparator);
- verifyNumberOfTransfers(comparator);
- verifyDurationComparator(comparator);
- verifyRelaxedC1Comparator(comparator);
- verifyC2Comparator(comparator);
- }
-
- @Test
- public void testComparatorDepartureTimeAndRelaxedC1AndC2() {
- var comparator = PathParetoSetComparators.paretoComparator(
- true,
- false,
- true,
- SearchDirection.FORWARD,
- RELAX_FUNCTION,
- DOMINANCE_FUNCTION
- );
-
- verifyStartTimeComparator(comparator);
+ @ParameterizedTest
+ @MethodSource("testCases")
+ public void testComparator(
+ ParetoSetTime time,
+ ParetoSetCost cost,
+ RelaxFunction relaxC1,
+ DominanceFunction comp2
+ ) {
+ var comparator = paretoComparator(time, cost, relaxC1, comp2);
verifyNumberOfTransfers(comparator);
- verifyDurationComparator(comparator);
- verifyRelaxedC1Comparator(comparator);
- verifyC2Comparator(comparator);
+ switch (time) {
+ case USE_ARRIVAL_TIME:
+ verifyEndTimeComparator(comparator);
+ break;
+ case USE_DEPARTURE_TIME:
+ verifyStartTimeComparator(comparator);
+ break;
+ case USE_TIMETABLE:
+ verifyIterationDepartureTime(comparator);
+ verifyEndTimeComparator(comparator);
+ break;
+ }
+
+ switch (cost) {
+ case USE_C1:
+ verifyC1Comparator(comparator);
+ break;
+ case USE_C1_AND_C2:
+ verifyC1Comparator(comparator);
+ verifyC2Comparator(comparator);
+ break;
+ case USE_C1_RELAXED_IF_C2_IS_OPTIMAL:
+ verifyRelaxedC1IfC2Optimal(comparator);
+ break;
+ case USE_C1_RELAX_DESTINATION:
+ verifyRelaxedC1Comparator(comparator);
+ break;
+ case NONE:
+ default:
+ break;
+ }
}
/**
@@ -340,14 +120,14 @@ private void verifyStartTimeComparator(
) {
assertTrue(
comparator.leftDominanceExist(
- new TestRaptorPath(NO_VALUE, BIG_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE),
- new TestRaptorPath(NO_VALUE, SMALL_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE)
+ new TestRaptorPath(ANY, NORMAL, ANY, ANY, ANY, LARGE, ANY),
+ new TestRaptorPath(ANY, SMALL, ANY, ANY, ANY, ANY, ANY)
)
);
assertFalse(
comparator.leftDominanceExist(
- new TestRaptorPath(NO_VALUE, SMALL_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE),
- new TestRaptorPath(NO_VALUE, BIG_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE)
+ new TestRaptorPath(ANY, NORMAL, ANY, ANY, ANY, LARGE, ANY),
+ new TestRaptorPath(ANY, LARGE, ANY, ANY, ANY, ANY, ANY)
)
);
}
@@ -355,20 +135,20 @@ private void verifyStartTimeComparator(
/**
* Verify that bigger rangeRaptorIterationDepartureTime always wins
*/
- private void verifyRangeRaptorIterationDepartureTime(
+ private void verifyIterationDepartureTime(
ParetoComparator> comparator
) {
// Verify that bigger rangeRaptorIterationDepartureTime always wins
assertTrue(
comparator.leftDominanceExist(
- new TestRaptorPath(BIG_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE),
- new TestRaptorPath(SMALL_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE)
+ new TestRaptorPath(NORMAL, ANY, ANY, ANY, ANY, LARGE, ANY),
+ new TestRaptorPath(SMALL, ANY, ANY, ANY, ANY, ANY, ANY)
)
);
assertFalse(
comparator.leftDominanceExist(
- new TestRaptorPath(SMALL_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE),
- new TestRaptorPath(BIG_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE)
+ new TestRaptorPath(NORMAL, ANY, ANY, ANY, ANY, LARGE, ANY),
+ new TestRaptorPath(LARGE, ANY, ANY, ANY, ANY, ANY, ANY)
)
);
}
@@ -382,14 +162,14 @@ private void verifyEndTimeComparator(
// Verify that lower endTime always wins
assertTrue(
comparator.leftDominanceExist(
- new TestRaptorPath(NO_VALUE, NO_VALUE, SMALL_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE),
- new TestRaptorPath(NO_VALUE, NO_VALUE, BIG_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE)
+ new TestRaptorPath(ANY, ANY, NORMAL, ANY, ANY, LARGE, ANY),
+ new TestRaptorPath(ANY, ANY, LARGE, ANY, ANY, SMALL, ANY)
)
);
assertFalse(
comparator.leftDominanceExist(
- new TestRaptorPath(NO_VALUE, NO_VALUE, BIG_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE),
- new TestRaptorPath(NO_VALUE, NO_VALUE, SMALL_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE)
+ new TestRaptorPath(ANY, ANY, NORMAL, ANY, ANY, LARGE, ANY),
+ new TestRaptorPath(ANY, ANY, SMALL, ANY, ANY, SMALL, ANY)
)
);
}
@@ -402,14 +182,14 @@ private void verifyNumberOfTransfers(
) {
assertTrue(
comparator.leftDominanceExist(
- new TestRaptorPath(NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, SMALL_VALUE, NO_VALUE, NO_VALUE),
- new TestRaptorPath(NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, BIG_VALUE, NO_VALUE, NO_VALUE)
+ new TestRaptorPath(ANY, ANY, ANY, ANY, NORMAL, LARGE, ANY),
+ new TestRaptorPath(ANY, ANY, ANY, ANY, LARGE, SMALL, ANY)
)
);
assertFalse(
comparator.leftDominanceExist(
- new TestRaptorPath(NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, BIG_VALUE, NO_VALUE, NO_VALUE),
- new TestRaptorPath(NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, SMALL_VALUE, NO_VALUE, NO_VALUE)
+ new TestRaptorPath(ANY, ANY, ANY, ANY, NORMAL, LARGE, ANY),
+ new TestRaptorPath(ANY, ANY, ANY, ANY, SMALL, SMALL, ANY)
)
);
}
@@ -422,14 +202,14 @@ private void verifyDurationComparator(
) {
assertTrue(
comparator.leftDominanceExist(
- new TestRaptorPath(NO_VALUE, NO_VALUE, NO_VALUE, SMALL_VALUE, NO_VALUE, NO_VALUE, NO_VALUE),
- new TestRaptorPath(NO_VALUE, NO_VALUE, NO_VALUE, BIG_VALUE, NO_VALUE, NO_VALUE, NO_VALUE)
+ new TestRaptorPath(ANY, ANY, ANY, NORMAL, ANY, ANY, ANY),
+ new TestRaptorPath(ANY, ANY, ANY, LARGE, ANY, ANY, ANY)
)
);
assertFalse(
comparator.leftDominanceExist(
- new TestRaptorPath(NO_VALUE, NO_VALUE, NO_VALUE, BIG_VALUE, NO_VALUE, NO_VALUE, NO_VALUE),
- new TestRaptorPath(NO_VALUE, NO_VALUE, NO_VALUE, SMALL_VALUE, NO_VALUE, NO_VALUE, NO_VALUE)
+ new TestRaptorPath(ANY, ANY, ANY, NORMAL, ANY, ANY, ANY),
+ new TestRaptorPath(ANY, ANY, ANY, SMALL, ANY, ANY, ANY)
)
);
}
@@ -440,14 +220,14 @@ private void verifyDurationComparator(
private void verifyC1Comparator(ParetoComparator> comparator) {
assertTrue(
comparator.leftDominanceExist(
- new TestRaptorPath(NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, SMALL_VALUE, NO_VALUE),
- new TestRaptorPath(NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, BIG_VALUE, NO_VALUE)
+ new TestRaptorPath(ANY, ANY, ANY, ANY, ANY, NORMAL, ANY),
+ new TestRaptorPath(ANY, ANY, ANY, ANY, ANY, LARGE, ANY)
)
);
assertFalse(
comparator.leftDominanceExist(
- new TestRaptorPath(NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, BIG_VALUE, NO_VALUE),
- new TestRaptorPath(NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, SMALL_VALUE, NO_VALUE)
+ new TestRaptorPath(ANY, ANY, ANY, ANY, ANY, NORMAL, ANY),
+ new TestRaptorPath(ANY, ANY, ANY, ANY, ANY, SMALL, ANY)
)
);
}
@@ -460,34 +240,63 @@ private void verifyRelaxedC1Comparator(
) {
assertTrue(
comparator.leftDominanceExist(
- new TestRaptorPath(
- NO_VALUE,
- NO_VALUE,
- NO_VALUE,
- NO_VALUE,
- NO_VALUE,
- (int) (SMALL_VALUE * (1.4)),
- NO_VALUE
- ),
- new TestRaptorPath(NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, SMALL_VALUE, NO_VALUE)
+ new TestRaptorPath(ANY, ANY, ANY, ANY, ANY, RELAXED, ANY),
+ new TestRaptorPath(ANY, ANY, ANY, ANY, ANY, NORMAL, ANY)
)
);
assertFalse(
comparator.leftDominanceExist(
- new TestRaptorPath(
- NO_VALUE,
- NO_VALUE,
- NO_VALUE,
- NO_VALUE,
- NO_VALUE,
- (int) (SMALL_VALUE * (1.6)),
- NO_VALUE
- ),
- new TestRaptorPath(NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, SMALL_VALUE, NO_VALUE)
+ new TestRaptorPath(ANY, ANY, ANY, ANY, ANY, LARGE, ANY),
+ new TestRaptorPath(ANY, ANY, ANY, ANY, ANY, NORMAL, ANY)
)
);
}
+ /**
+ * Verify that relax function is used in the c1 comparator, if and only if c2 is optimal.
+ */
+ private void verifyRelaxedC1IfC2Optimal(
+ ParetoComparator> comparator
+ ) {
+ assertTrue(
+ comparator.leftDominanceExist(
+ new TestRaptorPath(ANY, ANY, ANY, ANY, ANY, SMALL, NORMAL),
+ new TestRaptorPath(ANY, ANY, ANY, ANY, ANY, NORMAL, NORMAL)
+ ),
+ "c1 is optimal, c2 is not => path is optimal"
+ );
+ assertTrue(
+ comparator.leftDominanceExist(
+ new TestRaptorPath(ANY, ANY, ANY, ANY, ANY, RELAXED, SMALL),
+ new TestRaptorPath(ANY, ANY, ANY, ANY, ANY, NORMAL, NORMAL)
+ ),
+ "c2 is optimal, c1 is within relaxed limit => path is optimal"
+ );
+ assertFalse(
+ comparator.leftDominanceExist(
+ new TestRaptorPath(ANY, ANY, ANY, ANY, ANY, RELAXED, ANY),
+ new TestRaptorPath(ANY, ANY, ANY, ANY, ANY, NORMAL, ANY)
+ ),
+ "c2 is not optimal, c1 is within relaxed limit => path is NOT optimal"
+ );
+ assertFalse(
+ // c2 is optimal, but c1 is not within relaxed limit
+ comparator.leftDominanceExist(
+ new TestRaptorPath(ANY, ANY, ANY, ANY, ANY, LARGE, SMALL),
+ new TestRaptorPath(ANY, ANY, ANY, ANY, ANY, NORMAL, NORMAL)
+ ),
+ "c2 is optimal, c1 is not within relaxed limit => path is NOT optimal"
+ );
+ assertFalse(
+ // c1 and c2 is not optimal (they are equal)
+ comparator.leftDominanceExist(
+ new TestRaptorPath(ANY, ANY, ANY, ANY, ANY, NORMAL, NORMAL),
+ new TestRaptorPath(ANY, ANY, ANY, ANY, ANY, NORMAL, NORMAL)
+ ),
+ "c1 and c2 is not optimal"
+ );
+ }
+
/**
* Verify that dominance function is used in a comparator. This method operates on an assumption
* that dominance function is l.c2 > r.c2
@@ -496,14 +305,14 @@ private void verifyC2Comparator(ParetoComparator>
// Verify that dominance function is used
assertTrue(
comparator.leftDominanceExist(
- new TestRaptorPath(NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, BIG_VALUE),
- new TestRaptorPath(NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, SMALL_VALUE)
+ new TestRaptorPath(ANY, ANY, ANY, ANY, ANY, ANY, SMALL),
+ new TestRaptorPath(ANY, ANY, ANY, ANY, ANY, ANY, NORMAL)
)
);
assertFalse(
comparator.leftDominanceExist(
- new TestRaptorPath(NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, SMALL_VALUE),
- new TestRaptorPath(NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, BIG_VALUE)
+ new TestRaptorPath(ANY, ANY, ANY, ANY, ANY, ANY, LARGE),
+ new TestRaptorPath(ANY, ANY, ANY, ANY, ANY, ANY, NORMAL)
)
);
}
diff --git a/src/test/java/org/opentripplanner/raptor/util/PathStringBuilderTest.java b/src/test/java/org/opentripplanner/raptor/util/PathStringBuilderTest.java
index d347f3848e4..771efe6ab03 100644
--- a/src/test/java/org/opentripplanner/raptor/util/PathStringBuilderTest.java
+++ b/src/test/java/org/opentripplanner/raptor/util/PathStringBuilderTest.java
@@ -71,7 +71,7 @@ public void summary() {
@Test
public void summaryGeneralizedCostOnly() {
- assertEquals("[C₁0.01]", subject.summary(1).toString());
+ assertEquals("[C₁0.01 C₂7]", subject.summary(1, 7).toString());
}
@Test
diff --git a/src/test/java/org/opentripplanner/routing/algorithm/mapping/BikeRentalSnapshotTest.java b/src/test/java/org/opentripplanner/routing/algorithm/mapping/BikeRentalSnapshotTest.java
index 3fef2d957b9..988c6c5abf4 100644
--- a/src/test/java/org/opentripplanner/routing/algorithm/mapping/BikeRentalSnapshotTest.java
+++ b/src/test/java/org/opentripplanner/routing/algorithm/mapping/BikeRentalSnapshotTest.java
@@ -89,7 +89,7 @@ public void directBikeRentalArrivingAtDestinationWithDepartAt() {
request.journey().setModes(RequestModes.of().withDirectMode(StreetMode.BIKE_RENTAL).build());
request.journey().transit().setFilters(List.of(ExcludeAllTransitFilter.of()));
- request.journey().rental().setAllowArrivingInRentedVehicleAtDestination(true);
+ allowArrivalWithRentalVehicle(request);
request.setFrom(p1);
request.setTo(p2);
@@ -104,7 +104,7 @@ public void directBikeRentalArrivingAtDestinationWithArriveBy() {
request.journey().setModes(RequestModes.of().withDirectMode(StreetMode.BIKE_RENTAL).build());
request.journey().transit().setFilters(List.of(ExcludeAllTransitFilter.of()));
- request.journey().rental().setAllowArrivingInRentedVehicleAtDestination(true);
+ allowArrivalWithRentalVehicle(request);
request.setFrom(p1);
request.setTo(p2);
request.setArriveBy(true);
@@ -161,4 +161,12 @@ public void egressBikeRental() {
expectArriveByToMatchDepartAtAndSnapshot(request);
}
+
+ private void allowArrivalWithRentalVehicle(RouteRequest request) {
+ request.withPreferences(preferences ->
+ preferences.withBike(bike ->
+ bike.withRental(rental -> rental.withAllowArrivingInRentedVehicleAtDestination(true))
+ )
+ );
+ }
}
diff --git a/src/test/java/org/opentripplanner/routing/algorithm/mapping/SnapshotTestBase.java b/src/test/java/org/opentripplanner/routing/algorithm/mapping/SnapshotTestBase.java
index bf0e62dbc19..8dbf5536ea2 100644
--- a/src/test/java/org/opentripplanner/routing/algorithm/mapping/SnapshotTestBase.java
+++ b/src/test/java/org/opentripplanner/routing/algorithm/mapping/SnapshotTestBase.java
@@ -36,11 +36,11 @@
import org.opentripplanner.ConstantsForTests;
import org.opentripplanner.TestOtpModel;
import org.opentripplanner.TestServerContext;
-import org.opentripplanner.api.mapping.ItineraryMapper;
-import org.opentripplanner.api.model.ApiLeg;
import org.opentripplanner.api.parameter.ApiRequestMode;
import org.opentripplanner.api.parameter.QualifiedMode;
import org.opentripplanner.api.parameter.Qualifier;
+import org.opentripplanner.ext.restapi.mapping.ItineraryMapper;
+import org.opentripplanner.ext.restapi.model.ApiLeg;
import org.opentripplanner.framework.time.TimeUtils;
import org.opentripplanner.model.GenericLocation;
import org.opentripplanner.model.plan.Itinerary;
diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfiguratorTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfiguratorTest.java
index 4cd2b65e8c3..777aee5352c 100644
--- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfiguratorTest.java
+++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfiguratorTest.java
@@ -10,66 +10,92 @@
import org.opentripplanner.routing.api.request.request.filter.TransitPriorityGroupSelect;
import org.opentripplanner.transit.model.basic.TransitMode;
import org.opentripplanner.transit.model.network.RoutingTripPattern;
+import org.opentripplanner.transit.model.site.RegularStop;
class PriorityGroupConfiguratorTest {
- private final TestRouteData routeA = TestRouteData.of(
+ private static final String AGENCY_A1 = "A1";
+ private static final String AGENCY_A2 = "A2";
+ private static final String AGENCY_A3 = "A3";
+
+ private static final int EXP_GROUP_ID_BASE = 1;
+ private static final int EXP_GROUP_1 = 2;
+ private static final int EXP_GROUP_2 = 4;
+ private static final int EXP_GROUP_3 = 8;
+
+ private final TestRouteData routeR1 = route(
"R1",
TransitMode.RAIL,
+ AGENCY_A1,
List.of(STOP_A, STOP_B),
"10:00 10:10"
);
- private final TestRouteData routeB = TestRouteData.of(
+
+ private final TestRouteData routeB2 = route(
"B2",
TransitMode.BUS,
+ AGENCY_A2,
List.of(STOP_B, STOP_D),
"10:15 10:40"
);
- private final TestRouteData routeC = TestRouteData.of(
+ private final TestRouteData routeR3 = route(
"R3",
TransitMode.RAIL,
+ AGENCY_A3,
List.of(STOP_A, STOP_B),
"10:00 10:10"
);
- private final TestRouteData routeD = TestRouteData.of(
- "R3",
+ private final TestRouteData routeF3 = route(
+ "F3",
TransitMode.FERRY,
+ AGENCY_A3,
+ List.of(STOP_A, STOP_B),
+ "10:00 10:10"
+ );
+ private final TestRouteData routeB3 = route(
+ "B3",
+ TransitMode.BUS,
+ AGENCY_A3,
List.of(STOP_A, STOP_B),
"10:00 10:10"
);
- private final RoutingTripPattern railA = routeA.getTripPattern().getRoutingTripPattern();
- private final RoutingTripPattern busB = routeB.getTripPattern().getRoutingTripPattern();
- private final RoutingTripPattern railC = routeC.getTripPattern().getRoutingTripPattern();
- private final RoutingTripPattern ferryC = routeD.getTripPattern().getRoutingTripPattern();
+ private final RoutingTripPattern railR1 = routeR1.getTripPattern().getRoutingTripPattern();
+ private final RoutingTripPattern busB2 = routeB2.getTripPattern().getRoutingTripPattern();
+ private final RoutingTripPattern railR3 = routeR3.getTripPattern().getRoutingTripPattern();
+ private final RoutingTripPattern ferryF3 = routeF3.getTripPattern().getRoutingTripPattern();
+ private final RoutingTripPattern busB3 = routeB3.getTripPattern().getRoutingTripPattern();
@Test
void emptyConfigurationShouldReturnGroupZero() {
var subject = PriorityGroupConfigurator.of(List.of(), List.of());
- assertEquals(0, subject.lookupTransitPriorityGroupId(railA));
- assertEquals(0, subject.lookupTransitPriorityGroupId(busB));
- assertEquals(0, subject.lookupTransitPriorityGroupId(null));
+ assertEquals(subject.baseGroupId(), subject.lookupTransitPriorityGroupId(railR1));
+ assertEquals(subject.baseGroupId(), subject.lookupTransitPriorityGroupId(busB2));
+ assertEquals(subject.baseGroupId(), subject.lookupTransitPriorityGroupId(null));
}
@Test
- void lookupTransitPriorityGroupIdBySameAgency() {
- var subject = PriorityGroupConfigurator.of(
- List.of(
- TransitPriorityGroupSelect.of().addModes(List.of(TransitMode.BUS)).build(),
- TransitPriorityGroupSelect.of().addModes(List.of(TransitMode.RAIL)).build()
- ),
- List.of()
- );
+ void lookupTransitPriorityGroupIdByAgency() {
+ var select = TransitPriorityGroupSelect
+ .of()
+ .addModes(List.of(TransitMode.BUS, TransitMode.RAIL))
+ .build();
+
+ // Add matcher `byAgency` for bus and real
+ var subject = PriorityGroupConfigurator.of(List.of(select), List.of());
- assertEquals(0, subject.lookupTransitPriorityGroupId(null));
- assertEquals(0, subject.lookupTransitPriorityGroupId(ferryC));
- assertEquals(1, subject.lookupTransitPriorityGroupId(railA));
- assertEquals(2, subject.lookupTransitPriorityGroupId(busB));
- assertEquals(1, subject.lookupTransitPriorityGroupId(railC));
+ // Agency groups are indexed (group-id set) at request time
+ assertEquals(EXP_GROUP_ID_BASE, subject.lookupTransitPriorityGroupId(null));
+ assertEquals(EXP_GROUP_1, subject.lookupTransitPriorityGroupId(busB2));
+ assertEquals(EXP_GROUP_2, subject.lookupTransitPriorityGroupId(railR3));
+ assertEquals(EXP_GROUP_3, subject.lookupTransitPriorityGroupId(railR1));
+ assertEquals(EXP_GROUP_2, subject.lookupTransitPriorityGroupId(busB3));
+ assertEquals(EXP_GROUP_ID_BASE, subject.lookupTransitPriorityGroupId(ferryF3));
}
@Test
void lookupTransitPriorityGroupIdByGlobalMode() {
+ // Global groups are indexed (group-id set) at construction time
var subject = PriorityGroupConfigurator.of(
List.of(),
List.of(
@@ -78,10 +104,25 @@ void lookupTransitPriorityGroupIdByGlobalMode() {
)
);
- assertEquals(0, subject.lookupTransitPriorityGroupId(null));
- assertEquals(0, subject.lookupTransitPriorityGroupId(ferryC));
- assertEquals(2, subject.lookupTransitPriorityGroupId(railA));
- assertEquals(1, subject.lookupTransitPriorityGroupId(busB));
- assertEquals(2, subject.lookupTransitPriorityGroupId(railC));
+ assertEquals(EXP_GROUP_ID_BASE, subject.lookupTransitPriorityGroupId(null));
+ assertEquals(EXP_GROUP_2, subject.lookupTransitPriorityGroupId(railR1));
+ assertEquals(EXP_GROUP_1, subject.lookupTransitPriorityGroupId(busB2));
+ assertEquals(EXP_GROUP_2, subject.lookupTransitPriorityGroupId(railR3));
+ assertEquals(EXP_GROUP_1, subject.lookupTransitPriorityGroupId(busB3));
+ assertEquals(EXP_GROUP_ID_BASE, subject.lookupTransitPriorityGroupId(ferryF3));
+ }
+
+ private static TestRouteData route(
+ String route,
+ TransitMode mode,
+ String agency,
+ List stops,
+ String times
+ ) {
+ return new TestRouteData.Builder(route)
+ .withMode(mode)
+ .withAgency(agency)
+ .withStops(stops)
+ .build();
}
}
diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcherTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcherTest.java
index 1205b6b2205..880d8bc9b45 100644
--- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcherTest.java
+++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcherTest.java
@@ -13,7 +13,11 @@
class PriorityGroupMatcherTest {
- private final TestRouteData r1 = TestRouteData.rail("R1").withAgency("A1").build();
+ private final TestRouteData r1 = TestRouteData
+ .rail("R1")
+ .withSubmode("express")
+ .withAgency("A1")
+ .build();
private final TestRouteData b1 = TestRouteData.bus("B2").withAgency("A2").build();
private final TestRouteData f1 = TestRouteData
.ferry("F1")
@@ -42,17 +46,16 @@ void testMode() {
@Test
void testAgencyIds() {
- var matchers = List.of(
- PriorityGroupMatcher.of(
- TransitPriorityGroupSelect.of().addAgencyIds(List.of(r1agencyId)).build()
- ),
- PriorityGroupMatcher.of(
- TransitPriorityGroupSelect.of().addAgencyIds(List.of(r1agencyId, anyId)).build()
- )
+ var m1 = PriorityGroupMatcher.of(
+ TransitPriorityGroupSelect.of().addAgencyIds(List.of(r1agencyId)).build()
+ );
+ var m2 = PriorityGroupMatcher.of(
+ TransitPriorityGroupSelect.of().addAgencyIds(List.of(r1agencyId, anyId)).build()
);
+ var matchers = List.of(m1, m2);
- assertEquals("AgencyId(F:A1)", matchers.get(0).toString());
- assertEquals("AgencyId(F:A1 | F:ANY)", matchers.get(1).toString());
+ assertEquals("AgencyId(F:A1)", m1.toString());
+ assertEquals("AgencyId(F:A1 | F:ANY)", m2.toString());
for (PriorityGroupMatcher m : matchers) {
assertFalse(m.isEmpty());
@@ -64,17 +67,16 @@ void testAgencyIds() {
@Test
void routeIds() {
- var matchers = List.of(
- PriorityGroupMatcher.of(
- TransitPriorityGroupSelect.of().addRouteIds(List.of(r1routeId)).build()
- ),
- PriorityGroupMatcher.of(
- TransitPriorityGroupSelect.of().addRouteIds(List.of(r1routeId, anyId)).build()
- )
+ var m1 = PriorityGroupMatcher.of(
+ TransitPriorityGroupSelect.of().addRouteIds(List.of(r1routeId)).build()
);
+ var m2 = PriorityGroupMatcher.of(
+ TransitPriorityGroupSelect.of().addRouteIds(List.of(r1routeId, anyId)).build()
+ );
+ var matchers = List.of(m1, m2);
- assertEquals("RouteId(F:R1)", matchers.get(0).toString());
- assertEquals("RouteId(F:R1 | F:ANY)", matchers.get(1).toString());
+ assertEquals("RouteId(F:R1)", m1.toString());
+ assertEquals("RouteId(F:R1 | F:ANY)", m2.toString());
for (PriorityGroupMatcher m : matchers) {
assertFalse(m.isEmpty());
@@ -98,9 +100,31 @@ void testSubMode() {
assertFalse(subject.match(bus));
}
+ @Test
+ void testAnd() {
+ var subject = PriorityGroupMatcher.of(
+ TransitPriorityGroupSelect
+ .of()
+ .addSubModeRegexp(List.of("express"))
+ .addRouteIds(List.of(r1routeId))
+ .addModes(List.of(TransitMode.RAIL, TransitMode.TRAM))
+ .build()
+ );
+
+ assertEquals(
+ "(Mode(RAIL | TRAM) & SubModeRegexp(express) & RouteId(F:R1))",
+ subject.toString()
+ );
+
+ assertFalse(subject.isEmpty());
+ assertTrue(subject.match(rail1));
+ assertFalse(subject.match(ferry));
+ assertFalse(subject.match(bus));
+ }
+
@Test
void testToString() {
- var m = PriorityGroupMatcher.of(
+ var subject = PriorityGroupMatcher.of(
TransitPriorityGroupSelect
.of()
.addModes(List.of(TransitMode.BUS, TransitMode.TRAM))
@@ -111,8 +135,8 @@ void testToString() {
);
assertEquals(
- "(Mode(BUS | TRAM) | SubModeRegexp(.*local.*) | AgencyId(F:A1 | F:ANY) | RouteId(F:R1))",
- m.toString()
+ "(Mode(BUS | TRAM) & SubModeRegexp(.*local.*) & AgencyId(F:A1 | F:ANY) & RouteId(F:R1))",
+ subject.toString()
);
}
}
diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceTest.java
index d05a4090b39..be927046c43 100644
--- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceTest.java
+++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceTest.java
@@ -74,6 +74,7 @@ public void testTripWithoutTransfers() {
var original = pathBuilder()
.access(ITERATION_START_TIME, STOP_B, D1m)
.bus(trip1, STOP_C)
+ .c2(345)
.egress(D1m);
var subject = subject(transfers, null);
diff --git a/src/test/java/org/opentripplanner/routing/api/request/preference/BikePreferencesTest.java b/src/test/java/org/opentripplanner/routing/api/request/preference/BikePreferencesTest.java
index e8e3ff576a9..9da7852cc2c 100644
--- a/src/test/java/org/opentripplanner/routing/api/request/preference/BikePreferencesTest.java
+++ b/src/test/java/org/opentripplanner/routing/api/request/preference/BikePreferencesTest.java
@@ -21,6 +21,8 @@ class BikePreferencesTest {
.withSlope(1)
.build();
public static final BicycleOptimizeType OPTIMIZE_TYPE = BicycleOptimizeType.TRIANGLE;
+ public static final int RENTAL_PICKUP_TIME = 30;
+ public static final int PARK_COST = 30;
private final BikePreferences subject = BikePreferences
.of()
@@ -32,6 +34,8 @@ class BikePreferencesTest {
.withSwitchTime(SWITCH_TIME)
.withSwitchCost(SWITCH_COST)
.withOptimizeType(OPTIMIZE_TYPE)
+ .withRental(rental -> rental.withPickupTime(RENTAL_PICKUP_TIME).build())
+ .withParking(parking -> parking.withParkCost(PARK_COST).build())
.withOptimizeTriangle(it -> it.withSlope(1).build())
.build();
@@ -80,6 +84,18 @@ void optimizeTriangle() {
assertEquals(TRIANGLE, subject.optimizeTriangle());
}
+ @Test
+ void rental() {
+ var vehicleRental = VehicleRentalPreferences.of().withPickupTime(RENTAL_PICKUP_TIME).build();
+ assertEquals(vehicleRental, subject.rental());
+ }
+
+ @Test
+ void parking() {
+ var vehicleParking = VehicleParkingPreferences.of().withParkCost(PARK_COST).build();
+ assertEquals(vehicleParking, subject.parking());
+ }
+
@Test
void testOfAndCopyOf() {
// Return same object if no value is set
@@ -107,6 +123,8 @@ void testToString() {
"walkingReluctance: 1.45, " +
"switchTime: 3m20s, " +
"switchCost: $450, " +
+ "parking: VehicleParkingPreferences{parkCost: $30}, " +
+ "rental: VehicleRentalPreferences{pickupTime: 30s}, " +
"optimizeType: TRIANGLE, " +
"optimizeTriangle: TimeSlopeSafetyTriangle[time=0.0, slope=1.0, safety=0.0]" +
"}",
diff --git a/src/test/java/org/opentripplanner/routing/api/request/preference/CarPreferencesTest.java b/src/test/java/org/opentripplanner/routing/api/request/preference/CarPreferencesTest.java
index 7bc0a1620ab..c81cb41d883 100644
--- a/src/test/java/org/opentripplanner/routing/api/request/preference/CarPreferencesTest.java
+++ b/src/test/java/org/opentripplanner/routing/api/request/preference/CarPreferencesTest.java
@@ -17,6 +17,8 @@ class CarPreferencesTest {
private static final double ACCELERATION_SPEED = 3.1;
private static final double DECELERATION_SPEED = 3.5;
public static final int DROPOFF_TIME = 450;
+ public static final int RENTAL_PICKUP_TIME = 30;
+ public static final int PARK_COST = 30;
private final CarPreferences subject = CarPreferences
.of()
@@ -27,6 +29,8 @@ class CarPreferencesTest {
.withDropoffTime(DROPOFF_TIME)
.withAccelerationSpeed(ACCELERATION_SPEED)
.withDecelerationSpeed(DECELERATION_SPEED)
+ .withRental(rental -> rental.withPickupTime(RENTAL_PICKUP_TIME).build())
+ .withParking(parking -> parking.withParkCost(PARK_COST).build())
.build();
@Test
@@ -64,6 +68,18 @@ void decelerationSpeed() {
assertEquals(DECELERATION_SPEED, subject.decelerationSpeed());
}
+ @Test
+ void rental() {
+ var vehicleRental = VehicleRentalPreferences.of().withPickupTime(RENTAL_PICKUP_TIME).build();
+ assertEquals(vehicleRental, subject.rental());
+ }
+
+ @Test
+ void parking() {
+ var vehicleParking = VehicleParkingPreferences.of().withParkCost(PARK_COST).build();
+ assertEquals(vehicleParking, subject.parking());
+ }
+
@Test
void testCopyOfEqualsAndHashCode() {
// Return same object if no value is set
@@ -83,6 +99,8 @@ void testToString() {
"CarPreferences{" +
"speed: 20.0, " +
"reluctance: 5.1, " +
+ "parking: VehicleParkingPreferences{parkCost: $30}, " +
+ "rental: VehicleRentalPreferences{pickupTime: 30s}, " +
"pickupTime: 600, " +
"pickupCost: $500, " +
"dropoffTime: 450, " +
diff --git a/src/test/java/org/opentripplanner/routing/api/request/preference/VehicleRentalPreferencesTest.java b/src/test/java/org/opentripplanner/routing/api/request/preference/VehicleRentalPreferencesTest.java
index 402b885529c..6092c8139bc 100644
--- a/src/test/java/org/opentripplanner/routing/api/request/preference/VehicleRentalPreferencesTest.java
+++ b/src/test/java/org/opentripplanner/routing/api/request/preference/VehicleRentalPreferencesTest.java
@@ -4,6 +4,7 @@
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.opentripplanner.routing.api.request.preference.ImmutablePreferencesAsserts.assertEqualsAndHashCode;
+import java.util.Set;
import org.junit.jupiter.api.Test;
class VehicleRentalPreferencesTest {
@@ -14,6 +15,10 @@ class VehicleRentalPreferencesTest {
public static final int DROPOFF_COST = 450;
public static final int ARRIVE_IN_RENTAL_COST = 500;
public static final boolean USE_AVAILABILITY_INFORMATION = true;
+ public static final boolean ALLOW_ARRIVING_IN_RENTED_VEHICLE = true;
+ public static final Set ALLOWED_NETWORKS = Set.of("foo");
+ public static final Set BANNED_NETWORKS = Set.of("bar");
+
private final VehicleRentalPreferences subject = VehicleRentalPreferences
.of()
.withPickupTime(PICKUP_TIME)
@@ -22,6 +27,9 @@ class VehicleRentalPreferencesTest {
.withDropoffCost(DROPOFF_COST)
.withArrivingInRentalVehicleAtDestinationCost(ARRIVE_IN_RENTAL_COST)
.withUseAvailabilityInformation(USE_AVAILABILITY_INFORMATION)
+ .withAllowArrivingInRentedVehicleAtDestination(ALLOW_ARRIVING_IN_RENTED_VEHICLE)
+ .withAllowedNetworks(ALLOWED_NETWORKS)
+ .withBannedNetworks(BANNED_NETWORKS)
.build();
@Test
@@ -54,6 +62,25 @@ void arrivingInRentalVehicleAtDestinationCost() {
assertEquals(ARRIVE_IN_RENTAL_COST, subject.arrivingInRentalVehicleAtDestinationCost());
}
+ @Test
+ void allowArrivingInRentedVehicleAtDestination() {
+ assertEquals(
+ ALLOW_ARRIVING_IN_RENTED_VEHICLE,
+ subject.allowArrivingInRentedVehicleAtDestination()
+ );
+ }
+
+ @Test
+ void allowedNetworks() {
+ assertEquals(ALLOWED_NETWORKS, subject.allowedNetworks());
+ }
+
+ @Test
+ void bannedNetworks() {
+ assertEquals(BANNED_NETWORKS, subject.bannedNetworks());
+ }
+
+ @Test
void testOfAndCopyOf() {
// Return same object if no value is set
assertSame(VehicleRentalPreferences.DEFAULT, VehicleRentalPreferences.of().build());
@@ -78,7 +105,10 @@ void testToString() {
"dropoffTime: 45s, " +
"dropoffCost: $450, " +
"useAvailabilityInformation, " +
- "arrivingInRentalVehicleAtDestinationCost: 500.0" +
+ "arrivingInRentalVehicleAtDestinationCost: 500.0, " +
+ "allowArrivingInRentedVehicleAtDestination, " +
+ "allowedNetworks: [foo], " +
+ "bannedNetworks: [bar]" +
"}",
subject.toString()
);
diff --git a/src/test/java/org/opentripplanner/routing/core/RoutingPreferencesTest.java b/src/test/java/org/opentripplanner/routing/core/RoutingPreferencesTest.java
index 8131fdd1299..c67d539e902 100644
--- a/src/test/java/org/opentripplanner/routing/core/RoutingPreferencesTest.java
+++ b/src/test/java/org/opentripplanner/routing/core/RoutingPreferencesTest.java
@@ -24,7 +24,6 @@ public void copyOfShouldReturnTheSameInstanceWhenBuild() {
assertSame(pref.wheelchair(), copy.wheelchair());
assertSame(pref.transit(), copy.transit());
assertSame(pref.street(), copy.street());
- assertSame(pref.rental(), copy.rental());
assertSame(pref.itineraryFilter(), copy.itineraryFilter());
assertSame(pref.system(), copy.system());
}
@@ -110,16 +109,6 @@ public void copyOfWithStreetChanges() {
assertNotSame(pref.street(), copy.street());
}
- @Test
- public void copyOfWithRentalChanges() {
- var pref = new RoutingPreferences();
- var copy = pref.copyOf().withRental(r -> r.withDropoffCost(2)).build();
-
- assertNotSame(pref, copy);
- assertNotSame(pref.rental(), copy.rental());
- assertSame(pref.itineraryFilter(), copy.itineraryFilter());
- }
-
@Test
public void copyOfWithItineraryFilterChanges() {
var pref = new RoutingPreferences();
diff --git a/src/test/java/org/opentripplanner/smoketest/PortlandSmokeTest.java b/src/test/java/org/opentripplanner/smoketest/PortlandSmokeTest.java
index f55810283ca..9881b6eb0a2 100644
--- a/src/test/java/org/opentripplanner/smoketest/PortlandSmokeTest.java
+++ b/src/test/java/org/opentripplanner/smoketest/PortlandSmokeTest.java
@@ -1,27 +1,35 @@
package org.opentripplanner.smoketest;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.opentripplanner.client.model.RequestMode.SCOOTER_RENT;
import static org.opentripplanner.client.model.RequestMode.TRAM;
+import static org.opentripplanner.client.model.RequestMode.TRANSIT;
import static org.opentripplanner.client.model.RequestMode.WALK;
+import java.io.IOException;
import java.util.List;
import java.util.Set;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.opentripplanner.client.model.Coordinate;
+import org.opentripplanner.client.parameters.TripPlanParameters;
+import org.opentripplanner.smoketest.util.RequestCombinationsBuilder;
import org.opentripplanner.smoketest.util.SmokeTestRequest;
@Tag("smoke-test")
@Tag("portland")
public class PortlandSmokeTest {
- Coordinate cennentenial = new Coordinate(45.504602, -122.4968719);
- Coordinate buckman = new Coordinate(45.51720, -122.652289867);
- Coordinate hazelwood = new Coordinate(45.52463, -122.5583);
- Coordinate piedmont = new Coordinate(45.5746, -122.6697);
- Coordinate mountTaborPark = new Coordinate(45.511399, -122.594203);
+ static final Coordinate cennentenial = new Coordinate(45.504602, -122.4968719);
+ static final Coordinate buckman = new Coordinate(45.51720, -122.652289867);
+ static final Coordinate hazelwood = new Coordinate(45.52463, -122.5583);
+ static final Coordinate piedmont = new Coordinate(45.5746, -122.6697);
+ static final Coordinate mountTaborPark = new Coordinate(45.511399, -122.594203);
@Test
public void railTrip() {
@@ -35,25 +43,48 @@ public void railTrip() {
SmokeTest.assertThatAllTransitLegsHaveFareProducts(plan);
}
- /**
- * Checks that a scooter rental finishes at the edge of the park area and is continued on
- * foot rather than scootering all the way to the destination.
- */
- @ParameterizedTest(name = "scooter rental in a geofencing zone with arriveBy={0}")
- @ValueSource(booleans = { true, false })
- public void geofencingZone(boolean arriveBy) {
- SmokeTest.basicRouteTest(
- new SmokeTestRequest(buckman, mountTaborPark, Set.of(SCOOTER_RENT, WALK), arriveBy),
- List.of("WALK", "SCOOTER", "WALK")
- );
+ static List buildCombinations() {
+ return new RequestCombinationsBuilder()
+ .withLocations(cennentenial, buckman, hazelwood, piedmont, mountTaborPark)
+ .withModes(TRANSIT, WALK)
+ .withTime(SmokeTest.weekdayAtNoon())
+ .includeWheelchair()
+ .includeArriveBy()
+ .build();
}
- @ParameterizedTest(name = "scooter rental with arriveBy={0}")
- @ValueSource(booleans = { true, false })
- void scooterRent(boolean arriveBy) {
- SmokeTest.basicRouteTest(
- new SmokeTestRequest(cennentenial, piedmont, Set.of(SCOOTER_RENT, WALK), arriveBy),
- List.of("WALK", "SCOOTER")
- );
+ @ParameterizedTest
+ @MethodSource("buildCombinations")
+ public void accessibleRouting(TripPlanParameters params) throws IOException {
+ var tripPlan = SmokeTest.API_CLIENT.plan(params);
+ assertFalse(tripPlan.transitItineraries().isEmpty());
+ SmokeTest.assertThatAllTransitLegsHaveFareProducts(tripPlan);
+ }
+
+ @Nested
+ @Disabled("Disabled because it seems that the rental services have closed for the winter")
+ class GeofencingZones {
+
+ /**
+ * Checks that a scooter rental finishes at the edge of the park area and is continued on
+ * foot rather than scootering all the way to the destination.
+ */
+ @ParameterizedTest(name = "scooter rental in a geofencing zone with arriveBy={0}")
+ @ValueSource(booleans = { true, false })
+ public void geofencingZone(boolean arriveBy) {
+ SmokeTest.basicRouteTest(
+ new SmokeTestRequest(buckman, mountTaborPark, Set.of(SCOOTER_RENT, WALK), arriveBy),
+ List.of("WALK", "SCOOTER", "WALK")
+ );
+ }
+
+ @ParameterizedTest(name = "scooter rental with arriveBy={0}")
+ @ValueSource(booleans = { true, false })
+ void scooterRent(boolean arriveBy) {
+ SmokeTest.basicRouteTest(
+ new SmokeTestRequest(cennentenial, piedmont, Set.of(SCOOTER_RENT, WALK), arriveBy),
+ List.of("WALK", "SCOOTER")
+ );
+ }
}
}
diff --git a/src/test/java/org/opentripplanner/street/integration/BikeRentalTest.java b/src/test/java/org/opentripplanner/street/integration/BikeRentalTest.java
index 634c10863da..0694a991069 100644
--- a/src/test/java/org/opentripplanner/street/integration/BikeRentalTest.java
+++ b/src/test/java/org/opentripplanner/street/integration/BikeRentalTest.java
@@ -433,8 +433,14 @@ private void assertNoRental(
Set allowedNetworks
) {
Consumer setter = options -> {
- options.journey().rental().setAllowedNetworks(allowedNetworks);
- options.journey().rental().setBannedNetworks(bannedNetworks);
+ options.withPreferences(preferences ->
+ preferences.withBike(bike ->
+ bike.withRental(rental -> {
+ rental.withAllowedNetworks(allowedNetworks);
+ rental.withBannedNetworks(bannedNetworks);
+ })
+ )
+ );
};
assertEquals(
@@ -457,8 +463,14 @@ private void assertPathWithNetwork(
Set allowedNetworks
) {
Consumer setter = options -> {
- options.journey().rental().setAllowedNetworks(allowedNetworks);
- options.journey().rental().setBannedNetworks(bannedNetworks);
+ options.withPreferences(preferences ->
+ preferences.withBike(bike ->
+ bike.withRental(rental -> {
+ rental.withAllowedNetworks(allowedNetworks);
+ rental.withBannedNetworks(bannedNetworks);
+ })
+ )
+ );
};
assertEquals(
@@ -580,17 +592,15 @@ private List runStreetSearchAndCreateDescriptor(
toVertex,
arriveBy,
options -> {
- options.withPreferences(p ->
- p.withRental(rental ->
- rental
- .withUseAvailabilityInformation(useAvailabilityInformation)
- .withArrivingInRentalVehicleAtDestinationCost(keepRentedBicycleCost)
+ options.withPreferences(preferences ->
+ preferences.withBike(bike ->
+ bike.withRental(rental -> {
+ rental.withUseAvailabilityInformation(useAvailabilityInformation);
+ rental.withArrivingInRentalVehicleAtDestinationCost(keepRentedBicycleCost);
+ rental.withAllowArrivingInRentedVehicleAtDestination(keepRentedBicycleCost > 0);
+ })
)
);
- options
- .journey()
- .rental()
- .setAllowArrivingInRentedVehicleAtDestination(keepRentedBicycleCost > 0);
}
);
}
@@ -605,14 +615,16 @@ private List runStreetSearchAndCreateDescriptor(
request.setArriveBy(arriveBy);
+ optionsSetter.accept(request);
+
request.withPreferences(preferences ->
- preferences.withRental(rental ->
- rental.withPickupTime(42).withPickupCost(62).withDropoffCost(33).withDropoffTime(15)
+ preferences.withBike(bike ->
+ bike.withRental(rental ->
+ rental.withPickupTime(42).withPickupCost(62).withDropoffCost(33).withDropoffTime(15)
+ )
)
);
- optionsSetter.accept(request);
-
return runStreetSearchAndCreateDescriptor(
fromVertex,
toVertex,
diff --git a/src/test/java/org/opentripplanner/street/model/edge/VehicleRentalEdgeTest.java b/src/test/java/org/opentripplanner/street/model/edge/VehicleRentalEdgeTest.java
index 8cdb0bf3ed8..84f5efcc1c5 100644
--- a/src/test/java/org/opentripplanner/street/model/edge/VehicleRentalEdgeTest.java
+++ b/src/test/java/org/opentripplanner/street/model/edge/VehicleRentalEdgeTest.java
@@ -10,7 +10,6 @@
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.opentripplanner.routing.api.request.StreetMode;
-import org.opentripplanner.routing.api.request.request.VehicleRentalRequest;
import org.opentripplanner.service.vehiclerental.model.GeofencingZone;
import org.opentripplanner.service.vehiclerental.model.RentalVehicleType;
import org.opentripplanner.service.vehiclerental.model.TestVehicleRentalStationBuilder;
@@ -219,15 +218,18 @@ private void initEdgeAndRequest(
vehicleRentalEdge = VehicleRentalEdge.createVehicleRentalEdge(vertex, RentalFormFactor.BICYCLE);
- var rentalRequest = new VehicleRentalRequest();
this.request =
StreetSearchRequest
.of()
.withMode(mode)
- .withRental(rentalRequest)
.withPreferences(preferences ->
preferences
- .withRental(rental -> rental.withUseAvailabilityInformation(useRealtime).build())
+ .withCar(car ->
+ car.withRental(rental -> rental.withUseAvailabilityInformation(useRealtime))
+ )
+ .withBike(bike ->
+ bike.withRental(rental -> rental.withUseAvailabilityInformation(useRealtime))
+ )
.build()
)
.build();
diff --git a/src/test/java/org/opentripplanner/street/search/state/TestStateBuilder.java b/src/test/java/org/opentripplanner/street/search/state/TestStateBuilder.java
index 042ff5ba553..a4dbf76e55e 100644
--- a/src/test/java/org/opentripplanner/street/search/state/TestStateBuilder.java
+++ b/src/test/java/org/opentripplanner/street/search/state/TestStateBuilder.java
@@ -1,6 +1,7 @@
package org.opentripplanner.street.search.state;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.opentripplanner.routing.algorithm.raptoradapter.router.street.AccessEgressType.EGRESS;
import static org.opentripplanner.transit.model.site.PathwayMode.WALKWAY;
import java.time.Instant;
@@ -10,6 +11,7 @@
import javax.annotation.Nonnull;
import org.opentripplanner.framework.i18n.I18NString;
import org.opentripplanner.framework.i18n.NonLocalizedString;
+import org.opentripplanner.routing.algorithm.raptoradapter.router.street.AccessEgressType;
import org.opentripplanner.routing.api.request.StreetMode;
import org.opentripplanner.service.vehiclerental.model.TestFreeFloatingRentalVehicleBuilder;
import org.opentripplanner.service.vehiclerental.model.TestVehicleRentalStationBuilder;
@@ -20,6 +22,7 @@
import org.opentripplanner.street.model.RentalFormFactor;
import org.opentripplanner.street.model.StreetTraversalPermission;
import org.opentripplanner.street.model._data.StreetModelForTest;
+import org.opentripplanner.street.model.edge.Edge;
import org.opentripplanner.street.model.edge.ElevatorAlightEdge;
import org.opentripplanner.street.model.edge.ElevatorBoardEdge;
import org.opentripplanner.street.model.edge.ElevatorHopEdge;
@@ -51,10 +54,19 @@ public class TestStateBuilder {
private State currentState;
private TestStateBuilder(StreetMode mode) {
+ this(mode, AccessEgressType.ACCESS);
+ }
+
+ private TestStateBuilder(StreetMode mode, AccessEgressType type) {
currentState =
new State(
StreetModelForTest.intersectionVertex(count, count),
- StreetSearchRequest.of().withMode(mode).withStartTime(DEFAULT_START_TIME).build()
+ StreetSearchRequest
+ .of()
+ .withArriveBy(type.isEgress())
+ .withMode(mode)
+ .withStartTime(DEFAULT_START_TIME)
+ .build()
);
}
@@ -80,6 +92,14 @@ public static TestStateBuilder ofScooterRental() {
return new TestStateBuilder(StreetMode.SCOOTER_RENTAL);
}
+ /**
+ * Creates a state that starts the scooter rental in arriveBy mode, so starting with
+ * a rental scooter and going backwards until it finds a rental vertex where to drop it.
+ */
+ public static TestStateBuilder ofScooterRentalArriveBy() {
+ return new TestStateBuilder(StreetMode.SCOOTER_RENTAL, EGRESS);
+ }
+
public static TestStateBuilder ofBikeRental() {
return new TestStateBuilder(StreetMode.BIKE_RENTAL);
}
@@ -248,7 +268,12 @@ private TestStateBuilder arriveAtStop(RegularStop stop) {
var from = (StreetVertex) currentState.vertex;
var to = new TransitStopVertexBuilder().withStop(stop).build();
- var edge = StreetTransitStopLink.createStreetTransitStopLink(from, to);
+ Edge edge;
+ if (currentState.getRequest().arriveBy()) {
+ edge = StreetTransitStopLink.createStreetTransitStopLink(to, from);
+ } else {
+ edge = StreetTransitStopLink.createStreetTransitStopLink(from, to);
+ }
var states = edge.traverse(currentState);
if (states.length != 1) {
throw new IllegalStateException("Only single state transitions are supported.");
diff --git a/src/test/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesTest.java b/src/test/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesTest.java
index ed6c5c70f92..e2c1148ef51 100644
--- a/src/test/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesTest.java
+++ b/src/test/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesTest.java
@@ -13,6 +13,7 @@
import org.opentripplanner.framework.time.TimeUtils;
import org.opentripplanner.transit.model._data.TransitModelForTest;
import org.opentripplanner.transit.model.basic.Accessibility;
+import org.opentripplanner.transit.model.framework.DataValidationException;
import org.opentripplanner.transit.model.framework.FeedScopedId;
class ScheduledTripTimesTest {
@@ -102,7 +103,7 @@ void isTimepoint() {
@Test
void validateLastArrivalTimeIsNotMoreThan20DaysAfterFirstDepartureTime() {
var ex = assertThrows(
- IllegalArgumentException.class,
+ DataValidationException.class,
() ->
ScheduledTripTimes
.of()
@@ -111,7 +112,10 @@ void validateLastArrivalTimeIsNotMoreThan20DaysAfterFirstDepartureTime() {
.withTrip(TRIP)
.build()
);
- assertEquals("The value is not in range[-43200, 1728000]: 1728001", ex.getMessage());
+ assertEquals(
+ "The arrivalTime is not in range[-12h, 20d]. Time: 10:00:01+20d, stop-pos: 2, trip: F:Trip-1.",
+ ex.getMessage()
+ );
}
@Test
diff --git a/src/test/resources/org/opentripplanner/graph_builder/module/islandpruning/matinkyla-escalator.pbf b/src/test/resources/org/opentripplanner/graph_builder/module/islandpruning/matinkyla-escalator.pbf
new file mode 100644
index 00000000000..bcae465ae99
Binary files /dev/null and b/src/test/resources/org/opentripplanner/graph_builder/module/islandpruning/matinkyla-escalator.pbf differ