diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/SchemaFactory.java b/application/src/main/java/org/opentripplanner/apis/gtfs/SchemaFactory.java index 86561416006..f1476e7c441 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/SchemaFactory.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/SchemaFactory.java @@ -55,6 +55,7 @@ import org.opentripplanner.apis.gtfs.datafetchers.StopCallImpl; import org.opentripplanner.apis.gtfs.datafetchers.StopGeometriesImpl; import org.opentripplanner.apis.gtfs.datafetchers.StopImpl; +import org.opentripplanner.apis.gtfs.datafetchers.StopInPatternImpl; import org.opentripplanner.apis.gtfs.datafetchers.StopOnRouteImpl; import org.opentripplanner.apis.gtfs.datafetchers.StopOnTripImpl; import org.opentripplanner.apis.gtfs.datafetchers.StopRelationshipImpl; @@ -165,6 +166,7 @@ public static GraphQLSchema createSchema() { .type(typeWiring.build(stepImpl.class)) .type(typeWiring.build(StopImpl.class)) .type(typeWiring.build(stopAtDistanceImpl.class)) + .type(typeWiring.build(StopInPatternImpl.class)) .type(typeWiring.build(StoptimeImpl.class)) .type(typeWiring.build(StoptimesInPatternImpl.class)) .type(typeWiring.build(TicketTypeImpl.class)) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/PatternImpl.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/PatternImpl.java index db0dae0fb26..f3aa8f281c7 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/PatternImpl.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/PatternImpl.java @@ -16,6 +16,7 @@ import org.opentripplanner.apis.gtfs.GraphQLRequestContext; import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; +import org.opentripplanner.apis.gtfs.model.StopInPatternModel; import org.opentripplanner.apis.support.SemanticHash; import org.opentripplanner.framework.graphql.GraphQLUtils; import org.opentripplanner.routing.alertpatch.EntitySelector; @@ -194,6 +195,19 @@ public DataFetcher<Iterable<Object>> stops() { return this::getStops; } + @Override + public DataFetcher<Iterable<Object>> stopsInPattern() { + return environment -> { + var pattern = getSource(environment); + var numberOfStops = pattern.numberOfStops(); + var result = new StopInPatternModel[numberOfStops]; + for (var i = 0; i < numberOfStops; i++) { + result[i] = StopInPatternModel.fromPatternAndIndex(pattern, i); + } + return List.of(result); + }; + } + @Override public DataFetcher<Iterable<Trip>> trips() { return this::getTrips; diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StopInPatternImpl.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StopInPatternImpl.java new file mode 100644 index 00000000000..3e4b6bb4883 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StopInPatternImpl.java @@ -0,0 +1,35 @@ +package org.opentripplanner.apis.gtfs.datafetchers; + +import graphql.schema.DataFetcher; +import graphql.schema.DataFetchingEnvironment; +import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers; +import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; +import org.opentripplanner.apis.gtfs.mapping.PickDropMapper; +import org.opentripplanner.apis.gtfs.model.StopInPatternModel; + +public class StopInPatternImpl implements GraphQLDataFetchers.GraphQLStopInPattern { + + @Override + public DataFetcher<GraphQLTypes.GraphQLPickupDropoffType> dropOffType() { + return environment -> PickDropMapper.map(getSource(environment).dropoffType()); + } + + @Override + public DataFetcher<Integer> indexInPattern() { + return environment -> getSource(environment).indexInPattern(); + } + + @Override + public DataFetcher<GraphQLTypes.GraphQLPickupDropoffType> pickupType() { + return environment -> PickDropMapper.map(getSource(environment).pickupType()); + } + + @Override + public DataFetcher<Object> stop() { + return environment -> getSource(environment).stop(); + } + + private StopInPatternModel getSource(DataFetchingEnvironment environment) { + return environment.getSource(); + } +} diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java index 5f7c72c1377..3e3d4671daf 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java @@ -673,6 +673,8 @@ public interface GraphQLPattern { public DataFetcher<Iterable<Object>> stops(); + public DataFetcher<Iterable<Object>> stopsInPattern(); + public DataFetcher<Iterable<Trip>> trips(); public DataFetcher<Iterable<Trip>> tripsForDate(); @@ -1138,6 +1140,16 @@ public interface GraphQLStopGeometries { public DataFetcher<Iterable<Geometry>> googleEncoded(); } + public interface GraphQLStopInPattern { + public DataFetcher<GraphQLPickupDropoffType> dropOffType(); + + public DataFetcher<Integer> indexInPattern(); + + public DataFetcher<GraphQLPickupDropoffType> pickupType(); + + public DataFetcher<Object> stop(); + } + /** Stop that should (but not guaranteed) to exist on a route. */ public interface GraphQLStopOnRoute { public DataFetcher<Route> route(); diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java index 7973b909b3a..da8e135d4a2 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java @@ -1,6 +1,7 @@ // THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. package org.opentripplanner.apis.gtfs.generated; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/model/StopInPatternModel.java b/application/src/main/java/org/opentripplanner/apis/gtfs/model/StopInPatternModel.java new file mode 100644 index 00000000000..afdd1c0c98e --- /dev/null +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/model/StopInPatternModel.java @@ -0,0 +1,21 @@ +package org.opentripplanner.apis.gtfs.model; + +import org.opentripplanner.model.PickDrop; +import org.opentripplanner.transit.model.network.TripPattern; +import org.opentripplanner.transit.model.site.StopLocation; + +public record StopInPatternModel( + StopLocation stop, + int indexInPattern, + PickDrop pickupType, + PickDrop dropoffType +) { + public static StopInPatternModel fromPatternAndIndex(TripPattern pattern, int indexInPattern) { + return new StopInPatternModel( + pattern.getStop(indexInPattern), + indexInPattern, + pattern.getBoardType(indexInPattern), + pattern.getAlightType(indexInPattern) + ); + } +} diff --git a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 9b1628577b6..3c33b6b4d3a 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -960,6 +960,8 @@ type Pattern implements Node { semanticHash: String "List of stops served by this pattern" stops: [Stop!] + "List of stops with pickup / dropoff type served by this pattern" + stopsInPattern: [StopInPattern!]! "Trips which run on this pattern" trips: [Trip!] "Trips which run on this pattern on the specified date" @@ -2280,6 +2282,15 @@ type StopGeometries { googleEncoded: [Geometry] } +type StopInPattern { + "NULL means that the stop is cancelled from the pattern." + dropOffType: PickupDropoffType + indexInPattern: Int! + "NULL means that the stop is cancelled from the pattern." + pickupType: PickupDropoffType + stop: Stop! +} + "Stop that should (but not guaranteed) to exist on a route." type StopOnRoute { "Route which contains the stop." diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/model/StopInPatternModelTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/model/StopInPatternModelTest.java new file mode 100644 index 00000000000..2f249024152 --- /dev/null +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/model/StopInPatternModelTest.java @@ -0,0 +1,62 @@ +package org.opentripplanner.apis.gtfs.model; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; + +import org.junit.jupiter.api.Test; +import org.opentripplanner.model.PickDrop; +import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; +import org.opentripplanner.transit.model.network.Route; +import org.opentripplanner.transit.model.network.StopPattern; +import org.opentripplanner.transit.model.network.TripPattern; +import org.opentripplanner.transit.model.site.RegularStop; + +public class StopInPatternModelTest { + + private static final String ID = "1"; + private static final String NAME = "short name"; + private static final TimetableRepositoryForTest TEST_MODEL = TimetableRepositoryForTest.of(); + + private static final Route ROUTE = TimetableRepositoryForTest.route("routeId").build(); + public static final RegularStop STOP_A = TEST_MODEL.stop("A").build(); + public static final RegularStop STOP_B = TEST_MODEL.stop("B").build(); + public static final RegularStop STOP_C = TEST_MODEL.stop("C").build(); + private static final StopPattern STOP_PATTERN = getStopPattern(); + + private static StopPattern getStopPattern() { + var builder = StopPattern.create(3); + + builder.stops.with(0, STOP_A); + builder.stops.with(1, STOP_B); + builder.stops.with(2, STOP_C); + builder.pickups.with(0, PickDrop.SCHEDULED); + builder.pickups.with(1, PickDrop.CALL_AGENCY); + builder.pickups.with(2, PickDrop.NONE); + builder.dropoffs.with(0, PickDrop.NONE); + builder.dropoffs.with(1, PickDrop.COORDINATE_WITH_DRIVER); + builder.dropoffs.with(2, PickDrop.SCHEDULED); + return builder.build(); + } + + public static final TripPattern PATTERN = TripPattern.of(id(ID)) + .withName(NAME) + .withRoute(ROUTE) + .withStopPattern(STOP_PATTERN) + .build(); + + @Test + public void fromPatternAndIndex() { + assertEquals( + new StopInPatternModel(STOP_A, 0, PickDrop.SCHEDULED, PickDrop.NONE), + StopInPatternModel.fromPatternAndIndex(PATTERN, 0) + ); + assertEquals( + new StopInPatternModel(STOP_B, 1, PickDrop.CALL_AGENCY, PickDrop.COORDINATE_WITH_DRIVER), + StopInPatternModel.fromPatternAndIndex(PATTERN, 1) + ); + assertEquals( + new StopInPatternModel(STOP_C, 2, PickDrop.NONE, PickDrop.SCHEDULED), + StopInPatternModel.fromPatternAndIndex(PATTERN, 2) + ); + } +}