
org.opentripplanner.graph_builder.module.DirectTransferGenerator Maven / Gradle / Ivy
Show all versions of otp Show documentation
package org.opentripplanner.graph_builder.module;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimaps;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import org.opentripplanner.framework.application.OTPFeature;
import org.opentripplanner.framework.logging.ProgressTracker;
import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore;
import org.opentripplanner.graph_builder.issues.StopNotLinkedForTransfers;
import org.opentripplanner.graph_builder.model.GraphBuilderModule;
import org.opentripplanner.graph_builder.module.nearbystops.NearbyStopFinder;
import org.opentripplanner.graph_builder.module.nearbystops.PatternConsideringNearbyStopFinder;
import org.opentripplanner.graph_builder.module.nearbystops.StraightLineNearbyStopFinder;
import org.opentripplanner.graph_builder.module.nearbystops.StreetNearbyStopFinder;
import org.opentripplanner.model.PathTransfer;
import org.opentripplanner.routing.api.request.RouteRequest;
import org.opentripplanner.routing.api.request.request.StreetRequest;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.graphfinder.NearbyStop;
import org.opentripplanner.street.model.edge.Edge;
import org.opentripplanner.street.model.vertex.TransitStopVertex;
import org.opentripplanner.street.model.vertex.Vertex;
import org.opentripplanner.transit.model.site.RegularStop;
import org.opentripplanner.transit.model.site.StopLocation;
import org.opentripplanner.transit.service.DefaultTransitService;
import org.opentripplanner.transit.service.TransitModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* {@link GraphBuilderModule} module that links up the
* stops of a transit network among themselves. This is necessary for routing in long-distance
* mode.
*
* It will use the street network if OSM data has already been loaded into the graph. Otherwise it
* will use straight-line distance between stops.
*/
public class DirectTransferGenerator implements GraphBuilderModule {
private static final Logger LOG = LoggerFactory.getLogger(DirectTransferGenerator.class);
private final Duration radiusByDuration;
private final List transferRequests;
private final Graph graph;
private final TransitModel transitModel;
private final DataImportIssueStore issueStore;
public DirectTransferGenerator(
Graph graph,
TransitModel transitModel,
DataImportIssueStore issueStore,
Duration radiusByDuration,
List transferRequests
) {
this.graph = graph;
this.transitModel = transitModel;
this.issueStore = issueStore;
this.radiusByDuration = radiusByDuration;
this.transferRequests = transferRequests;
}
@Override
public void buildGraph() {
/* Initialize transit model index which is needed by the nearby stop finder. */
transitModel.index();
/* The linker will use streets if they are available, or straight-line distance otherwise. */
NearbyStopFinder nearbyStopFinder = createNearbyStopFinder();
List stops = graph.getVerticesOfType(TransitStopVertex.class);
ProgressTracker progress = ProgressTracker.track(
"Create transfer edges for stops",
1000,
stops.size()
);
AtomicInteger nTransfersTotal = new AtomicInteger();
AtomicInteger nLinkedStops = new AtomicInteger();
// This is a synchronizedMultimap so that a parallel stream may be used to insert elements.
var transfersByStop = Multimaps.synchronizedMultimap(
HashMultimap.create()
);
stops
.stream()
.parallel()
.forEach(ts0 -> {
/* Make transfers to each nearby stop that has lowest weight on some trip pattern.
* Use map based on the list of edges, so that only distinct transfers are stored. */
Map distinctTransfers = new HashMap<>();
RegularStop stop = ts0.getStop();
if (stop.transfersNotAllowed()) {
return;
}
LOG.debug("Linking stop '{}' {}", stop, ts0);
for (RouteRequest transferProfile : transferRequests) {
for (NearbyStop sd : findNearbyStops(
nearbyStopFinder,
ts0,
transferProfile,
transferProfile.journey().transfer(),
false
)) {
// Skip the origin stop, loop transfers are not needed.
if (sd.stop == stop) {
continue;
}
if (sd.stop.transfersNotAllowed()) {
continue;
}
distinctTransfers.put(
new TransferKey(stop, sd.stop, sd.edges),
new PathTransfer(stop, sd.stop, sd.distance, sd.edges)
);
}
if (OTPFeature.FlexRouting.isOn()) {
// This code is for finding transfers from AreaStops to Stops, transfers
// from Stops to AreaStops and between Stops are already covered above.
for (NearbyStop sd : findNearbyStops(
nearbyStopFinder,
ts0,
transferProfile,
transferProfile.journey().transfer(),
true
)) {
// Skip the origin stop, loop transfers are not needed.
if (sd.stop == stop) {
continue;
}
if (sd.stop instanceof RegularStop) {
continue;
}
distinctTransfers.put(
new TransferKey(sd.stop, stop, sd.edges),
new PathTransfer(sd.stop, stop, sd.distance, sd.edges)
);
}
}
}
LOG.debug(
"Linked stop {} with {} transfers to stops with different patterns.",
stop,
distinctTransfers.size()
);
if (distinctTransfers.isEmpty()) {
issueStore.add(new StopNotLinkedForTransfers(ts0));
} else {
distinctTransfers
.values()
.forEach(transfer -> transfersByStop.put(transfer.from, transfer));
nLinkedStops.incrementAndGet();
nTransfersTotal.addAndGet(distinctTransfers.size());
}
//Keep lambda! A method-ref would causes incorrect class and line number to be logged
//noinspection Convert2MethodRef
progress.step(m -> LOG.info(m));
});
transitModel.addAllTransfersByStops(transfersByStop);
LOG.info(progress.completeMessage());
LOG.info(
"Done connecting stops to one another. Created a total of {} transfers from {} stops.",
nTransfersTotal,
nLinkedStops
);
}
/**
* Factory method for creating a NearbyStopFinder. Will create different finders depending on
* whether the graph has a street network and if ConsiderPatternsForDirectTransfers feature is
* enabled.
*/
private NearbyStopFinder createNearbyStopFinder() {
var transitService = new DefaultTransitService(transitModel);
NearbyStopFinder finder;
if (!graph.hasStreets) {
LOG.info(
"Creating direct transfer edges between stops using straight line distance (not streets)..."
);
finder = new StraightLineNearbyStopFinder(transitService, radiusByDuration);
} else {
LOG.info("Creating direct transfer edges between stops using the street network from OSM...");
finder = new StreetNearbyStopFinder(radiusByDuration, 0, null);
}
if (OTPFeature.ConsiderPatternsForDirectTransfers.isOn()) {
return new PatternConsideringNearbyStopFinder(transitService, finder);
} else {
return finder;
}
}
private static Iterable findNearbyStops(
NearbyStopFinder nearbyStopFinder,
Vertex vertex,
RouteRequest request,
StreetRequest streetRequest,
boolean reverseDirection
) {
return nearbyStopFinder.findNearbyStops(vertex, request, streetRequest, reverseDirection);
}
private record TransferKey(StopLocation source, StopLocation target, List edges) {}
}