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

Make flex linking work together with boarding locations #6311

Merged
merged 5 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ public void linkTransitStops(Graph graph, TimetableRepository timetableRepositor
continue;
}
// check if stop is already linked, to allow multiple idempotent linking cycles
if (tStop.isConnectedToGraph()) {
if (isAlreadyLinked(tStop, stopLocationsUsedForFlexTrips)) {
continue;
}

Expand All @@ -127,6 +127,26 @@ public void linkTransitStops(Graph graph, TimetableRepository timetableRepositor
LOG.info(progress.completeMessage());
}

/**
* Determines if a given transit stop vertex is already linked to the street network, taking into
* account that flex stops need special linking to both a walkable and drivable edge. For example,
* the {@link OsmBoardingLocationsModule}, which runs before this one, often links stops to
* walkable edges only.
*
* @param stopVertex The transit stop vertex to be checked.
* @param stopLocationsUsedForFlexTrips A set of stop locations that are used for flexible trips.
*/
private static boolean isAlreadyLinked(
TransitStopVertex stopVertex,
Set<StopLocation> stopLocationsUsedForFlexTrips
) {
if (stopLocationsUsedForFlexTrips.contains(stopVertex.getStop())) {
return stopVertex.isLinkedToDrivableEdge() && stopVertex.isLinkedToWalkableEdge();
} else {
return stopVertex.isConnectedToGraph();
}
}

/**
* Link a stop to the nearest "relevant" edges.
* <p>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package org.opentripplanner.street.model.vertex;

import static org.opentripplanner.street.search.TraverseMode.CAR;
import static org.opentripplanner.street.search.TraverseMode.WALK;

import java.util.HashSet;
import java.util.Set;
import org.opentripplanner.street.model.edge.Edge;
import org.opentripplanner.street.model.edge.PathwayEdge;
import org.opentripplanner.street.model.edge.StreetTransitEntityLink;
import org.opentripplanner.street.search.TraverseMode;
import org.opentripplanner.transit.model.basic.Accessibility;
import org.opentripplanner.transit.model.basic.TransitMode;
import org.opentripplanner.transit.model.site.RegularStop;
Expand Down Expand Up @@ -94,4 +99,37 @@ public StationElement getStationElement() {
public boolean isConnectedToGraph() {
return getDegreeOut() + getDegreeIn() > 0;
}

/**
* Determines if this vertex is linked (via a {@link StreetTransitEntityLink}) to a drivable edge
* in the street network.
* <p>
* This method is slow: only use this during graph build.
*/
public boolean isLinkedToDrivableEdge() {
return isLinkedToEdgeWhichAllows(CAR);
}

/**
* Determines if this vertex is linked (via a {@link StreetTransitEntityLink}) to a walkable edge
* in the street network.
* <p>
* This method is slow: only use this during graph build.
*/
public boolean isLinkedToWalkableEdge() {
return isLinkedToEdgeWhichAllows(WALK);
}

private boolean isLinkedToEdgeWhichAllows(TraverseMode traverseMode) {
return getOutgoing()
.stream()
.anyMatch(edge ->
edge instanceof StreetTransitEntityLink<?> link &&
link
.getToVertex()
.getOutgoingStreetEdges()
.stream()
.anyMatch(se -> se.canTraverse(traverseMode))
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import java.util.Arrays;
import java.util.List;
import java.util.Set;
import org.junit.jupiter.api.Test;
import org.opentripplanner.ext.flex.trip.UnscheduledTrip;
import org.opentripplanner.framework.application.OTPFeature;
Expand All @@ -20,8 +21,10 @@
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.service.vehicleparking.internal.DefaultVehicleParkingRepository;
import org.opentripplanner.street.model._data.StreetModelForTest;
import org.opentripplanner.street.model.edge.BoardingLocationToStopLink;
import org.opentripplanner.street.model.edge.Edge;
import org.opentripplanner.street.model.edge.StreetTransitStopLink;
import org.opentripplanner.street.model.vertex.OsmBoardingLocationVertex;
import org.opentripplanner.street.model.vertex.SplitterVertex;
import org.opentripplanner.street.model.vertex.TransitStopVertex;
import org.opentripplanner.transit.model._data.TimetableRepositoryForTest;
Expand Down Expand Up @@ -103,6 +106,47 @@ void linkFlexStop() {
});
}

@Test
void linkFlexStopWithBoardingLocation() {
OTPFeature.FlexRouting.testOn(() -> {
var model = new TestModel().withStopLinkedToBoardingLocation();
var flexTrip = TimetableRepositoryForTest.of().unscheduledTrip("flex", model.stop());
model.withFlexTrip(flexTrip);

var module = model.streetLinkerModule();

module.buildGraph();

assertTrue(model.stopVertex().isConnectedToGraph());

// stop is used by a flex trip, needs to be linked to both the walk and car edge,
// also linked to the boarding location
assertThat(model.stopVertex().getOutgoing()).hasSize(3);

// while the order of the link doesn't matter, it _is_ deterministic.
// first we have the link to the boarding location where the passengers are expected
// to wait.
var links = model.outgoingLinks();
assertInstanceOf(BoardingLocationToStopLink.class, links.getFirst());
leonardehrenfried marked this conversation as resolved.
Show resolved Hide resolved

// the second link is the link to the walkable street network. this is not really necessary
// because the boarding location is walkable. this will be refactored away in the future.
var linkToWalk = links.get(1);
SplitterVertex walkSplit = (SplitterVertex) linkToWalk.getToVertex();

assertTrue(walkSplit.isConnectedToWalkingEdge());
assertFalse(walkSplit.isConnectedToDriveableEdge());

// lastly we have the link to the drivable street network because vehicles also need to
// reach the stop if it's part of a flex trip.
var linkToCar = links.getLast();
SplitterVertex carSplit = (SplitterVertex) linkToCar.getToVertex();

assertFalse(carSplit.isConnectedToWalkingEdge());
assertTrue(carSplit.isConnectedToDriveableEdge());
});
}

@Test
void linkCarsAllowedStop() {
var model = new TestModel();
Expand Down Expand Up @@ -140,6 +184,7 @@ private static class TestModel {
private final StreetLinkerModule module;
private final RegularStop stop;
private final TimetableRepository timetableRepository;
private final Graph graph;

public TestModel() {
var from = StreetModelForTest.intersectionVertex(
Expand All @@ -151,7 +196,7 @@ public TestModel() {
KONGSBERG_PLATFORM_1.x + DELTA
);

Graph graph = new Graph();
this.graph = new Graph();
graph.addVertex(from);
graph.addVertex(to);

Expand Down Expand Up @@ -232,5 +277,23 @@ public void withCarsAllowedTrip(Trip trip, StopLocation... stops) {

timetableRepository.addTripPattern(tripPattern.getId(), tripPattern);
}

/**
* Links the stop to a boarding location as can happen during regular graph build.
*/
public TestModel withStopLinkedToBoardingLocation() {
var boardingLocation = new OsmBoardingLocationVertex(
"boarding-location",
KONGSBERG_PLATFORM_1.x - 0.0001,
KONGSBERG_PLATFORM_1.y - 0.0001,
null,
Set.of(stop.getId().getId())
);
graph.addVertex(boardingLocation);

BoardingLocationToStopLink.createBoardingLocationToStopLink(boardingLocation, stopVertex);
BoardingLocationToStopLink.createBoardingLocationToStopLink(stopVertex, boardingLocation);
return this;
}
}
}
Loading