Skip to content

Commit

Permalink
Merge pull request #6343 from leonardehrenfried/station-entrances
Browse files Browse the repository at this point in the history
If configured, add subway station entrances from OSM to walk steps
  • Loading branch information
leonardehrenfried authored Jan 16, 2025
2 parents 88e5252 + 4248ffe commit 2691404
Show file tree
Hide file tree
Showing 42 changed files with 516 additions and 76 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,39 +8,11 @@
import java.util.Map;
import java.util.function.Function;
import org.junit.jupiter.api.Test;
import org.opentripplanner.ext.restapi.model.ApiAbsoluteDirection;
import org.opentripplanner.ext.restapi.model.ApiRelativeDirection;
import org.opentripplanner.ext.restapi.model.ApiVertexType;
import org.opentripplanner.model.plan.AbsoluteDirection;
import org.opentripplanner.model.plan.RelativeDirection;
import org.opentripplanner.model.plan.VertexType;

public class EnumMapperTest {

private static final String MSG =
"Assert that the API enums have the exact same values that " +
"the domain enums of the same type, and that the specialized mapper is mapping all " +
"values. If this assumtion does not hold, create a new test.";

@Test
public void map() {
try {
verifyExactMatch(
AbsoluteDirection.class,
ApiAbsoluteDirection.class,
AbsoluteDirectionMapper::mapAbsoluteDirection
);
verifyExactMatch(
RelativeDirection.class,
ApiRelativeDirection.class,
RelativeDirectionMapper::mapRelativeDirection
);
} catch (RuntimeException ex) {
System.out.println(MSG);
throw ex;
}
}

@Test
public void testVertexTypeMapping() {
verifyExplicitMatch(
Expand Down Expand Up @@ -75,17 +47,4 @@ private <D extends Enum<?>, A extends Enum<?>> void verifyExplicitMatch(
assertTrue(rest.isEmpty());
}

private <D extends Enum<?>, A extends Enum<?>> void verifyExactMatch(
Class<D> domainClass,
Class<A> apiClass,
Function<D, A> mapper
) {
List<A> rest = new ArrayList<>(List.of(apiClass.getEnumConstants()));
for (D it : domainClass.getEnumConstants()) {
A result = mapper.apply(it);
assertEquals(result.name(), it.name());
rest.remove(result);
}
assertTrue(rest.isEmpty());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public static ApiRelativeDirection mapRelativeDirection(RelativeDirection domain
case HARD_LEFT -> ApiRelativeDirection.HARD_LEFT;
case LEFT -> ApiRelativeDirection.LEFT;
case SLIGHTLY_LEFT -> ApiRelativeDirection.SLIGHTLY_LEFT;
case CONTINUE -> ApiRelativeDirection.CONTINUE;
case CONTINUE, ENTER_OR_EXIT_STATION -> ApiRelativeDirection.CONTINUE;
case SLIGHTLY_RIGHT -> ApiRelativeDirection.SLIGHTLY_RIGHT;
case RIGHT -> ApiRelativeDirection.RIGHT;
case HARD_RIGHT -> ApiRelativeDirection.HARD_RIGHT;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public ApiWalkStep mapWalkStep(WalkStep domain) {
api.streetName = domain.getDirectionText().toString(locale);
api.absoluteDirection =
domain.getAbsoluteDirection().map(AbsoluteDirectionMapper::mapAbsoluteDirection).orElse(null);
api.exit = domain.getExit();
api.exit = domain.highwayExit().orElse(null);
api.stayOn = domain.isStayOn();
api.area = domain.getArea();
api.bogusName = domain.nameIsDerived();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import org.opentripplanner.apis.gtfs.datafetchers.CurrencyImpl;
import org.opentripplanner.apis.gtfs.datafetchers.DefaultFareProductImpl;
import org.opentripplanner.apis.gtfs.datafetchers.DepartureRowImpl;
import org.opentripplanner.apis.gtfs.datafetchers.EntranceImpl;
import org.opentripplanner.apis.gtfs.datafetchers.EstimatedTimeImpl;
import org.opentripplanner.apis.gtfs.datafetchers.FareProductTypeResolver;
import org.opentripplanner.apis.gtfs.datafetchers.FareProductUseImpl;
Expand All @@ -64,6 +65,7 @@
import org.opentripplanner.apis.gtfs.datafetchers.RouteImpl;
import org.opentripplanner.apis.gtfs.datafetchers.RouteTypeImpl;
import org.opentripplanner.apis.gtfs.datafetchers.RoutingErrorImpl;
import org.opentripplanner.apis.gtfs.datafetchers.StepFeatureTypeResolver;
import org.opentripplanner.apis.gtfs.datafetchers.StopCallImpl;
import org.opentripplanner.apis.gtfs.datafetchers.StopGeometriesImpl;
import org.opentripplanner.apis.gtfs.datafetchers.StopImpl;
Expand Down Expand Up @@ -137,6 +139,7 @@ protected static GraphQLSchema buildSchema() {
.type("AlertEntity", type -> type.typeResolver(new AlertEntityTypeResolver()))
.type("CallStopLocation", type -> type.typeResolver(new CallStopLocationTypeResolver()))
.type("CallScheduledTime", type -> type.typeResolver(new CallScheduledTimeTypeResolver()))
.type("StepFeature", type -> type.typeResolver(new StepFeatureTypeResolver()))
.type(typeWiring.build(AgencyImpl.class))
.type(typeWiring.build(AlertImpl.class))
.type(typeWiring.build(BikeParkImpl.class))
Expand Down Expand Up @@ -195,6 +198,7 @@ protected static GraphQLSchema buildSchema() {
.type(typeWiring.build(LegTimeImpl.class))
.type(typeWiring.build(RealTimeEstimateImpl.class))
.type(typeWiring.build(EstimatedTimeImpl.class))
.type(typeWiring.build(EntranceImpl.class))
.build();
SchemaGenerator schemaGenerator = new SchemaGenerator();
return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package org.opentripplanner.apis.gtfs.datafetchers;

import graphql.schema.DataFetcher;
import org.opentripplanner.apis.gtfs.GraphQLUtils;
import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers;
import org.opentripplanner.apis.gtfs.generated.GraphQLTypes;
import org.opentripplanner.transit.model.site.Entrance;

public class EntranceImpl implements GraphQLDataFetchers.GraphQLEntrance {

@Override
public DataFetcher<String> publicCode() {
return environment -> {
Entrance entrance = environment.getSource();
return entrance.getCode();
};
}

@Override
public DataFetcher<String> entranceId() {
return environment -> {
Entrance entrance = environment.getSource();
return entrance.getId().toString();
};
}

@Override
public DataFetcher<String> name() {
return environment -> {
Entrance entrance = environment.getSource();
return org.opentripplanner.framework.graphql.GraphQLUtils.getTranslation(
entrance.getName(),
environment
);
};
}

@Override
public DataFetcher<GraphQLTypes.GraphQLWheelchairBoarding> wheelchairAccessible() {
return environment -> {
Entrance entrance = environment.getSource();
return GraphQLUtils.toGraphQL(entrance.getWheelchairAccessibility());
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.opentripplanner.apis.gtfs.datafetchers;

import graphql.TypeResolutionEnvironment;
import graphql.schema.GraphQLObjectType;
import graphql.schema.GraphQLSchema;
import graphql.schema.TypeResolver;
import org.opentripplanner.transit.model.site.Entrance;

public class StepFeatureTypeResolver implements TypeResolver {

@Override
public GraphQLObjectType getType(TypeResolutionEnvironment environment) {
Object o = environment.getObject();
GraphQLSchema schema = environment.getSchema();

if (o instanceof Entrance) {
return schema.getObjectType("Entrance");
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,12 @@ public DataFetcher<Iterable<Step>> elevationProfile() {

@Override
public DataFetcher<String> exit() {
return environment -> getSource(environment).getExit();
return environment -> getSource(environment).highwayExit().orElse(null);
}

@Override
public DataFetcher<Object> feature() {
return environment -> getSource(environment).entrance().orElse(null);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,17 @@ public interface GraphQLEmissions {
public DataFetcher<org.opentripplanner.framework.model.Grams> co2();
}

/** Station entrance or exit, originating from OSM or GTFS data. */
public interface GraphQLEntrance {
public DataFetcher<String> entranceId();

public DataFetcher<String> name();

public DataFetcher<String> publicCode();

public DataFetcher<org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLWheelchairBoarding> wheelchairAccessible();
}

/** Real-time estimates for an arrival or departure at a certain place. */
public interface GraphQLEstimatedTime {
public DataFetcher<java.time.Duration> delay();
Expand Down Expand Up @@ -1024,6 +1035,9 @@ public interface GraphQLRoutingError {
public DataFetcher<GraphQLInputField> inputField();
}

/** A feature for a step */
public interface GraphQLStepFeature extends TypeResolver {}

/**
* Stop can represent either a single public transport stop, where passengers can
* board and/or disembark vehicles, or a station, which contains multiple stops.
Expand Down Expand Up @@ -1521,6 +1535,8 @@ public interface GraphQLStep {

public DataFetcher<String> exit();

public DataFetcher<Object> feature();

public DataFetcher<Double> lat();

public DataFetcher<Double> lon();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4327,7 +4327,11 @@ public enum GraphQLRealtimeState {
UPDATED,
}

/** Actions to take relative to the current position when engaging a walking/driving step. */
/**
* A direction that is not absolute but rather fuzzy and context-dependent.
* It provides the passenger with information what they should do in this step depending on where they
* were in the previous one.
*/
public enum GraphQLRelativeDirection {
CIRCLE_CLOCKWISE,
CIRCLE_COUNTERCLOCKWISE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public static GraphQLRelativeDirection map(RelativeDirection relativeDirection)
case HARD_LEFT -> GraphQLRelativeDirection.HARD_LEFT;
case LEFT -> GraphQLRelativeDirection.LEFT;
case SLIGHTLY_LEFT -> GraphQLRelativeDirection.SLIGHTLY_LEFT;
case CONTINUE -> GraphQLRelativeDirection.CONTINUE;
case CONTINUE, ENTER_OR_EXIT_STATION -> GraphQLRelativeDirection.CONTINUE;
case SLIGHTLY_RIGHT -> GraphQLRelativeDirection.SLIGHTLY_RIGHT;
case RIGHT -> GraphQLRelativeDirection.RIGHT;
case HARD_RIGHT -> GraphQLRelativeDirection.HARD_RIGHT;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.opentripplanner.apis.transmodel.mapping;

import org.opentripplanner.model.plan.RelativeDirection;

/**
* This mapper makes sure that only those values are returned which have a mapping in the Transmodel API,
* as we don't really want to return all of them.
*/
public class RelativeDirectionMapper {

public static RelativeDirection map(RelativeDirection relativeDirection) {
return switch (relativeDirection) {
case DEPART,
SLIGHTLY_LEFT,
HARD_LEFT,
LEFT,
CONTINUE,
SLIGHTLY_RIGHT,
RIGHT,
HARD_RIGHT,
CIRCLE_CLOCKWISE,
CIRCLE_COUNTERCLOCKWISE,
ELEVATOR,
UTURN_LEFT,
UTURN_RIGHT -> relativeDirection;
// for these the Transmodel API doesn't have a mapping. should it?
case ENTER_STATION,
EXIT_STATION,
ENTER_OR_EXIT_STATION,
FOLLOW_SIGNS -> RelativeDirection.CONTINUE;
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import graphql.schema.GraphQLList;
import graphql.schema.GraphQLNonNull;
import graphql.schema.GraphQLObjectType;
import org.opentripplanner.apis.transmodel.mapping.RelativeDirectionMapper;
import org.opentripplanner.apis.transmodel.model.EnumTypes;
import org.opentripplanner.framework.graphql.GraphQLUtils;
import org.opentripplanner.model.plan.WalkStep;
Expand All @@ -31,7 +32,9 @@ public static GraphQLObjectType create(GraphQLObjectType elevationStepType) {
.name("relativeDirection")
.description("The relative direction of this step.")
.type(EnumTypes.RELATIVE_DIRECTION)
.dataFetcher(environment -> ((WalkStep) environment.getSource()).getRelativeDirection())
.dataFetcher(environment ->
RelativeDirectionMapper.map(((WalkStep) environment.getSource()).getRelativeDirection())
)
.build()
)
.field(
Expand Down Expand Up @@ -65,7 +68,9 @@ public static GraphQLObjectType create(GraphQLObjectType elevationStepType) {
.name("exit")
.description("When exiting a highway or traffic circle, the exit name/number.")
.type(Scalars.GraphQLString)
.dataFetcher(environment -> ((WalkStep) environment.getSource()).getExit())
.dataFetcher(environment ->
((WalkStep) environment.getSource()).highwayExit().orElse(null)
)
.build()
)
.field(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ static OsmModule provideOsmModule(
osmConfiguredDataSource.dataSource(),
osmConfiguredDataSource.config().osmTagMapper(),
osmConfiguredDataSource.config().timeZone(),
osmConfiguredDataSource.config().includeOsmSubwayEntrances(),
config.osmCacheDataInMem,
issueStore
)
Expand All @@ -88,6 +89,7 @@ static OsmModule provideOsmModule(
.withStaticBikeParkAndRide(config.staticBikeParkAndRide)
.withMaxAreaNodes(config.maxAreaNodes)
.withBoardingAreaRefTags(config.boardingLocationTags)
.withIncludeOsmSubwayEntrances(config.osmDefaults.includeOsmSubwayEntrances())
.withIssueStore(issueStore)
.withStreetLimitationParameters(streetLimitationParameters)
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public void buildElevatorEdges(Graph graph) {
}
int travelTime = parseDuration(node).orElse(-1);

var wheelchair = node.getWheelchairAccessibility();
var wheelchair = node.wheelchairAccessibility();

createElevatorHopEdges(
onboardVertices,
Expand Down Expand Up @@ -138,7 +138,7 @@ public void buildElevatorEdges(Graph graph) {

int travelTime = parseDuration(elevatorWay).orElse(-1);
int levels = nodes.size();
var wheelchair = elevatorWay.getWheelchairAccessibility();
var wheelchair = elevatorWay.wheelchairAccessibility();

createElevatorHopEdges(
onboardVertices,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,13 @@ public class OsmModule implements GraphBuilderModule {
this.issueStore = issueStore;
this.params = params;
this.osmdb = new OsmDatabase(issueStore);
this.vertexGenerator = new VertexGenerator(osmdb, graph, params.boardingAreaRefTags());
this.vertexGenerator =
new VertexGenerator(
osmdb,
graph,
params.boardingAreaRefTags(),
params.includeOsmSubwayEntrances()
);
this.normalizer = new SafetyValueNormalizer(graph, issueStore);
this.streetLimitationParameters = Objects.requireNonNull(streetLimitationParameters);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public class OsmModuleBuilder {
private boolean platformEntriesLinking = false;
private boolean staticParkAndRide = false;
private boolean staticBikeParkAndRide = false;
private boolean includeOsmSubwayEntrances = false;
private int maxAreaNodes;
private StreetLimitationParameters streetLimitationParameters = new StreetLimitationParameters();

Expand Down Expand Up @@ -83,6 +84,11 @@ public OsmModuleBuilder withMaxAreaNodes(int maxAreaNodes) {
return this;
}

public OsmModuleBuilder withIncludeOsmSubwayEntrances(boolean includeOsmSubwayEntrances) {
this.includeOsmSubwayEntrances = includeOsmSubwayEntrances;
return this;
}

public OsmModuleBuilder withStreetLimitationParameters(StreetLimitationParameters parameters) {
this.streetLimitationParameters = parameters;
return this;
Expand All @@ -103,7 +109,8 @@ public OsmModule build() {
areaVisibility,
platformEntriesLinking,
staticParkAndRide,
staticBikeParkAndRide
staticBikeParkAndRide,
includeOsmSubwayEntrances
)
);
}
Expand Down
Loading

0 comments on commit 2691404

Please sign in to comment.