c, String nullText) {
/**
* A null-safe version of isEmpty() for a collection.
*
+ * The main strategy handling collections in OTP is to avoid nullable collection fields and use empty
+ * collections instead. So, before using this method check if the variable/field is indeed `@Nullable`.
+ *
* If the collection is {@code null} then {@code true} is returned.
*
* If the collection is empty then {@code true} is returned.
diff --git a/src/main/java/org/opentripplanner/framework/collection/ListUtils.java b/src/main/java/org/opentripplanner/framework/collection/ListUtils.java
index 5964a1674e3..35b7e083695 100644
--- a/src/main/java/org/opentripplanner/framework/collection/ListUtils.java
+++ b/src/main/java/org/opentripplanner/framework/collection/ListUtils.java
@@ -69,4 +69,13 @@ public static List ofNullable(T input) {
return List.of(input);
}
}
+
+ /**
+ * This method converts the given collection to an instance of a List. If the input is
+ * {@code null} an empty collection is returned. If not the {@link List#copyOf(Collection)} is
+ * called.
+ */
+ public static List nullSafeImmutableList(Collection c) {
+ return (c == null) ? List.of() : List.copyOf(c);
+ }
}
diff --git a/src/main/java/org/opentripplanner/inspector/vector/LayerParameters.java b/src/main/java/org/opentripplanner/inspector/vector/LayerParameters.java
index cb849e0f0ac..70cb5a2ce43 100644
--- a/src/main/java/org/opentripplanner/inspector/vector/LayerParameters.java
+++ b/src/main/java/org/opentripplanner/inspector/vector/LayerParameters.java
@@ -1,6 +1,7 @@
package org.opentripplanner.inspector.vector;
import org.opentripplanner.apis.support.mapping.PropertyMapper;
+import org.opentripplanner.ext.vectortiles.layers.LayerFilters;
/**
* Configuration options for a single vector tile layer.
@@ -53,4 +54,8 @@ default int cacheMaxSeconds() {
default double expansionFactor() {
return EXPANSION_FACTOR;
}
+
+ default LayerFilters.FilterType filterType() {
+ return LayerFilters.FilterType.NONE;
+ }
}
diff --git a/src/main/java/org/opentripplanner/standalone/config/routerconfig/VectorTileConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerconfig/VectorTileConfig.java
index 96cd49b72bb..26d56dc0dba 100644
--- a/src/main/java/org/opentripplanner/standalone/config/routerconfig/VectorTileConfig.java
+++ b/src/main/java/org/opentripplanner/standalone/config/routerconfig/VectorTileConfig.java
@@ -6,20 +6,22 @@
import static org.opentripplanner.inspector.vector.LayerParameters.MIN_ZOOM;
import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_0;
import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_5;
+import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_6;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import javax.annotation.Nullable;
import org.opentripplanner.ext.vectortiles.VectorTilesResource;
+import org.opentripplanner.ext.vectortiles.VectorTilesResource.LayerType;
+import org.opentripplanner.ext.vectortiles.layers.LayerFilters;
import org.opentripplanner.inspector.vector.LayerParameters;
import org.opentripplanner.standalone.config.framework.json.NodeAdapter;
-public class VectorTileConfig
- implements VectorTilesResource.LayersParameters {
+public class VectorTileConfig implements VectorTilesResource.LayersParameters {
public static final VectorTileConfig DEFAULT = new VectorTileConfig(List.of(), null, null);
- private final List> layers;
+ private final List> layers;
@Nullable
private final String basePath;
@@ -28,7 +30,7 @@ public class VectorTileConfig
private final String attribution;
VectorTileConfig(
- Collection extends LayerParameters> layers,
+ Collection extends LayerParameters> layers,
@Nullable String basePath,
@Nullable String attribution
) {
@@ -38,7 +40,7 @@ public class VectorTileConfig
}
@Override
- public List> layers() {
+ public List> layers() {
return layers;
}
@@ -144,7 +146,18 @@ public static Layer mapLayer(NodeAdapter node) {
"The value is a fraction of the tile size. If you are having problem with icons and " +
"shapes being clipped at tile edges, then increase this number."
)
- .asDouble(EXPANSION_FACTOR)
+ .asDouble(EXPANSION_FACTOR),
+ node
+ .of("filter")
+ .since(V2_6)
+ .summary("Reduce the result set of a layer further by a specific filter.")
+ .description(
+ """
+ This is useful for when the schema of a layer, say stops, should remain unchanged but some
+ elements should not be included in the result.
+ """
+ )
+ .asEnum(LayerFilters.FilterType.NONE)
);
}
@@ -155,7 +168,8 @@ record Layer(
int maxZoom,
int minZoom,
int cacheMaxSeconds,
- double expansionFactor
+ double expansionFactor,
+ LayerFilters.FilterType filterType
)
implements LayerParameters {}
}
diff --git a/src/main/java/org/opentripplanner/transit/api/request/TripOnServiceDateRequest.java b/src/main/java/org/opentripplanner/transit/api/request/TripOnServiceDateRequest.java
new file mode 100644
index 00000000000..6735dc1db29
--- /dev/null
+++ b/src/main/java/org/opentripplanner/transit/api/request/TripOnServiceDateRequest.java
@@ -0,0 +1,77 @@
+package org.opentripplanner.transit.api.request;
+
+import java.time.LocalDate;
+import java.util.List;
+import org.opentripplanner.framework.collection.ListUtils;
+import org.opentripplanner.transit.model.framework.FeedScopedId;
+import org.opentripplanner.transit.model.timetable.TripAlteration;
+
+/*
+ * A request for trips on a specific service date.
+ *
+ * This request is used to retrieve TripsOnServiceDates that match the provided criteria.
+ * At least one operatingDay must be provided.
+ */
+public class TripOnServiceDateRequest {
+
+ private final List operatingDays;
+ private final List authorities;
+ private final List lines;
+ private final List serviceJourneys;
+ private final List replacementFor;
+ private final List privateCodes;
+ private final List alterations;
+
+ protected TripOnServiceDateRequest(
+ List operatingDays,
+ List authorities,
+ List lines,
+ List serviceJourneys,
+ List replacementFor,
+ List privateCodes,
+ List alterations
+ ) {
+ if (operatingDays == null || operatingDays.isEmpty()) {
+ throw new IllegalArgumentException("operatingDays must have at least one date");
+ }
+ this.operatingDays = ListUtils.nullSafeImmutableList(operatingDays);
+ this.authorities = ListUtils.nullSafeImmutableList(authorities);
+ this.lines = ListUtils.nullSafeImmutableList(lines);
+ this.serviceJourneys = ListUtils.nullSafeImmutableList(serviceJourneys);
+ this.replacementFor = ListUtils.nullSafeImmutableList(replacementFor);
+ this.privateCodes = ListUtils.nullSafeImmutableList(privateCodes);
+ this.alterations = ListUtils.nullSafeImmutableList(alterations);
+ }
+
+ public static TripOnServiceDateRequestBuilder of() {
+ return new TripOnServiceDateRequestBuilder();
+ }
+
+ public List authorities() {
+ return authorities;
+ }
+
+ public List lines() {
+ return lines;
+ }
+
+ public List serviceJourneys() {
+ return serviceJourneys;
+ }
+
+ public List replacementFor() {
+ return replacementFor;
+ }
+
+ public List privateCodes() {
+ return privateCodes;
+ }
+
+ public List alterations() {
+ return alterations;
+ }
+
+ public List operatingDays() {
+ return operatingDays;
+ }
+}
diff --git a/src/main/java/org/opentripplanner/transit/api/request/TripOnServiceDateRequestBuilder.java b/src/main/java/org/opentripplanner/transit/api/request/TripOnServiceDateRequestBuilder.java
new file mode 100644
index 00000000000..7aa2644fdc9
--- /dev/null
+++ b/src/main/java/org/opentripplanner/transit/api/request/TripOnServiceDateRequestBuilder.java
@@ -0,0 +1,66 @@
+package org.opentripplanner.transit.api.request;
+
+import java.time.LocalDate;
+import java.util.List;
+import org.opentripplanner.transit.model.framework.FeedScopedId;
+import org.opentripplanner.transit.model.timetable.TripAlteration;
+
+public class TripOnServiceDateRequestBuilder {
+
+ private List authorities;
+ private List lines;
+ private List serviceJourneys;
+ private List replacementFor;
+ private List privateCodes;
+ private List alterations;
+ private List operatingDays;
+
+ protected TripOnServiceDateRequestBuilder() {}
+
+ public TripOnServiceDateRequestBuilder withOperatingDays(List operatingDays) {
+ this.operatingDays = operatingDays;
+ return this;
+ }
+
+ public TripOnServiceDateRequestBuilder withAuthorities(List authorities) {
+ this.authorities = authorities;
+ return this;
+ }
+
+ public TripOnServiceDateRequestBuilder withLines(List lines) {
+ this.lines = lines;
+ return this;
+ }
+
+ public TripOnServiceDateRequestBuilder withServiceJourneys(List serviceJourneys) {
+ this.serviceJourneys = serviceJourneys;
+ return this;
+ }
+
+ public TripOnServiceDateRequestBuilder withReplacementFor(List replacementFor) {
+ this.replacementFor = replacementFor;
+ return this;
+ }
+
+ public TripOnServiceDateRequestBuilder withPrivateCodes(List privateCodes) {
+ this.privateCodes = privateCodes;
+ return this;
+ }
+
+ public TripOnServiceDateRequestBuilder withAlterations(List alterations) {
+ this.alterations = alterations;
+ return this;
+ }
+
+ public TripOnServiceDateRequest build() {
+ return new TripOnServiceDateRequest(
+ operatingDays,
+ authorities,
+ lines,
+ serviceJourneys,
+ replacementFor,
+ privateCodes,
+ alterations
+ );
+ }
+}
diff --git a/src/main/java/org/opentripplanner/transit/model/filter/expr/AndMatcher.java b/src/main/java/org/opentripplanner/transit/model/filter/expr/AndMatcher.java
new file mode 100644
index 00000000000..74f38efa8b7
--- /dev/null
+++ b/src/main/java/org/opentripplanner/transit/model/filter/expr/AndMatcher.java
@@ -0,0 +1,43 @@
+package org.opentripplanner.transit.model.filter.expr;
+
+import static org.opentripplanner.transit.model.filter.expr.BinaryOperator.AND;
+
+import java.util.List;
+
+/**
+ * Takes a list of matchers and provides a single interface. All matchers in the list must match for
+ * the composite matcher to return a match.
+ *
+ * @param The entity type the AndMatcher matches.
+ */
+public final class AndMatcher implements Matcher {
+
+ private final Matcher[] matchers;
+
+ private AndMatcher(List> matchers) {
+ this.matchers = matchers.toArray(Matcher[]::new);
+ }
+
+ public static Matcher of(List> matchers) {
+ // simplify a list of one element
+ if (matchers.size() == 1) {
+ return matchers.get(0);
+ }
+ return new AndMatcher<>(matchers);
+ }
+
+ @Override
+ public boolean match(T entity) {
+ for (var m : matchers) {
+ if (!m.match(entity)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "(" + AND.arrayToString(matchers) + ')';
+ }
+}
diff --git a/src/main/java/org/opentripplanner/transit/model/filter/expr/BinaryOperator.java b/src/main/java/org/opentripplanner/transit/model/filter/expr/BinaryOperator.java
new file mode 100644
index 00000000000..62f3fa30f27
--- /dev/null
+++ b/src/main/java/org/opentripplanner/transit/model/filter/expr/BinaryOperator.java
@@ -0,0 +1,37 @@
+package org.opentripplanner.transit.model.filter.expr;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * Used to concatenate matches with either the logical "AND" or "OR" operator.
+ */
+enum BinaryOperator {
+ AND("&"),
+ OR("|");
+
+ private final String token;
+
+ BinaryOperator(String token) {
+ this.token = token;
+ }
+
+ @Override
+ public String toString() {
+ return token;
+ }
+
+ String arrayToString(T[] values) {
+ return colToString(Arrays.asList(values));
+ }
+
+ String colToString(Collection values) {
+ return values.stream().map(Objects::toString).collect(Collectors.joining(" " + token + " "));
+ }
+
+ String toString(T a, T b) {
+ return a.toString() + " " + token + " " + b.toString();
+ }
+}
diff --git a/src/main/java/org/opentripplanner/transit/model/filter/expr/ContainsMatcher.java b/src/main/java/org/opentripplanner/transit/model/filter/expr/ContainsMatcher.java
new file mode 100644
index 00000000000..ed3731897ec
--- /dev/null
+++ b/src/main/java/org/opentripplanner/transit/model/filter/expr/ContainsMatcher.java
@@ -0,0 +1,55 @@
+package org.opentripplanner.transit.model.filter.expr;
+
+import java.util.function.Function;
+
+/**
+ * A matcher that applies a provided matcher to an iterable of child entities returned from the main
+ * entity that this matcher is for.
+ *
+ * If any of the iterable entities match the valueMatcher, then the match method returns true. In
+ * this way it is similar to an OR.
+ *
+ * @param The main entity type this matcher is applied to.
+ * @param The type of the child entities, for which there is a mapping from S to T.
+ */
+public class ContainsMatcher implements Matcher {
+
+ private final String relationshipName;
+ private final Function> valuesProvider;
+ private final Matcher valueMatcher;
+
+ /**
+ * @param relationshipName The name of the type of relationship between the main entity and the
+ * entity matched by the valueMatcher.
+ * @param valuesProvider The function that maps the entity being matched by this matcher (S) to
+ * the iterable of items being matched by valueMatcher.
+ * @param valueMatcher The matcher that is applied each of the iterable entities returned from the
+ * valuesProvider function.
+ */
+ public ContainsMatcher(
+ String relationshipName,
+ Function> valuesProvider,
+ Matcher valueMatcher
+ ) {
+ this.relationshipName = relationshipName;
+ this.valuesProvider = valuesProvider;
+ this.valueMatcher = valueMatcher;
+ }
+
+ public boolean match(S entity) {
+ if (valuesProvider.apply(entity) == null) {
+ return false;
+ }
+ for (T it : valuesProvider.apply(entity)) {
+ if (valueMatcher.match(it)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "ContainsMatcher: " + relationshipName + ": " + valueMatcher.toString();
+ }
+}
diff --git a/src/main/java/org/opentripplanner/transit/model/filter/expr/EqualityMatcher.java b/src/main/java/org/opentripplanner/transit/model/filter/expr/EqualityMatcher.java
new file mode 100644
index 00000000000..1380131e07a
--- /dev/null
+++ b/src/main/java/org/opentripplanner/transit/model/filter/expr/EqualityMatcher.java
@@ -0,0 +1,40 @@
+package org.opentripplanner.transit.model.filter.expr;
+
+import java.util.function.Function;
+
+/**
+ * A matcher that checks if a value is equal to another value derived from the matched entities.
+ *
+ * The derived entity value is provided by a function that takes the entity being matched as an argument.
+ *
+ * @param The type of the entity being matched.
+ * @param The type of the value that the matcher will test equality for.
+ */
+public class EqualityMatcher implements Matcher {
+
+ private final String typeName;
+ private final V value;
+ private final Function valueProvider;
+
+ /**
+ * @param typeName The typeName appears in the toString for easier debugging.
+ * @param value The value that this matcher will check equality for.
+ * @param valueProvider The function that maps the entity being matched by this matcher (T) to
+ * the value being matched by this matcher.
+ */
+ public EqualityMatcher(String typeName, V value, Function valueProvider) {
+ this.typeName = typeName;
+ this.value = value;
+ this.valueProvider = valueProvider;
+ }
+
+ @Override
+ public boolean match(T entity) {
+ return value.equals(valueProvider.apply(entity));
+ }
+
+ @Override
+ public String toString() {
+ return typeName + "==" + value;
+ }
+}
diff --git a/src/main/java/org/opentripplanner/transit/model/filter/expr/ExpressionBuilder.java b/src/main/java/org/opentripplanner/transit/model/filter/expr/ExpressionBuilder.java
new file mode 100644
index 00000000000..b1b4d5be322
--- /dev/null
+++ b/src/main/java/org/opentripplanner/transit/model/filter/expr/ExpressionBuilder.java
@@ -0,0 +1,37 @@
+package org.opentripplanner.transit.model.filter.expr;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Function;
+
+/**
+ * A builder for creating complex matchers composed of other matchers.
+ *
+ * This builder contains convenience methods for creating complex matchers from simpler ones. The
+ * resulting matcher "ands" together all the matchers it has built up. This supports the common
+ * pattern of narrowing results with multiple filters.
+ *
+ * @param The type of entity to match in the expression.
+ */
+public class ExpressionBuilder {
+
+ private final List> matchers = new ArrayList<>();
+
+ public static ExpressionBuilder of() {
+ return new ExpressionBuilder<>();
+ }
+
+ public ExpressionBuilder or(Collection values, Function> valueProvider) {
+ if (values.isEmpty()) {
+ return this;
+ }
+
+ matchers.add(OrMatcher.of(values.stream().map(valueProvider).toList()));
+ return this;
+ }
+
+ public Matcher build() {
+ return AndMatcher.of(matchers);
+ }
+}
diff --git a/src/main/java/org/opentripplanner/transit/model/filter/expr/Matcher.java b/src/main/java/org/opentripplanner/transit/model/filter/expr/Matcher.java
new file mode 100644
index 00000000000..db3c02296b9
--- /dev/null
+++ b/src/main/java/org/opentripplanner/transit/model/filter/expr/Matcher.java
@@ -0,0 +1,19 @@
+package org.opentripplanner.transit.model.filter.expr;
+
+/**
+ * Generic matcher interface - this is the root of the matcher type hierarchy.
+ *
+ * @param Domain type to match.
+ */
+@FunctionalInterface
+public interface Matcher {
+ boolean match(T entity);
+
+ static Matcher everything() {
+ return e -> true;
+ }
+
+ static Matcher nothing() {
+ return e -> false;
+ }
+}
diff --git a/src/main/java/org/opentripplanner/transit/model/filter/expr/OrMatcher.java b/src/main/java/org/opentripplanner/transit/model/filter/expr/OrMatcher.java
new file mode 100644
index 00000000000..62da7af63f4
--- /dev/null
+++ b/src/main/java/org/opentripplanner/transit/model/filter/expr/OrMatcher.java
@@ -0,0 +1,58 @@
+package org.opentripplanner.transit.model.filter.expr;
+
+import static org.opentripplanner.transit.model.filter.expr.BinaryOperator.OR;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Takes a list of matchers and provides a single interface. At least one of the matchers in the
+ * list must match for the composite matcher to return a match.
+ *
+ * @param The entity type the OrMatcher matches.
+ */
+public final class OrMatcher implements Matcher {
+
+ private final Matcher[] matchers;
+
+ private OrMatcher(List> matchers) {
+ this.matchers = matchers.toArray(Matcher[]::new);
+ }
+
+ public static Matcher of(Matcher a, Matcher b) {
+ return of(List.of(a, b));
+ }
+
+ public static Matcher of(List> matchers) {
+ // Simplify if there is just one matcher in the list
+ if (matchers.size() == 1) {
+ return matchers.get(0);
+ }
+ // Collapse nested or matchers
+ var expr = new ArrayList>();
+ for (Matcher it : matchers) {
+ if (it instanceof OrMatcher orMatcher) {
+ expr.addAll(Arrays.asList(orMatcher.matchers));
+ } else {
+ expr.add(it);
+ }
+ }
+ return new OrMatcher<>(expr);
+ }
+
+ @Override
+ public boolean match(T entity) {
+ for (var m : matchers) {
+ if (m.match(entity)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "(" + OR.arrayToString(matchers) + ')';
+ }
+}
diff --git a/src/main/java/org/opentripplanner/transit/model/filter/transit/TripOnServiceDateMatcherFactory.java b/src/main/java/org/opentripplanner/transit/model/filter/transit/TripOnServiceDateMatcherFactory.java
new file mode 100644
index 00000000000..f86e7a1ff77
--- /dev/null
+++ b/src/main/java/org/opentripplanner/transit/model/filter/transit/TripOnServiceDateMatcherFactory.java
@@ -0,0 +1,70 @@
+package org.opentripplanner.transit.model.filter.transit;
+
+import java.time.LocalDate;
+import org.opentripplanner.transit.api.request.TripOnServiceDateRequest;
+import org.opentripplanner.transit.model.filter.expr.ContainsMatcher;
+import org.opentripplanner.transit.model.filter.expr.EqualityMatcher;
+import org.opentripplanner.transit.model.filter.expr.ExpressionBuilder;
+import org.opentripplanner.transit.model.filter.expr.Matcher;
+import org.opentripplanner.transit.model.framework.AbstractTransitEntity;
+import org.opentripplanner.transit.model.framework.FeedScopedId;
+import org.opentripplanner.transit.model.timetable.TripAlteration;
+import org.opentripplanner.transit.model.timetable.TripOnServiceDate;
+
+/**
+ * A factory for creating matchers for TripOnServiceDate objects.
+ *
+ * This factory is used to create matchers for TripOnServiceDate objects based on a request. The
+ * resulting matcher can be used to filter a list of TripOnServiceDate objects.
+ */
+public class TripOnServiceDateMatcherFactory {
+
+ public static Matcher of(TripOnServiceDateRequest request) {
+ ExpressionBuilder expr = ExpressionBuilder.of();
+
+ expr.or(request.operatingDays(), TripOnServiceDateMatcherFactory::operatingDay);
+ expr.or(request.authorities(), TripOnServiceDateMatcherFactory::authorityId);
+ expr.or(request.lines(), TripOnServiceDateMatcherFactory::routeId);
+ expr.or(request.serviceJourneys(), TripOnServiceDateMatcherFactory::serviceJourneyId);
+ expr.or(request.replacementFor(), TripOnServiceDateMatcherFactory::replacementFor);
+ expr.or(request.privateCodes(), TripOnServiceDateMatcherFactory::privateCode);
+ expr.or(request.alterations(), TripOnServiceDateMatcherFactory::alteration);
+ return expr.build();
+ }
+
+ static Matcher authorityId(FeedScopedId id) {
+ return new EqualityMatcher<>("agency", id, t -> t.getTrip().getRoute().getAgency().getId());
+ }
+
+ static Matcher routeId(FeedScopedId id) {
+ return new EqualityMatcher<>("route", id, t -> t.getTrip().getRoute().getId());
+ }
+
+ static Matcher serviceJourneyId(FeedScopedId id) {
+ return new EqualityMatcher<>("serviceJourney", id, t -> t.getTrip().getId());
+ }
+
+ static Matcher replacementFor(FeedScopedId id) {
+ return new ContainsMatcher<>(
+ "replacementForContains",
+ t -> t.getReplacementFor().stream().map(AbstractTransitEntity::getId).toList(),
+ new EqualityMatcher<>("replacementForIdEquals", id, (idToMatch -> idToMatch))
+ );
+ }
+
+ static Matcher privateCode(String code) {
+ return new EqualityMatcher<>(
+ "privateCode",
+ code,
+ t -> t.getTrip().getNetexInternalPlanningCode()
+ );
+ }
+
+ static Matcher operatingDay(LocalDate date) {
+ return new EqualityMatcher<>("operatingDay", date, TripOnServiceDate::getServiceDate);
+ }
+
+ static Matcher alteration(TripAlteration alteration) {
+ return new EqualityMatcher<>("alteration", alteration, TripOnServiceDate::getTripAlteration);
+ }
+}
diff --git a/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java b/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java
index 8fa18443bab..1738c66bab0 100644
--- a/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java
+++ b/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java
@@ -35,8 +35,11 @@
import org.opentripplanner.routing.services.TransitAlertService;
import org.opentripplanner.routing.stoptimes.ArrivalDeparture;
import org.opentripplanner.routing.stoptimes.StopTimesHelper;
+import org.opentripplanner.transit.api.request.TripOnServiceDateRequest;
import org.opentripplanner.transit.model.basic.Notice;
import org.opentripplanner.transit.model.basic.TransitMode;
+import org.opentripplanner.transit.model.filter.expr.Matcher;
+import org.opentripplanner.transit.model.filter.transit.TripOnServiceDateMatcherFactory;
import org.opentripplanner.transit.model.framework.AbstractTransitEntity;
import org.opentripplanner.transit.model.framework.Deduplicator;
import org.opentripplanner.transit.model.framework.FeedScopedId;
@@ -591,6 +594,23 @@ public TripOnServiceDate getTripOnServiceDateForTripAndDay(
return transitModelIndex.getTripOnServiceDateForTripAndDay().get(tripIdAndServiceDate);
}
+ /**
+ * Returns a list of TripOnServiceDates that match the filtering defined in the request.
+ *
+ * @param request - A TripOnServiceDateRequest object with filtering defined.
+ * @return - A list of TripOnServiceDates
+ */
+ @Override
+ public List getTripOnServiceDates(TripOnServiceDateRequest request) {
+ Matcher matcher = TripOnServiceDateMatcherFactory.of(request);
+ return transitModelIndex
+ .getTripOnServiceDateForTripAndDay()
+ .values()
+ .stream()
+ .filter(matcher::match)
+ .collect(Collectors.toList());
+ }
+
/**
* TODO OTP2 - This is NOT THREAD-SAFE and is used in the real-time updaters, we need to fix
* this when doing the issue #3030.
diff --git a/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java b/src/main/java/org/opentripplanner/transit/service/PatternByServiceDatesFilter.java
similarity index 79%
rename from src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java
rename to src/main/java/org/opentripplanner/transit/service/PatternByServiceDatesFilter.java
index 8eecfe6273b..21890975acf 100644
--- a/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java
+++ b/src/main/java/org/opentripplanner/transit/service/PatternByServiceDatesFilter.java
@@ -1,16 +1,13 @@
-package org.opentripplanner.apis.gtfs;
+package org.opentripplanner.transit.service;
import java.time.LocalDate;
import java.util.Collection;
import java.util.Objects;
import java.util.function.Function;
-import java.util.stream.Stream;
-import org.opentripplanner.apis.gtfs.generated.GraphQLTypes;
import org.opentripplanner.apis.gtfs.model.LocalDateRange;
import org.opentripplanner.transit.model.network.Route;
import org.opentripplanner.transit.model.network.TripPattern;
import org.opentripplanner.transit.model.timetable.Trip;
-import org.opentripplanner.transit.service.TransitService;
/**
* Encapsulates the logic to filter patterns by the service dates that they operate on. It also
@@ -29,7 +26,7 @@ public class PatternByServiceDatesFilter {
* This method is not private to enable unit testing.
*
*/
- PatternByServiceDatesFilter(
+ public PatternByServiceDatesFilter(
LocalDateRange range,
Function> getPatternsForRoute,
Function> getServiceDatesForTrip
@@ -45,17 +42,6 @@ public class PatternByServiceDatesFilter {
}
}
- public PatternByServiceDatesFilter(
- GraphQLTypes.GraphQLLocalDateRangeInput filterInput,
- TransitService transitService
- ) {
- this(
- new LocalDateRange(filterInput.getGraphQLStart(), filterInput.getGraphQLEnd()),
- transitService::getPatternsForRoute,
- trip -> transitService.getCalendarService().getServiceDatesForServiceId(trip.getServiceId())
- );
- }
-
/**
* Filter the patterns by the service dates that it operates on.
*/
@@ -67,8 +53,9 @@ public Collection filterPatterns(Collection tripPatter
* Filter the routes by listing all their patterns' service dates and checking if they
* operate on the specified dates.
*/
- public Collection filterRoutes(Stream routeStream) {
+ public Collection filterRoutes(Collection routeStream) {
return routeStream
+ .stream()
.filter(r -> {
var patterns = getPatternsForRoute.apply(r);
return !this.filterPatterns(patterns).isEmpty();
diff --git a/src/main/java/org/opentripplanner/transit/service/TransitService.java b/src/main/java/org/opentripplanner/transit/service/TransitService.java
index 1836b5612d2..1e7c4ff8397 100644
--- a/src/main/java/org/opentripplanner/transit/service/TransitService.java
+++ b/src/main/java/org/opentripplanner/transit/service/TransitService.java
@@ -24,6 +24,7 @@
import org.opentripplanner.routing.algorithm.raptoradapter.transit.TransitLayer;
import org.opentripplanner.routing.services.TransitAlertService;
import org.opentripplanner.routing.stoptimes.ArrivalDeparture;
+import org.opentripplanner.transit.api.request.TripOnServiceDateRequest;
import org.opentripplanner.transit.model.basic.Notice;
import org.opentripplanner.transit.model.basic.TransitMode;
import org.opentripplanner.transit.model.framework.AbstractTransitEntity;
@@ -307,4 +308,12 @@ List stopTimesForPatternAtStop(
Set getAllServiceCodes();
Map getServiceCodesRunningForDate();
+
+ /**
+ * Returns a list of TripOnServiceDates that match the filtering defined in the request.
+ *
+ * @param request - A TripOnServiceDateRequest object with filtering defined.
+ * @return - A list of TripOnServiceDates
+ */
+ List getTripOnServiceDates(TripOnServiceDateRequest request);
}
diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls
index 927af19f8b1..78f18f4e654 100644
--- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls
+++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls
@@ -2001,7 +2001,16 @@ type Stop implements Node & PlaceInterface {
"Identifier of the platform, usually a number. This value is only present for stops that are part of a station"
platformCode: String
"Routes which pass through this stop"
- routes: [Route!]
+ routes(
+ """
+ Only include routes which are operational on at least one service date specified by this filter.
+
+ **Note**: A service date is a technical term useful for transit planning purposes and might not
+ correspond to a how a passenger thinks of a calendar date. For example, a night bus running
+ on Sunday morning at 1am to 3am, might have the previous Saturday's service date.
+ """
+ serviceDates: LocalDateRangeInput
+ ): [Route!]
"Returns timetable of the specified pattern at this stop"
stopTimesForPattern(
"Id of the pattern"
diff --git a/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java b/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java
index 79590ca2775..0e0eb159c1b 100644
--- a/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java
+++ b/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java
@@ -28,6 +28,7 @@
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
+import java.util.Set;
import java.util.stream.Stream;
import javax.annotation.Nonnull;
import org.glassfish.jersey.message.internal.OutboundJaxrsResponse;
@@ -86,6 +87,7 @@
import org.opentripplanner.transit.model.framework.Deduplicator;
import org.opentripplanner.transit.model.framework.FeedScopedId;
import org.opentripplanner.transit.model.network.BikeAccess;
+import org.opentripplanner.transit.model.network.Route;
import org.opentripplanner.transit.model.network.TripPattern;
import org.opentripplanner.transit.model.organization.Agency;
import org.opentripplanner.transit.model.site.RegularStop;
@@ -114,6 +116,7 @@ class GraphQLIntegrationTest {
.of(A, B, C, D, E, F, G, H)
.map(p -> (RegularStop) p.stop)
.toList();
+ private static final Route ROUTE = TransitModelForTest.route("a-route").build();
private static VehicleRentalStation VEHICLE_RENTAL_STATION = new TestVehicleRentalStationBuilder()
.withVehicles(10)
@@ -209,6 +212,11 @@ public List getModesOfStopLocation(StopLocation stop) {
public TransitAlertService getTransitAlertService() {
return alertService;
}
+
+ @Override
+ public Set getRoutesForStop(StopLocation stop) {
+ return Set.of(ROUTE);
+ }
};
routes.forEach(transitService::addRoutes);
diff --git a/src/test/java/org/opentripplanner/framework/collection/CollectionUtilsTest.java b/src/test/java/org/opentripplanner/framework/collection/CollectionUtilsTest.java
index ae33e493bcc..e2eaab520bc 100644
--- a/src/test/java/org/opentripplanner/framework/collection/CollectionUtilsTest.java
+++ b/src/test/java/org/opentripplanner/framework/collection/CollectionUtilsTest.java
@@ -1,6 +1,8 @@
package org.opentripplanner.framework.collection;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import com.google.type.Month;
import java.time.Duration;
@@ -15,6 +17,13 @@ class CollectionUtilsTest {
public static final String NULL_STRING = "";
+ @Test
+ void testIsEmpty() {
+ assertTrue(CollectionUtils.isEmpty(null));
+ assertTrue(CollectionUtils.isEmpty(List.of()));
+ assertFalse(CollectionUtils.isEmpty(List.of(1)));
+ }
+
@Test
void testToString() {
assertEquals("", CollectionUtils.toString(null, NULL_STRING));
diff --git a/src/test/java/org/opentripplanner/transit/model/_data/PatternTestModel.java b/src/test/java/org/opentripplanner/transit/model/_data/PatternTestModel.java
new file mode 100644
index 00000000000..c3afd0ddf61
--- /dev/null
+++ b/src/test/java/org/opentripplanner/transit/model/_data/PatternTestModel.java
@@ -0,0 +1,46 @@
+package org.opentripplanner.transit.model._data;
+
+import static org.opentripplanner.transit.model._data.TransitModelForTest.id;
+
+import org.opentripplanner.transit.model.framework.FeedScopedId;
+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;
+import org.opentripplanner.transit.model.timetable.ScheduledTripTimes;
+import org.opentripplanner.transit.model.timetable.Trip;
+import org.opentripplanner.transit.service.StopModel;
+
+public class PatternTestModel {
+
+ public static final Route ROUTE_1 = TransitModelForTest.route("1").build();
+
+ private static final FeedScopedId SERVICE_ID = id("service");
+ private static final Trip TRIP = TransitModelForTest
+ .trip("t1")
+ .withRoute(ROUTE_1)
+ .withServiceId(SERVICE_ID)
+ .build();
+ private static final TransitModelForTest MODEL = new TransitModelForTest(StopModel.of());
+ private static final RegularStop STOP_1 = MODEL.stop("1").build();
+ private static final StopPattern STOP_PATTERN = TransitModelForTest.stopPattern(STOP_1, STOP_1);
+
+ /**
+ * Creates a trip pattern that has a stop pattern, trip times and a trip with a service id.
+ */
+ public static TripPattern pattern() {
+ var pattern = TransitModelForTest
+ .tripPattern("1", ROUTE_1)
+ .withStopPattern(STOP_PATTERN)
+ .build();
+
+ var tt = ScheduledTripTimes
+ .of()
+ .withTrip(TRIP)
+ .withArrivalTimes("10:00 10:05")
+ .withDepartureTimes("10:00 10:05")
+ .build();
+ pattern.add(tt);
+ return pattern;
+ }
+}
diff --git a/src/test/java/org/opentripplanner/transit/model/filter/expr/AndMatcherTest.java b/src/test/java/org/opentripplanner/transit/model/filter/expr/AndMatcherTest.java
new file mode 100644
index 00000000000..c79260193ff
--- /dev/null
+++ b/src/test/java/org/opentripplanner/transit/model/filter/expr/AndMatcherTest.java
@@ -0,0 +1,39 @@
+package org.opentripplanner.transit.model.filter.expr;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.List;
+import org.junit.jupiter.api.Test;
+
+class AndMatcherTest {
+
+ @Test
+ void testMatchSingleMatcher() {
+ var matcher = AndMatcher.of(List.of(new EqualityMatcher<>("int", 42, i -> i)));
+ assertTrue(matcher.match(42));
+ assertFalse(matcher.match(43));
+ }
+
+ @Test
+ void testMatchMultiple() {
+ var matcher = AndMatcher.of(
+ List.of(new EqualityMatcher<>("int", 42, i -> i), new EqualityMatcher<>("int", 43, i -> i))
+ );
+ assertFalse(matcher.match(42));
+ assertFalse(matcher.match(43));
+ assertFalse(matcher.match(44));
+ }
+
+ @Test
+ void testMatchComposites() {
+ var matcher = AndMatcher.of(
+ List.of(
+ OrMatcher.of(List.of(new EqualityMatcher<>("int", 42, i -> i))),
+ OrMatcher.of(List.of(new EqualityMatcher<>("int", 43, i -> i)))
+ )
+ );
+ assertFalse(matcher.match(42));
+ assertFalse(matcher.match(43));
+ assertFalse(matcher.match(44));
+ }
+}
diff --git a/src/test/java/org/opentripplanner/transit/model/filter/expr/ContainsMatcherTest.java b/src/test/java/org/opentripplanner/transit/model/filter/expr/ContainsMatcherTest.java
new file mode 100644
index 00000000000..1709fc7bf86
--- /dev/null
+++ b/src/test/java/org/opentripplanner/transit/model/filter/expr/ContainsMatcherTest.java
@@ -0,0 +1,33 @@
+package org.opentripplanner.transit.model.filter.expr;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.List;
+import java.util.Map;
+import org.junit.jupiter.api.Test;
+
+class ContainsMatcherTest {
+
+ private static final Map> integerListMap = Map.of(
+ 1,
+ List.of("foo"),
+ 2,
+ List.of("bar"),
+ 3,
+ List.of("foo", "bar")
+ );
+
+ @Test
+ void testMatch() {
+ var matcher = new ContainsMatcher<>(
+ "contains",
+ integerListMap::get,
+ new EqualityMatcher<>("string", "foo", s -> s)
+ );
+
+ assertTrue(matcher.match(1));
+ assertFalse(matcher.match(2));
+ assertTrue(matcher.match(3));
+ assertFalse(matcher.match(4));
+ }
+}
diff --git a/src/test/java/org/opentripplanner/transit/model/filter/expr/EqualityMatcherTest.java b/src/test/java/org/opentripplanner/transit/model/filter/expr/EqualityMatcherTest.java
new file mode 100644
index 00000000000..31d208a768a
--- /dev/null
+++ b/src/test/java/org/opentripplanner/transit/model/filter/expr/EqualityMatcherTest.java
@@ -0,0 +1,22 @@
+package org.opentripplanner.transit.model.filter.expr;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.junit.jupiter.api.Test;
+
+class EqualityMatcherTest {
+
+ @Test
+ void testMatchesPrimitive() {
+ var matcher = new EqualityMatcher<>("int", 42, i -> i);
+ assertTrue(matcher.match(42));
+ assertFalse(matcher.match(43));
+ }
+
+ @Test
+ void testMatchesObject() {
+ var matcher = new EqualityMatcher<>("string", "foo", s -> s);
+ assertTrue(matcher.match("foo"));
+ assertFalse(matcher.match("bar"));
+ }
+}
diff --git a/src/test/java/org/opentripplanner/transit/model/filter/expr/OrMatcherTest.java b/src/test/java/org/opentripplanner/transit/model/filter/expr/OrMatcherTest.java
new file mode 100644
index 00000000000..415f64e40ed
--- /dev/null
+++ b/src/test/java/org/opentripplanner/transit/model/filter/expr/OrMatcherTest.java
@@ -0,0 +1,31 @@
+package org.opentripplanner.transit.model.filter.expr;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.List;
+import org.junit.jupiter.api.Test;
+
+class OrMatcherTest {
+
+ @Test
+ void testMatch() {
+ var matcher = OrMatcher.of(
+ new EqualityMatcher<>("int", 42, i -> i),
+ new EqualityMatcher<>("int", 43, i -> i)
+ );
+ assertTrue(matcher.match(42));
+ assertTrue(matcher.match(43));
+ assertFalse(matcher.match(44));
+ }
+
+ @Test
+ void testMatchComposites() {
+ var matcher = OrMatcher.of(
+ AndMatcher.of(List.of(new EqualityMatcher<>("int", 42, i -> i))),
+ AndMatcher.of(List.of(new EqualityMatcher<>("int", 43, i -> i)))
+ );
+ assertTrue(matcher.match(42));
+ assertTrue(matcher.match(43));
+ assertFalse(matcher.match(44));
+ }
+}
diff --git a/src/test/java/org/opentripplanner/transit/model/filter/transit/TripOnServiceDateMatcherFactoryTest.java b/src/test/java/org/opentripplanner/transit/model/filter/transit/TripOnServiceDateMatcherFactoryTest.java
new file mode 100644
index 00000000000..b7cb7aa6698
--- /dev/null
+++ b/src/test/java/org/opentripplanner/transit/model/filter/transit/TripOnServiceDateMatcherFactoryTest.java
@@ -0,0 +1,151 @@
+package org.opentripplanner.transit.model.filter.transit;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.time.LocalDate;
+import java.util.List;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.opentripplanner.transit.api.request.TripOnServiceDateRequest;
+import org.opentripplanner.transit.model.basic.TransitMode;
+import org.opentripplanner.transit.model.filter.expr.Matcher;
+import org.opentripplanner.transit.model.framework.FeedScopedId;
+import org.opentripplanner.transit.model.network.Route;
+import org.opentripplanner.transit.model.organization.Agency;
+import org.opentripplanner.transit.model.timetable.Trip;
+import org.opentripplanner.transit.model.timetable.TripOnServiceDate;
+
+class TripOnServiceDateMatcherFactoryTest {
+
+ private TripOnServiceDate tripOnServiceDateRut;
+ private TripOnServiceDate tripOnServiceDateRut2;
+ private TripOnServiceDate tripOnServiceDateAkt;
+
+ @BeforeEach
+ void setup() {
+ tripOnServiceDateRut =
+ TripOnServiceDate
+ .of(new FeedScopedId("RUT:route:trip:date", "123"))
+ .withTrip(
+ Trip
+ .of(new FeedScopedId("RUT:route:trip", "1"))
+ .withRoute(
+ Route
+ .of(new FeedScopedId("RUT:route", "2"))
+ .withAgency(
+ Agency
+ .of(new FeedScopedId("RUT", "3"))
+ .withName("RUT")
+ .withTimezone("Europe/Oslo")
+ .build()
+ )
+ .withMode(TransitMode.BUS)
+ .withShortName("BUS")
+ .build()
+ )
+ .build()
+ )
+ .withServiceDate(LocalDate.of(2024, 2, 22))
+ .build();
+
+ tripOnServiceDateRut2 =
+ TripOnServiceDate
+ .of(new FeedScopedId("RUT:route:trip:date", "123"))
+ .withTrip(
+ Trip
+ .of(new FeedScopedId("RUT:route:trip2", "1"))
+ .withRoute(
+ Route
+ .of(new FeedScopedId("RUT:route", "2"))
+ .withAgency(
+ Agency
+ .of(new FeedScopedId("RUT", "3"))
+ .withName("RUT")
+ .withTimezone("Europe/Oslo")
+ .build()
+ )
+ .withMode(TransitMode.BUS)
+ .withShortName("BUS")
+ .build()
+ )
+ .build()
+ )
+ .withServiceDate(LocalDate.of(2024, 2, 22))
+ .build();
+
+ tripOnServiceDateAkt =
+ TripOnServiceDate
+ .of(new FeedScopedId("AKT:route:trip:date", "123"))
+ .withTrip(
+ Trip
+ .of(new FeedScopedId("AKT:route:trip", "1"))
+ .withRoute(
+ Route
+ .of(new FeedScopedId("AKT:route", "2"))
+ .withAgency(
+ Agency
+ .of(new FeedScopedId("AKT", "3"))
+ .withName("AKT")
+ .withTimezone("Europe/Oslo")
+ .build()
+ )
+ .withMode(TransitMode.BUS)
+ .withShortName("BUS")
+ .build()
+ )
+ .build()
+ )
+ .withServiceDate(LocalDate.of(2024, 2, 22))
+ .build();
+ }
+
+ @Test
+ void testMatchOperatingDays() {
+ TripOnServiceDateRequest request = TripOnServiceDateRequest
+ .of()
+ .withOperatingDays(List.of(LocalDate.of(2024, 2, 22)))
+ .build();
+
+ Matcher matcher = TripOnServiceDateMatcherFactory.of(request);
+
+ assertTrue(matcher.match(tripOnServiceDateRut));
+ assertTrue(matcher.match(tripOnServiceDateRut2));
+ assertTrue(matcher.match(tripOnServiceDateAkt));
+ }
+
+ @Test
+ void testMatchMultiple() {
+ TripOnServiceDateRequest request = TripOnServiceDateRequest
+ .of()
+ .withOperatingDays(List.of(LocalDate.of(2024, 2, 22)))
+ .withAuthorities(List.of(new FeedScopedId("RUT", "3")))
+ .withLines(List.of(new FeedScopedId("RUT:route", "2")))
+ .withServiceJourneys(List.of(new FeedScopedId("RUT:route:trip", "1")))
+ .build();
+
+ Matcher matcher = TripOnServiceDateMatcherFactory.of(request);
+
+ assertTrue(matcher.match(tripOnServiceDateRut));
+ assertFalse(matcher.match(tripOnServiceDateRut2));
+ assertFalse(matcher.match(tripOnServiceDateAkt));
+ }
+
+ @Test
+ void testMatchMultipleServiceJourneyMatchers() {
+ TripOnServiceDateRequest request = TripOnServiceDateRequest
+ .of()
+ .withOperatingDays(List.of(LocalDate.of(2024, 2, 22)))
+ .withAuthorities(List.of(new FeedScopedId("RUT", "3")))
+ .withLines(List.of(new FeedScopedId("RUT:route", "2")))
+ .withServiceJourneys(
+ List.of(new FeedScopedId("RUT:route:trip", "1"), new FeedScopedId("RUT:route:trip2", "1"))
+ )
+ .build();
+
+ Matcher matcher = TripOnServiceDateMatcherFactory.of(request);
+
+ assertTrue(matcher.match(tripOnServiceDateRut));
+ assertTrue(matcher.match(tripOnServiceDateRut2));
+ assertFalse(matcher.match(tripOnServiceDateAkt));
+ }
+}
diff --git a/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java b/src/test/java/org/opentripplanner/transit/service/PatternByServiceDatesFilterTest.java
similarity index 66%
rename from src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java
rename to src/test/java/org/opentripplanner/transit/service/PatternByServiceDatesFilterTest.java
index f01bac12006..93f50ce5b1e 100644
--- a/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java
+++ b/src/test/java/org/opentripplanner/transit/service/PatternByServiceDatesFilterTest.java
@@ -1,12 +1,12 @@
-package org.opentripplanner.apis.gtfs;
+package org.opentripplanner.transit.service;
import static java.time.LocalDate.parse;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.opentripplanner.apis.gtfs.PatternByServiceDatesFilterTest.FilterExpectation.NOT_REMOVED;
-import static org.opentripplanner.apis.gtfs.PatternByServiceDatesFilterTest.FilterExpectation.REMOVED;
-import static org.opentripplanner.transit.model._data.TransitModelForTest.id;
+import static org.opentripplanner.transit.model._data.PatternTestModel.ROUTE_1;
+import static org.opentripplanner.transit.service.PatternByServiceDatesFilterTest.FilterExpectation.NOT_REMOVED;
+import static org.opentripplanner.transit.service.PatternByServiceDatesFilterTest.FilterExpectation.REMOVED;
import java.time.LocalDate;
import java.util.List;
@@ -14,51 +14,18 @@
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.opentripplanner.apis.gtfs.model.LocalDateRange;
-import org.opentripplanner.transit.model._data.TransitModelForTest;
-import org.opentripplanner.transit.model.framework.FeedScopedId;
-import org.opentripplanner.transit.model.network.Route;
-import org.opentripplanner.transit.model.network.StopPattern;
+import org.opentripplanner.transit.model._data.PatternTestModel;
import org.opentripplanner.transit.model.network.TripPattern;
-import org.opentripplanner.transit.model.site.RegularStop;
-import org.opentripplanner.transit.model.timetable.ScheduledTripTimes;
-import org.opentripplanner.transit.model.timetable.Trip;
-import org.opentripplanner.transit.service.StopModel;
class PatternByServiceDatesFilterTest {
- private static final Route ROUTE_1 = TransitModelForTest.route("1").build();
- private static final FeedScopedId SERVICE_ID = id("service");
- private static final Trip TRIP = TransitModelForTest
- .trip("t1")
- .withRoute(ROUTE_1)
- .withServiceId(SERVICE_ID)
- .build();
- private static final TransitModelForTest MODEL = new TransitModelForTest(StopModel.of());
- private static final RegularStop STOP_1 = MODEL.stop("1").build();
- private static final StopPattern STOP_PATTERN = TransitModelForTest.stopPattern(STOP_1, STOP_1);
- private static final TripPattern PATTERN_1 = pattern();
+ private static final TripPattern PATTERN_1 = PatternTestModel.pattern();
enum FilterExpectation {
REMOVED,
NOT_REMOVED,
}
- private static TripPattern pattern() {
- var pattern = TransitModelForTest
- .tripPattern("1", ROUTE_1)
- .withStopPattern(STOP_PATTERN)
- .build();
-
- var tt = ScheduledTripTimes
- .of()
- .withTrip(TRIP)
- .withArrivalTimes("10:00 10:05")
- .withDepartureTimes("10:00 10:05")
- .build();
- pattern.add(tt);
- return pattern;
- }
-
static List invalidRangeCases() {
return List.of(
Arguments.of(null, null),
@@ -140,7 +107,7 @@ void filterRoutes(LocalDate start, LocalDate end, FilterExpectation expectation)
var filter = defaultFilter(start, end);
var filterInput = List.of(ROUTE_1);
- var filterOutput = filter.filterRoutes(filterInput.stream());
+ var filterOutput = filter.filterRoutes(filterInput);
if (expectation == NOT_REMOVED) {
assertEquals(filterOutput, filterInput);
diff --git a/src/test/java/org/opentripplanner/updater/trip/RealtimeTestConstants.java b/src/test/java/org/opentripplanner/updater/trip/RealtimeTestConstants.java
new file mode 100644
index 00000000000..bad0c1982e9
--- /dev/null
+++ b/src/test/java/org/opentripplanner/updater/trip/RealtimeTestConstants.java
@@ -0,0 +1,48 @@
+package org.opentripplanner.updater.trip;
+
+import static org.opentripplanner.transit.model._data.TransitModelForTest.id;
+
+import java.time.LocalDate;
+import java.time.ZoneId;
+import org.opentripplanner.transit.model._data.TransitModelForTest;
+import org.opentripplanner.transit.model.framework.FeedScopedId;
+import org.opentripplanner.transit.model.network.Route;
+import org.opentripplanner.transit.model.organization.Operator;
+import org.opentripplanner.transit.model.site.RegularStop;
+import org.opentripplanner.transit.model.site.Station;
+import org.opentripplanner.transit.service.StopModel;
+
+public interface RealtimeTestConstants {
+ LocalDate SERVICE_DATE = LocalDate.of(2024, 5, 8);
+ FeedScopedId SERVICE_ID = TransitModelForTest.id("CAL_1");
+ String STOP_A1_ID = "A1";
+ String STOP_B1_ID = "B1";
+ String STOP_C1_ID = "C1";
+ String TRIP_1_ID = "TestTrip1";
+ String TRIP_2_ID = "TestTrip2";
+ String OPERATOR_1_ID = "TestOperator1";
+ Operator OPERATOR1 = Operator.of(id(OPERATOR_1_ID)).withName(OPERATOR_1_ID).build();
+ String ROUTE_1_ID = "TestRoute1";
+
+ TransitModelForTest TEST_MODEL = TransitModelForTest.of();
+ ZoneId TIME_ZONE = ZoneId.of(TransitModelForTest.TIME_ZONE_ID);
+ Station STATION_A = TEST_MODEL.station("A").build();
+ Station STATION_B = TEST_MODEL.station("B").build();
+ Station STATION_C = TEST_MODEL.station("C").build();
+ Station STATION_D = TEST_MODEL.station("D").build();
+ RegularStop STOP_A1 = TEST_MODEL.stop(STOP_A1_ID).withParentStation(STATION_A).build();
+ RegularStop STOP_B1 = TEST_MODEL.stop(STOP_B1_ID).withParentStation(STATION_B).build();
+ RegularStop STOP_B2 = TEST_MODEL.stop("B2").withParentStation(STATION_B).build();
+ RegularStop STOP_C1 = TEST_MODEL.stop(STOP_C1_ID).withParentStation(STATION_C).build();
+ RegularStop STOP_D1 = TEST_MODEL.stop("D1").withParentStation(STATION_D).build();
+ StopModel STOP_MODEL = TEST_MODEL
+ .stopModelBuilder()
+ .withRegularStop(STOP_A1)
+ .withRegularStop(STOP_B1)
+ .withRegularStop(STOP_B2)
+ .withRegularStop(STOP_C1)
+ .withRegularStop(STOP_D1)
+ .build();
+
+ Route ROUTE_1 = TransitModelForTest.route(ROUTE_1_ID).build();
+}
diff --git a/src/test/java/org/opentripplanner/updater/trip/RealtimeTestEnvironment.java b/src/test/java/org/opentripplanner/updater/trip/RealtimeTestEnvironment.java
index a40bd8bd797..682bff038b6 100644
--- a/src/test/java/org/opentripplanner/updater/trip/RealtimeTestEnvironment.java
+++ b/src/test/java/org/opentripplanner/updater/trip/RealtimeTestEnvironment.java
@@ -1,41 +1,25 @@
package org.opentripplanner.updater.trip;
+import static org.opentripplanner.transit.model._data.TransitModelForTest.id;
import static org.opentripplanner.updater.trip.UpdateIncrementality.DIFFERENTIAL;
import static org.opentripplanner.updater.trip.UpdateIncrementality.FULL_DATASET;
import com.google.transit.realtime.GtfsRealtime;
import java.time.Duration;
import java.time.LocalDate;
-import java.time.ZoneId;
import java.util.List;
import java.util.Objects;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
import org.opentripplanner.DateTimeHelper;
import org.opentripplanner.ext.siri.SiriTimetableSnapshotSource;
import org.opentripplanner.ext.siri.updater.EstimatedTimetableHandler;
-import org.opentripplanner.framework.i18n.I18NString;
-import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore;
-import org.opentripplanner.model.StopTime;
import org.opentripplanner.model.TimetableSnapshot;
-import org.opentripplanner.model.calendar.CalendarServiceData;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.transit.model._data.TransitModelForTest;
-import org.opentripplanner.transit.model.framework.Deduplicator;
import org.opentripplanner.transit.model.framework.FeedScopedId;
-import org.opentripplanner.transit.model.network.Route;
import org.opentripplanner.transit.model.network.TripPattern;
-import org.opentripplanner.transit.model.organization.Operator;
-import org.opentripplanner.transit.model.site.RegularStop;
-import org.opentripplanner.transit.model.site.Station;
-import org.opentripplanner.transit.model.site.StopLocation;
-import org.opentripplanner.transit.model.timetable.Trip;
-import org.opentripplanner.transit.model.timetable.TripOnServiceDate;
import org.opentripplanner.transit.model.timetable.TripTimes;
-import org.opentripplanner.transit.model.timetable.TripTimesFactory;
import org.opentripplanner.transit.model.timetable.TripTimesStringBuilder;
import org.opentripplanner.transit.service.DefaultTransitService;
-import org.opentripplanner.transit.service.StopModel;
import org.opentripplanner.transit.service.TransitModel;
import org.opentripplanner.transit.service.TransitService;
import org.opentripplanner.updater.DefaultRealTimeUpdateContext;
@@ -50,47 +34,20 @@
*
* It is however a goal to change that and then these two can be combined.
*/
-public final class RealtimeTestEnvironment {
+public final class RealtimeTestEnvironment implements RealtimeTestConstants {
+ // static constants
private static final TimetableSnapshotSourceParameters PARAMETERS = new TimetableSnapshotSourceParameters(
Duration.ZERO,
false
);
- public static final LocalDate SERVICE_DATE = LocalDate.of(2024, 5, 8);
- public static final FeedScopedId SERVICE_ID = TransitModelForTest.id("CAL_1");
- public static final String STOP_A1_ID = "A1";
- public static final String STOP_B1_ID = "B1";
- public static final String STOP_C1_ID = "C1";
- private final TransitModelForTest testModel = TransitModelForTest.of();
- public final ZoneId timeZone = ZoneId.of(TransitModelForTest.TIME_ZONE_ID);
- public final Station stationA = testModel.station("A").build();
- public final Station stationB = testModel.station("B").build();
- public final Station stationC = testModel.station("C").build();
- public final Station stationD = testModel.station("D").build();
- public final RegularStop stopA1 = testModel.stop(STOP_A1_ID).withParentStation(stationA).build();
- public final RegularStop stopB1 = testModel.stop(STOP_B1_ID).withParentStation(stationB).build();
- public final RegularStop stopB2 = testModel.stop("B2").withParentStation(stationB).build();
- public final RegularStop stopC1 = testModel.stop(STOP_C1_ID).withParentStation(stationC).build();
- public final RegularStop stopD1 = testModel.stop("D1").withParentStation(stationD).build();
- public final StopModel stopModel = testModel
- .stopModelBuilder()
- .withRegularStop(stopA1)
- .withRegularStop(stopB1)
- .withRegularStop(stopB2)
- .withRegularStop(stopC1)
- .withRegularStop(stopD1)
- .build();
- public final FeedScopedId operator1Id = TransitModelForTest.id("TestOperator1");
- public final FeedScopedId route1Id = TransitModelForTest.id("TestRoute1");
- public final Trip trip1;
- public final Trip trip2;
- public final Operator operator1;
+
public final TransitModel transitModel;
private final SiriTimetableSnapshotSource siriSource;
private final TimetableSnapshotSource gtfsSource;
private final DateTimeHelper dateTimeHelper;
- private enum SourceType {
+ enum SourceType {
GTFS_RT,
SIRI,
}
@@ -98,54 +55,22 @@ private enum SourceType {
/**
* Siri and GTFS-RT cannot be run at the same time, so you need to decide.
*/
- public static RealtimeTestEnvironment siri() {
- return new RealtimeTestEnvironment(SourceType.SIRI);
+ public static RealtimeTestEnvironmentBuilder siri() {
+ return new RealtimeTestEnvironmentBuilder().withSourceType(SourceType.SIRI);
}
/**
* Siri and GTFS-RT cannot be run at the same time, so you need to decide.
*/
- public static RealtimeTestEnvironment gtfs() {
- return new RealtimeTestEnvironment(SourceType.GTFS_RT);
+ public static RealtimeTestEnvironmentBuilder gtfs() {
+ return new RealtimeTestEnvironmentBuilder().withSourceType(SourceType.GTFS_RT);
}
- private RealtimeTestEnvironment(SourceType sourceType) {
- transitModel = new TransitModel(stopModel, new Deduplicator());
- transitModel.initTimeZone(timeZone);
- transitModel.addAgency(TransitModelForTest.AGENCY);
-
- operator1 = Operator.of(operator1Id).withName("Operator 1").build();
- transitModel.getOperators().add(operator1);
-
- Route route1 = TransitModelForTest.route(route1Id).withOperator(operator1).build();
-
- trip1 =
- createTrip(
- "TestTrip1",
- route1,
- List.of(new StopCall(stopA1, 10, 11), new StopCall(stopB1, 20, 21))
- );
- trip2 =
- createTrip(
- "TestTrip2",
- route1,
- List.of(
- new StopCall(stopA1, 60, 61),
- new StopCall(stopB1, 70, 71),
- new StopCall(stopC1, 80, 81)
- )
- );
-
- CalendarServiceData calendarServiceData = new CalendarServiceData();
- calendarServiceData.putServiceDatesForServiceId(
- SERVICE_ID,
- List.of(SERVICE_DATE.minusDays(1), SERVICE_DATE, SERVICE_DATE.plusDays(1))
- );
- transitModel.getServiceCodes().put(SERVICE_ID, 0);
- transitModel.updateCalendarServiceData(true, calendarServiceData, DataImportIssueStore.NOOP);
-
- transitModel.index();
+ RealtimeTestEnvironment(SourceType sourceType, TransitModel transitModel) {
+ Objects.requireNonNull(sourceType);
+ this.transitModel = transitModel;
+ this.transitModel.index();
// SIRI and GTFS-RT cannot be registered with the transit model at the same time
// we are actively refactoring to remove this restriction
// for the time being you cannot run a SIRI and GTFS-RT test at the same time
@@ -156,11 +81,7 @@ private RealtimeTestEnvironment(SourceType sourceType) {
gtfsSource = new TimetableSnapshotSource(PARAMETERS, transitModel);
siriSource = null;
}
- dateTimeHelper = new DateTimeHelper(timeZone, RealtimeTestEnvironment.SERVICE_DATE);
- }
-
- public static FeedScopedId id(String id) {
- return TransitModelForTest.id(id);
+ dateTimeHelper = new DateTimeHelper(TIME_ZONE, SERVICE_DATE);
}
/**
@@ -190,7 +111,11 @@ private EstimatedTimetableHandler getEstimatedTimetableHandler(boolean fuzzyMatc
}
public TripPattern getPatternForTrip(FeedScopedId tripId) {
- return getPatternForTrip(tripId, RealtimeTestEnvironment.SERVICE_DATE);
+ return getPatternForTrip(tripId, SERVICE_DATE);
+ }
+
+ public TripPattern getPatternForTrip(String id) {
+ return getPatternForTrip(id(id));
}
public TripPattern getPatternForTrip(FeedScopedId tripId, LocalDate serviceDate) {
@@ -199,13 +124,6 @@ public TripPattern getPatternForTrip(FeedScopedId tripId, LocalDate serviceDate)
return transitService.getPatternForTrip(trip.getTrip(), serviceDate);
}
- /**
- * Find the current TripTimes for a trip id on the default serviceDate
- */
- public TripTimes getTripTimesForTrip(Trip trip) {
- return getTripTimesForTrip(trip.getId(), SERVICE_DATE);
- }
-
/**
* Find the current TripTimes for a trip id on the default serviceDate
*/
@@ -217,10 +135,6 @@ public DateTimeHelper getDateTimeHelper() {
return dateTimeHelper;
}
- public TripPattern getPatternForTrip(Trip trip) {
- return getTransitService().getPatternForTrip(trip);
- }
-
public TimetableSnapshot getTimetableSnapshot() {
if (siriSource != null) {
return siriSource.getTimetableSnapshot();
@@ -233,10 +147,6 @@ public String getRealtimeTimetable(String tripId) {
return getRealtimeTimetable(id(tripId), SERVICE_DATE);
}
- public String getRealtimeTimetable(Trip trip) {
- return getRealtimeTimetable(trip.getId(), SERVICE_DATE);
- }
-
public String getRealtimeTimetable(FeedScopedId tripId, LocalDate serviceDate) {
var tt = getTripTimesForTrip(tripId, serviceDate);
var pattern = getPatternForTrip(tripId);
@@ -325,59 +235,4 @@ private void commitTimetableSnapshot() {
gtfsSource.flushBuffer();
}
}
-
- private Trip createTrip(String id, Route route, List stops) {
- var trip = Trip
- .of(id(id))
- .withRoute(route)
- .withHeadsign(I18NString.of("Headsign of %s".formatted(id)))
- .withServiceId(SERVICE_ID)
- .build();
-
- var tripOnServiceDate = TripOnServiceDate
- .of(trip.getId())
- .withTrip(trip)
- .withServiceDate(SERVICE_DATE)
- .build();
-
- transitModel.addTripOnServiceDate(tripOnServiceDate.getId(), tripOnServiceDate);
-
- var stopTimes = IntStream
- .range(0, stops.size())
- .mapToObj(i -> {
- var stop = stops.get(i);
- return createStopTime(trip, i, stop.stop(), stop.arrivalTime(), stop.departureTime());
- })
- .collect(Collectors.toList());
-
- TripTimes tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, null);
-
- final TripPattern pattern = TransitModelForTest
- .tripPattern(id + "Pattern", route)
- .withStopPattern(TransitModelForTest.stopPattern(stops.stream().map(StopCall::stop).toList()))
- .build();
- pattern.add(tripTimes);
-
- transitModel.addTripPattern(pattern.getId(), pattern);
-
- return trip;
- }
-
- private StopTime createStopTime(
- Trip trip,
- int stopSequence,
- StopLocation stop,
- int arrivalTime,
- int departureTime
- ) {
- var st = new StopTime();
- st.setTrip(trip);
- st.setStopSequence(stopSequence);
- st.setStop(stop);
- st.setArrivalTime(arrivalTime);
- st.setDepartureTime(departureTime);
- return st;
- }
-
- private record StopCall(RegularStop stop, int arrivalTime, int departureTime) {}
}
diff --git a/src/test/java/org/opentripplanner/updater/trip/RealtimeTestEnvironmentBuilder.java b/src/test/java/org/opentripplanner/updater/trip/RealtimeTestEnvironmentBuilder.java
new file mode 100644
index 00000000000..7a79b27923e
--- /dev/null
+++ b/src/test/java/org/opentripplanner/updater/trip/RealtimeTestEnvironmentBuilder.java
@@ -0,0 +1,114 @@
+package org.opentripplanner.updater.trip;
+
+import static org.opentripplanner.transit.model._data.TransitModelForTest.id;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.IntStream;
+import org.opentripplanner.framework.i18n.I18NString;
+import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore;
+import org.opentripplanner.model.StopTime;
+import org.opentripplanner.model.calendar.CalendarServiceData;
+import org.opentripplanner.transit.model._data.TransitModelForTest;
+import org.opentripplanner.transit.model.framework.Deduplicator;
+import org.opentripplanner.transit.model.network.TripPattern;
+import org.opentripplanner.transit.model.site.StopLocation;
+import org.opentripplanner.transit.model.timetable.Trip;
+import org.opentripplanner.transit.model.timetable.TripOnServiceDate;
+import org.opentripplanner.transit.model.timetable.TripTimes;
+import org.opentripplanner.transit.model.timetable.TripTimesFactory;
+import org.opentripplanner.transit.service.TransitModel;
+
+public class RealtimeTestEnvironmentBuilder implements RealtimeTestConstants {
+
+ private RealtimeTestEnvironment.SourceType sourceType;
+ private final TransitModel transitModel = new TransitModel(STOP_MODEL, new Deduplicator());
+
+ RealtimeTestEnvironmentBuilder withSourceType(RealtimeTestEnvironment.SourceType sourceType) {
+ this.sourceType = sourceType;
+ return this;
+ }
+
+ public RealtimeTestEnvironmentBuilder addTrip(TripInput trip) {
+ createTrip(trip);
+ transitModel.index();
+ return this;
+ }
+
+ public RealtimeTestEnvironment build() {
+ Objects.requireNonNull(sourceType, "sourceType cannot be null");
+ transitModel.initTimeZone(TIME_ZONE);
+ transitModel.addAgency(TransitModelForTest.AGENCY);
+
+ CalendarServiceData calendarServiceData = new CalendarServiceData();
+ calendarServiceData.putServiceDatesForServiceId(
+ SERVICE_ID,
+ List.of(SERVICE_DATE.minusDays(1), SERVICE_DATE, SERVICE_DATE.plusDays(1))
+ );
+ transitModel.getServiceCodes().put(SERVICE_ID, 0);
+ transitModel.updateCalendarServiceData(true, calendarServiceData, DataImportIssueStore.NOOP);
+
+ return new RealtimeTestEnvironment(sourceType, transitModel);
+ }
+
+ private Trip createTrip(TripInput tripInput) {
+ var trip = Trip
+ .of(id(tripInput.id()))
+ .withRoute(tripInput.route())
+ .withHeadsign(I18NString.of("Headsign of %s".formatted(tripInput.id())))
+ .withServiceId(SERVICE_ID)
+ .build();
+
+ var tripOnServiceDate = TripOnServiceDate
+ .of(trip.getId())
+ .withTrip(trip)
+ .withServiceDate(SERVICE_DATE)
+ .build();
+
+ transitModel.addTripOnServiceDate(tripOnServiceDate.getId(), tripOnServiceDate);
+
+ if (tripInput.route().getOperator() != null) {
+ transitModel.getOperators().add(tripInput.route().getOperator());
+ }
+
+ var stopTimes = IntStream
+ .range(0, tripInput.stops().size())
+ .mapToObj(i -> {
+ var stop = tripInput.stops().get(i);
+ return createStopTime(trip, i, stop.stop(), stop.arrivalTime(), stop.departureTime());
+ })
+ .toList();
+
+ TripTimes tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, null);
+
+ final TripPattern pattern = TransitModelForTest
+ .tripPattern(tripInput.id() + "Pattern", tripInput.route())
+ .withStopPattern(
+ TransitModelForTest.stopPattern(
+ tripInput.stops().stream().map(TripInput.StopCall::stop).toList()
+ )
+ )
+ .build();
+ pattern.add(tripTimes);
+
+ transitModel.addTripPattern(pattern.getId(), pattern);
+
+ return trip;
+ }
+
+ private static StopTime createStopTime(
+ Trip trip,
+ int stopSequence,
+ StopLocation stop,
+ int arrivalTime,
+ int departureTime
+ ) {
+ var st = new StopTime();
+ st.setTrip(trip);
+ st.setStopSequence(stopSequence);
+ st.setStop(stop);
+ st.setArrivalTime(arrivalTime);
+ st.setDepartureTime(departureTime);
+ return st;
+ }
+}
diff --git a/src/test/java/org/opentripplanner/updater/trip/TripInput.java b/src/test/java/org/opentripplanner/updater/trip/TripInput.java
new file mode 100644
index 00000000000..e4d9309061a
--- /dev/null
+++ b/src/test/java/org/opentripplanner/updater/trip/TripInput.java
@@ -0,0 +1,47 @@
+package org.opentripplanner.updater.trip;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.opentripplanner.framework.time.TimeUtils;
+import org.opentripplanner.transit.model.network.Route;
+import org.opentripplanner.transit.model.site.RegularStop;
+
+/**
+ * A simple data structure that is used by the {@link RealtimeTestEnvironment} to create
+ * trips, trips on date and patterns.
+ */
+public record TripInput(String id, Route route, List stops) {
+ public static TripInputBuilder of(String id) {
+ return new TripInputBuilder(id);
+ }
+
+ public static class TripInputBuilder implements RealtimeTestConstants {
+
+ private final String id;
+ private final List stops = new ArrayList<>();
+ // can be made configurable if needed
+ private Route route = ROUTE_1;
+
+ TripInputBuilder(String id) {
+ this.id = id;
+ }
+
+ public TripInputBuilder addStop(RegularStop stopId, String arrivalTime, String departureTime) {
+ this.stops.add(
+ new StopCall(stopId, TimeUtils.time(arrivalTime), TimeUtils.time(departureTime))
+ );
+ return this;
+ }
+
+ public TripInput build() {
+ return new TripInput(id, route, stops);
+ }
+
+ public TripInputBuilder withRoute(Route route) {
+ this.route = route;
+ return this;
+ }
+ }
+
+ record StopCall(RegularStop stop, int arrivalTime, int departureTime) {}
+}
diff --git a/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/AddedTest.java b/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/AddedTest.java
index 2e0b9d3d88e..6b662e8c8f3 100644
--- a/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/AddedTest.java
+++ b/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/AddedTest.java
@@ -7,10 +7,6 @@
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.opentripplanner.updater.spi.UpdateResultAssertions.assertSuccess;
-import static org.opentripplanner.updater.trip.RealtimeTestEnvironment.SERVICE_DATE;
-import static org.opentripplanner.updater.trip.RealtimeTestEnvironment.STOP_A1_ID;
-import static org.opentripplanner.updater.trip.RealtimeTestEnvironment.STOP_B1_ID;
-import static org.opentripplanner.updater.trip.RealtimeTestEnvironment.STOP_C1_ID;
import de.mfdz.MfdzRealtimeExtensions.StopTimePropertiesExtension.DropOffPickupType;
import java.util.List;
@@ -23,18 +19,19 @@
import org.opentripplanner.transit.model.timetable.Trip;
import org.opentripplanner.transit.service.TransitService;
import org.opentripplanner.updater.spi.UpdateSuccess;
+import org.opentripplanner.updater.trip.RealtimeTestConstants;
import org.opentripplanner.updater.trip.RealtimeTestEnvironment;
import org.opentripplanner.updater.trip.TripUpdateBuilder;
-class AddedTest {
+class AddedTest implements RealtimeTestConstants {
final String ADDED_TRIP_ID = "added_trip";
@Test
void addedTrip() {
- var env = RealtimeTestEnvironment.gtfs();
+ var env = RealtimeTestEnvironment.gtfs().build();
- var tripUpdate = new TripUpdateBuilder(ADDED_TRIP_ID, SERVICE_DATE, ADDED, env.timeZone)
+ var tripUpdate = new TripUpdateBuilder(ADDED_TRIP_ID, SERVICE_DATE, ADDED, TIME_ZONE)
.addStopTime(STOP_A1_ID, 30)
.addStopTime(STOP_B1_ID, 40)
.addStopTime(STOP_C1_ID, 55)
@@ -46,8 +43,8 @@ void addedTrip() {
@Test
void addedTripWithNewRoute() {
- var env = RealtimeTestEnvironment.gtfs();
- var tripUpdate = new TripUpdateBuilder(ADDED_TRIP_ID, SERVICE_DATE, ADDED, env.timeZone)
+ var env = RealtimeTestEnvironment.gtfs().build();
+ var tripUpdate = new TripUpdateBuilder(ADDED_TRIP_ID, SERVICE_DATE, ADDED, TIME_ZONE)
.addTripExtension()
.addStopTime(STOP_A1_ID, 30, DropOffPickupType.PHONE_AGENCY)
.addStopTime(STOP_B1_ID, 40, DropOffPickupType.COORDINATE_WITH_DRIVER)
@@ -81,8 +78,8 @@ void addedTripWithNewRoute() {
@Test
void addedWithUnknownStop() {
- var env = RealtimeTestEnvironment.gtfs();
- var tripUpdate = new TripUpdateBuilder(ADDED_TRIP_ID, SERVICE_DATE, ADDED, env.timeZone)
+ var env = RealtimeTestEnvironment.gtfs().build();
+ var tripUpdate = new TripUpdateBuilder(ADDED_TRIP_ID, SERVICE_DATE, ADDED, TIME_ZONE)
// add extension to set route name, url, mode
.addTripExtension()
.addStopTime(STOP_A1_ID, 30, DropOffPickupType.PHONE_AGENCY)
@@ -105,8 +102,8 @@ void addedWithUnknownStop() {
@Test
void repeatedlyAddedTripWithNewRoute() {
- var env = RealtimeTestEnvironment.gtfs();
- var tripUpdate = new TripUpdateBuilder(ADDED_TRIP_ID, SERVICE_DATE, ADDED, env.timeZone)
+ var env = RealtimeTestEnvironment.gtfs().build();
+ var tripUpdate = new TripUpdateBuilder(ADDED_TRIP_ID, SERVICE_DATE, ADDED, TIME_ZONE)
// add extension to set route name, url, mode
.addTripExtension()
.addStopTime(STOP_A1_ID, 30, DropOffPickupType.PHONE_AGENCY)
@@ -135,7 +132,7 @@ private TripPattern assertAddedTrip(String tripId, RealtimeTestEnvironment env)
assertNotNull(trip);
assertNotNull(transitService.getPatternForTrip(trip));
- var stopA = env.transitModel.getStopModel().getRegularStop(env.stopA1.getId());
+ var stopA = env.transitModel.getStopModel().getRegularStop(STOP_A1.getId());
// Get the trip pattern of the added trip which goes through stopA
var patternsAtA = env.getTimetableSnapshot().getPatternsForStop(stopA);
diff --git a/src/test/java/org/opentripplanner/updater/trip/moduletests/cancellation/CancellationDeletionTest.java b/src/test/java/org/opentripplanner/updater/trip/moduletests/cancellation/CancellationDeletionTest.java
index c85225b7828..b8bd5c4574b 100644
--- a/src/test/java/org/opentripplanner/updater/trip/moduletests/cancellation/CancellationDeletionTest.java
+++ b/src/test/java/org/opentripplanner/updater/trip/moduletests/cancellation/CancellationDeletionTest.java
@@ -4,6 +4,7 @@
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.opentripplanner.transit.model._data.TransitModelForTest.id;
import static org.opentripplanner.updater.spi.UpdateResultAssertions.assertSuccess;
import static org.opentripplanner.updater.trip.UpdateIncrementality.DIFFERENTIAL;
@@ -13,14 +14,16 @@
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.opentripplanner.transit.model.timetable.RealTimeState;
+import org.opentripplanner.updater.trip.RealtimeTestConstants;
import org.opentripplanner.updater.trip.RealtimeTestEnvironment;
+import org.opentripplanner.updater.trip.TripInput;
import org.opentripplanner.updater.trip.TripUpdateBuilder;
/**
* Cancellations and deletions should end up in the internal data model and make trips unavailable
* for routing.
*/
-public class CancellationDeletionTest {
+public class CancellationDeletionTest implements RealtimeTestConstants {
static List cases() {
return List.of(
@@ -32,22 +35,25 @@ static List cases() {
@ParameterizedTest
@MethodSource("cases")
void cancelledTrip(ScheduleRelationship relationship, RealTimeState state) {
- var env = RealtimeTestEnvironment.gtfs();
- var pattern1 = env.getPatternForTrip(env.trip1);
+ var env = RealtimeTestEnvironment
+ .gtfs()
+ .addTrip(
+ TripInput
+ .of(TRIP_1_ID)
+ .addStop(STOP_A1, "0:00:10", "0:00:11")
+ .addStop(STOP_B1, "0:00:20", "0:00:21")
+ .build()
+ )
+ .build();
+ var pattern1 = env.getPatternForTrip(TRIP_1_ID);
- final int tripIndex1 = pattern1.getScheduledTimetable().getTripIndex(env.trip1.getId());
+ final int tripIndex1 = pattern1.getScheduledTimetable().getTripIndex(id(TRIP_1_ID));
- var update = new TripUpdateBuilder(
- env.trip1.getId().getId(),
- RealtimeTestEnvironment.SERVICE_DATE,
- relationship,
- env.timeZone
- )
- .build();
+ var update = new TripUpdateBuilder(TRIP_1_ID, SERVICE_DATE, relationship, TIME_ZONE).build();
assertSuccess(env.applyTripUpdate(update));
var snapshot = env.getTimetableSnapshot();
- var forToday = snapshot.resolve(pattern1, RealtimeTestEnvironment.SERVICE_DATE);
+ var forToday = snapshot.resolve(pattern1, SERVICE_DATE);
var schedule = snapshot.resolve(pattern1, null);
assertNotSame(forToday, schedule);
assertNotSame(forToday.getTripTimes(tripIndex1), schedule.getTripTimes(tripIndex1));
@@ -71,41 +77,34 @@ void cancelledTrip(ScheduleRelationship relationship, RealTimeState state) {
@ParameterizedTest
@MethodSource("cases")
void cancelingAddedTrip(ScheduleRelationship relationship, RealTimeState state) {
- var env = RealtimeTestEnvironment.gtfs();
+ var env = RealtimeTestEnvironment.gtfs().build();
var addedTripId = "added-trip";
// First add ADDED trip
var update = new TripUpdateBuilder(
addedTripId,
- RealtimeTestEnvironment.SERVICE_DATE,
+ SERVICE_DATE,
ScheduleRelationship.ADDED,
- env.timeZone
+ TIME_ZONE
)
- .addStopTime(env.stopA1.getId().getId(), 30)
- .addStopTime(env.stopB1.getId().getId(), 40)
- .addStopTime(env.stopC1.getId().getId(), 55)
+ .addStopTime(STOP_A1_ID, 30)
+ .addStopTime(STOP_B1_ID, 40)
+ .addStopTime(STOP_C1_ID, 55)
.build();
assertSuccess(env.applyTripUpdate(update, DIFFERENTIAL));
// Cancel or delete the added trip
- update =
- new TripUpdateBuilder(
- addedTripId,
- RealtimeTestEnvironment.SERVICE_DATE,
- relationship,
- env.timeZone
- )
- .build();
+ update = new TripUpdateBuilder(addedTripId, SERVICE_DATE, relationship, TIME_ZONE).build();
assertSuccess(env.applyTripUpdate(update, DIFFERENTIAL));
var snapshot = env.getTimetableSnapshot();
// Get the trip pattern of the added trip which goes through stopA
- var patternsAtA = snapshot.getPatternsForStop(env.stopA1);
+ var patternsAtA = snapshot.getPatternsForStop(STOP_A1);
assertNotNull(patternsAtA, "Added trip pattern should be found");
var tripPattern = patternsAtA.stream().findFirst().get();
- var forToday = snapshot.resolve(tripPattern, RealtimeTestEnvironment.SERVICE_DATE);
+ var forToday = snapshot.resolve(tripPattern, SERVICE_DATE);
var schedule = snapshot.resolve(tripPattern, null);
assertNotSame(forToday, schedule);
diff --git a/src/test/java/org/opentripplanner/updater/trip/moduletests/delay/DelayedTest.java b/src/test/java/org/opentripplanner/updater/trip/moduletests/delay/DelayedTest.java
index 5298853f36d..f45a82b9ba0 100644
--- a/src/test/java/org/opentripplanner/updater/trip/moduletests/delay/DelayedTest.java
+++ b/src/test/java/org/opentripplanner/updater/trip/moduletests/delay/DelayedTest.java
@@ -5,34 +5,34 @@
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.opentripplanner.transit.model._data.TransitModelForTest.id;
import static org.opentripplanner.updater.spi.UpdateResultAssertions.assertSuccess;
-import static org.opentripplanner.updater.trip.RealtimeTestEnvironment.SERVICE_DATE;
import org.junit.jupiter.api.Test;
-import org.opentripplanner.transit.model.network.TripPattern;
import org.opentripplanner.transit.model.timetable.RealTimeState;
-import org.opentripplanner.transit.model.timetable.TripTimes;
+import org.opentripplanner.updater.trip.RealtimeTestConstants;
import org.opentripplanner.updater.trip.RealtimeTestEnvironment;
+import org.opentripplanner.updater.trip.TripInput;
import org.opentripplanner.updater.trip.TripUpdateBuilder;
/**
* Delays should be applied to the first trip but should leave the second trip untouched.
*/
-class DelayedTest {
+class DelayedTest implements RealtimeTestConstants {
private static final int DELAY = 1;
private static final int STOP_SEQUENCE = 1;
@Test
void singleStopDelay() {
- var env = RealtimeTestEnvironment.gtfs();
-
- var tripUpdate = new TripUpdateBuilder(
- env.trip1.getId().getId(),
- RealtimeTestEnvironment.SERVICE_DATE,
- SCHEDULED,
- env.timeZone
- )
+ var TRIP_INPUT = TripInput
+ .of(TRIP_1_ID)
+ .addStop(STOP_A1, "0:00:10", "0:00:11")
+ .addStop(STOP_B1, "0:00:20", "0:00:21")
+ .build();
+ var env = RealtimeTestEnvironment.gtfs().addTrip(TRIP_INPUT).build();
+
+ var tripUpdate = new TripUpdateBuilder(TRIP_1_ID, SERVICE_DATE, SCHEDULED, TIME_ZONE)
.addDelayedStopTime(STOP_SEQUENCE, DELAY)
.build();
@@ -40,11 +40,11 @@ void singleStopDelay() {
assertEquals(1, result.successful());
- var pattern1 = env.getPatternForTrip(env.trip1);
- int trip1Index = pattern1.getScheduledTimetable().getTripIndex(env.trip1.getId());
+ var pattern1 = env.getPatternForTrip(TRIP_1_ID);
+ int trip1Index = pattern1.getScheduledTimetable().getTripIndex(id(TRIP_1_ID));
var snapshot = env.getTimetableSnapshot();
- var trip1Realtime = snapshot.resolve(pattern1, RealtimeTestEnvironment.SERVICE_DATE);
+ var trip1Realtime = snapshot.resolve(pattern1, SERVICE_DATE);
var trip1Scheduled = snapshot.resolve(pattern1, null);
assertNotSame(trip1Realtime, trip1Scheduled);
@@ -59,11 +59,11 @@ void singleStopDelay() {
assertEquals(
"SCHEDULED | A1 0:00:10 0:00:11 | B1 0:00:20 0:00:21",
- env.getScheduledTimetable(env.trip1.getId())
+ env.getScheduledTimetable(TRIP_1_ID)
);
assertEquals(
"UPDATED | A1 [ND] 0:00:10 0:00:11 | B1 0:00:21 0:00:22",
- env.getRealtimeTimetable(env.trip1.getId().getId())
+ env.getRealtimeTimetable(TRIP_1_ID)
);
}
@@ -72,11 +72,15 @@ void singleStopDelay() {
*/
@Test
void complexDelay() {
- var env = RealtimeTestEnvironment.gtfs();
-
- var tripId = env.trip2.getId().getId();
+ var tripInput = TripInput
+ .of(TRIP_2_ID)
+ .addStop(STOP_A1, "0:01:00", "0:01:01")
+ .addStop(STOP_B1, "0:01:10", "0:01:11")
+ .addStop(STOP_C1, "0:01:20", "0:01:21")
+ .build();
+ var env = RealtimeTestEnvironment.gtfs().addTrip(tripInput).build();
- var tripUpdate = new TripUpdateBuilder(tripId, SERVICE_DATE, SCHEDULED, env.timeZone)
+ var tripUpdate = new TripUpdateBuilder(TRIP_2_ID, SERVICE_DATE, SCHEDULED, TIME_ZONE)
.addDelayedStopTime(0, 0)
.addDelayedStopTime(1, 60, 80)
.addDelayedStopTime(2, 90, 90)
@@ -86,19 +90,20 @@ void complexDelay() {
var snapshot = env.getTimetableSnapshot();
- final TripPattern originalTripPattern = env.getTransitService().getPatternForTrip(env.trip2);
+ var trip2 = env.getTransitService().getTripForId(id(TRIP_2_ID));
+ var originalTripPattern = env.getTransitService().getPatternForTrip(trip2);
var originalTimetableForToday = snapshot.resolve(originalTripPattern, SERVICE_DATE);
var originalTimetableScheduled = snapshot.resolve(originalTripPattern, null);
assertNotSame(originalTimetableForToday, originalTimetableScheduled);
- final int originalTripIndexScheduled = originalTimetableScheduled.getTripIndex(tripId);
+ final int originalTripIndexScheduled = originalTimetableScheduled.getTripIndex(TRIP_2_ID);
assertTrue(
originalTripIndexScheduled > -1,
"Original trip should be found in scheduled time table"
);
- final TripTimes originalTripTimesScheduled = originalTimetableScheduled.getTripTimes(
+ var originalTripTimesScheduled = originalTimetableScheduled.getTripTimes(
originalTripIndexScheduled
);
assertFalse(
@@ -107,7 +112,7 @@ void complexDelay() {
);
assertEquals(RealTimeState.SCHEDULED, originalTripTimesScheduled.getRealTimeState());
- final int originalTripIndexForToday = originalTimetableForToday.getTripIndex(tripId);
+ final int originalTripIndexForToday = originalTimetableForToday.getTripIndex(TRIP_2_ID);
assertTrue(
originalTripIndexForToday > -1,
"Original trip should be found in time table for service date"
@@ -115,11 +120,11 @@ void complexDelay() {
assertEquals(
"SCHEDULED | A1 0:01 0:01:01 | B1 0:01:10 0:01:11 | C1 0:01:20 0:01:21",
- env.getScheduledTimetable(env.trip2.getId())
+ env.getScheduledTimetable(TRIP_2_ID)
);
assertEquals(
"UPDATED | A1 0:01 0:01:01 | B1 0:02:10 0:02:31 | C1 0:02:50 0:02:51",
- env.getRealtimeTimetable(env.trip2)
+ env.getRealtimeTimetable(TRIP_2_ID)
);
}
}
diff --git a/src/test/java/org/opentripplanner/updater/trip/moduletests/delay/SkippedTest.java b/src/test/java/org/opentripplanner/updater/trip/moduletests/delay/SkippedTest.java
index de699324bb6..f9799ba6512 100644
--- a/src/test/java/org/opentripplanner/updater/trip/moduletests/delay/SkippedTest.java
+++ b/src/test/java/org/opentripplanner/updater/trip/moduletests/delay/SkippedTest.java
@@ -6,28 +6,35 @@
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.opentripplanner.transit.model._data.TransitModelForTest.id;
import static org.opentripplanner.updater.spi.UpdateResultAssertions.assertSuccess;
-import static org.opentripplanner.updater.trip.RealtimeTestEnvironment.SERVICE_DATE;
import static org.opentripplanner.updater.trip.UpdateIncrementality.DIFFERENTIAL;
import org.junit.jupiter.api.Test;
-import org.opentripplanner.transit.model.framework.FeedScopedId;
import org.opentripplanner.transit.model.timetable.RealTimeState;
import org.opentripplanner.transit.model.timetable.TripTimesStringBuilder;
+import org.opentripplanner.updater.trip.RealtimeTestConstants;
import org.opentripplanner.updater.trip.RealtimeTestEnvironment;
+import org.opentripplanner.updater.trip.TripInput;
import org.opentripplanner.updater.trip.TripUpdateBuilder;
/**
* A mixture of delayed and skipped stops should result in both delayed and cancelled stops.
*/
-public class SkippedTest {
+class SkippedTest implements RealtimeTestConstants {
+
+ private static final TripInput TRIP_INPUT = TripInput
+ .of(TRIP_2_ID)
+ .addStop(STOP_A1, "0:01:00", "0:01:01")
+ .addStop(STOP_B1, "0:01:10", "0:01:11")
+ .addStop(STOP_C1, "0:01:20", "0:01:21")
+ .build();
@Test
void scheduledTripWithSkippedAndScheduled() {
- var env = RealtimeTestEnvironment.gtfs();
- String scheduledTripId = env.trip2.getId().getId();
+ var env = RealtimeTestEnvironment.gtfs().addTrip(TRIP_INPUT).build();
- var tripUpdate = new TripUpdateBuilder(scheduledTripId, SERVICE_DATE, SCHEDULED, env.timeZone)
+ var tripUpdate = new TripUpdateBuilder(TRIP_2_ID, SERVICE_DATE, SCHEDULED, TIME_ZONE)
.addDelayedStopTime(0, 0)
.addSkippedStop(1)
.addDelayedStopTime(2, 90)
@@ -35,13 +42,13 @@ void scheduledTripWithSkippedAndScheduled() {
assertSuccess(env.applyTripUpdate(tripUpdate));
- assertOriginalTripPatternIsDeleted(env, env.trip2.getId());
+ assertOriginalTripPatternIsDeleted(env, TRIP_2_ID);
- assertNewTripTimesIsUpdated(env, env.trip2.getId());
+ assertNewTripTimesIsUpdated(env, TRIP_2_ID);
assertEquals(
"UPDATED | A1 0:01 0:01:01 | B1 [C] 0:01:52 0:01:58 | C1 0:02:50 0:02:51",
- env.getRealtimeTimetable(scheduledTripId)
+ env.getRealtimeTimetable(TRIP_2_ID)
);
}
@@ -56,10 +63,9 @@ void scheduledTripWithSkippedAndScheduled() {
*/
@Test
void scheduledTripWithPreviouslySkipped() {
- var env = RealtimeTestEnvironment.gtfs();
- var tripId = env.trip2.getId();
+ var env = RealtimeTestEnvironment.gtfs().addTrip(TRIP_INPUT).build();
- var tripUpdate = new TripUpdateBuilder(tripId.getId(), SERVICE_DATE, SCHEDULED, env.timeZone)
+ var tripUpdate = new TripUpdateBuilder(TRIP_2_ID, SERVICE_DATE, SCHEDULED, TIME_ZONE)
.addDelayedStopTime(0, 0)
.addSkippedStop(1)
.addDelayedStopTime(2, 90)
@@ -68,12 +74,7 @@ void scheduledTripWithPreviouslySkipped() {
assertSuccess(env.applyTripUpdate(tripUpdate, DIFFERENTIAL));
// Create update to the same trip but now the skipped stop is no longer skipped
- var scheduledBuilder = new TripUpdateBuilder(
- tripId.getId(),
- SERVICE_DATE,
- SCHEDULED,
- env.timeZone
- )
+ var scheduledBuilder = new TripUpdateBuilder(TRIP_2_ID, SERVICE_DATE, SCHEDULED, TIME_ZONE)
.addDelayedStopTime(0, 0)
.addDelayedStopTime(1, 50)
.addDelayedStopTime(2, 90);
@@ -87,17 +88,17 @@ void scheduledTripWithPreviouslySkipped() {
// stoptime updates have gone through
var snapshot = env.getTimetableSnapshot();
- assertNull(snapshot.getRealtimeAddedTripPattern(tripId, SERVICE_DATE));
+ assertNull(snapshot.getRealtimeAddedTripPattern(id(TRIP_2_ID), SERVICE_DATE));
- assertNewTripTimesIsUpdated(env, tripId);
+ assertNewTripTimesIsUpdated(env, TRIP_2_ID);
assertEquals(
"SCHEDULED | A1 0:01 0:01:01 | B1 0:01:10 0:01:11 | C1 0:01:20 0:01:21",
- env.getScheduledTimetable(tripId)
+ env.getScheduledTimetable(TRIP_2_ID)
);
assertEquals(
"UPDATED | A1 0:01 0:01:01 | B1 0:02 0:02:01 | C1 0:02:50 0:02:51",
- env.getRealtimeTimetable(tripId, SERVICE_DATE)
+ env.getRealtimeTimetable(id(TRIP_2_ID), SERVICE_DATE)
);
}
@@ -106,11 +107,11 @@ void scheduledTripWithPreviouslySkipped() {
*/
@Test
void skippedNoData() {
- var env = RealtimeTestEnvironment.gtfs();
+ var env = RealtimeTestEnvironment.gtfs().addTrip(TRIP_INPUT).build();
- final FeedScopedId tripId = env.trip2.getId();
+ String tripId = TRIP_2_ID;
- var tripUpdate = new TripUpdateBuilder(tripId.getId(), SERVICE_DATE, SCHEDULED, env.timeZone)
+ var tripUpdate = new TripUpdateBuilder(tripId, SERVICE_DATE, SCHEDULED, TIME_ZONE)
.addNoDataStop(0)
.addSkippedStop(1)
.addNoDataStop(2)
@@ -124,15 +125,15 @@ void skippedNoData() {
assertEquals(
"UPDATED | A1 [ND] 0:01 0:01:01 | B1 [C] 0:01:10 0:01:11 | C1 [ND] 0:01:20 0:01:21",
- env.getRealtimeTimetable(env.trip2)
+ env.getRealtimeTimetable(tripId)
);
}
private static void assertOriginalTripPatternIsDeleted(
RealtimeTestEnvironment env,
- FeedScopedId tripId
+ String tripId
) {
- var trip = env.getTransitService().getTripForId(tripId);
+ var trip = env.getTransitService().getTripForId(id(tripId));
var originalTripPattern = env.getTransitService().getPatternForTrip(trip);
var snapshot = env.getTimetableSnapshot();
var originalTimetableForToday = snapshot.resolve(originalTripPattern, SERVICE_DATE);
@@ -174,11 +175,9 @@ private static void assertOriginalTripPatternIsDeleted(
assertEquals(RealTimeState.DELETED, originalTripTimesForToday.getRealTimeState());
}
- private static void assertNewTripTimesIsUpdated(
- RealtimeTestEnvironment env,
- FeedScopedId tripId
- ) {
- var originalTripPattern = env.getTransitService().getPatternForTrip(env.trip2);
+ private static void assertNewTripTimesIsUpdated(RealtimeTestEnvironment env, String tripId) {
+ var trip = env.getTransitService().getTripForId(id(tripId));
+ var originalTripPattern = env.getTransitService().getPatternForTrip(trip);
var snapshot = env.getTimetableSnapshot();
var originalTimetableForToday = snapshot.resolve(originalTripPattern, SERVICE_DATE);
diff --git a/src/test/java/org/opentripplanner/updater/trip/moduletests/rejection/InvalidInputTest.java b/src/test/java/org/opentripplanner/updater/trip/moduletests/rejection/InvalidInputTest.java
index da362451753..2ba6749b4b0 100644
--- a/src/test/java/org/opentripplanner/updater/trip/moduletests/rejection/InvalidInputTest.java
+++ b/src/test/java/org/opentripplanner/updater/trip/moduletests/rejection/InvalidInputTest.java
@@ -4,20 +4,21 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.opentripplanner.updater.spi.UpdateError.UpdateErrorType.NO_SERVICE_ON_DATE;
import static org.opentripplanner.updater.spi.UpdateResultAssertions.assertFailure;
-import static org.opentripplanner.updater.trip.RealtimeTestEnvironment.SERVICE_DATE;
import java.time.LocalDate;
import java.util.List;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
+import org.opentripplanner.updater.trip.RealtimeTestConstants;
import org.opentripplanner.updater.trip.RealtimeTestEnvironment;
+import org.opentripplanner.updater.trip.TripInput;
import org.opentripplanner.updater.trip.TripUpdateBuilder;
/**
* A trip with start date that is outside the service period shouldn't throw an exception and is
* ignored instead.
*/
-class InvalidInputTest {
+class InvalidInputTest implements RealtimeTestConstants {
public static List cases() {
return List.of(SERVICE_DATE.minusYears(10), SERVICE_DATE.plusYears(10));
@@ -26,9 +27,14 @@ public static List cases() {
@ParameterizedTest
@MethodSource("cases")
void invalidTripDate(LocalDate date) {
- var env = RealtimeTestEnvironment.gtfs();
+ var tripInput = TripInput
+ .of(TRIP_1_ID)
+ .addStop(STOP_A1, "0:00:10", "0:00:11")
+ .addStop(STOP_B1, "0:00:20", "0:00:21")
+ .build();
+ var env = RealtimeTestEnvironment.gtfs().addTrip(tripInput).build();
- var update = new TripUpdateBuilder(env.trip1.getId().getId(), date, SCHEDULED, env.timeZone)
+ var update = new TripUpdateBuilder(TRIP_1_ID, date, SCHEDULED, TIME_ZONE)
.addDelayedStopTime(2, 60, 80)
.build();
diff --git a/src/test/java/org/opentripplanner/updater/trip/moduletests/rejection/InvalidTripIdTest.java b/src/test/java/org/opentripplanner/updater/trip/moduletests/rejection/InvalidTripIdTest.java
index 83c2547dbc7..699e8fe865c 100644
--- a/src/test/java/org/opentripplanner/updater/trip/moduletests/rejection/InvalidTripIdTest.java
+++ b/src/test/java/org/opentripplanner/updater/trip/moduletests/rejection/InvalidTripIdTest.java
@@ -22,7 +22,7 @@ static Stream invalidCases() {
@ParameterizedTest(name = "tripId=\"{0}\"")
@MethodSource("invalidCases")
void invalidTripId(String tripId) {
- var env = RealtimeTestEnvironment.gtfs();
+ var env = RealtimeTestEnvironment.gtfs().build();
var tripDescriptorBuilder = GtfsRealtime.TripDescriptor.newBuilder();
if (tripId != null) {
tripDescriptorBuilder.setTripId(tripId);
diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/expectations/stops.json b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/stops.json
index 307b07b58aa..4e3d4c8a18c 100644
--- a/src/test/resources/org/opentripplanner/apis/gtfs/expectations/stops.json
+++ b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/stops.json
@@ -6,56 +6,120 @@
"lat" : 5.0,
"lon" : 8.0,
"name" : "A",
- "vehicleMode" : "BUS"
+ "vehicleMode" : "BUS",
+ "allRoutes" : [
+ {
+ "gtfsId" : "F:a-route",
+ "longName" : null,
+ "shortName" : "Ra-route"
+ }
+ ],
+ "routesWithinRange" : [ ]
},
{
"gtfsId" : "F:B",
"lat" : 6.0,
"lon" : 8.5,
"name" : "B",
- "vehicleMode" : "BUS"
+ "vehicleMode" : "BUS",
+ "allRoutes" : [
+ {
+ "gtfsId" : "F:a-route",
+ "longName" : null,
+ "shortName" : "Ra-route"
+ }
+ ],
+ "routesWithinRange" : [ ]
},
{
"gtfsId" : "F:C",
"lat" : 7.0,
"lon" : 9.0,
"name" : "C",
- "vehicleMode" : "BUS"
+ "vehicleMode" : "BUS",
+ "allRoutes" : [
+ {
+ "gtfsId" : "F:a-route",
+ "longName" : null,
+ "shortName" : "Ra-route"
+ }
+ ],
+ "routesWithinRange" : [ ]
},
{
"gtfsId" : "F:D",
"lat" : 8.0,
"lon" : 9.5,
"name" : "D",
- "vehicleMode" : "BUS"
+ "vehicleMode" : "BUS",
+ "allRoutes" : [
+ {
+ "gtfsId" : "F:a-route",
+ "longName" : null,
+ "shortName" : "Ra-route"
+ }
+ ],
+ "routesWithinRange" : [ ]
},
{
"gtfsId" : "F:E",
"lat" : 9.0,
"lon" : 10.0,
"name" : "E",
- "vehicleMode" : "BUS"
+ "vehicleMode" : "BUS",
+ "allRoutes" : [
+ {
+ "gtfsId" : "F:a-route",
+ "longName" : null,
+ "shortName" : "Ra-route"
+ }
+ ],
+ "routesWithinRange" : [ ]
},
{
"gtfsId" : "F:F",
"lat" : 9.0,
"lon" : 10.5,
"name" : "F",
- "vehicleMode" : "BUS"
+ "vehicleMode" : "BUS",
+ "allRoutes" : [
+ {
+ "gtfsId" : "F:a-route",
+ "longName" : null,
+ "shortName" : "Ra-route"
+ }
+ ],
+ "routesWithinRange" : [ ]
},
{
"gtfsId" : "F:G",
"lat" : 9.5,
"lon" : 11.0,
"name" : "G",
- "vehicleMode" : "BUS"
+ "vehicleMode" : "BUS",
+ "allRoutes" : [
+ {
+ "gtfsId" : "F:a-route",
+ "longName" : null,
+ "shortName" : "Ra-route"
+ }
+ ],
+ "routesWithinRange" : [ ]
},
{
"gtfsId" : "F:H",
"lat" : 10.0,
"lon" : 11.5,
"name" : "H",
- "vehicleMode" : "BUS"
+ "vehicleMode" : "BUS",
+ "allRoutes" : [
+ {
+ "gtfsId" : "F:a-route",
+ "longName" : null,
+ "shortName" : "Ra-route"
+ }
+ ],
+ "routesWithinRange" : [ ]
}
]
}
diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/queries/stops.graphql b/src/test/resources/org/opentripplanner/apis/gtfs/queries/stops.graphql
index 5f3df71f8e7..af4fd904096 100644
--- a/src/test/resources/org/opentripplanner/apis/gtfs/queries/stops.graphql
+++ b/src/test/resources/org/opentripplanner/apis/gtfs/queries/stops.graphql
@@ -5,5 +5,17 @@
lon
name
vehicleMode
+ allRoutes: routes {
+ gtfsId
+ longName
+ shortName
+ }
+ routesWithinRange: routes(
+ serviceDates: { start: "2024-09-10", end: "2024-09-10" }
+ ) {
+ gtfsId
+ longName
+ shortName
+ }
}
}