Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Not allowed transfers and support for GTFS transfer points #3792

Merged
merged 26 commits into from
Jan 26, 2022
Merged
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
4a931a5
feat: Extend internal model to allow implementing not-allowed transfers.
t2gran Nov 15, 2021
e472a11
feat: Extend Raptor with support for not-allowed transfers.
t2gran Nov 15, 2021
4f44616
feat: Extend Raptor and optimized transfers with support for not-allo…
t2gran Nov 16, 2021
cca1168
feat: Extend ConstrainedBoardingSearch with support for not-allowed t…
t2gran Nov 15, 2021
ad6c2cc
refactor: Make transfer constraint none null.
t2gran Dec 20, 2021
268b079
test: Add test on ConstrainedBoardingSearch
t2gran Dec 20, 2021
072731f
refactor: remove unused method in ConstrainedBoardingSearchStrategy
t2gran Dec 20, 2021
ddaa114
refactor: Improve TransferPoints toString()
t2gran Dec 20, 2021
2be63f4
refactor: Cleanup T2 equals() and add test
t2gran Nov 29, 2021
539bf9a
refactor: cleanup TripPattern
t2gran Dec 17, 2021
29be403
refactor: StopTransferPriorityMapper renamed from TransferPriorityMapper
t2gran Dec 20, 2021
c706ecf
refactor: rename local variable in RangeRaptorWorker
t2gran Dec 20, 2021
0a0cf45
refactor: improve encapsulation in StopIndexForRaptor
t2gran Dec 20, 2021
5ce3d16
feat: Support for GTFS Trip, Route, Stop & Station Transfers
t2gran Dec 18, 2021
d1cbca0
refactor: Update otp.serialization.version.id
t2gran Dec 20, 2021
594f8f4
refactor: Encapsulate StopPattern in TripPattern
t2gran Jan 7, 2022
1390922
feat: Support for GTFS Trip, Route, Stop & Station Transfers
t2gran Jan 6, 2022
4950704
fix: NPE in TransferIndexGenerator
t2gran Jan 13, 2022
4e4339a
Apply suggestions from code review
t2gran Jan 13, 2022
2a08080
refactor: Review
t2gran Jan 13, 2022
768117c
Merge remote-tracking branch 'otp/dev-2.x' into otp2_not_allowed_tran…
t2gran Jan 14, 2022
0ccdf70
Merge remote-tracking branch 'otp/dev-2.x' into otp2_not_allowed_tran…
t2gran Jan 14, 2022
1e82238
review: Update src/main/java/org/opentripplanner/model/transfer/Trans…
t2gran Jan 25, 2022
6028793
Merge remote-tracking branch 'otp/dev-2.x' into otp2_not_allowed_tran…
t2gran Jan 26, 2022
244b30e
review: Cleanup AccessEgressMapper
t2gran Jan 26, 2022
3e447f4
feat: Abort constrained transfer search after 5 normal boardings found
t2gran Jan 26, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat: Extend Raptor and optimized transfers with support for not-allo…
…wed transfers.
t2gran committed Dec 20, 2021
commit 4f4461685694bf91981cc3576f435c6bf1be0b24
Original file line number Diff line number Diff line change
@@ -110,6 +110,11 @@ public boolean isFacilitated() {
return staySeated || guaranteed;
}

@Override
public boolean isNotAllowed() {
return priority == NOT_ALLOWED;
}

/**
* Maximum time after scheduled departure time the connecting transport is guarantied to wait
* for the delayed trip.
Original file line number Diff line number Diff line change
@@ -123,6 +123,10 @@ private Collection<TripToTripTransfer<T>> transferFromSameStop(TripStopTime<T> f
final int stop = from.stop();
var tx = transferServiceAdaptor.findTransfer(from, toTrip, stop);

if(tx != null && tx.getTransferConstraint().isNotAllowed()) {
return List.of();
}

final int earliestDepartureTime = earliestDepartureTime(
from.time(), SAME_STOP_TRANSFER_TIME, tx
);
@@ -154,6 +158,9 @@ private Collection<? extends TripToTripTransfer<T>> findStandardTransfers(TripSt
int toStop = it.stop();

ConstrainedTransfer tx = transferServiceAdaptor.findTransfer(from, toTrip, toStop);
if(tx != null && tx.getTransferConstraint().isNotAllowed()) {
continue;
}

int earliestDepartureTime = earliestDepartureTime(from.time(), it.durationInSeconds(), tx);
int toTripStopPos = toTrip.findDepartureStopPosition(earliestDepartureTime, toStop);
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.opentripplanner.routing.algorithm.transferoptimization.services;

import java.util.function.IntFunction;
import javax.annotation.Nullable;
import org.opentripplanner.model.Stop;
import org.opentripplanner.model.StopLocation;
import org.opentripplanner.model.Trip;
@@ -51,6 +52,7 @@ public static <T extends RaptorTripSchedule> TransferServiceAdaptor<T> noop() {
/**
* Find transfer in the same stop for the given from location and to trip/stop.
*/
@Nullable
protected ConstrainedTransfer findTransfer(TripStopTime<T> from, T toTrip, int toStop) {
return transferService.findTransfer(
stop(from.stop()),
Original file line number Diff line number Diff line change
@@ -45,7 +45,7 @@
* <li>Multi-criteria pareto optimal Range Raptor (McRR)
* <li>Reverse search in combination with R and RR
* </ul>
* This version do NOT support the following features:
* This version does NOT support the following features:
* <ul>
* <li>Frequency routes, supported by the original code using Monte Carlo methods
* (generating randomized schedules)
Original file line number Diff line number Diff line change
@@ -16,6 +16,7 @@ public class TransferConstraintTest implements TransferTestData {
private final TransferConstraint RECOMMENDED = TransferConstraint.create().recommended().build();
private final TransferConstraint STAY_SEATED = TransferConstraint.create().staySeated().build();
private final TransferConstraint GUARANTIED = TransferConstraint.create().guaranteed().build();
private final TransferConstraint NOT_ALLOWED = TransferConstraint.create().notAllowed().build();
private final TransferConstraint MAX_WAIT_TIME = TransferConstraint.create()
.guaranteed().maxWaitTime(MAX_WAIT_TIME_ONE_HOUR).build();
private final TransferConstraint EVERYTHING = TransferConstraint.create()
@@ -44,6 +45,14 @@ public void isFacilitated() {
assertTrue(GUARANTIED.isFacilitated());
assertTrue(STAY_SEATED.isFacilitated());
assertFalse(NO_CONSTRAINS.isFacilitated());
assertFalse(NOT_ALLOWED.isFacilitated());
}

@Test
public void isNotAllowed() {
assertTrue(NOT_ALLOWED.isNotAllowed());
assertFalse(GUARANTIED.isNotAllowed());
assertFalse(NO_CONSTRAINS.isNotAllowed());
}

@Test
Original file line number Diff line number Diff line change
@@ -10,7 +10,6 @@
import java.util.List;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import org.opentripplanner.routing.algorithm.raptor.transit.SlackProvider;
import org.opentripplanner.transit.raptor._data.RaptorTestConstants;
import org.opentripplanner.transit.raptor._data.api.TestPathBuilder;
import org.opentripplanner.transit.raptor._data.transit.TestRoute;
@@ -330,6 +329,48 @@ void findTransferWithBoardingForbiddenAtDifferentStop() {
);
}

@Test
void findTransferWithNotAllowedConstrainedTransfer() {
// given 3 possible expected transfers
var expBxB = "TripToTripTransfer{from: [2 10:10 BUS L1], to: [2 10:20 BUS L2]}";
var expCxD = "TripToTripTransfer{from: [3 10:20 BUS L1], to: [4 10:30 BUS L2], transfer: On-Street 1m ~ 4}";
var expExE = "TripToTripTransfer{from: [5 10:30 BUS L1], to: [5 10:40 BUS L2]}";

var l1 = route("L1", STOP_A, STOP_B, STOP_C, STOP_E)
.withTimetable(schedule("10:00 10:10 10:20 10:30"));

var l2 = route("L2", STOP_B, STOP_D, STOP_E, STOP_F)
.withTimetable(schedule("10:20 10:30 10:40 10:50"));

data.withRoutes(l1, l2).withTransfer(STOP_C, walk(STOP_D, D1m));

final Path<TestTripSchedule> path = pathBuilder
.access(ACCESS_START, ACCESS_DURATION, STOP_A)
.bus(l1.getTripSchedule(0), STOP_C)
.walk(D1m, STOP_D)
.bus(l2.getTripSchedule(0), STOP_F)
.egress(D1m);

var transitLegs = path.transitLegs().collect(Collectors.toList());
var subject = new TransferGenerator<>(TS_ADAPTOR, SLACK_PROVIDER, data);

var tripA = l1.getTripSchedule(0);
var tripB = l2.getTripSchedule(0);

data.withConstrainedTransfer(tripA, STOP_B, tripB, STOP_B, TestTransitData.TX_NOT_ALLOWED);
var result = subject.findAllPossibleTransfers(transitLegs);

// The same stop transfer is no longer an option
assertEquals("[[" + expCxD + ", " + expExE + "]]", result.toString());

data.clearConstrainedTransfers();
data.withConstrainedTransfer(tripA, STOP_C, tripB, STOP_D, TestTransitData.TX_NOT_ALLOWED);
result = subject.findAllPossibleTransfers(transitLegs);

// The same stop transfer is no longer an option
assertEquals("[[" + expBxB + ", " + expExE + "]]", result.toString());
}

@Test
void findDependedTransfersForThreeRoutes() {
TestRoute l1 = route("L1", STOP_A, STOP_B, STOP_C)
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.BiPredicate;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.opentripplanner.model.base.ToStringBuilder;
@@ -16,14 +17,17 @@
public class TestConstrainedBoardingSearch
implements RaptorConstrainedTripScheduleBoardingSearch<TestTripSchedule> {

private static final TransferConstraint GUARANTEED = TransferConstraint.create().guaranteed().build();


/** Index of guaranteed transfers by fromStopPos */
private final TIntObjectMap<List<TestConstrainedTransferBoarding>> transfersByFromStopPos = new TIntObjectHashMap<>();

private int currentTargetStopPos;

private final BiPredicate<Integer, Integer> timeAfterOrEqual;

TestConstrainedBoardingSearch(boolean forward) {
this.timeAfterOrEqual = forward ? (a,b) -> a >= b : (a,b) -> a <= b;
}

@Override
public boolean transferExist(int targetStopPos) {
this.currentTargetStopPos = targetStopPos;
@@ -43,7 +47,8 @@ public RaptorTripScheduleBoardOrAlightEvent<TestTripSchedule> find(
var trip = tx.getSourceTrip();
if(trip == sourceTrip) {
int stopPos = trip.findDepartureStopPosition(sourceArrivalTime, sourceStopIndex);
if(tx.getSourceStopPos() == stopPos) {
boolean boardAlightPossible = timeAfterOrEqual.test(tx.getTime(), sourceArrivalTime);
if(tx.getSourceStopPos() == stopPos && boardAlightPossible) {
return tx;
}
}
@@ -65,21 +70,22 @@ public List<TestConstrainedTransferBoarding> constrainedBoardings() {
* The the {@code source/target} is the trips in order of the search direction (forward or
* reverse). For reverse search it is the opposite from {@code from/to} in the result path.
*/
void addGuaranteedTransfers(
void addConstraintTransfers(
TestTripSchedule sourceTrip,
int sourceStopPos,
TestTripSchedule targetTrip,
int targetTripIndex,
int targetStopPos,
int targetTime
int targetTime,
TransferConstraint constraint
) {
List<TestConstrainedTransferBoarding> list = transfersByFromStopPos.get(targetStopPos);
if(list == null) {
list = new ArrayList<>();
transfersByFromStopPos.put(targetStopPos, list);
}
list.add(new TestConstrainedTransferBoarding(
GUARANTEED,
constraint,
sourceTrip, sourceStopPos,
targetTrip, targetTripIndex, targetStopPos, targetTime
));
@@ -93,4 +99,7 @@ public String toString() {
.toString();
}

void clear() {
transfersByFromStopPos.clear();
}
}
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@
import java.util.Collections;
import java.util.List;
import org.opentripplanner.model.base.ToStringBuilder;
import org.opentripplanner.model.transfer.TransferConstraint;
import org.opentripplanner.transit.raptor.api.transit.RaptorConstrainedTripScheduleBoardingSearch;
import org.opentripplanner.transit.raptor.api.transit.RaptorRoute;
import org.opentripplanner.transit.raptor.api.transit.RaptorTimeTable;
@@ -15,9 +16,9 @@ public class TestRoute implements RaptorRoute<TestTripSchedule>, RaptorTimeTable
private final TestTripPattern pattern;
private final List<TestTripSchedule> schedules = new ArrayList<>();
private final TestConstrainedBoardingSearch transferConstraintsForwardSearch =
new TestConstrainedBoardingSearch();
new TestConstrainedBoardingSearch(true);
private final TestConstrainedBoardingSearch transferConstraintsReverseSearch =
new TestConstrainedBoardingSearch();
new TestConstrainedBoardingSearch(false);


private TestRoute(TestTripPattern pattern) {
@@ -87,35 +88,43 @@ public String toString() {
.toString();
}

void addGuaranteedTxForwardSearch(
TestTripSchedule sourceTrip,
int sourceStopPos,
TestTripSchedule targetTrip,
int targetTripIndex,
int targetStopPos
) {
final int targetTime = targetTrip.arrival(targetStopPos);

this.transferConstraintsForwardSearch.addGuaranteedTransfers(
sourceTrip, sourceStopPos, targetTrip, targetTripIndex, targetStopPos, targetTime
);
void clearTransferConstraints() {
transferConstraintsForwardSearch.clear();
transferConstraintsReverseSearch.clear();
}

/**
* Reverse search transfer, the {@code source/target} is the trips in order of the reverse
* search, which is opposite from {@code from/to} in the result path.
* Add a transfer constraint to the route by iterating over all trips and matching
* the provided {@code toTrip}(added to forward search) {@code fromTrip}(added to reverse
* search) with the rips in the route timetable.
*/
void addGuaranteedTxReverseSearch(
TestTripSchedule sourceTrip,
int sourceStopPos,
TestTripSchedule targetTrip,
int targetTripIndex,
int targetStopPos
void addTransferConstraint(
TestTripSchedule fromTrip,
int fromStopPos,
TestTripSchedule toTrip,
int toStopPos,
TransferConstraint constraint
) {
final int targetTime = targetTrip.departure(targetStopPos);
// This is used in the revers search
this.transferConstraintsReverseSearch.addGuaranteedTransfers(
sourceTrip, sourceStopPos, targetTrip, targetTripIndex, targetStopPos, targetTime
);
for (int i = 0; i < timetable().numberOfTripSchedules(); i++) {
var trip = timetable().getTripSchedule(i);
if(toTrip == trip) {
this.transferConstraintsForwardSearch.addConstraintTransfers(
fromTrip, fromStopPos,
trip, i, toStopPos,
trip.arrival(toStopPos),
constraint
);
}
// Reverse search transfer, the {@code source/target} is the trips in order of the
// reverse search, which is opposite from {@code from/to} in the result path.
if(fromTrip == trip) {
this.transferConstraintsReverseSearch.addConstraintTransfers(
toTrip, toStopPos,
trip, i, fromStopPos,
trip.departure(fromStopPos),
constraint
);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -31,14 +31,16 @@
@SuppressWarnings("UnusedReturnValue")
public class TestTransitData implements RaptorTransitDataProvider<TestTripSchedule>, RaptorTestConstants {

private static final TransferConstraint GUARANTEED = TransferConstraint.create()
.guaranteed().build();
public static final TransferConstraint TX_GUARANTEED = TransferConstraint.create().guaranteed()
.build();
public static final TransferConstraint TX_NOT_ALLOWED = TransferConstraint.create().notAllowed()
.build();

private final List<List<RaptorTransfer>> transfersFromStop = new ArrayList<>();
private final List<List<RaptorTransfer>> transfersToStop = new ArrayList<>();
private final List<Set<TestRoute>> routesByStop = new ArrayList<>();
private final List<TestRoute> routes = new ArrayList<>();
private final List<ConstrainedTransfer> guaranteedTransfers = new ArrayList<>();
private final List<ConstrainedTransfer> constrainedTransfers = new ArrayList<>();
private final McCostParamsBuilder costParamsBuilder = new McCostParamsBuilder();

@Override
@@ -174,27 +176,34 @@ public TestTransitData withTransfer(int fromStop, TestTransfer transfer) {
public TestTransitData withGuaranteedTransfer(
TestTripSchedule fromTrip, int fromStop,
TestTripSchedule toTrip, int toStop
) {
return withConstrainedTransfer(fromTrip, fromStop, toTrip, toStop, TX_GUARANTEED);
}

public void clearConstrainedTransfers() {
constrainedTransfers.clear();
for (TestRoute route : routes) {
route.clearTransferConstraints();
}
}

public TestTransitData withConstrainedTransfer(
TestTripSchedule fromTrip, int fromStop,
TestTripSchedule toTrip, int toStop,
TransferConstraint constraint
) {
int fromStopPos = fromTrip.pattern().findStopPositionAfter(0, fromStop);
int toStopPos = toTrip.pattern().findStopPositionAfter(0, toStop);

for (TestRoute route : routes) {
for (int i = 0; i < route.timetable().numberOfTripSchedules(); i++) {
var trip = route.timetable().getTripSchedule(i);
if(toTrip == trip) {
route.addGuaranteedTxForwardSearch(fromTrip, fromStopPos, trip, i, toStopPos);
}
if(fromTrip == trip) {
route.addGuaranteedTxReverseSearch(toTrip, toStopPos, trip, i, fromStopPos);
}
}
route.addTransferConstraint(fromTrip, fromStopPos, toTrip, toStopPos, constraint);
}
guaranteedTransfers.add(
constrainedTransfers.add(
new ConstrainedTransfer(
null,
new TestTransferPoint(fromStop, fromTrip, false),
new TestTransferPoint(toStop, toTrip, false),
GUARANTEED
constraint
)
);
return this;
@@ -204,13 +213,13 @@ public McCostParamsBuilder mcCostParamsBuilder() {
return costParamsBuilder;
}

public ConstrainedTransfer findGuaranteedTransfer(
public ConstrainedTransfer findConstrainedTransfer(
TestTripSchedule fromTrip,
int fromStop,
TestTripSchedule toTrip,
int toStop
) {
for (ConstrainedTransfer tx : guaranteedTransfers) {
for (ConstrainedTransfer tx : constrainedTransfers) {
if(
((TestTransferPoint)tx.getFrom()).matches(fromTrip, fromStop) &&
((TestTransferPoint)tx.getTo()).matches(toTrip, toStop)
@@ -226,7 +235,7 @@ public TransferServiceAdaptor<TestTripSchedule> transferServiceAdaptor() {
@Override protected ConstrainedTransfer findTransfer(
TripStopTime<TestTripSchedule> from, TestTripSchedule toTrip, int toStop
) {
return findGuaranteedTransfer(from.trip(), from.stop(), toTrip, toStop);
return findConstrainedTransfer(from.trip(), from.stop(), toTrip, toStop);
}
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package org.opentripplanner.transit.raptor.moduletests;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.opentripplanner.transit.raptor._data.api.PathUtils.pathsToString;
import static org.opentripplanner.transit.raptor._data.transit.TestRoute.route;
import static org.opentripplanner.transit.raptor._data.transit.TestTransfer.walk;
import static org.opentripplanner.transit.raptor._data.transit.TestTripSchedule.schedule;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.opentripplanner.transit.raptor.RaptorService;
import org.opentripplanner.transit.raptor._data.RaptorTestConstants;
import org.opentripplanner.transit.raptor._data.transit.TestTransitData;
import org.opentripplanner.transit.raptor._data.transit.TestTripSchedule;
import org.opentripplanner.transit.raptor.api.request.Optimization;
import org.opentripplanner.transit.raptor.api.request.RaptorProfile;
import org.opentripplanner.transit.raptor.api.request.RaptorRequestBuilder;
import org.opentripplanner.transit.raptor.api.request.SearchDirection;
import org.opentripplanner.transit.raptor.rangeraptor.configure.RaptorConfig;

/**
* FEATURE UNDER TEST
* <p>
* Raptor should NOT return a path with a NOT-ALLOWED transfer, instead it should try to find
* another option.
*/
public class E02_NotAllowedConstrainedTransferTest implements RaptorTestConstants {

private final TestTransitData data = new TestTransitData();
private final RaptorRequestBuilder<TestTripSchedule> requestBuilder =
new RaptorRequestBuilder<>();
private final RaptorService<TestTripSchedule> raptorService =
new RaptorService<>(RaptorConfig.defaultConfigForTest());

private static final String EXP_PATH = "Walk 30s ~ A ~ BUS R1 0:02 0:05 ~ B "
+ "~ BUS R3 0:15 0:20 ~ C ~ Walk 30s [0:01:30 0:20:30 19m";
private static final String EXP_PATH_NO_COST = EXP_PATH + "]";
private static final String EXP_PATH_WITH_COST = EXP_PATH + " $2500]";

/**
* Schedule: Stop: 1 2 3 R1: 00:02 - 00:05 R2: 00:05 - 00:10
* <p>
* Access(stop 1) and egress(stop 3) is 30s.
*/
@BeforeEach
public void setup() {
var r1 = route("R1", STOP_A, STOP_B)
.withTimetable(schedule("0:02 0:05"));
var r2 = route("R2", STOP_B, STOP_C)
.withTimetable(
schedule("0:10 0:15"),
// Add another schedule - should not be used even if the not-allowed is
// attached to the first one - not-allowed in Raptor apply to the Route.
// The trip/timetable search should handle not-allowed on trip level.
schedule("0:12 0:17")
);
var r3 = route("R3", STOP_B, STOP_C)
.withTimetable(schedule("0:15 0:20"));

var tripA = r1.timetable().getTripSchedule(0);
var tripB = r2.timetable().getTripSchedule(0);

data.withRoutes(r1, r2, r3);

// Apply not-allowed on the first trip from R1 and R2 - when found this will apply to
// the second trip in R2 as well. This is of cause not a correct way to implement the
// transit model, a not-allowed transfer should apply to ALL trips if constraint is passed
// to raptor.
data.withConstrainedTransfer(tripA, STOP_B, tripB, STOP_B, TestTransitData.TX_NOT_ALLOWED);
data.mcCostParamsBuilder().transferCost(100);

requestBuilder.searchParams()
.constrainedTransfersEnabled(true)
.addAccessPaths(walk(STOP_A, D30s))
.addEgressPaths(walk(STOP_C, D30s))
.earliestDepartureTime(T00_00)
.latestArrivalTime(T00_30)
.timetableEnabled(true);

ModuleTestDebugLogging.setupDebugLogging(data, requestBuilder);
}

@Test
public void standardOneIteration() {
var request = requestBuilder
.profile(RaptorProfile.STANDARD)
.searchParams().searchOneIterationOnly()
.build();
var response = raptorService.route(request, data);
assertEquals(EXP_PATH_NO_COST, pathsToString(response));
}

@Test
public void standardDynamicSearchWindow() {
var request = requestBuilder
.profile(RaptorProfile.STANDARD)
.build();
var response = raptorService.route(request, data);
assertEquals(EXP_PATH_NO_COST, pathsToString(response));
}

@Test
public void standardReverseOneIteration() {
var request = requestBuilder
.searchDirection(SearchDirection.REVERSE)
.profile(RaptorProfile.STANDARD)
.searchParams().searchOneIterationOnly()
.build();

var response = raptorService.route(request, data);

assertEquals(EXP_PATH_NO_COST, pathsToString(response));
}

@Test
public void multiCriteria() {
requestBuilder.optimizations().add(Optimization.PARETO_CHECK_AGAINST_DESTINATION);
var request = requestBuilder
.profile(RaptorProfile.MULTI_CRITERIA)
.build();

var response = raptorService.route(request, data);

assertEquals(EXP_PATH_WITH_COST, pathsToString(response));
}
}