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

Support for routing to Station centroid instead of child stops #6047

Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
213 changes: 117 additions & 96 deletions doc/user/BuildConfiguration.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.opentripplanner.street.model.edge.EscalatorEdge;
import org.opentripplanner.street.model.edge.PathwayEdge;
import org.opentripplanner.street.model.edge.StreetEdge;
import org.opentripplanner.street.model.edge.StreetStationCentroidLink;
import org.opentripplanner.street.model.edge.StreetTransitEntranceLink;
import org.opentripplanner.street.model.edge.StreetTransitStopLink;
import org.opentripplanner.street.model.edge.StreetVehicleParkingLink;
Expand Down Expand Up @@ -109,7 +110,8 @@ static StyleSpec build(
StreetTransitEntranceLink.class,
BoardingLocationToStopLink.class,
StreetVehicleRentalLink.class,
StreetVehicleParkingLink.class
StreetVehicleParkingLink.class,
StreetStationCentroidLink.class
)
.lineWidth(LINE_WIDTH)
.minZoom(13)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.Map;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -199,6 +200,7 @@ public boolean isOff() {
/**
* If feature is turned on, then return supplied object if not return {@code null}.
*/
@Nullable
public <T> T isOnElseNull(Supplier<T> supplier) {
return isOn() ? supplier.get() : null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,20 @@ public static double metersToLonDegrees(double distanceMeters, double latDeg) {
return dLatDeg / minCosLat;
}

/**
* Approximately move a coordinate a given number of meters. This will fail if crossing the anti-
* meridian or any of the poles.
*/
public static WgsCoordinate moveMeters(
WgsCoordinate coordinate,
double latMeters,
double lonMeters
) {
var degreesLat = metersToDegrees(latMeters);
var degreesLon = metersToLonDegrees(lonMeters, coordinate.latitude());
return coordinate.add(degreesLat, degreesLon);
}

public static Envelope bounds(double lat, double lon, double latDistance, double lonDistance) {
double radiusOfEarth = RADIUS_OF_EARTH_IN_M;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,34 @@ public WgsCoordinate roundToApproximate100m() {
return new WgsCoordinate(lat, lng);
}

/**
* Return a new coordinate that is moved an approximate number of meters east.
*/
public WgsCoordinate moveEastMeters(double meters) {
return SphericalDistanceLibrary.moveMeters(this, 0, meters);
}

/**
* Return a new coordinate that is moved an approximate number of meters west.
*/
public WgsCoordinate moveWestMeters(double meters) {
return SphericalDistanceLibrary.moveMeters(this, 0, -meters);
}

/**
* Return a new coordinate that is moved an approximate number of meters north.
*/
public WgsCoordinate moveNorthMeters(double meters) {
return SphericalDistanceLibrary.moveMeters(this, meters, 0);
}

/**
* Return a new coordinate that is moved an approximate number of meters south.
*/
public WgsCoordinate moveSouthMeters(double meters) {
return SphericalDistanceLibrary.moveMeters(this, -meters, 0);
}

/**
* Return a string on the form: {@code "(60.12345, 11.12345)"}. Up to 5 digits are used after the
* period(.), even if the coordinate is specified with a higher precision.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ public static GraphBuilder create(
graphBuilder.addModule(factory.emissionsModule());
}

graphBuilder.addModuleOptional(factory.routeToCentroidStationIdValidator());

if (config.dataImportReport) {
graphBuilder.addModule(factory.dataImportIssueReporter());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import org.opentripplanner.transit.model.site.PathwayMode;
import org.opentripplanner.transit.model.site.PathwayNode;
import org.opentripplanner.transit.model.site.RegularStop;
import org.opentripplanner.transit.model.site.Station;
import org.opentripplanner.transit.model.site.StationElement;
import org.opentripplanner.transit.model.site.StopLocation;
import org.opentripplanner.transit.service.TransitModel;
Expand Down Expand Up @@ -86,6 +87,7 @@ private void applyToGraph(TransitModel transitModel) {

addStopsToGraphAndGenerateStopVertexes(transitModel);
addEntrancesToGraph();
addStationCentroidsToGraph();
addPathwayNodesToGraph();
addBoardingAreasToGraph();

Expand Down Expand Up @@ -142,6 +144,14 @@ private void addEntrancesToGraph() {
}
}

private void addStationCentroidsToGraph() {
for (Station station : otpTransitService.stopModel().listStations()) {
if (station.shouldRouteToCentroid()) {
vertexFactory.stationCentroid(station);
}
}
}

private void addPathwayNodesToGraph() {
for (PathwayNode node : otpTransitService.getAllPathwayNodes()) {
TransitPathwayNodeVertex nodeVertex = vertexFactory.transitPathwayNode(node);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,7 @@ public void buildGraph() {
LOG.debug("Linking stop '{}' {}", stop, ts0);

for (RouteRequest transferProfile : transferRequests) {
for (NearbyStop sd : findNearbyStops(
nearbyStopFinder,
for (NearbyStop sd : nearbyStopFinder.findNearbyStops(
ts0,
transferProfile,
transferProfile.journey().transfer(),
Expand All @@ -126,8 +125,7 @@ public void buildGraph() {
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,
for (NearbyStop sd : nearbyStopFinder.findNearbyStops(
ts0,
transferProfile,
transferProfile.journey().transfer(),
Expand Down Expand Up @@ -203,15 +201,5 @@ private NearbyStopFinder createNearbyStopFinder() {
}
}

private static Iterable<NearbyStop> 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<Edge> edges) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package org.opentripplanner.graph_builder.module;

import java.util.Collection;
import java.util.stream.Collectors;
import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore;
import org.opentripplanner.graph_builder.model.GraphBuilderModule;
import org.opentripplanner.transit.model.framework.AbstractTransitEntity;
import org.opentripplanner.transit.model.framework.FeedScopedId;
import org.opentripplanner.transit.service.TransitModel;

public class RouteToCentroidStationIdsValidator implements GraphBuilderModule {

private final DataImportIssueStore issueStore;
private final Collection<FeedScopedId> transitRouteToStationCentroid;
private final TransitModel transitModel;

public RouteToCentroidStationIdsValidator(
DataImportIssueStore issueStore,
Collection<FeedScopedId> transitRouteToStationCentroid,
TransitModel transitModel
) {
this.issueStore = issueStore;
this.transitRouteToStationCentroid = transitRouteToStationCentroid;
this.transitModel = transitModel;
}

private void validate() {
var stationIds = transitModel
.getStopModel()
.listStations()
.stream()
.map(AbstractTransitEntity::getId)
.collect(Collectors.toSet());
transitRouteToStationCentroid
.stream()
.filter(id -> !stationIds.contains(id))
.forEach(id ->
issueStore.add(
"UnknownStationId",
"Config parameter 'transitRouteToStationCentroid' specified a station that does not exist: %s",
id
)
);
}

@Override
public void buildGraph() {
validate();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import org.opentripplanner.framework.application.OTPFeature;
Expand All @@ -15,14 +16,17 @@
import org.opentripplanner.routing.vehicle_parking.VehicleParking;
import org.opentripplanner.routing.vehicle_parking.VehicleParkingHelper;
import org.opentripplanner.street.model.edge.Edge;
import org.opentripplanner.street.model.edge.StreetStationCentroidLink;
import org.opentripplanner.street.model.edge.StreetTransitEntranceLink;
import org.opentripplanner.street.model.edge.StreetTransitStopLink;
import org.opentripplanner.street.model.edge.StreetVehicleParkingLink;
import org.opentripplanner.street.model.edge.VehicleParkingEdge;
import org.opentripplanner.street.model.vertex.StationCentroidVertex;
import org.opentripplanner.street.model.vertex.StreetVertex;
import org.opentripplanner.street.model.vertex.TransitEntranceVertex;
import org.opentripplanner.street.model.vertex.TransitStopVertex;
import org.opentripplanner.street.model.vertex.VehicleParkingEntranceVertex;
import org.opentripplanner.street.model.vertex.Vertex;
import org.opentripplanner.street.search.TraverseMode;
import org.opentripplanner.street.search.TraverseModeSet;
import org.opentripplanner.transit.model.site.GroupStop;
Expand Down Expand Up @@ -70,6 +74,7 @@ public void buildGraph() {
if (graph.hasStreets) {
linkTransitStops(graph, transitModel);
linkTransitEntrances(graph);
linkStationCentroids(graph);
linkVehicleParks(graph, issueStore);
}

Expand Down Expand Up @@ -257,6 +262,31 @@ private void linkTransitEntrances(Graph graph) {
}
}

private void linkStationCentroids(Graph graph) {
BiFunction<Vertex, StreetVertex, List<Edge>> createLinkEdges = (theStation, streetVertex) ->
List.of(
StreetStationCentroidLink.createStreetStationLink(
(StationCentroidVertex) theStation,
streetVertex
),
StreetStationCentroidLink.createStreetStationLink(
streetVertex,
(StationCentroidVertex) theStation
)
);

for (StationCentroidVertex station : graph.getVerticesOfType(StationCentroidVertex.class)) {
graph
.getLinker()
.linkVertexPermanently(
station,
new TraverseModeSet(TraverseMode.WALK),
LinkingDirection.BOTH_WAYS,
createLinkEdges
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
createLinkEdges
linkStationAndStreetVetex

I sometimes add linkStationAndStreetVetexOp to make sure it is clear that it is a lambda, do you know any best practices here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also sometimes like to use a suffix for clarification. But I think in most cases having the name be a verb is clear enough.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use stationAndStreetVetexLinker

);
}
}

private void linkVehicleParks(Graph graph, DataImportIssueStore issueStore) {
LOG.info("Linking vehicle parks to graph...");
List<VehicleParking> vehicleParkingToRemove = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.opentripplanner.graph_builder.module.DirectTransferGenerator;
import org.opentripplanner.graph_builder.module.GraphCoherencyCheckerModule;
import org.opentripplanner.graph_builder.module.OsmBoardingLocationsModule;
import org.opentripplanner.graph_builder.module.RouteToCentroidStationIdsValidator;
import org.opentripplanner.graph_builder.module.StreetLinkerModule;
import org.opentripplanner.graph_builder.module.TimeZoneAdjusterModule;
import org.opentripplanner.graph_builder.module.TripPatternNamer;
Expand Down Expand Up @@ -58,6 +59,9 @@ public interface GraphBuilderFactory {
CalculateWorldEnvelopeModule calculateWorldEnvelopeModule();
StreetLimitationParameters streetLimitationParameters();

@Nullable
RouteToCentroidStationIdsValidator routeToCentroidStationIdValidator();

@Nullable
StopConsolidationModule stopConsolidationModule();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.opentripplanner.graph_builder.issue.report.DataImportIssueReporter;
import org.opentripplanner.graph_builder.issue.service.DefaultDataImportIssueStore;
import org.opentripplanner.graph_builder.module.DirectTransferGenerator;
import org.opentripplanner.graph_builder.module.RouteToCentroidStationIdsValidator;
import org.opentripplanner.graph_builder.module.StreetLinkerModule;
import org.opentripplanner.graph_builder.module.islandpruning.PruneIslands;
import org.opentripplanner.graph_builder.module.ned.DegreeGridNEDTileSource;
Expand Down Expand Up @@ -302,6 +303,20 @@ static StopConsolidationModule providesStopConsolidationModule(
.orElse(null);
}

@Provides
@Singleton
@Nullable
static RouteToCentroidStationIdsValidator routeToCentroidStationIdValidator(
DataImportIssueStore issueStore,
BuildConfig config,
TransitModel transitModel
) {
var ids = config.transitRouteToStationCentroid();
return ids.isEmpty()
? null
: new RouteToCentroidStationIdsValidator(issueStore, ids, transitModel);
}

/* private methods */

private static ElevationGridCoverageFactory createNedElevationFactory(
Expand Down
Loading
Loading