From f23e4104c22d11eb0dc06f6927e8b6cc539a0e75 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 21 Mar 2023 14:00:51 +0100 Subject: [PATCH 01/85] Add "relaxTransitPriorityGroup" to REST API This enables us to pass in the relax function to use with transit-group-priority (model not implemented jet). --- .../common/RequestToPreferencesMapper.java | 30 +++++++- .../api/common/RoutingResource.java | 3 + .../api/mapping/RelaxMapper.java | 73 +++++++++++++++++++ .../TransitPriorityGroup32n.java | 67 +++++++++++++++++ .../transit/mappers/RaptorRequestMapper.java | 29 ++++++-- .../request/preference/RaptorPreferences.java | 19 ++++- .../routing/api/request/preference/Relax.java | 19 +++++ .../preference/RoutingPreferences.java | 36 +++++++++ .../api/mapping/RelaxMapperTest.java | 27 +++++++ .../TransitPriorityGroup32nTest.java | 59 +++++++++++++++ .../mappers/RaptorRequestMapperTest.java | 36 +++++++++ .../preference/RaptorPreferencesTest.java | 3 + .../routing/core/RoutingPreferencesTest.java | 8 +- 13 files changed, 395 insertions(+), 14 deletions(-) create mode 100644 src/main/java/org/opentripplanner/api/mapping/RelaxMapper.java create mode 100644 src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitPriorityGroup32n.java create mode 100644 src/main/java/org/opentripplanner/routing/api/request/preference/Relax.java create mode 100644 src/test/java/org/opentripplanner/api/mapping/RelaxMapperTest.java create mode 100644 src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitPriorityGroup32nTest.java create mode 100644 src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java diff --git a/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java b/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java index 20ca8054308..b5c092e17fe 100644 --- a/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java +++ b/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java @@ -2,10 +2,12 @@ import jakarta.validation.constraints.NotNull; import java.util.function.Consumer; +import org.opentripplanner.api.mapping.RelaxMapper; import org.opentripplanner.framework.lang.ObjectUtils; import org.opentripplanner.routing.algorithm.filterchain.api.TransitGeneralizedCostFilterParams; import org.opentripplanner.routing.api.request.framework.CostLinearFunction; import org.opentripplanner.routing.api.request.preference.ItineraryFilterPreferences; +import org.opentripplanner.routing.api.request.preference.Relax; import org.opentripplanner.routing.api.request.preference.RoutingPreferences; import org.opentripplanner.routing.core.BicycleOptimizeType; @@ -85,10 +87,19 @@ private BoardAndAlightSlack mapTransit() { setIfNotNull(req.alightSlack, tr::withDefaultAlightSlackSec); setIfNotNull(req.otherThanPreferredRoutesPenalty, tr::setOtherThanPreferredRoutesPenalty); setIfNotNull(req.ignoreRealtimeUpdates, tr::setIgnoreRealtimeUpdates); - setIfNotNull( - req.relaxTransitSearchGeneralizedCostAtDestination, - value -> tr.withRaptor(it -> it.withRelaxGeneralizedCostAtDestination(value)) - ); + + tr.withRaptor(r -> { + if (req.relaxTransitPriorityGroup != null) { + r.withTransitGroupPriorityGeneralizedCostSlack( + RelaxMapper.mapRelax(req.relaxTransitPriorityGroup) + ); + } else { + setIfNotNull( + req.relaxTransitSearchGeneralizedCostAtDestination, + r::withRelaxGeneralizedCostAtDestination + ); + } + }); }); return new BoardAndAlightSlack( @@ -171,6 +182,17 @@ static void setIfNotNull(T value, @NotNull Consumer body) { } } + static void mapRelaxIfNotNull(String fx, @NotNull Consumer body) { + if (fx == null) { + return; + } + var a = fx.split("[\\sxXuUvVtT*+]+"); + if (a.length != 2) { + return; + } + body.accept(new Relax(Double.parseDouble(a[0]), Integer.parseInt(a[1]))); + } + /** * The combined value of board and alight slack is used in the initialization of transfer * preferences. diff --git a/src/main/java/org/opentripplanner/api/common/RoutingResource.java b/src/main/java/org/opentripplanner/api/common/RoutingResource.java index e60bfe41b82..74d06e234f1 100644 --- a/src/main/java/org/opentripplanner/api/common/RoutingResource.java +++ b/src/main/java/org/opentripplanner/api/common/RoutingResource.java @@ -656,6 +656,9 @@ public abstract class RoutingResource { @QueryParam("useVehicleParkingAvailabilityInformation") protected Boolean useVehicleParkingAvailabilityInformation; + @QueryParam("relaxTransitPriorityGroup") + protected String relaxTransitPriorityGroup; + /** * Whether non-optimal transit paths at the destination should be returned. * This is optional. Use values between 1.0 and 2.0. For example to relax 10% use 1.1. diff --git a/src/main/java/org/opentripplanner/api/mapping/RelaxMapper.java b/src/main/java/org/opentripplanner/api/mapping/RelaxMapper.java new file mode 100644 index 00000000000..80925648145 --- /dev/null +++ b/src/main/java/org/opentripplanner/api/mapping/RelaxMapper.java @@ -0,0 +1,73 @@ +package org.opentripplanner.api.mapping; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; +import org.opentripplanner.routing.api.request.preference.Relax; + +/** + * Map a text to a Relax instance. The patter used is: + *
+ *   NNN.NN '*' [variable-placeholder] '+' NNN
+ * 
+ * {@code NNN.NN} is any positive decimal number. + * {@code NNN} is any positive integer number. + * {@code variable-placeholder} is any alphabetical variable name - this is just a placeholder + * to make it clear what is the ratio/factor and what is the + * constant/slack. + */ +public class RelaxMapper { + + private static final String SEP = "\\s*"; + private static final String NUM = "(\\d+(?:\\.\\d+)?+)"; + private static final String INT = "(\\d+)"; + private static final String ALFA = "[a-zA-Z]+"; + private static final String PLUS = SEP + Pattern.quote("+") + SEP; + private static final String TIMES = Pattern.quote("*"); + + private static final String RATIO = NUM + SEP + TIMES + "?" + SEP + ALFA; + private static final String SLACK = INT; + + private static final Pattern RELAX_PATTERN_1 = Pattern.compile(RATIO + PLUS + SLACK); + private static final Pattern RELAX_PATTERN_2 = Pattern.compile(SLACK + PLUS + RATIO); + + public static Relax mapRelax(String input) { + if (input == null || input.isBlank()) { + return null; + } + String inputTrimmed = input.trim(); + List errors = new ArrayList<>(); + + return parse(RELAX_PATTERN_1, inputTrimmed, 1, 2, errors) + .or(() -> parse(RELAX_PATTERN_2, inputTrimmed, 2, 1, errors)) + .orElseThrow(() -> + new IllegalArgumentException( + "Unable to parse function: '" + + input + + "'. Use: '2.0 * x + 100'." + + (errors.isEmpty() ? "" : " Details: " + errors) + ) + ); + } + + private static Optional parse( + Pattern pattern, + String input, + int ratioIdx, + int slackIdx, + List errors + ) { + var m = pattern.matcher(input); + if (m.matches()) { + try { + return Optional.of( + new Relax(Double.parseDouble(m.group(ratioIdx)), Integer.parseInt(m.group(slackIdx))) + ); + } catch (Exception e) { + errors.add(e); + } + } + return Optional.empty(); + } +} diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitPriorityGroup32n.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitPriorityGroup32n.java new file mode 100644 index 00000000000..7032796792b --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitPriorityGroup32n.java @@ -0,0 +1,67 @@ +package org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.grouppriority; + +import org.opentripplanner.raptor.api.model.DominanceFunction; +import org.opentripplanner.raptor.api.request.RaptorTransitPriorityGroupCalculator; + +/** + * This is a "BitSet" implementation for groupId. It can store until 32 groups, + * a set with few elements does NOT dominate a set with more elements. + */ +public class TransitPriorityGroup32n { + + private static final int GROUP_ZERO = 0; + private static final int MIN_SEQ_NO = 0; + private static final int MAX_SEQ_NO = 32; + + public static RaptorTransitPriorityGroupCalculator priorityCalculator() { + return new RaptorTransitPriorityGroupCalculator() { + @Override + public int mergeTransitPriorityGroupIds(int currentGroupIds, int boardingGroupId) { + return mergeInGroupId(currentGroupIds, boardingGroupId); + } + + @Override + public DominanceFunction dominanceFunction() { + return TransitPriorityGroup32n::dominate; + } + }; + } + + /** To groups dominate each other if they are different. */ + public static boolean dominate(int left, int right) { + return left != right; + } + + /** + * Use this method to map from a continuous group index [0..32) to the groupId used + * during routing. The ID is implementation specific and optimized for performance. + */ + public static int groupId(final int priorityGroupIndex) { + assertValidGroupSeqNo(priorityGroupIndex); + return priorityGroupIndex == MIN_SEQ_NO ? GROUP_ZERO : 0x01 << (priorityGroupIndex - 1); + } + + /** + * Merge a groupId into a set of groupIds. + */ + public static int mergeInGroupId(final int currentSetOfGroupIds, final int newGroupId) { + return currentSetOfGroupIds | newGroupId; + } + + private static void assertValidGroupSeqNo(int priorityGroupIndex) { + if (priorityGroupIndex < MIN_SEQ_NO) { + throw new IllegalArgumentException( + "Transit priority group can not be a negative number: " + priorityGroupIndex + ); + } + if (priorityGroupIndex > MAX_SEQ_NO) { + throw new IllegalArgumentException( + "Transit priority group exceeds max number of groups: " + + priorityGroupIndex + + " (MAX=" + + MAX_SEQ_NO + + ")" + ); + } + } +} diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java index 8e6121edffe..3c09b4b1653 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java @@ -9,8 +9,10 @@ import java.util.Collection; import java.util.List; import org.opentripplanner.framework.application.OTPFeature; +import org.opentripplanner.raptor.api.model.GeneralizedCostRelaxFunction; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; import org.opentripplanner.raptor.api.model.RaptorConstants; +import org.opentripplanner.raptor.api.model.RelaxFunction; import org.opentripplanner.raptor.api.request.Optimization; import org.opentripplanner.raptor.api.request.PassThroughPoint; import org.opentripplanner.raptor.api.request.RaptorRequest; @@ -18,7 +20,10 @@ import org.opentripplanner.raptor.rangeraptor.SystemErrDebugLogger; import org.opentripplanner.routing.algorithm.raptoradapter.router.performance.PerformanceTimersForRaptor; import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.RaptorCostConverter; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.grouppriority.TransitPriorityGroup32n; import org.opentripplanner.routing.api.request.RouteRequest; +import org.opentripplanner.routing.api.request.preference.Relax; import org.opentripplanner.transit.model.site.StopLocation; public class RaptorRequestMapper { @@ -110,12 +115,14 @@ private RaptorRequest doMap() { } builder.withMultiCriteria(mcBuilder -> { - preferences - .transit() - .raptor() - .relaxGeneralizedCostAtDestination() - .ifPresent(mcBuilder::withRelaxCostAtDestination); - mcBuilder.withPassThroughPoints(mapPassThroughPoints()); + var r = preferences.transit().raptor(); + if (!r.relaxTransitPriorityGroup().isNormal()) { + mcBuilder.withTransitPriorityCalculator(TransitPriorityGroup32n.priorityCalculator()); + mcBuilder.withRelaxC1(mapRelaxCost(r.relaxTransitPriorityGroup())); + } else { + mcBuilder.withPassThroughPoints(mapPassThroughPoints()); + r.relaxGeneralizedCostAtDestination().ifPresent(mcBuilder::withRelaxCostAtDestination); + } }); for (Optimization optimization : preferences.transit().raptor().optimizations()) { @@ -185,6 +192,16 @@ private List mapPassThroughPoints() { .toList(); } + static RelaxFunction mapRelaxCost(Relax relax) { + if (relax == null) { + return null; + } + return GeneralizedCostRelaxFunction.of( + relax.ratio(), + RaptorCostConverter.toRaptorCost(relax.slack()) + ); + } + private int relativeTime(Instant time) { if (time == null) { return RaptorConstants.TIME_NOT_SET; diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/RaptorPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/RaptorPreferences.java index 905aa648945..52f50044353 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/RaptorPreferences.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/RaptorPreferences.java @@ -35,6 +35,7 @@ public final class RaptorPreferences implements Serializable { private final Instant timeLimit; + private final Relax relaxTransitPriorityGroup; private final Double relaxGeneralizedCostAtDestination; private RaptorPreferences() { @@ -42,6 +43,7 @@ private RaptorPreferences() { this.profile = RaptorProfile.MULTI_CRITERIA; this.searchDirection = SearchDirection.FORWARD; this.timeLimit = null; + this.relaxTransitPriorityGroup = Relax.NORMAL; this.relaxGeneralizedCostAtDestination = null; } @@ -50,7 +52,7 @@ private RaptorPreferences(RaptorPreferences.Builder builder) { this.profile = Objects.requireNonNull(builder.profile); this.searchDirection = Objects.requireNonNull(builder.searchDirection); this.timeLimit = builder.timeLimit; - + this.relaxTransitPriorityGroup = Objects.requireNonNull(builder.relaxTransitPriorityGroup); this.relaxGeneralizedCostAtDestination = Units.normalizedOptionalFactor( builder.relaxGeneralizedCostAtDestination, @@ -88,9 +90,14 @@ public Instant timeLimit() { return timeLimit; } + public Relax relaxTransitPriorityGroup() { + return relaxTransitPriorityGroup; + } + /** * See {@link SearchParams#relaxCostAtDestination()} for documentation. */ + @Deprecated public Optional relaxGeneralizedCostAtDestination() { return Optional.ofNullable(relaxGeneralizedCostAtDestination); } @@ -110,6 +117,7 @@ public boolean equals(Object o) { profile == that.profile && searchDirection == that.searchDirection && Objects.equals(timeLimit, that.timeLimit) && + Objects.equals(relaxTransitPriorityGroup, that.relaxTransitPriorityGroup) && Objects.equals(relaxGeneralizedCostAtDestination, that.relaxGeneralizedCostAtDestination) ); } @@ -121,6 +129,7 @@ public int hashCode() { profile, searchDirection, timeLimit, + relaxTransitPriorityGroup, relaxGeneralizedCostAtDestination ); } @@ -134,6 +143,7 @@ public String toString() { .addEnum("searchDirection", searchDirection, DEFAULT.searchDirection) // Ignore time limit if null (default value) .addDateTime("timeLimit", timeLimit) + .addObj("relaxTransitPriorityGroup", relaxTransitPriorityGroup, Relax.NORMAL) .addNum( "relaxGeneralizedCostAtDestination", relaxGeneralizedCostAtDestination, @@ -150,6 +160,7 @@ public static class Builder { private SearchDirection searchDirection; private Set optimizations; private Instant timeLimit; + private Relax relaxTransitPriorityGroup; private Double relaxGeneralizedCostAtDestination; public Builder(RaptorPreferences original) { @@ -158,6 +169,7 @@ public Builder(RaptorPreferences original) { this.searchDirection = original.searchDirection; this.optimizations = null; this.timeLimit = original.timeLimit; + this.relaxTransitPriorityGroup = original.relaxTransitPriorityGroup; this.relaxGeneralizedCostAtDestination = original.relaxGeneralizedCostAtDestination; } @@ -184,6 +196,11 @@ public Builder withTimeLimit(Instant timeLimit) { return this; } + public Builder withTransitGroupPriorityGeneralizedCostSlack(Relax value) { + this.relaxTransitPriorityGroup = value; + return this; + } + public Builder withRelaxGeneralizedCostAtDestination( @Nullable Double relaxGeneralizedCostAtDestination ) { diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/Relax.java b/src/main/java/org/opentripplanner/routing/api/request/preference/Relax.java new file mode 100644 index 00000000000..7ee2346e87b --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/Relax.java @@ -0,0 +1,19 @@ +package org.opentripplanner.routing.api.request.preference; + +/** + * Relax a value by the given ratio and slack. The relaxed value + * can be calculated using this function: + *
+ *   f(x) = ratio * x + slack
+ * 
+ * + * @param ratio the factor to multiply into the value, must be minimum 1.0 and max 4.0 + * @param slack the amount of slack to add to the value. + */ +public record Relax(double ratio, int slack) { + public static final Relax NORMAL = new Relax(1.0, 0); + + public boolean isNormal() { + return this.equals(NORMAL); + } +} diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/RoutingPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/RoutingPreferences.java index 1077ce49334..a4dfcedd0c7 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/RoutingPreferences.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/RoutingPreferences.java @@ -4,6 +4,7 @@ import static org.opentripplanner.framework.lang.ObjectUtils.ifNotNull; import java.io.Serializable; +import java.util.Objects; import java.util.function.Consumer; import javax.annotation.Nonnull; import org.opentripplanner.street.search.TraverseMode; @@ -116,6 +117,41 @@ public double getSpeed(TraverseMode mode, boolean walkingBike) { }; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + RoutingPreferences that = (RoutingPreferences) o; + return ( + Objects.equals(transit, that.transit) && + Objects.equals(transfer, that.transfer) && + Objects.equals(walk, that.walk) && + Objects.equals(street, that.street) && + Objects.equals(wheelchair, that.wheelchair) && + Objects.equals(bike, that.bike) && + Objects.equals(car, that.car) && + Objects.equals(rental, that.rental) && + Objects.equals(system, that.system) && + Objects.equals(itineraryFilter, that.itineraryFilter) + ); + } + + @Override + public int hashCode() { + return Objects.hash( + transit, + transfer, + walk, + street, + wheelchair, + bike, + car, + rental, + system, + itineraryFilter + ); + } + public static class Builder { private final RoutingPreferences original; diff --git a/src/test/java/org/opentripplanner/api/mapping/RelaxMapperTest.java b/src/test/java/org/opentripplanner/api/mapping/RelaxMapperTest.java new file mode 100644 index 00000000000..9e182708619 --- /dev/null +++ b/src/test/java/org/opentripplanner/api/mapping/RelaxMapperTest.java @@ -0,0 +1,27 @@ +package org.opentripplanner.api.mapping; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; +import org.opentripplanner.routing.api.request.preference.Relax; + +class RelaxMapperTest { + + @Test + void mapRelax() { + assertNull(RelaxMapper.mapRelax("")); + assertNull(RelaxMapper.mapRelax(null)); + + Relax expected = new Relax(2.0, 10); + + assertEquals(expected, RelaxMapper.mapRelax("2 * generalizedCost + 10")); + assertEquals(expected, RelaxMapper.mapRelax("10 + 2 * generalizedCost")); + assertEquals(expected, RelaxMapper.mapRelax("2.0x+10")); + assertEquals(expected, RelaxMapper.mapRelax("10+2.0x")); + assertThrows(IllegalArgumentException.class, () -> RelaxMapper.mapRelax("2.0")); + assertThrows(IllegalArgumentException.class, () -> RelaxMapper.mapRelax("2.0+10")); + assertThrows(IllegalArgumentException.class, () -> RelaxMapper.mapRelax("2.0*10")); + } +} diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitPriorityGroup32nTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitPriorityGroup32nTest.java new file mode 100644 index 00000000000..2f90553e22c --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitPriorityGroup32nTest.java @@ -0,0 +1,59 @@ +package org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.grouppriority; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; +import org.opentripplanner.raptor.api.request.RaptorTransitPriorityGroupCalculator; + +class TransitPriorityGroup32nTest { + + private static final int GROUP_INDEX_0 = 0; + private static final int GROUP_INDEX_1 = 1; + private static final int GROUP_INDEX_2 = 2; + private static final int GROUP_INDEX_31 = 31; + + private static final int GROUP_0 = TransitPriorityGroup32n.groupId(GROUP_INDEX_0); + private static final int GROUP_1 = TransitPriorityGroup32n.groupId(GROUP_INDEX_1); + private static final int GROUP_2 = TransitPriorityGroup32n.groupId(GROUP_INDEX_2); + private static final int GROUP_30 = TransitPriorityGroup32n.groupId(GROUP_INDEX_31); + private static final int GROUP_31 = TransitPriorityGroup32n.groupId(GROUP_INDEX_31); + private static final RaptorTransitPriorityGroupCalculator subjct = TransitPriorityGroup32n.priorityCalculator(); + + @Test + void groupId() { + assertEqualsHex(0x00_00_00_00, TransitPriorityGroup32n.groupId(0)); + assertEqualsHex(0x00_00_00_01, TransitPriorityGroup32n.groupId(1)); + assertEqualsHex(0x00_00_00_02, TransitPriorityGroup32n.groupId(2)); + assertEqualsHex(0x00_00_00_04, TransitPriorityGroup32n.groupId(3)); + assertEqualsHex(0x40_00_00_00, TransitPriorityGroup32n.groupId(31)); + assertEqualsHex(0x80_00_00_00, TransitPriorityGroup32n.groupId(32)); + + assertThrows(IllegalArgumentException.class, () -> TransitPriorityGroup32n.groupId(-1)); + assertThrows(IllegalArgumentException.class, () -> TransitPriorityGroup32n.groupId(33)); + } + + @Test + void mergeTransitPriorityGroupIds() { + assertEqualsHex(GROUP_0, subjct.mergeTransitPriorityGroupIds(GROUP_0, GROUP_0)); + assertEqualsHex(GROUP_1, subjct.mergeTransitPriorityGroupIds(GROUP_1, GROUP_1)); + assertEqualsHex(GROUP_0 | GROUP_1, subjct.mergeTransitPriorityGroupIds(GROUP_0, GROUP_1)); + assertEqualsHex(GROUP_30 | GROUP_31, subjct.mergeTransitPriorityGroupIds(GROUP_30, GROUP_31)); + assertEqualsHex( + GROUP_0 | GROUP_1 | GROUP_2 | GROUP_30 | GROUP_31, + subjct.mergeTransitPriorityGroupIds(GROUP_0 | GROUP_1 | GROUP_2 | GROUP_30, GROUP_31) + ); + } + + @Test + void dominanceFunction() { + assertFalse(subjct.dominanceFunction().leftDominateRight(GROUP_0, GROUP_0)); + assertFalse(subjct.dominanceFunction().leftDominateRight(GROUP_31, GROUP_31)); + assertFalse(subjct.dominanceFunction().leftDominateRight(GROUP_1 | GROUP_2, GROUP_1 | GROUP_2)); + } + + static void assertEqualsHex(int expected, int actual) { + assertEquals(expected, actual, "%08x == %08x".formatted(expected, actual)); + } +} diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java new file mode 100644 index 00000000000..cf83ce528e0 --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java @@ -0,0 +1,36 @@ +package org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.opentripplanner.routing.api.request.preference.Relax; + +class RaptorRequestMapperTest { + + private static final Relax R1 = new Relax(1.0, 50); + private static final Relax R2 = new Relax(1.5, 0); + private static final Relax R3 = new Relax(2.0, 30); + + static List testCasesRelaxedCost() { + return List.of( + Arguments.of(Relax.NORMAL, 0, 0), + Arguments.of(Relax.NORMAL, 10, 10), + Arguments.of(R1, 0, 5000), + Arguments.of(R1, 7, 5007), + Arguments.of(R2, 0, 0), + Arguments.of(R2, 100, 150), + Arguments.of(R3, 0, 3000), + Arguments.of(R3, 100, 3200) + ); + } + + @ParameterizedTest + @MethodSource("testCasesRelaxedCost") + void mapRelaxCost(Relax input, int cost, int expected) { + var calcCost = RaptorRequestMapper.mapRelaxCost(input); + assertEquals(expected, calcCost.relax(cost)); + } +} diff --git a/src/test/java/org/opentripplanner/routing/api/request/preference/RaptorPreferencesTest.java b/src/test/java/org/opentripplanner/routing/api/request/preference/RaptorPreferencesTest.java index 2894e1a1ae0..aa9ac8eab42 100644 --- a/src/test/java/org/opentripplanner/routing/api/request/preference/RaptorPreferencesTest.java +++ b/src/test/java/org/opentripplanner/routing/api/request/preference/RaptorPreferencesTest.java @@ -28,6 +28,7 @@ class RaptorPreferencesTest { .atStartOfDay(ZoneIds.UTC) .toInstant(); + private static final Relax TRANSIT_GROUP_PRIORITY_RELAX = new Relax(1.5, 300); private static final double RELAX_GENERALIZED_COST_AT_DESTINATION = 1.2; private final RaptorPreferences subject = RaptorPreferences @@ -36,6 +37,7 @@ class RaptorPreferencesTest { .withProfile(PROFILE) .withOptimizations(OPTIMIZATIONS) .withTimeLimit(TIME_LIMIT) + .withTransitGroupPriorityGeneralizedCostSlack(TRANSIT_GROUP_PRIORITY_RELAX) .withRelaxGeneralizedCostAtDestination(RELAX_GENERALIZED_COST_AT_DESTINATION) .build(); @@ -132,6 +134,7 @@ void testToString() { "profile: STANDARD, " + "searchDirection: REVERSE, " + "timeLimit: 2020-06-09T00:00:00Z, " + + "relaxTransitPriorityGroup: Relax[ratio=1.5, slack=300], " + "relaxGeneralizedCostAtDestination: 1.2" + "}", subject.toString() diff --git a/src/test/java/org/opentripplanner/routing/core/RoutingPreferencesTest.java b/src/test/java/org/opentripplanner/routing/core/RoutingPreferencesTest.java index 1ca95b1abdb..8131fdd1299 100644 --- a/src/test/java/org/opentripplanner/routing/core/RoutingPreferencesTest.java +++ b/src/test/java/org/opentripplanner/routing/core/RoutingPreferencesTest.java @@ -11,11 +11,13 @@ public class RoutingPreferencesTest { @Test public void copyOfShouldReturnTheSameInstanceWhenBuild() { var pref = new RoutingPreferences(); - var copy = pref.copyOf().build(); - assertNotSame(pref, copy); + var same = pref.copyOf().build(); + assertSame(pref, same); + // Change one thing to force making a copy + var copy = pref.copyOf().withCar(c -> c.withReluctance(3.5)).build(); + assertNotSame(pref.car(), copy.car()); // Immutable classes should not change - assertSame(pref.car(), copy.car()); assertSame(pref.bike(), copy.bike()); assertSame(pref.walk(), copy.walk()); assertSame(pref.transfer(), copy.transfer()); From f2ad3641a2175350aa4c658870fe6887e745a650 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Wed, 22 Mar 2023 17:15:28 +0100 Subject: [PATCH 02/85] feature: Make transit-priority-groups configurable. --- docs/RouteRequest.md | 15 ++ .../common/RequestToPreferencesMapper.java | 22 ++- .../transit/mappers/RaptorRequestMapper.java | 7 +- .../request/PriorityGroupConfigurator.java | 116 +++++++++++++ .../transit/request/PriorityGroupMatcher.java | 152 ++++++++++++++++++ .../RaptorRoutingRequestTransitData.java | 15 +- ...aptorRoutingRequestTransitDataCreator.java | 11 +- .../transit/request/TripPatternForDates.java | 9 +- .../request/preference/RaptorPreferences.java | 18 --- .../routing/api/request/preference/Relax.java | 20 ++- .../preference/TransitPreferences.java | 25 ++- .../api/request/request/TransitRequest.java | 53 ++++++ .../filter/TransitPriorityGroupSelect.java | 148 +++++++++++++++++ .../routerequest/RouteRequestConfig.java | 2 + .../TransitPriorityGroupConfig.java | 96 +++++++++++ ...rRoutingRequestTransitDataCreatorTest.java | 3 +- .../transit/request/TestRouteData.java | 3 +- .../preference/RaptorPreferencesTest.java | 3 - .../preference/TransitPreferencesTest.java | 8 + 19 files changed, 674 insertions(+), 52 deletions(-) create mode 100644 src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfigurator.java create mode 100644 src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcher.java create mode 100644 src/main/java/org/opentripplanner/routing/api/request/request/filter/TransitPriorityGroupSelect.java create mode 100644 src/main/java/org/opentripplanner/standalone/config/routerequest/TransitPriorityGroupConfig.java diff --git a/docs/RouteRequest.md b/docs/RouteRequest.md index f814753df4a..fb2d567ad68 100644 --- a/docs/RouteRequest.md +++ b/docs/RouteRequest.md @@ -107,6 +107,7 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe |    [extraStopBoardAlightCostsFactor](#rd_to_extraStopBoardAlightCostsFactor) | `double` | Add an extra board- and alight-cost for prioritized stops. | *Optional* | `0.0` | 2.1 | |    [minSafeWaitTimeFactor](#rd_to_minSafeWaitTimeFactor) | `double` | Used to set a maximum wait-time cost, base on min-safe-transfer-time. | *Optional* | `5.0` | 2.1 | |    [optimizeTransferWaitTime](#rd_to_optimizeTransferWaitTime) | `boolean` | This enables the transfer wait time optimization. | *Optional* | `true` | 2.1 | +| [transitPriorityGroups](#rd_transitPriorityGroups) | `object` | Transit priority groups configuration | *Optional* | | 2.3 | | [transitReluctanceForMode](#rd_transitReluctanceForMode) | `enum map of double` | Transit reluctance for a given transport mode | *Optional* | | 2.1 | | [unpreferred](#rd_unpreferred) | `object` | Parameters listing authorities or lines that preferably should not be used in trip patters. | *Optional* | | 2.2 | |    [agencies](#rd_unpreferred_agencies) | `feed-scoped-id[]` | The ids of the agencies that incur an extra cost when being used. Format: `FeedId:AgencyId` | *Optional* | | 2.2 | @@ -789,6 +790,20 @@ This enables the transfer wait time optimization. If not enabled generalizedCost function is used to pick the optimal transfer point. +

transitPriorityGroups

+ +**Since version:** `2.3` ∙ **Type:** `object` ∙ **Cardinality:** `Optional` +**Path:** /routingDefaults + +Transit priority groups configuration + +Use this to group transit patterns into groups. Each group will be given priority +over others groups in the trip search. Hence; To paths with a different different +set of groups will BOTH be returned unless the cost is worse then the relaxation +specified in the `` parameter . + + +

transitReluctanceForMode

**Since version:** `2.1` ∙ **Type:** `enum map of double` ∙ **Cardinality:** `Optional` diff --git a/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java b/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java index b5c092e17fe..cab5fdf35b8 100644 --- a/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java +++ b/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java @@ -88,18 +88,16 @@ private BoardAndAlightSlack mapTransit() { setIfNotNull(req.otherThanPreferredRoutesPenalty, tr::setOtherThanPreferredRoutesPenalty); setIfNotNull(req.ignoreRealtimeUpdates, tr::setIgnoreRealtimeUpdates); - tr.withRaptor(r -> { - if (req.relaxTransitPriorityGroup != null) { - r.withTransitGroupPriorityGeneralizedCostSlack( - RelaxMapper.mapRelax(req.relaxTransitPriorityGroup) - ); - } else { - setIfNotNull( - req.relaxTransitSearchGeneralizedCostAtDestination, - r::withRelaxGeneralizedCostAtDestination - ); - } - }); + if (req.relaxTransitPriorityGroup != null) { + tr.withTransitGroupPriorityGeneralizedCostSlack( + RelaxMapper.mapRelax(req.relaxTransitPriorityGroup) + ); + } else { + setIfNotNull( + req.relaxTransitSearchGeneralizedCostAtDestination, + v -> tr.withRaptor(r -> r.withRelaxGeneralizedCostAtDestination(v)) + ); + } }); return new BoardAndAlightSlack( diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java index 3c09b4b1653..2e524883e38 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java @@ -115,10 +115,11 @@ private RaptorRequest doMap() { } builder.withMultiCriteria(mcBuilder -> { - var r = preferences.transit().raptor(); - if (!r.relaxTransitPriorityGroup().isNormal()) { + var pt = preferences.transit(); + var r = pt.raptor(); + if (pt.relaxTransitPriorityGroup().hasEffect()) { mcBuilder.withTransitPriorityCalculator(TransitPriorityGroup32n.priorityCalculator()); - mcBuilder.withRelaxC1(mapRelaxCost(r.relaxTransitPriorityGroup())); + mcBuilder.withRelaxC1(mapRelaxCost(pt.relaxTransitPriorityGroup())); } else { mcBuilder.withPassThroughPoints(mapPassThroughPoints()); r.relaxGeneralizedCostAtDestination().ifPresent(mcBuilder::withRelaxCostAtDestination); diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfigurator.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfigurator.java new file mode 100644 index 00000000000..f8ba2882325 --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfigurator.java @@ -0,0 +1,116 @@ +package org.opentripplanner.routing.algorithm.raptoradapter.transit.request; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Predicate; +import java.util.stream.Stream; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.grouppriority.TransitPriorityGroup32n; +import org.opentripplanner.routing.api.request.request.filter.TransitPriorityGroupSelect; +import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.model.network.RoutingTripPattern; + +/** + * This class dynamically build an index of transit-group-ids from the + * provided {@link TransitPriorityGroupSelect}s while serving the caller with + * group-ids for each requested pattern. It is made for optimal + * performance, since it is used in request scope. + *

+ * THIS CLASS IS NOT THREAD-SAFE. + */ +public class PriorityGroupConfigurator { + + private static final int BASE_GROUP_ID = TransitPriorityGroup32n.groupId(0); + private int groupIndexCounter = 0; + private final boolean enabled; + private final PriorityGroupMatcher[] baseMatchers; + private final PriorityGroupMatcher[] agencyMatchers; + private final PriorityGroupMatcher[] globalMatchers; + private final Map> agencyMatchersIds = new HashMap<>(); + private final Map globalMatchersIds = new HashMap<>(); + + private PriorityGroupConfigurator() { + this.enabled = false; + this.baseMatchers = null; + this.agencyMatchers = null; + this.globalMatchers = null; + } + + private PriorityGroupConfigurator( + Collection base, + Collection byAgency, + Collection global + ) { + this.baseMatchers = + base + .stream() + .map(PriorityGroupMatcher::of) + .filter(Predicate.not(PriorityGroupMatcher::isEmpty)) + .toArray(PriorityGroupMatcher[]::new); + this.agencyMatchers = + byAgency + .stream() + .map(PriorityGroupMatcher::of) + .filter(Predicate.not(PriorityGroupMatcher::isEmpty)) + .toArray(PriorityGroupMatcher[]::new); + this.globalMatchers = + global + .stream() + .map(PriorityGroupMatcher::of) + .filter(Predicate.not(PriorityGroupMatcher::isEmpty)) + .toArray(PriorityGroupMatcher[]::new); + this.enabled = + (baseMatchers.length > 0) || (agencyMatchers.length > 0) || (globalMatchers.length > 0); + } + + public static PriorityGroupConfigurator empty() { + return new PriorityGroupConfigurator(); + } + + public static PriorityGroupConfigurator of( + Collection base, + Collection byAgency, + Collection global + ) { + if (Stream.of(base, byAgency, global).allMatch(Collection::isEmpty)) { + return empty(); + } + return new PriorityGroupConfigurator(base, byAgency, global); + } + + /** + * Fetch/lookup the transit-group-id for the given pattern. + *

+ * @throws IllegalArgumentException if more than 32 group-ids are requested. + */ + public int lookupTransitPriorityGroupId(RoutingTripPattern tripPattern) { + if (!enabled) { + return BASE_GROUP_ID; + } + + var p = tripPattern.getPattern(); + + for (PriorityGroupMatcher m : baseMatchers) { + if (m.match(p)) { + return BASE_GROUP_ID; + } + } + for (var matcher : agencyMatchers) { + if (matcher.match(p)) { + var agencyIds = agencyMatchersIds.computeIfAbsent(matcher, m -> new HashMap<>()); + return agencyIds.computeIfAbsent(p.getRoute().getAgency().getId(), id -> nextGroupId()); + } + } + for (var matcher : globalMatchers) { + if (matcher.match(p)) { + return globalMatchersIds.computeIfAbsent(matcher, it -> nextGroupId()); + } + } + // Fallback to base-group-id + return BASE_GROUP_ID; + } + + private int nextGroupId() { + return TransitPriorityGroup32n.groupId(++groupIndexCounter); + } +} diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcher.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcher.java new file mode 100644 index 00000000000..d0bda5802df --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcher.java @@ -0,0 +1,152 @@ +package org.opentripplanner.routing.algorithm.raptoradapter.transit.request; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.regex.Pattern; +import org.opentripplanner.routing.api.request.request.filter.TransitPriorityGroupSelect; +import org.opentripplanner.transit.model.basic.TransitMode; +import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.model.network.TripPattern; + +/** + * This class turn a {@link TransitPriorityGroupSelect} into a matcher. + *

+ * Design: It uses the composite design pattern. A matcher is created for each + * value in the "select", then the list of none empty matchers are merged into + * a `CompositeMatcher`. So, a new matcher is only created if the field in the + * select is present. + */ +public abstract class PriorityGroupMatcher { + + private static final PriorityGroupMatcher NOOP = new PriorityGroupMatcher() { + @Override + boolean match(TripPattern pattern) { + return false; + } + + @Override + boolean isEmpty() { + return true; + } + }; + + public static PriorityGroupMatcher of(TransitPriorityGroupSelect select) { + if (select.isEmpty()) { + return NOOP; + } + List list = new ArrayList<>(); + + if (!select.modes().isEmpty()) { + list.add(new ModeMatcher(select.modes())); + } + if (!select.subModeRegexp().isEmpty()) { + list.add(new RegExpMatcher(select.subModeRegexp(), p -> p.getNetexSubmode().name())); + } + if (!select.agencyIds().isEmpty()) { + list.add(new IdMatcher(select.agencyIds(), p -> p.getRoute().getAgency().getId())); + } + if (!select.routeIds().isEmpty()) { + list.add(new IdMatcher(select.agencyIds(), p -> p.getRoute().getId())); + } + return compositeOf(list); + } + + private static PriorityGroupMatcher compositeOf(List list) { + // Remove empty/noop matchers + list = list.stream().filter(Predicate.not(PriorityGroupMatcher::isEmpty)).toList(); + + if (list.isEmpty()) { + return NOOP; + } + if (list.size() == 1) { + return list.get(0); + } + return new CompositeMatcher(list); + } + + abstract boolean match(TripPattern pattern); + + boolean isEmpty() { + return false; + } + + private static final class ModeMatcher extends PriorityGroupMatcher { + + private final Set modes; + + public ModeMatcher(List modes) { + this.modes = EnumSet.copyOf(modes); + } + + @Override + boolean match(TripPattern pattern) { + return modes.contains(pattern.getMode()); + } + } + + private static final class RegExpMatcher extends PriorityGroupMatcher { + + private final Pattern[] subModeRegexp; + private final Function toValue; + + public RegExpMatcher(List subModeRegexp, Function toValue) { + this.subModeRegexp = subModeRegexp.stream().map(Pattern::compile).toArray(Pattern[]::new); + this.toValue = toValue; + } + + @Override + boolean match(TripPattern pattern) { + var value = toValue.apply(pattern); + for (Pattern p : subModeRegexp) { + if (p.matcher(value).matches()) { + return true; + } + } + return false; + } + } + + private static final class IdMatcher extends PriorityGroupMatcher { + + private final Set ids; + private final Function idProvider; + + public IdMatcher(List ids, Function idProvider) { + this.ids = new HashSet<>(ids); + this.idProvider = idProvider; + } + + @Override + boolean match(TripPattern pattern) { + return ids.contains(idProvider.apply(pattern)); + } + } + + /** + * Take a list of matchers and provide a single interface. At least one matcher in the + * list must match for the composite mather to return a match. + */ + private static final class CompositeMatcher extends PriorityGroupMatcher { + + private final PriorityGroupMatcher[] matchers; + + public CompositeMatcher(List matchers) { + this.matchers = matchers.toArray(PriorityGroupMatcher[]::new); + } + + @Override + boolean match(TripPattern pattern) { + for (var m : matchers) { + if (m.match(pattern)) { + return true; + } + } + return false; + } + } +} diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java index a0b62414181..ba7ae70652f 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java @@ -91,7 +91,8 @@ public RaptorRoutingRequestTransitData( List tripPatterns = transitDataCreator.createTripPatterns( additionalPastSearchDays, additionalFutureSearchDays, - filter + filter, + createTransitPriorityGroupConfigurator(request) ); this.patternIndex = transitDataCreator.createPatternIndex(tripPatterns); this.activeTripPatternsPerStop = transitDataCreator.createTripPatternsPerStop(tripPatterns); @@ -241,4 +242,16 @@ public RaptorConstrainedBoardingSearch transferConstraintsReverseS } return new ConstrainedBoardingSearch(false, toStopTransfers, fromStopTransfers); } + + private PriorityGroupConfigurator createTransitPriorityGroupConfigurator(RouteRequest request) { + if (request.preferences().transit().relaxTransitPriorityGroup().hasNoEffect()) { + return PriorityGroupConfigurator.empty(); + } + var transitRequest = request.journey().transit(); + return PriorityGroupConfigurator.of( + transitRequest.priorityGroupsBase(), + transitRequest.priorityGroupsByAgency(), + transitRequest.priorityGroupsGlobal() + ); + } } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreator.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreator.java index 3aa7a6cf5ad..0cb155facd5 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreator.java @@ -92,7 +92,8 @@ public List createPatternIndex(List tr static List merge( ZonedDateTime transitSearchTimeZero, List patternForDateList, - TransitDataProviderFilter filter + TransitDataProviderFilter filter, + PriorityGroupConfigurator priorityGroupConfigurator ) { // Group TripPatternForDate objects by TripPattern. // This is done in a loop to increase performance. @@ -145,7 +146,8 @@ static List merge( tripPattern, tripPattern.getAlightingPossible(), BoardAlight.ALIGHT - ) + ), + priorityGroupConfigurator.lookupTransitPriorityGroupId(tripPattern) ) ); } @@ -156,7 +158,8 @@ static List merge( List createTripPatterns( int additionalPastSearchDays, int additionalFutureSearchDays, - TransitDataProviderFilter filter + TransitDataProviderFilter filter, + PriorityGroupConfigurator priorityGroupConfigurator ) { List tripPatternForDates = getTripPatternsForDateRange( additionalPastSearchDays, @@ -164,7 +167,7 @@ List createTripPatterns( filter ); - return merge(transitSearchTimeZero, tripPatternForDates, filter); + return merge(transitSearchTimeZero, tripPatternForDates, filter, priorityGroupConfigurator); } private static List filterActiveTripPatterns( diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripPatternForDates.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripPatternForDates.java index 215796aa2ef..0fb39b227ea 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripPatternForDates.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripPatternForDates.java @@ -60,18 +60,22 @@ public class TripPatternForDates private final BitSet boardingPossible; private final BitSet alightingPossible; + private final int priorityGroupId; + TripPatternForDates( RoutingTripPattern tripPattern, TripPatternForDate[] tripPatternForDates, int[] offsets, BitSet boardingPossible, - BitSet alightningPossible + BitSet alightningPossible, + int priorityGroupId ) { this.tripPattern = tripPattern; this.tripPatternForDates = tripPatternForDates; this.offsets = offsets; this.boardingPossible = boardingPossible; this.alightingPossible = alightningPossible; + this.priorityGroupId = priorityGroupId; int numberOfTripSchedules = 0; boolean hasFrequencies = false; @@ -173,8 +177,7 @@ public int slackIndex() { @Override public int priorityGroupId() { - // TODO C2 - Implement this. - throw new UnsupportedOperationException(); + return priorityGroupId; } public int transitReluctanceFactorIndex() { diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/RaptorPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/RaptorPreferences.java index 52f50044353..03ed9482ff8 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/RaptorPreferences.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/RaptorPreferences.java @@ -34,8 +34,6 @@ public final class RaptorPreferences implements Serializable { private final SearchDirection searchDirection; private final Instant timeLimit; - - private final Relax relaxTransitPriorityGroup; private final Double relaxGeneralizedCostAtDestination; private RaptorPreferences() { @@ -43,7 +41,6 @@ private RaptorPreferences() { this.profile = RaptorProfile.MULTI_CRITERIA; this.searchDirection = SearchDirection.FORWARD; this.timeLimit = null; - this.relaxTransitPriorityGroup = Relax.NORMAL; this.relaxGeneralizedCostAtDestination = null; } @@ -52,7 +49,6 @@ private RaptorPreferences(RaptorPreferences.Builder builder) { this.profile = Objects.requireNonNull(builder.profile); this.searchDirection = Objects.requireNonNull(builder.searchDirection); this.timeLimit = builder.timeLimit; - this.relaxTransitPriorityGroup = Objects.requireNonNull(builder.relaxTransitPriorityGroup); this.relaxGeneralizedCostAtDestination = Units.normalizedOptionalFactor( builder.relaxGeneralizedCostAtDestination, @@ -90,10 +86,6 @@ public Instant timeLimit() { return timeLimit; } - public Relax relaxTransitPriorityGroup() { - return relaxTransitPriorityGroup; - } - /** * See {@link SearchParams#relaxCostAtDestination()} for documentation. */ @@ -117,7 +109,6 @@ public boolean equals(Object o) { profile == that.profile && searchDirection == that.searchDirection && Objects.equals(timeLimit, that.timeLimit) && - Objects.equals(relaxTransitPriorityGroup, that.relaxTransitPriorityGroup) && Objects.equals(relaxGeneralizedCostAtDestination, that.relaxGeneralizedCostAtDestination) ); } @@ -129,7 +120,6 @@ public int hashCode() { profile, searchDirection, timeLimit, - relaxTransitPriorityGroup, relaxGeneralizedCostAtDestination ); } @@ -143,7 +133,6 @@ public String toString() { .addEnum("searchDirection", searchDirection, DEFAULT.searchDirection) // Ignore time limit if null (default value) .addDateTime("timeLimit", timeLimit) - .addObj("relaxTransitPriorityGroup", relaxTransitPriorityGroup, Relax.NORMAL) .addNum( "relaxGeneralizedCostAtDestination", relaxGeneralizedCostAtDestination, @@ -160,7 +149,6 @@ public static class Builder { private SearchDirection searchDirection; private Set optimizations; private Instant timeLimit; - private Relax relaxTransitPriorityGroup; private Double relaxGeneralizedCostAtDestination; public Builder(RaptorPreferences original) { @@ -169,7 +157,6 @@ public Builder(RaptorPreferences original) { this.searchDirection = original.searchDirection; this.optimizations = null; this.timeLimit = original.timeLimit; - this.relaxTransitPriorityGroup = original.relaxTransitPriorityGroup; this.relaxGeneralizedCostAtDestination = original.relaxGeneralizedCostAtDestination; } @@ -196,11 +183,6 @@ public Builder withTimeLimit(Instant timeLimit) { return this; } - public Builder withTransitGroupPriorityGeneralizedCostSlack(Relax value) { - this.relaxTransitPriorityGroup = value; - return this; - } - public Builder withRelaxGeneralizedCostAtDestination( @Nullable Double relaxGeneralizedCostAtDestination ) { diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/Relax.java b/src/main/java/org/opentripplanner/routing/api/request/preference/Relax.java index 7ee2346e87b..e137e322d68 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/Relax.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/Relax.java @@ -11,9 +11,25 @@ * @param slack the amount of slack to add to the value. */ public record Relax(double ratio, int slack) { + /** + * The "normal" will produce the same result: {@code f(x) == x } + */ public static final Relax NORMAL = new Relax(1.0, 0); - public boolean isNormal() { - return this.equals(NORMAL); + /** + * {@code true} if {@link #NORMAL}, this is the same as not applying the function. + *

+ * The relax operation should be skipped to save resources in this case, but it is + * safe to do it. + */ + public boolean hasNoEffect() { + return NORMAL.equals(this); + } + + /** + * Opposite of {@link #hasNoEffect()} + */ + public boolean hasEffect() { + return !hasNoEffect(); } } diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java index 7da4516d782..bfc578ad1b5 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java @@ -27,6 +27,7 @@ public final class TransitPreferences implements Serializable { private final Map reluctanceForMode; private final Cost otherThanPreferredRoutesPenalty; private final CostLinearFunction unpreferredCost; + private final Relax relaxTransitPriorityGroup; private final boolean ignoreRealtimeUpdates; private final boolean includePlannedCancellations; private final boolean includeRealtimeCancellations; @@ -37,6 +38,7 @@ private TransitPreferences() { this.reluctanceForMode = Map.of(); this.otherThanPreferredRoutesPenalty = Cost.costOfMinutes(5); this.unpreferredCost = CostLinearFunction.of(Duration.ZERO, 1.0); + this.relaxTransitPriorityGroup = Relax.NORMAL; this.ignoreRealtimeUpdates = false; this.includePlannedCancellations = false; this.includeRealtimeCancellations = false; @@ -49,6 +51,7 @@ private TransitPreferences(Builder builder) { this.reluctanceForMode = Map.copyOf(requireNonNull(builder.reluctanceForMode)); this.otherThanPreferredRoutesPenalty = builder.otherThanPreferredRoutesPenalty; this.unpreferredCost = requireNonNull(builder.unpreferredCost); + this.relaxTransitPriorityGroup = Objects.requireNonNull(builder.relaxTransitPriorityGroup); this.ignoreRealtimeUpdates = builder.ignoreRealtimeUpdates; this.includePlannedCancellations = builder.includePlannedCancellations; this.includeRealtimeCancellations = builder.includeRealtimeCancellations; @@ -125,6 +128,10 @@ public CostLinearFunction unpreferredCost() { return unpreferredCost; } + public Relax relaxTransitPriorityGroup() { + return relaxTransitPriorityGroup; + } + /** * When true, realtime updates are ignored during this search. */ @@ -159,14 +166,15 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; TransitPreferences that = (TransitPreferences) o; return ( - otherThanPreferredRoutesPenalty == that.otherThanPreferredRoutesPenalty && - ignoreRealtimeUpdates == that.ignoreRealtimeUpdates && - includePlannedCancellations == that.includePlannedCancellations && - includeRealtimeCancellations == that.includeRealtimeCancellations && boardSlack.equals(that.boardSlack) && alightSlack.equals(that.alightSlack) && reluctanceForMode.equals(that.reluctanceForMode) && + otherThanPreferredRoutesPenalty == that.otherThanPreferredRoutesPenalty && unpreferredCost.equals(that.unpreferredCost) && + Objects.equals(relaxTransitPriorityGroup, that.relaxTransitPriorityGroup) && + ignoreRealtimeUpdates == that.ignoreRealtimeUpdates && + includePlannedCancellations == that.includePlannedCancellations && + includeRealtimeCancellations == that.includeRealtimeCancellations && raptor.equals(that.raptor) ); } @@ -179,6 +187,7 @@ public int hashCode() { reluctanceForMode, otherThanPreferredRoutesPenalty, unpreferredCost, + relaxTransitPriorityGroup, ignoreRealtimeUpdates, includePlannedCancellations, includeRealtimeCancellations, @@ -199,6 +208,7 @@ public String toString() { DEFAULT.otherThanPreferredRoutesPenalty ) .addObj("unpreferredCost", unpreferredCost, DEFAULT.unpreferredCost) + .addObj("relaxTransitPriorityGroup", relaxTransitPriorityGroup, Relax.NORMAL) .addBoolIfTrue( "ignoreRealtimeUpdates", ignoreRealtimeUpdates != DEFAULT.ignoreRealtimeUpdates @@ -225,6 +235,7 @@ public static class Builder { private Map reluctanceForMode; private Cost otherThanPreferredRoutesPenalty; private CostLinearFunction unpreferredCost; + private Relax relaxTransitPriorityGroup; private boolean ignoreRealtimeUpdates; private boolean includePlannedCancellations; private boolean includeRealtimeCancellations; @@ -237,6 +248,7 @@ public Builder(TransitPreferences original) { this.reluctanceForMode = original.reluctanceForMode; this.otherThanPreferredRoutesPenalty = original.otherThanPreferredRoutesPenalty; this.unpreferredCost = original.unpreferredCost; + this.relaxTransitPriorityGroup = original.relaxTransitPriorityGroup; this.ignoreRealtimeUpdates = original.ignoreRealtimeUpdates; this.includePlannedCancellations = original.includePlannedCancellations; this.includeRealtimeCancellations = original.includeRealtimeCancellations; @@ -285,6 +297,11 @@ public Builder setUnpreferredCostString(String constFunction) { return setUnpreferredCost(CostLinearFunction.of(constFunction)); } + public Builder withTransitGroupPriorityGeneralizedCostSlack(Relax value) { + this.relaxTransitPriorityGroup = value; + return this; + } + public Builder setIgnoreRealtimeUpdates(boolean ignoreRealtimeUpdates) { this.ignoreRealtimeUpdates = ignoreRealtimeUpdates; return this; diff --git a/src/main/java/org/opentripplanner/routing/api/request/request/TransitRequest.java b/src/main/java/org/opentripplanner/routing/api/request/request/TransitRequest.java index 628b5753aea..35a0fabaafb 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/request/TransitRequest.java +++ b/src/main/java/org/opentripplanner/routing/api/request/request/TransitRequest.java @@ -2,11 +2,13 @@ import java.io.Serializable; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import org.opentripplanner.model.modes.ExcludeAllTransitFilter; import org.opentripplanner.routing.api.request.DebugRaptor; import org.opentripplanner.routing.api.request.request.filter.AllowAllTransitFilter; import org.opentripplanner.routing.api.request.request.filter.TransitFilter; +import org.opentripplanner.routing.api.request.request.filter.TransitPriorityGroupSelect; import org.opentripplanner.transit.model.framework.FeedScopedId; // TODO VIA: Javadoc @@ -28,6 +30,10 @@ public class TransitRequest implements Cloneable, Serializable { private List preferredRoutes = List.of(); private List unpreferredRoutes = List.of(); + + private List priorityGroupsBase = new ArrayList<>(); + private List priorityGroupsByAgency = new ArrayList<>(); + private List priorityGroupsGlobal = new ArrayList<>(); private DebugRaptor raptorDebugging = new DebugRaptor(); public void setBannedTripsFromString(String ids) { @@ -52,6 +58,49 @@ public void setFilters(List filters) { this.filters = filters; } + /** + * All transit patterns matching one of the {@link TransitPriorityGroupSelect}s is assigned the + * BASE-GROUP-ID. This is normally EVERYTHING including local-traffic, that does not + * need to be threaded in a special way. + *

+ * Note! Entities that do not mach any of the three sets({@code #priorityGroupsBase()}, + * {@link #priorityGroupsByAgency} and {@link #priorityGroupsGlobal()}) + * will also be put in this group. + */ + public List priorityGroupsBase() { + return priorityGroupsBase; + } + + public void addPriorityGroupsBase(Collection priorityGroupsBase) { + this.priorityGroupsBase.addAll(priorityGroupsBase); + } + + /** + * A unique group-id is assigned all patterns grouped by matching select and agency. + * In other words, two patterns matching the same select and with the same agency-id + * will get the same group-id. + */ + public List priorityGroupsByAgency() { + return priorityGroupsByAgency; + } + + /** + * All patterns matching the same select will be assigned the same group-id. + */ + public void addPriorityGroupsByAgency( + Collection priorityGroupsByAgency + ) { + this.priorityGroupsByAgency.addAll(priorityGroupsByAgency); + } + + public List priorityGroupsGlobal() { + return priorityGroupsGlobal; + } + + public void addPriorityGroupsGlobal(Collection priorityGroupsGlobal) { + this.priorityGroupsGlobal.addAll(priorityGroupsGlobal); + } + @Deprecated public void setPreferredAgenciesFromString(String s) { if (!s.isEmpty()) { @@ -147,6 +196,10 @@ public TransitRequest clone() { clone.preferredRoutes = List.copyOf(this.preferredRoutes); clone.unpreferredRoutes = List.copyOf(this.unpreferredRoutes); clone.raptorDebugging = new DebugRaptor(this.raptorDebugging); + clone.priorityGroupsBase = new ArrayList<>(this.priorityGroupsBase); + clone.priorityGroupsByAgency = new ArrayList<>(this.priorityGroupsByAgency); + clone.priorityGroupsGlobal = new ArrayList<>(this.priorityGroupsGlobal); + // filters are immutable clone.setFilters(this.filters); diff --git a/src/main/java/org/opentripplanner/routing/api/request/request/filter/TransitPriorityGroupSelect.java b/src/main/java/org/opentripplanner/routing/api/request/request/filter/TransitPriorityGroupSelect.java new file mode 100644 index 00000000000..6d763e9c3bc --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/api/request/request/filter/TransitPriorityGroupSelect.java @@ -0,0 +1,148 @@ +package org.opentripplanner.routing.api.request.request.filter; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import org.opentripplanner.framework.tostring.ToStringBuilder; +import org.opentripplanner.transit.model.basic.TransitMode; +import org.opentripplanner.transit.model.framework.FeedScopedId; + +/** + * Select a given set of transit routes base on the list of + * modes, sub-modes, agencies and routes. A transit entity matches + * if mode, sub-mode, agencyId or routeId matches - only one + * "thing" needs to match. + *

+ * The {@code TransitGroupSelect(modes:[BUS, TRAM], agencyIds:[A1, A3])} matches both: + *

    + *
  • {@code Entity(mode:BUS, agency:ANY)} and
  • + *
  • {@code Entity(mode:SUBWAY, agency:A3)}
  • + *
+ */ +public class TransitPriorityGroupSelect { + + private static final TransitPriorityGroupSelect DEFAULT = new TransitPriorityGroupSelect(); + + private final List modes; + private final List subModeRegexp; + private final List agencyIds; + private final List routeIds; + + public TransitPriorityGroupSelect() { + this.modes = List.of(); + this.subModeRegexp = List.of(); + this.agencyIds = List.of(); + this.routeIds = List.of(); + } + + private TransitPriorityGroupSelect(Builder builder) { + // Sort and keep only unique entries, this make this + // implementation consistent for eq/hc/toString. + this.modes = + List.copyOf( + builder.modes.stream().sorted(Comparator.comparingInt(Enum::ordinal)).distinct().toList() + ); + this.subModeRegexp = List.copyOf(builder.subModeRegexp.stream().sorted().distinct().toList()); + this.agencyIds = List.copyOf(builder.agencyIds.stream().sorted().distinct().toList()); + this.routeIds = List.copyOf(builder.routeIds.stream().sorted().distinct().toList()); + } + + public static Builder of() { + return new Builder(DEFAULT); + } + + public List modes() { + return modes; + } + + public List subModeRegexp() { + return subModeRegexp; + } + + public List agencyIds() { + return agencyIds; + } + + public List routeIds() { + return routeIds; + } + + public boolean isEmpty() { + return modes.isEmpty() && subModeRegexp.isEmpty() && agencyIds.isEmpty() && routeIds.isEmpty(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TransitPriorityGroupSelect that = (TransitPriorityGroupSelect) o; + return ( + Objects.equals(modes, that.modes) && + Objects.equals(subModeRegexp, that.subModeRegexp) && + Objects.equals(agencyIds, that.agencyIds) && + Objects.equals(routeIds, that.routeIds) + ); + } + + @Override + public int hashCode() { + return Objects.hash(modes, subModeRegexp, agencyIds, routeIds); + } + + @Override + public String toString() { + return isEmpty() + ? "TransitGroupSelect{ EMPTY }" + : ToStringBuilder + .of(TransitPriorityGroupSelect.class) + .addCol("modes", modes) + .addCol("subModeRegexp", subModeRegexp) + .addCol("agencyIds", agencyIds) + .addCol("routeIds", routeIds) + .toString(); + } + + public static class Builder { + + private final TransitPriorityGroupSelect original; + private final List modes; + private final List subModeRegexp; + private final List agencyIds; + private final List routeIds; + + public Builder(TransitPriorityGroupSelect original) { + this.original = original; + this.modes = new ArrayList<>(original.modes); + this.subModeRegexp = new ArrayList<>(original.subModeRegexp); + this.agencyIds = new ArrayList<>(original.agencyIds); + this.routeIds = new ArrayList<>(original.routeIds); + } + + public Builder addModes(Collection modes) { + this.modes.addAll(modes); + return this; + } + + public Builder addSubModeRegexp(Collection subModeRegexp) { + this.subModeRegexp.addAll(subModeRegexp); + return this; + } + + public Builder addAgencyIds(Collection agencyIds) { + this.agencyIds.addAll(agencyIds); + return this; + } + + public Builder addRouteIds(Collection routeIds) { + this.routeIds.addAll(routeIds); + return this; + } + + public TransitPriorityGroupSelect build() { + var obj = new TransitPriorityGroupSelect(this); + return original.equals(obj) ? original : obj; + } + } +} diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java index c89670b7e0a..9eb9cc72045 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java @@ -206,6 +206,8 @@ cost function. The cost function (`unpreferredCost`) is defined as a linear func .asFeedScopedIds(request.journey().transit().unpreferredAgencies()) ); + TransitPriorityGroupConfig.mapTransitRequest(c, request.journey().transit()); + // Map preferences request.withPreferences(preferences -> mapPreferences(c, request, preferences)); diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/TransitPriorityGroupConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/TransitPriorityGroupConfig.java new file mode 100644 index 00000000000..2b1f02e1c62 --- /dev/null +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/TransitPriorityGroupConfig.java @@ -0,0 +1,96 @@ +package org.opentripplanner.standalone.config.routerequest; + +import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_3; + +import java.util.Collection; +import java.util.List; +import org.opentripplanner.routing.api.request.request.TransitRequest; +import org.opentripplanner.routing.api.request.request.filter.TransitPriorityGroupSelect; +import org.opentripplanner.standalone.config.framework.json.NodeAdapter; +import org.opentripplanner.standalone.config.framework.json.OtpVersion; +import org.opentripplanner.transit.model.basic.TransitMode; + +public class TransitPriorityGroupConfig { + + public static void mapTransitRequest(NodeAdapter root, TransitRequest transit) { + var c = root + .of("transitPriorityGroups") + .since(OtpVersion.V2_3) + .summary("Transit priority groups configuration") + .description( + """ + Use this to group transit patterns into groups. Each group will be given priority + over others groups in the trip search. Hence; To paths with a different different + set of groups will BOTH be returned unless the cost is worse then the relaxation + specified in the `` parameter . + + """.stripIndent() + ) + .asObject(); + + transit.addPriorityGroupsBase( + TransitPriorityGroupConfig.mapList( + c, + "base", + "All groups in base get the same group-id(GROUP_ZERO). Normally you will put all " + + "local-traffic and other 'none-problematic' services here." + ) + ); + transit.addPriorityGroupsByAgency( + TransitPriorityGroupConfig.mapList( + c, + "byAgency", + "All groups here are split by agency. For example if you list mode " + + "[RAIL, COACH] then all rail and coach services run by an agency get the same " + + "group-id." + ) + ); + transit.addPriorityGroupsGlobal( + TransitPriorityGroupConfig.mapList( + c, + "global", + "All services matching a 'global' group will get the same group-id. Use this " + + "to assign the same id to a specific mode/sub-mode/route." + ) + ); + } + + private static Collection mapList( + NodeAdapter root, + String parameterName, + String description + ) { + return root + .of(parameterName) + .since(V2_3) + .summary("Configuration for transit priority groups.") + .description(description + " The max total number of group-ids are 32, so be careful.") + .asObjects(TransitPriorityGroupConfig::mapTransitGroupSelect); + } + + private static TransitPriorityGroupSelect mapTransitGroupSelect(NodeAdapter c) { + return TransitPriorityGroupSelect + .of() + .addModes( + c + .of("modes") + .since(V2_3) + .summary("List all modes to select for this group.") + .asEnumSet(TransitMode.class) + ) + .addSubModeRegexp( + c + .of("subModes") + .since(V2_3) + .summary("List a set of regular expressions for matching sub-modes.") + .asStringList(List.of()) + ) + .addAgencyIds( + c.of("agencies").since(V2_3).summary("List agency ids to match.").asFeedScopedIds(List.of()) + ) + .addRouteIds( + c.of("routes").since(V2_3).summary("List route ids to match.").asFeedScopedIds(List.of()) + ) + .build(); + } +} diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java index 60f2baa8e68..c6087aac47c 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java @@ -63,7 +63,8 @@ public void testMergeTripPatterns() { List combinedTripPatterns = RaptorRoutingRequestTransitDataCreator.merge( startOfTime, tripPatternsForDates, - new TestTransitDataProviderFilter() + new TestTransitDataProviderFilter(), + PriorityGroupConfigurator.empty() ); // Get the results diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TestRouteData.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TestRouteData.java index 2c14a8b0b9d..fc5ceb350c9 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TestRouteData.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TestRouteData.java @@ -71,7 +71,8 @@ public TestRouteData(String route, TransitMode mode, List stops, St }, new int[] { OFFSET }, null, - null + null, + 0 ); int id = 0; for (Trip trip : trips) { diff --git a/src/test/java/org/opentripplanner/routing/api/request/preference/RaptorPreferencesTest.java b/src/test/java/org/opentripplanner/routing/api/request/preference/RaptorPreferencesTest.java index aa9ac8eab42..2894e1a1ae0 100644 --- a/src/test/java/org/opentripplanner/routing/api/request/preference/RaptorPreferencesTest.java +++ b/src/test/java/org/opentripplanner/routing/api/request/preference/RaptorPreferencesTest.java @@ -28,7 +28,6 @@ class RaptorPreferencesTest { .atStartOfDay(ZoneIds.UTC) .toInstant(); - private static final Relax TRANSIT_GROUP_PRIORITY_RELAX = new Relax(1.5, 300); private static final double RELAX_GENERALIZED_COST_AT_DESTINATION = 1.2; private final RaptorPreferences subject = RaptorPreferences @@ -37,7 +36,6 @@ class RaptorPreferencesTest { .withProfile(PROFILE) .withOptimizations(OPTIMIZATIONS) .withTimeLimit(TIME_LIMIT) - .withTransitGroupPriorityGeneralizedCostSlack(TRANSIT_GROUP_PRIORITY_RELAX) .withRelaxGeneralizedCostAtDestination(RELAX_GENERALIZED_COST_AT_DESTINATION) .build(); @@ -134,7 +132,6 @@ void testToString() { "profile: STANDARD, " + "searchDirection: REVERSE, " + "timeLimit: 2020-06-09T00:00:00Z, " + - "relaxTransitPriorityGroup: Relax[ratio=1.5, slack=300], " + "relaxGeneralizedCostAtDestination: 1.2" + "}", subject.toString() diff --git a/src/test/java/org/opentripplanner/routing/api/request/preference/TransitPreferencesTest.java b/src/test/java/org/opentripplanner/routing/api/request/preference/TransitPreferencesTest.java index 0b4e383dfde..1a761dd05a9 100644 --- a/src/test/java/org/opentripplanner/routing/api/request/preference/TransitPreferencesTest.java +++ b/src/test/java/org/opentripplanner/routing/api/request/preference/TransitPreferencesTest.java @@ -26,6 +26,7 @@ class TransitPreferencesTest { private static final Duration D25m = Duration.ofMinutes(25); private static final Duration D35m = Duration.ofMinutes(35); private static final SearchDirection RAPTOR_SEARCH_DIRECTION = SearchDirection.REVERSE; + private static final Relax TRANSIT_GROUP_PRIORITY_RELAX = new Relax(1.5, 300); private static final boolean IGNORE_REALTIME_UPDATES = true; private static final boolean INCLUDE_PLANNED_CANCELLATIONS = true; private static final boolean INCLUDE_REALTIME_CANCELLATIONS = true; @@ -37,6 +38,7 @@ class TransitPreferencesTest { .setUnpreferredCost(UNPREFERRED_COST) .withBoardSlack(b -> b.withDefault(D45s).with(TransitMode.AIRPLANE, D35m)) .withAlightSlack(b -> b.withDefault(D15s).with(TransitMode.AIRPLANE, D25m)) + .withTransitGroupPriorityGeneralizedCostSlack(TRANSIT_GROUP_PRIORITY_RELAX) .setIgnoreRealtimeUpdates(IGNORE_REALTIME_UPDATES) .setIncludePlannedCancellations(INCLUDE_PLANNED_CANCELLATIONS) .setIncludeRealtimeCancellations(INCLUDE_REALTIME_CANCELLATIONS) @@ -70,6 +72,11 @@ void unpreferredCost() { assertEquals(UNPREFERRED_COST, subject.unpreferredCost()); } + @Test + void relaxTransitPriorityGroup() { + assertEquals(TRANSIT_GROUP_PRIORITY_RELAX, subject.relaxTransitPriorityGroup()); + } + @Test void ignoreRealtimeUpdates() { assertFalse(TransitPreferences.DEFAULT.ignoreRealtimeUpdates()); @@ -114,6 +121,7 @@ void testToString() { "reluctanceForMode: {AIRPLANE=2.1}, " + "otherThanPreferredRoutesPenalty: $350, " + "unpreferredCost: 5m + 1.15 t, " + + "relaxTransitPriorityGroup: Relax[ratio=1.5, slack=300], " + "ignoreRealtimeUpdates, " + "includePlannedCancellations, " + "includeRealtimeCancellations, " + From d3aa57ce7e8020221794870115c5845840073c22 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 23 Mar 2023 00:44:23 +0100 Subject: [PATCH 03/85] feature: Enable transit-priority-groups in the Transmodel GraphQL API. --- .../model/scalars/RelaxScalarFactoryTest.java | 32 ++++++++ src/ext/graphql/transmodelapi/schema.graphql | 29 ++++++- .../preferences/TransitPreferencesMapper.java | 4 + .../transmodelapi/model/plan/TripQuery.java | 25 +++++++ .../model/scalars/RelaxScalarFactory.java | 75 +++++++++++++++++++ .../raptor/api/request/RaptorRequest.java | 4 +- .../raptor/api/request/SearchParams.java | 13 +++- .../TransitPriorityGroup32n.java | 10 +++ 8 files changed, 185 insertions(+), 7 deletions(-) create mode 100644 src/ext-test/java/org/opentripplanner/ext/transmodelapi/model/scalars/RelaxScalarFactoryTest.java create mode 100644 src/ext/java/org/opentripplanner/ext/transmodelapi/model/scalars/RelaxScalarFactory.java diff --git a/src/ext-test/java/org/opentripplanner/ext/transmodelapi/model/scalars/RelaxScalarFactoryTest.java b/src/ext-test/java/org/opentripplanner/ext/transmodelapi/model/scalars/RelaxScalarFactoryTest.java new file mode 100644 index 00000000000..ee6976b016c --- /dev/null +++ b/src/ext-test/java/org/opentripplanner/ext/transmodelapi/model/scalars/RelaxScalarFactoryTest.java @@ -0,0 +1,32 @@ +package org.opentripplanner.ext.transmodelapi.model.scalars; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import graphql.schema.GraphQLScalarType; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.opentripplanner.routing.api.request.preference.Relax; + +class RelaxScalarFactoryTest { + + public static final Relax RELAX = new Relax(1.05, 300); + public static final String SER = "1.05 * x + 300"; + private static GraphQLScalarType subject; + + @BeforeAll + static void setup() { + subject = RelaxScalarFactory.createRelaxFunctionScalar(1.2, 3600); + } + + @Test + void serialize() { + var result = subject.getCoercing().serialize(RELAX); + assertEquals(SER, result); + } + + @Test + void parseValue() { + var result = subject.getCoercing().parseValue(SER); + assertEquals(RELAX, result); + } +} diff --git a/src/ext/graphql/transmodelapi/schema.graphql b/src/ext/graphql/transmodelapi/schema.graphql index 474da1e34d7..f92dc927488 100644 --- a/src/ext/graphql/transmodelapi/schema.graphql +++ b/src/ext/graphql/transmodelapi/schema.graphql @@ -824,6 +824,21 @@ type QueryType { "The list of points the journey is required to pass through." passThroughPoints: [PassThroughPoint!], """ + Relax generalized-cost when comparing trips with a different set of + transit-priority-groups. The groups are set server side for service-journey and + can not be configured in the API. This mainly help return competition neutral + services. Long distance authorities are put in different transit-priority-groups. + + This relax the comparison inside the routing engine for each stop-arrival. If two + paths have a different set of transit-priority-groups, then the generalized-cost + comparison is relaxed. The final set of paths are filtered through the normal + itinerary-filters. + + - The `ratio` must be greater or equal to 1.0 and less then 1.2. + - The `slack` must be greater or equal to 0 and less then 3600. + """ + relaxTransitPriorityGroup: RelaxFunction, + """ Whether non-optimal transit paths at the destination should be returned. Let c be the existing minimum pareto optimal generalized-cost to beat. Then a trip with cost c' is accepted if the following is true: @@ -835,7 +850,7 @@ type QueryType { Values less than 1.0 is not allowed, and values greater than 2.0 are not supported, due to performance reasons. """ - relaxTransitSearchGeneralizedCostAtDestination: Float = null, + relaxTransitSearchGeneralizedCostAtDestination: Float = null @deprecated(reason : "This is replaced by 'relaxTransitPriorityGroup'."), """ The length of the search-window in minutes. This parameter is optional. @@ -1879,6 +1894,18 @@ scalar LocalTime "A 64-bit signed integer" scalar Long +""" +A linear function f(x) to calculate a relaxed value based on a parameter `x`: +``` +f(x) = A * x + C +``` +Pass in a ratio A and a slack C for use in the computation. The `x` and `+` sign +are required, but not the `*`. + +Example: `1.05 * x + 1800` +""" +scalar RelaxFunction + "Time using the format: `HH:MM:SS`. Example: `18:25:43`" scalar Time diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/TransitPreferencesMapper.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/TransitPreferencesMapper.java index d7fe4e254bc..8a03b570292 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/TransitPreferencesMapper.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/TransitPreferencesMapper.java @@ -33,6 +33,10 @@ public static void mapTransitPreferences( callWith.argument("ignoreRealtimeUpdates", transit::setIgnoreRealtimeUpdates); callWith.argument("includePlannedCancellations", transit::setIncludePlannedCancellations); callWith.argument("includeRealtimeCancellations", transit::setIncludeRealtimeCancellations); + callWith.argument( + "relaxTransitPriorityGroup", + transit::withTransitGroupPriorityGeneralizedCostSlack + ); callWith.argument( "relaxTransitSearchGeneralizedCostAtDestination", (Double value) -> transit.withRaptor(it -> it.withRelaxGeneralizedCostAtDestination(value)) diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java index 8fa4fa31512..924c66e9b23 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java @@ -17,6 +17,7 @@ import org.opentripplanner.ext.transmodelapi.model.framework.LocationInputType; import org.opentripplanner.ext.transmodelapi.model.framework.PassThroughPointInputType; import org.opentripplanner.ext.transmodelapi.model.framework.PenaltyForStreetModeType; +import org.opentripplanner.ext.transmodelapi.model.scalars.RelaxScalarFactory; import org.opentripplanner.ext.transmodelapi.support.GqlUtil; import org.opentripplanner.routing.api.request.preference.RoutingPreferences; import org.opentripplanner.routing.core.BicycleOptimizeType; @@ -273,6 +274,29 @@ public static GraphQLFieldDefinition create( .type(new GraphQLList(new GraphQLNonNull(FilterInputType.INPUT_TYPE))) .build() ) + .argument( + GraphQLArgument + .newArgument() + .name("relaxTransitPriorityGroup") + .description( + """ + Relax generalized-cost when comparing trips with a different set of + transit-priority-groups. The groups are set server side for service-journey and + can not be configured in the API. This mainly help return competition neutral + services. Long distance authorities are put in different transit-priority-groups. + + This relax the comparison inside the routing engine for each stop-arrival. If two + paths have a different set of transit-priority-groups, then the generalized-cost + comparison is relaxed. The final set of paths are filtered through the normal + itinerary-filters. + + - The `ratio` must be greater or equal to 1.0 and less then 1.2. + - The `slack` must be greater or equal to 0 and less then 3600. + """.stripIndent() + ) + .type(RelaxScalarFactory.createRelaxFunctionScalar(1.2, 3600)) + .build() + ) .argument( GraphQLArgument .newArgument() @@ -491,6 +515,7 @@ public static GraphQLFieldDefinition create( GraphQLArgument .newArgument() .name("relaxTransitSearchGeneralizedCostAtDestination") + .deprecate("This is replaced by 'relaxTransitPriorityGroup'.") .description( """ Whether non-optimal transit paths at the destination should be returned. Let c be the diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/scalars/RelaxScalarFactory.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/scalars/RelaxScalarFactory.java new file mode 100644 index 00000000000..d2b4b8f6269 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/scalars/RelaxScalarFactory.java @@ -0,0 +1,75 @@ +package org.opentripplanner.ext.transmodelapi.model.scalars; + +import graphql.language.StringValue; +import graphql.schema.Coercing; +import graphql.schema.CoercingParseLiteralException; +import graphql.schema.CoercingParseValueException; +import graphql.schema.GraphQLScalarType; +import java.util.Locale; +import org.opentripplanner.api.mapping.RelaxMapper; +import org.opentripplanner.routing.api.request.preference.Relax; + +public class RelaxScalarFactory { + + private static final String DOCUMENTATION = + """ + A linear function f(x) to calculate a relaxed value based on a parameter `x`: + ``` + f(x) = A * x + C + ``` + Pass in a ratio A and a slack C for use in the computation. The `x` and `+` sign + are required, but not the `*`. + + Example: `1.05 * x + 1800` + """; + + private RelaxScalarFactory() {} + + public static GraphQLScalarType createRelaxFunctionScalar( + final double maxRatio, + final int maxSlack + ) { + return GraphQLScalarType + .newScalar() + .name("RelaxFunction") + .description(DOCUMENTATION) + .coercing( + new Coercing() { + @Override + public String serialize(Object dataFetcherResult) { + var r = (Relax) (dataFetcherResult); + return String.format(Locale.ROOT, "%.2f * x + %d", r.ratio(), r.slack()); + } + + @Override + public Relax parseValue(Object input) throws CoercingParseValueException { + try { + Relax relax = RelaxMapper.mapRelax((String) input); + if (relax.ratio() < 1.0 || relax.ratio() > maxRatio) { + throw new CoercingParseValueException( + "Ratio %f is not in range: [1.0 .. %.1f]".formatted(relax.ratio(), maxRatio) + ); + } + if (relax.slack() < 0.0 || relax.slack() > maxSlack) { + throw new CoercingParseValueException( + "Ratio %f is not in range: [0 .. %d]".formatted(relax.ratio(), maxSlack) + ); + } + return relax; + } catch (IllegalArgumentException e) { + throw new CoercingParseValueException(e.getMessage(), e); + } + } + + @Override + public Relax parseLiteral(Object input) throws CoercingParseLiteralException { + if (input instanceof StringValue) { + return parseValue(((StringValue) input).getValue()); + } + return null; + } + } + ) + .build(); + } +} diff --git a/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java b/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java index 1f0621b8d36..010bff4bc08 100644 --- a/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java +++ b/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java @@ -183,10 +183,10 @@ public String toString() { .of(RaptorRequest.class) .addEnum("profile", profile) .addBoolIfTrue("reverse", searchDirection.isInReverse()) - .addCol("optimizations", optimizations) + .addCol("optimizations", optimizations, defaults.optimizations()) + .addObj("searchParams", searchParams) .addObj("multiCriteria", multiCriteria, defaults.multiCriteria()) .addObj("debug", debug, defaults.debug()) - .addObj("searchParams", searchParams) .addBoolIfTrue("withPerformanceTimers", performanceTimers != RaptorTimers.NOOP) .toString(); } diff --git a/src/main/java/org/opentripplanner/raptor/api/request/SearchParams.java b/src/main/java/org/opentripplanner/raptor/api/request/SearchParams.java index e009897557c..4f11e6c164f 100644 --- a/src/main/java/org/opentripplanner/raptor/api/request/SearchParams.java +++ b/src/main/java/org/opentripplanner/raptor/api/request/SearchParams.java @@ -281,13 +281,18 @@ public boolean equals(Object o) { @Override public String toString() { + var dft = defaults(); return ToStringBuilder .of(SearchParams.class) - .addServiceTime("earliestDepartureTime", earliestDepartureTime, RaptorConstants.TIME_NOT_SET) - .addServiceTime("latestArrivalTime", latestArrivalTime, RaptorConstants.TIME_NOT_SET) - .addDurationSec("searchWindow", searchWindowInSeconds) + .addServiceTime("earliestDepartureTime", earliestDepartureTime, dft.earliestDepartureTime) + .addServiceTime("latestArrivalTime", latestArrivalTime, dft.latestArrivalTime) + .addDurationSec("searchWindow", searchWindowInSeconds, dft.searchWindowInSeconds) .addBoolIfTrue("departAsLateAsPossible", preferLateArrival) - .addNum("numberOfAdditionalTransfers", numberOfAdditionalTransfers) + .addNum( + "numberOfAdditionalTransfers", + numberOfAdditionalTransfers, + dft.numberOfAdditionalTransfers + ) .addCollection("accessPaths", accessPaths, 5) .addCollection("egressPaths", egressPaths, 5) .toString(); diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitPriorityGroup32n.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitPriorityGroup32n.java index 7032796792b..076274b9cc5 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitPriorityGroup32n.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitPriorityGroup32n.java @@ -24,6 +24,11 @@ public int mergeTransitPriorityGroupIds(int currentGroupIds, int boardingGroupId public DominanceFunction dominanceFunction() { return TransitPriorityGroup32n::dominate; } + + @Override + public String toString() { + return "TransitPriorityGroup32nCalculator{}"; + } }; } @@ -32,6 +37,11 @@ public static boolean dominate(int left, int right) { return left != right; } + @Override + public String toString() { + return "TransitPriorityGroup32n{}"; + } + /** * Use this method to map from a continuous group index [0..32) to the groupId used * during routing. The ID is implementation specific and optimized for performance. From 1a489af74b8ee687ef56d8750310c826a13a198d Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 9 May 2023 15:12:30 +0200 Subject: [PATCH 04/85] fix: Fix the dominance function for TransitPriorityGroup32n --- .../cost/grouppriority/TransitPriorityGroup32n.java | 7 +++++-- .../cost/grouppriority/TransitPriorityGroup32nTest.java | 7 +++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitPriorityGroup32n.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitPriorityGroup32n.java index 076274b9cc5..befea6a511d 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitPriorityGroup32n.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitPriorityGroup32n.java @@ -32,9 +32,12 @@ public String toString() { }; } - /** To groups dominate each other if they are different. */ + /** + * Left dominate right, if right contains a group witch does not exist in left. Left + * do NOT dominate right if they are equals or left is a super set of right. + */ public static boolean dominate(int left, int right) { - return left != right; + return ((left ^ right) & right) != 0; } @Override diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitPriorityGroup32nTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitPriorityGroup32nTest.java index 2f90553e22c..7b989c4d6dd 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitPriorityGroup32nTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitPriorityGroup32nTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; import org.opentripplanner.raptor.api.request.RaptorTransitPriorityGroupCalculator; @@ -51,6 +52,12 @@ void dominanceFunction() { assertFalse(subjct.dominanceFunction().leftDominateRight(GROUP_0, GROUP_0)); assertFalse(subjct.dominanceFunction().leftDominateRight(GROUP_31, GROUP_31)); assertFalse(subjct.dominanceFunction().leftDominateRight(GROUP_1 | GROUP_2, GROUP_1 | GROUP_2)); + + assertTrue(subjct.dominanceFunction().leftDominateRight(GROUP_0, GROUP_1)); + assertFalse(subjct.dominanceFunction().leftDominateRight(GROUP_1, GROUP_0)); + + assertTrue(subjct.dominanceFunction().leftDominateRight(GROUP_1, GROUP_1 | GROUP_2)); + assertFalse(subjct.dominanceFunction().leftDominateRight(GROUP_1 | GROUP_2, GROUP_1)); } static void assertEqualsHex(int expected, int actual) { From 50c5709b7404e4e3f486d61483b3f51d49cd5518 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 2 Nov 2023 23:14:54 +0100 Subject: [PATCH 05/85] refactor: Improve IntUnits requireX functions --- .../framework/lang/IntUtils.java | 29 +++++++++--- .../framework/lang/IntUtilsTest.java | 44 ++++++++++++++++--- .../framework/model/UnitsTest.java | 2 +- 3 files changed, 60 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/opentripplanner/framework/lang/IntUtils.java b/src/main/java/org/opentripplanner/framework/lang/IntUtils.java index 01040bf0f93..bcbdae7754c 100644 --- a/src/main/java/org/opentripplanner/framework/lang/IntUtils.java +++ b/src/main/java/org/opentripplanner/framework/lang/IntUtils.java @@ -4,6 +4,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; +import javax.annotation.Nullable; /** * A utility class for integer functions. @@ -86,23 +87,37 @@ public static double standardDeviation(List v) { return Math.sqrt(sum / v.size()); } - public static int requireNotNegative(int value) { - if (value < 0) { - throw new IllegalArgumentException("Negative value not expected: " + value); + /** + * Check is given {@code value} is in range {@code [min .. max]}. Both {@code min} and + * {@code max} is inclusive. Throws a {@link IllegalArgumentException} if not in range. + */ + public static int requireInRange(int value, int min, int max, String field) { + if (value < min || value > max) { + throw new IllegalArgumentException( + "The %s is not in range[%d, %d]: %d".formatted(field(field), min, max, value) + ); } return value; } public static int requireInRange(int value, int minInclusive, int maxInclusive) { - return requireInRange(value, minInclusive, maxInclusive, "value"); + return requireInRange(value, minInclusive, maxInclusive, null); } - public static int requireInRange(int value, int minInclusive, int maxInclusive, String field) { - if (value < minInclusive || value > maxInclusive) { + public static int requireNotNegative(int value, String field) { + if (value < 0) { throw new IllegalArgumentException( - "The %s is not in range[%d, %d]: %d".formatted(field, minInclusive, maxInclusive, value) + "Negative value not expected for %s: %d".formatted(field(field), value) ); } return value; } + + public static int requireNotNegative(int value) { + return requireNotNegative(value, null); + } + + private static String field(@Nullable String field) { + return field == null ? "value" : '\'' + field + '\''; + } } diff --git a/src/test/java/org/opentripplanner/framework/lang/IntUtilsTest.java b/src/test/java/org/opentripplanner/framework/lang/IntUtilsTest.java index 09de5ab62ad..aa15e806fe6 100644 --- a/src/test/java/org/opentripplanner/framework/lang/IntUtilsTest.java +++ b/src/test/java/org/opentripplanner/framework/lang/IntUtilsTest.java @@ -35,6 +35,28 @@ void testIntArray() { assertArrayEquals(new int[] { 5, 5, 5 }, intArray(3, 5)); } + @Test + void testAssertInRange() { + IntUtils.requireInRange(1, 1, 1, "single-element-range"); + IntUtils.requireInRange(-2, -2, 1, "negative-start"); + IntUtils.requireInRange(-1, -2, -1, "negative-end"); + assertThrows( + IllegalArgumentException.class, + () -> IntUtils.requireInRange(1, 2, 1, "invalid-range") + ); + var ex = assertThrows( + IllegalArgumentException.class, + () -> IntUtils.requireInRange(1, 2, 3, "value-too-small") + ); + assertEquals("The 'value-too-small' is not in range[2, 3]: 1", ex.getMessage()); + ex = + assertThrows( + IllegalArgumentException.class, + () -> IntUtils.requireInRange(4, 0, 3, "value-too-big") + ); + assertEquals("The 'value-too-big' is not in range[0, 3]: 4", ex.getMessage()); + } + @Test public void testRound() { assertEquals(0, IntUtils.round(0.499)); @@ -67,11 +89,17 @@ void sestStandardDeviation() { @Test void testRequireNotNegative() { // OK - assertEquals(7, requireNotNegative(7)); - assertEquals(0, requireNotNegative(0)); + assertEquals(7, requireNotNegative(7, "ok")); + assertEquals(0, requireNotNegative(0, "ok")); - var ex = assertThrows(IllegalArgumentException.class, () -> requireNotNegative(-1)); - assertEquals("Negative value not expected: -1", ex.getMessage()); + var ex = assertThrows( + IllegalArgumentException.class, + () -> requireNotNegative(-1, "too-small") + ); + assertEquals("Negative value not expected for 'too-small': -1", ex.getMessage()); + + ex = assertThrows(IllegalArgumentException.class, () -> requireNotNegative(-1)); + assertEquals("Negative value not expected for value: -1", ex.getMessage()); } @Test @@ -80,9 +108,11 @@ void testRequireInRange() { assertEquals(5, requireInRange(5, 5, 5)); // Too small - assertThrows(IllegalArgumentException.class, () -> requireInRange(5, 6, 10)); + var ex = assertThrows(IllegalArgumentException.class, () -> requireInRange(5, 6, 10)); + assertEquals("The value is not in range[6, 10]: 5", ex.getMessage()); + // Too big - var ex = assertThrows(IllegalArgumentException.class, () -> requireInRange(5, 1, 4, "cost")); - assertEquals("The cost is not in range[1, 4]: 5", ex.getMessage()); + ex = assertThrows(IllegalArgumentException.class, () -> requireInRange(5, 1, 4, "cost")); + assertEquals("The 'cost' is not in range[1, 4]: 5", ex.getMessage()); } } diff --git a/src/test/java/org/opentripplanner/framework/model/UnitsTest.java b/src/test/java/org/opentripplanner/framework/model/UnitsTest.java index c1aaecfb1a0..818b9b7df16 100644 --- a/src/test/java/org/opentripplanner/framework/model/UnitsTest.java +++ b/src/test/java/org/opentripplanner/framework/model/UnitsTest.java @@ -39,7 +39,7 @@ void duration() { assertEquals(0, Units.duration(0)); assertEquals(10_000, Units.duration(10_000)); var ex = assertThrows(IllegalArgumentException.class, () -> Units.duration(-1)); - assertEquals("Negative value not expected: -1", ex.getMessage()); + assertEquals("Negative value not expected for value: -1", ex.getMessage()); } @Test From 8104d162c748b64b8ee30ee67f0d6aeb09617110 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 3 Nov 2023 12:24:50 +0100 Subject: [PATCH 06/85] refactor: Get rid of RelaxCost type --- docs/RouteRequest.md | 13 ++++ .../model/scalars/RelaxScalarFactoryTest.java | 32 -------- src/ext/graphql/transmodelapi/schema.graphql | 32 ++++---- .../model/plan/RelaxCostType.java | 71 ++++++++++++++++++ .../transmodelapi/model/plan/TripQuery.java | 13 +++- .../model/scalars/RelaxScalarFactory.java | 75 ------------------- .../common/RequestToPreferencesMapper.java | 3 +- .../api/mapping/RelaxMapper.java | 5 ++ .../transit/mappers/RaptorRequestMapper.java | 10 +-- .../RaptorRoutingRequestTransitData.java | 2 +- .../framework/AbstractLinearFunction.java | 7 ++ .../request/framework/CostLinearFunction.java | 8 ++ .../routing/api/request/preference/Relax.java | 15 ++++ .../preference/TransitPreferences.java | 21 ++++-- .../routerequest/RouteRequestConfig.java | 43 ++++++++--- .../api/mapping/RelaxMapperTest.java | 27 ------- .../mappers/RaptorRequestMapperTest.java | 14 ++-- .../api/request/preference/RelaxTest.java | 48 ++++++++++++ .../preference/TransitPreferencesTest.java | 8 +- 19 files changed, 261 insertions(+), 186 deletions(-) delete mode 100644 src/ext-test/java/org/opentripplanner/ext/transmodelapi/model/scalars/RelaxScalarFactoryTest.java create mode 100644 src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/RelaxCostType.java delete mode 100644 src/ext/java/org/opentripplanner/ext/transmodelapi/model/scalars/RelaxScalarFactory.java delete mode 100644 src/test/java/org/opentripplanner/api/mapping/RelaxMapperTest.java create mode 100644 src/test/java/org/opentripplanner/routing/api/request/preference/RelaxTest.java diff --git a/docs/RouteRequest.md b/docs/RouteRequest.md index fb2d567ad68..3d420e5922a 100644 --- a/docs/RouteRequest.md +++ b/docs/RouteRequest.md @@ -57,6 +57,7 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe | numItineraries | `integer` | The maximum number of itineraries to return. | *Optional* | `50` | 2.0 | | [optimize](#rd_optimize) | `enum` | The set of characteristics that the user wants to optimize for. | *Optional* | `"safe"` | 2.0 | | [otherThanPreferredRoutesPenalty](#rd_otherThanPreferredRoutesPenalty) | `integer` | Penalty added for using every route that is not preferred if user set any route as preferred. | *Optional* | `300` | 2.0 | +| [relaxTransitPriorityGroup](#rd_relaxTransitPriorityGroup) | `string` | The relax function for transit-priority-groups | *Optional* | `"0s + 1.00 t"` | 2.3 | | [relaxTransitSearchGeneralizedCostAtDestination](#rd_relaxTransitSearchGeneralizedCostAtDestination) | `double` | Whether non-optimal transit paths at the destination should be returned | *Optional* | | 2.3 | | [searchWindow](#rd_searchWindow) | `duration` | The duration of the search-window. | *Optional* | | 2.0 | | stairsReluctance | `double` | Used instead of walkReluctance for stairs. | *Optional* | `2.0` | 2.0 | @@ -246,6 +247,18 @@ Penalty added for using every route that is not preferred if user set any route We return number of seconds that we are willing to wait for preferred route. +

relaxTransitPriorityGroup

+ +**Since version:** `2.3` ∙ **Type:** `string` ∙ **Cardinality:** `Optional` ∙ **Default value:** `"0s + 1.00 t"` +**Path:** /routingDefaults + +The relax function for transit-priority-groups + +A path is considered optimal if it the generalized-cost is smaller than an other the +generalized-cost of another path. If this parameter is set, the comparison is relaxed +further if they belong to different transit-priority-groups. + +

relaxTransitSearchGeneralizedCostAtDestination

**Since version:** `2.3` ∙ **Type:** `double` ∙ **Cardinality:** `Optional` diff --git a/src/ext-test/java/org/opentripplanner/ext/transmodelapi/model/scalars/RelaxScalarFactoryTest.java b/src/ext-test/java/org/opentripplanner/ext/transmodelapi/model/scalars/RelaxScalarFactoryTest.java deleted file mode 100644 index ee6976b016c..00000000000 --- a/src/ext-test/java/org/opentripplanner/ext/transmodelapi/model/scalars/RelaxScalarFactoryTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.opentripplanner.ext.transmodelapi.model.scalars; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import graphql.schema.GraphQLScalarType; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.opentripplanner.routing.api.request.preference.Relax; - -class RelaxScalarFactoryTest { - - public static final Relax RELAX = new Relax(1.05, 300); - public static final String SER = "1.05 * x + 300"; - private static GraphQLScalarType subject; - - @BeforeAll - static void setup() { - subject = RelaxScalarFactory.createRelaxFunctionScalar(1.2, 3600); - } - - @Test - void serialize() { - var result = subject.getCoercing().serialize(RELAX); - assertEquals(SER, result); - } - - @Test - void parseValue() { - var result = subject.getCoercing().parseValue(SER); - assertEquals(RELAX, result); - } -} diff --git a/src/ext/graphql/transmodelapi/schema.graphql b/src/ext/graphql/transmodelapi/schema.graphql index f92dc927488..38bd44ffe27 100644 --- a/src/ext/graphql/transmodelapi/schema.graphql +++ b/src/ext/graphql/transmodelapi/schema.graphql @@ -836,8 +836,10 @@ type QueryType { - The `ratio` must be greater or equal to 1.0 and less then 1.2. - The `slack` must be greater or equal to 0 and less then 3600. + + THIS IS STILL AN EXPERIMENTAL FEATURE - IT MAY CHANGE WITHOUT ANY NOTICE! """ - relaxTransitPriorityGroup: RelaxFunction, + relaxTransitPriorityGroup: RelaxCostInput = null, """ Whether non-optimal transit paths at the destination should be returned. Let c be the existing minimum pareto optimal generalized-cost to beat. Then a trip with cost c' is @@ -1894,18 +1896,6 @@ scalar LocalTime "A 64-bit signed integer" scalar Long -""" -A linear function f(x) to calculate a relaxed value based on a parameter `x`: -``` -f(x) = A * x + C -``` -Pass in a ratio A and a slack C for use in the computation. The `x` and `+` sign -are required, but not the `*`. - -Example: `1.05 * x + 1800` -""" -scalar RelaxFunction - "Time using the format: `HH:MM:SS`. Example: `18:25:43`" scalar Time @@ -2028,6 +2018,22 @@ input PenaltyForStreetMode { timePenalty: DoubleFunction! } +""" +A relax-cost is used to increase the limit when comparing one cost to another cost. +This is used to include more results into the result. A `ratio=2.0` means a path(itinerary) +with twice as high cost as another one, is accepted. A `constant=$300` means a "fixed" +constant is added to the limit. A `{ratio=1.0, constant=0}` is said to be the NORMAL relaxed +cost - the limit is the same as the cost used to calculate the limit. The NORMAL is usually +the default. We can express the RelaxCost as a function `f(x) = constant + ratio * x`. +`f(x)=x` is the NORMAL function. +""" +input RelaxCostInput { + "The constant value to add to the limit. Must be a positive number. The unit is cost-seconds." + constant: [ID!] = 0 + "The factor to multiply with the 'other cost'. Minimum value is 1.0." + ratio: Float = 1.0 +} + "A combination of street mode and corresponding duration" input StreetModeDurationInput { duration: Duration! diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/RelaxCostType.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/RelaxCostType.java new file mode 100644 index 00000000000..b8cdc317cda --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/RelaxCostType.java @@ -0,0 +1,71 @@ +package org.opentripplanner.ext.transmodelapi.model.plan; + +import graphql.Scalars; +import graphql.language.FloatValue; +import graphql.language.IntValue; +import graphql.language.ObjectField; +import graphql.language.ObjectValue; +import graphql.schema.GraphQLInputObjectField; +import graphql.schema.GraphQLInputObjectType; +import graphql.schema.GraphQLList; +import graphql.schema.GraphQLNonNull; +import org.opentripplanner.routing.api.request.framework.CostLinearFunction; + +public class RelaxCostType { + + public static final String RATIO = "ratio"; + public static final String CONSTANT = "constant"; + + static final GraphQLInputObjectType INPUT_TYPE = GraphQLInputObjectType + .newInputObject() + .name("RelaxCostInput") + .description( + """ + A relax-cost is used to increase the limit when comparing one cost to another cost. + This is used to include more results into the result. A `ratio=2.0` means a path(itinerary) + with twice as high cost as another one, is accepted. A `constant=$300` means a "fixed" + constant is added to the limit. A `{ratio=1.0, constant=0}` is said to be the NORMAL relaxed + cost - the limit is the same as the cost used to calculate the limit. The NORMAL is usually + the default. We can express the RelaxCost as a function `f(x) = constant + ratio * x`. + `f(x)=x` is the NORMAL function. + """ + ) + .field( + GraphQLInputObjectField + .newInputObjectField() + .name(RATIO) + .description("The factor to multiply with the 'other cost'. Minimum value is 1.0.") + .defaultValueLiteral(FloatValue.of(1.0)) + .type(Scalars.GraphQLFloat) + .build() + ) + .field( + GraphQLInputObjectField + .newInputObjectField() + .name(CONSTANT) + .description( + "The constant value to add to the limit. Must be a positive number. The unit" + + " is cost-seconds." + ) + .defaultValueLiteral(IntValue.of(0)) + .type(new GraphQLList(new GraphQLNonNull(Scalars.GraphQLID))) + .build() + ) + .build(); + + public static ObjectValue valueOf(CostLinearFunction value) { + return ObjectValue + .newObjectValue() + .objectField( + ObjectField.newObjectField().name(RATIO).value(FloatValue.of(value.coefficient())).build() + ) + .objectField( + ObjectField + .newObjectField() + .name(CONSTANT) + .value(IntValue.of(value.constant().toSeconds())) + .build() + ) + .build(); + } +} diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java index 924c66e9b23..073de2e9003 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java @@ -3,6 +3,7 @@ import static org.opentripplanner.ext.transmodelapi.model.framework.StreetModeDurationInputType.mapDurationForStreetModeGraphQLValue; import graphql.Scalars; +import graphql.language.NullValue; import graphql.schema.GraphQLArgument; import graphql.schema.GraphQLEnumType; import graphql.schema.GraphQLFieldDefinition; @@ -17,7 +18,6 @@ import org.opentripplanner.ext.transmodelapi.model.framework.LocationInputType; import org.opentripplanner.ext.transmodelapi.model.framework.PassThroughPointInputType; import org.opentripplanner.ext.transmodelapi.model.framework.PenaltyForStreetModeType; -import org.opentripplanner.ext.transmodelapi.model.scalars.RelaxScalarFactory; import org.opentripplanner.ext.transmodelapi.support.GqlUtil; import org.opentripplanner.routing.api.request.preference.RoutingPreferences; import org.opentripplanner.routing.core.BicycleOptimizeType; @@ -287,14 +287,21 @@ public static GraphQLFieldDefinition create( This relax the comparison inside the routing engine for each stop-arrival. If two paths have a different set of transit-priority-groups, then the generalized-cost - comparison is relaxed. The final set of paths are filtered through the normal + comparison is relaxed. The final set of paths are filtered through the normal itinerary-filters. - The `ratio` must be greater or equal to 1.0 and less then 1.2. - The `slack` must be greater or equal to 0 and less then 3600. + + THIS IS STILL AN EXPERIMENTAL FEATURE - IT MAY CHANGE WITHOUT ANY NOTICE! """.stripIndent() ) - .type(RelaxScalarFactory.createRelaxFunctionScalar(1.2, 3600)) + .type(RelaxCostType.INPUT_TYPE) + .defaultValueLiteral( + preferences.transit().relaxTransitPriorityGroup().isNormal() + ? NullValue.of() + : RelaxCostType.valueOf(preferences.transit().relaxTransitPriorityGroup()) + ) .build() ) .argument( diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/scalars/RelaxScalarFactory.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/scalars/RelaxScalarFactory.java deleted file mode 100644 index d2b4b8f6269..00000000000 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/scalars/RelaxScalarFactory.java +++ /dev/null @@ -1,75 +0,0 @@ -package org.opentripplanner.ext.transmodelapi.model.scalars; - -import graphql.language.StringValue; -import graphql.schema.Coercing; -import graphql.schema.CoercingParseLiteralException; -import graphql.schema.CoercingParseValueException; -import graphql.schema.GraphQLScalarType; -import java.util.Locale; -import org.opentripplanner.api.mapping.RelaxMapper; -import org.opentripplanner.routing.api.request.preference.Relax; - -public class RelaxScalarFactory { - - private static final String DOCUMENTATION = - """ - A linear function f(x) to calculate a relaxed value based on a parameter `x`: - ``` - f(x) = A * x + C - ``` - Pass in a ratio A and a slack C for use in the computation. The `x` and `+` sign - are required, but not the `*`. - - Example: `1.05 * x + 1800` - """; - - private RelaxScalarFactory() {} - - public static GraphQLScalarType createRelaxFunctionScalar( - final double maxRatio, - final int maxSlack - ) { - return GraphQLScalarType - .newScalar() - .name("RelaxFunction") - .description(DOCUMENTATION) - .coercing( - new Coercing() { - @Override - public String serialize(Object dataFetcherResult) { - var r = (Relax) (dataFetcherResult); - return String.format(Locale.ROOT, "%.2f * x + %d", r.ratio(), r.slack()); - } - - @Override - public Relax parseValue(Object input) throws CoercingParseValueException { - try { - Relax relax = RelaxMapper.mapRelax((String) input); - if (relax.ratio() < 1.0 || relax.ratio() > maxRatio) { - throw new CoercingParseValueException( - "Ratio %f is not in range: [1.0 .. %.1f]".formatted(relax.ratio(), maxRatio) - ); - } - if (relax.slack() < 0.0 || relax.slack() > maxSlack) { - throw new CoercingParseValueException( - "Ratio %f is not in range: [0 .. %d]".formatted(relax.ratio(), maxSlack) - ); - } - return relax; - } catch (IllegalArgumentException e) { - throw new CoercingParseValueException(e.getMessage(), e); - } - } - - @Override - public Relax parseLiteral(Object input) throws CoercingParseLiteralException { - if (input instanceof StringValue) { - return parseValue(((StringValue) input).getValue()); - } - return null; - } - } - ) - .build(); - } -} diff --git a/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java b/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java index cab5fdf35b8..3729752a593 100644 --- a/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java +++ b/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java @@ -2,7 +2,6 @@ import jakarta.validation.constraints.NotNull; import java.util.function.Consumer; -import org.opentripplanner.api.mapping.RelaxMapper; import org.opentripplanner.framework.lang.ObjectUtils; import org.opentripplanner.routing.algorithm.filterchain.api.TransitGeneralizedCostFilterParams; import org.opentripplanner.routing.api.request.framework.CostLinearFunction; @@ -90,7 +89,7 @@ private BoardAndAlightSlack mapTransit() { if (req.relaxTransitPriorityGroup != null) { tr.withTransitGroupPriorityGeneralizedCostSlack( - RelaxMapper.mapRelax(req.relaxTransitPriorityGroup) + CostLinearFunction.of(req.relaxTransitPriorityGroup) ); } else { setIfNotNull( diff --git a/src/main/java/org/opentripplanner/api/mapping/RelaxMapper.java b/src/main/java/org/opentripplanner/api/mapping/RelaxMapper.java index 80925648145..1586ff55ee0 100644 --- a/src/main/java/org/opentripplanner/api/mapping/RelaxMapper.java +++ b/src/main/java/org/opentripplanner/api/mapping/RelaxMapper.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.Optional; import java.util.regex.Pattern; import org.opentripplanner.routing.api.request.preference.Relax; @@ -51,6 +52,10 @@ public static Relax mapRelax(String input) { ); } + public static String mapRelaxToString(Relax domain) { + return String.format(Locale.ROOT, "%.2f * x + %d", domain.ratio(), domain.slack()); + } + private static Optional parse( Pattern pattern, String input, diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java index 2e524883e38..147601cbbae 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java @@ -23,7 +23,7 @@ import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.RaptorCostConverter; import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.grouppriority.TransitPriorityGroup32n; import org.opentripplanner.routing.api.request.RouteRequest; -import org.opentripplanner.routing.api.request.preference.Relax; +import org.opentripplanner.routing.api.request.framework.CostLinearFunction; import org.opentripplanner.transit.model.site.StopLocation; public class RaptorRequestMapper { @@ -117,7 +117,7 @@ private RaptorRequest doMap() { builder.withMultiCriteria(mcBuilder -> { var pt = preferences.transit(); var r = pt.raptor(); - if (pt.relaxTransitPriorityGroup().hasEffect()) { + if (!pt.relaxTransitPriorityGroup().isNormal()) { mcBuilder.withTransitPriorityCalculator(TransitPriorityGroup32n.priorityCalculator()); mcBuilder.withRelaxC1(mapRelaxCost(pt.relaxTransitPriorityGroup())); } else { @@ -193,13 +193,13 @@ private List mapPassThroughPoints() { .toList(); } - static RelaxFunction mapRelaxCost(Relax relax) { + static RelaxFunction mapRelaxCost(CostLinearFunction relax) { if (relax == null) { return null; } return GeneralizedCostRelaxFunction.of( - relax.ratio(), - RaptorCostConverter.toRaptorCost(relax.slack()) + relax.coefficient(), + RaptorCostConverter.toRaptorCost(relax.constant().toSeconds()) ); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java index ba7ae70652f..b362587c773 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java @@ -244,7 +244,7 @@ public RaptorConstrainedBoardingSearch transferConstraintsReverseS } private PriorityGroupConfigurator createTransitPriorityGroupConfigurator(RouteRequest request) { - if (request.preferences().transit().relaxTransitPriorityGroup().hasNoEffect()) { + if (!request.preferences().transit().relaxTransitPriorityGroup().isNormal()) { return PriorityGroupConfigurator.empty(); } var transitRequest = request.journey().transit(); diff --git a/src/main/java/org/opentripplanner/routing/api/request/framework/AbstractLinearFunction.java b/src/main/java/org/opentripplanner/routing/api/request/framework/AbstractLinearFunction.java index b1fe459a7b7..622c6f9086a 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/framework/AbstractLinearFunction.java +++ b/src/main/java/org/opentripplanner/routing/api/request/framework/AbstractLinearFunction.java @@ -46,6 +46,13 @@ public final boolean isZero() { return isZero(constant) && coefficient == 0.0; } + /** + * Return true if the {@code f(x) = x} for all times (constant is zero and the coefficient is one). + */ + public final boolean isNormal() { + return isZero(constant) && coefficient == 1.0; + } + public final String serialize() { return LinearFunctionSerialization.serialize(this); } diff --git a/src/main/java/org/opentripplanner/routing/api/request/framework/CostLinearFunction.java b/src/main/java/org/opentripplanner/routing/api/request/framework/CostLinearFunction.java index 019bab48f7a..8ff119b91ad 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/framework/CostLinearFunction.java +++ b/src/main/java/org/opentripplanner/routing/api/request/framework/CostLinearFunction.java @@ -8,8 +8,16 @@ */ public final class CostLinearFunction extends AbstractLinearFunction { + /** + * Constant is zero and coefficient is zero. The result of {@code f(x) = 0}. + */ public static CostLinearFunction ZERO = new CostLinearFunction(Cost.ZERO, 0.0); + /** + * Constant is zero and coefficient is one. The result of {@code f(x) = x}. + */ + public static CostLinearFunction NORMAL = new CostLinearFunction(Cost.ZERO, 1.0); + private CostLinearFunction(Cost a, double b) { super(a, b); } diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/Relax.java b/src/main/java/org/opentripplanner/routing/api/request/preference/Relax.java index e137e322d68..8f10ffd1525 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/Relax.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/Relax.java @@ -1,5 +1,9 @@ package org.opentripplanner.routing.api.request.preference; +import java.util.Locale; +import org.opentripplanner.framework.lang.DoubleUtils; +import org.opentripplanner.framework.lang.IntUtils; + /** * Relax a value by the given ratio and slack. The relaxed value * can be calculated using this function: @@ -16,6 +20,12 @@ public record Relax(double ratio, int slack) { */ public static final Relax NORMAL = new Relax(1.0, 0); + public Relax { + ratio = DoubleUtils.roundTo2Decimals(ratio); + DoubleUtils.requireInRange(ratio, 1.0, 4.0, "ratio"); + IntUtils.requireNotNegative(slack, "slack"); + } + /** * {@code true} if {@link #NORMAL}, this is the same as not applying the function. *

@@ -32,4 +42,9 @@ public boolean hasNoEffect() { public boolean hasEffect() { return !hasNoEffect(); } + + @Override + public String toString() { + return String.format(Locale.ROOT, "%d + %.2f * x", slack, ratio); + } } diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java index bfc578ad1b5..ff72bf3efb5 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java @@ -3,7 +3,6 @@ import static java.util.Objects.requireNonNull; import java.io.Serializable; -import java.time.Duration; import java.util.Map; import java.util.Objects; import java.util.function.Consumer; @@ -27,7 +26,7 @@ public final class TransitPreferences implements Serializable { private final Map reluctanceForMode; private final Cost otherThanPreferredRoutesPenalty; private final CostLinearFunction unpreferredCost; - private final Relax relaxTransitPriorityGroup; + private final CostLinearFunction relaxTransitPriorityGroup; private final boolean ignoreRealtimeUpdates; private final boolean includePlannedCancellations; private final boolean includeRealtimeCancellations; @@ -37,8 +36,8 @@ private TransitPreferences() { this.boardSlack = this.alightSlack = DurationForEnum.of(TransitMode.class).build(); this.reluctanceForMode = Map.of(); this.otherThanPreferredRoutesPenalty = Cost.costOfMinutes(5); - this.unpreferredCost = CostLinearFunction.of(Duration.ZERO, 1.0); - this.relaxTransitPriorityGroup = Relax.NORMAL; + this.unpreferredCost = CostLinearFunction.NORMAL; + this.relaxTransitPriorityGroup = CostLinearFunction.NORMAL; this.ignoreRealtimeUpdates = false; this.includePlannedCancellations = false; this.includeRealtimeCancellations = false; @@ -128,7 +127,13 @@ public CostLinearFunction unpreferredCost() { return unpreferredCost; } - public Relax relaxTransitPriorityGroup() { + /** + * This is used to relax the cost when comparing transit-priority-groups. The default is the + * NORMAL function({@code f(x) = x}. This is the same as not using priority-groups. The + * coefficient must be in range {@code [1.0 to 4.0]} and the constant must be in range + * {@code [$0 to $1440(4h)]}. + */ + public CostLinearFunction relaxTransitPriorityGroup() { return relaxTransitPriorityGroup; } @@ -208,7 +213,7 @@ public String toString() { DEFAULT.otherThanPreferredRoutesPenalty ) .addObj("unpreferredCost", unpreferredCost, DEFAULT.unpreferredCost) - .addObj("relaxTransitPriorityGroup", relaxTransitPriorityGroup, Relax.NORMAL) + .addObj("relaxTransitPriorityGroup", relaxTransitPriorityGroup, CostLinearFunction.NORMAL) .addBoolIfTrue( "ignoreRealtimeUpdates", ignoreRealtimeUpdates != DEFAULT.ignoreRealtimeUpdates @@ -235,7 +240,7 @@ public static class Builder { private Map reluctanceForMode; private Cost otherThanPreferredRoutesPenalty; private CostLinearFunction unpreferredCost; - private Relax relaxTransitPriorityGroup; + private CostLinearFunction relaxTransitPriorityGroup; private boolean ignoreRealtimeUpdates; private boolean includePlannedCancellations; private boolean includeRealtimeCancellations; @@ -297,7 +302,7 @@ public Builder setUnpreferredCostString(String constFunction) { return setUnpreferredCost(CostLinearFunction.of(constFunction)); } - public Builder withTransitGroupPriorityGeneralizedCostSlack(Relax value) { + public Builder withTransitGroupPriorityGeneralizedCostSlack(CostLinearFunction value) { this.relaxTransitPriorityGroup = value; return this; } diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java index 9eb9cc72045..b682d36ee30 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java @@ -17,6 +17,7 @@ import org.opentripplanner.routing.api.request.RequestModes; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.StreetMode; +import org.opentripplanner.routing.api.request.framework.CostLinearFunction; import org.opentripplanner.routing.api.request.preference.BikePreferences; import org.opentripplanner.routing.api.request.preference.CarPreferences; import org.opentripplanner.routing.api.request.preference.RoutingPreferences; @@ -334,14 +335,34 @@ The board time is added to the time when going from the stop (offboard) to onboa """ ) .asCostLinearFunction(dft.unpreferredCost()) + ); + + String relaxTransitPriorityGroupValue = c + .of("relaxTransitPriorityGroup") + .since(V2_3) + .summary("The relax function for transit-priority-groups") + .description( + """ + A path is considered optimal if it the generalized-cost is smaller than an other the + generalized-cost of another path. If this parameter is set, the comparison is relaxed + further if they belong to different transit-priority-groups. + """ ) - .withRaptor(it -> - c - .of("relaxTransitSearchGeneralizedCostAtDestination") - .since(V2_3) - .summary("Whether non-optimal transit paths at the destination should be returned") - .description( - """ + .asString(dft.relaxTransitPriorityGroup().toString()); + + if (relaxTransitPriorityGroupValue != null) { + builder.withTransitGroupPriorityGeneralizedCostSlack( + CostLinearFunction.of(relaxTransitPriorityGroupValue) + ); + } + + builder.withRaptor(it -> + c + .of("relaxTransitSearchGeneralizedCostAtDestination") + .since(V2_3) + .summary("Whether non-optimal transit paths at the destination should be returned") + .description( + """ Let c be the existing minimum pareto optimal generalized cost to beat. Then a trip with cost c' is accepted if the following is true: `c' < Math.round(c * relaxRaptorCostCriteria)`. @@ -351,10 +372,10 @@ The board time is added to the time when going from the stop (offboard) to onboa Values equals or less than zero is not allowed. Values greater than 2.0 are not supported, due to performance reasons. """ - ) - .asDoubleOptional() - .ifPresent(it::withRelaxGeneralizedCostAtDestination) - ); + ) + .asDoubleOptional() + .ifPresent(it::withRelaxGeneralizedCostAtDestination) + ); } private static void mapBikePreferences(NodeAdapter c, BikePreferences.Builder builder) { diff --git a/src/test/java/org/opentripplanner/api/mapping/RelaxMapperTest.java b/src/test/java/org/opentripplanner/api/mapping/RelaxMapperTest.java deleted file mode 100644 index 9e182708619..00000000000 --- a/src/test/java/org/opentripplanner/api/mapping/RelaxMapperTest.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.opentripplanner.api.mapping; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.junit.jupiter.api.Test; -import org.opentripplanner.routing.api.request.preference.Relax; - -class RelaxMapperTest { - - @Test - void mapRelax() { - assertNull(RelaxMapper.mapRelax("")); - assertNull(RelaxMapper.mapRelax(null)); - - Relax expected = new Relax(2.0, 10); - - assertEquals(expected, RelaxMapper.mapRelax("2 * generalizedCost + 10")); - assertEquals(expected, RelaxMapper.mapRelax("10 + 2 * generalizedCost")); - assertEquals(expected, RelaxMapper.mapRelax("2.0x+10")); - assertEquals(expected, RelaxMapper.mapRelax("10+2.0x")); - assertThrows(IllegalArgumentException.class, () -> RelaxMapper.mapRelax("2.0")); - assertThrows(IllegalArgumentException.class, () -> RelaxMapper.mapRelax("2.0+10")); - assertThrows(IllegalArgumentException.class, () -> RelaxMapper.mapRelax("2.0*10")); - } -} diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java index cf83ce528e0..11c59030180 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java @@ -6,18 +6,18 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import org.opentripplanner.routing.api.request.preference.Relax; +import org.opentripplanner.routing.api.request.framework.CostLinearFunction; class RaptorRequestMapperTest { - private static final Relax R1 = new Relax(1.0, 50); - private static final Relax R2 = new Relax(1.5, 0); - private static final Relax R3 = new Relax(2.0, 30); + private static final CostLinearFunction R1 = CostLinearFunction.of("50 + 1.0x"); + private static final CostLinearFunction R2 = CostLinearFunction.of("0 + 1.5x"); + private static final CostLinearFunction R3 = CostLinearFunction.of("30 + 2.0x"); static List testCasesRelaxedCost() { return List.of( - Arguments.of(Relax.NORMAL, 0, 0), - Arguments.of(Relax.NORMAL, 10, 10), + Arguments.of(CostLinearFunction.NORMAL, 0, 0), + Arguments.of(CostLinearFunction.NORMAL, 10, 10), Arguments.of(R1, 0, 5000), Arguments.of(R1, 7, 5007), Arguments.of(R2, 0, 0), @@ -29,7 +29,7 @@ static List testCasesRelaxedCost() { @ParameterizedTest @MethodSource("testCasesRelaxedCost") - void mapRelaxCost(Relax input, int cost, int expected) { + void mapRelaxCost(CostLinearFunction input, int cost, int expected) { var calcCost = RaptorRequestMapper.mapRelaxCost(input); assertEquals(expected, calcCost.relax(cost)); } diff --git a/src/test/java/org/opentripplanner/routing/api/request/preference/RelaxTest.java b/src/test/java/org/opentripplanner/routing/api/request/preference/RelaxTest.java new file mode 100644 index 00000000000..d3c1a2702c2 --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/api/request/preference/RelaxTest.java @@ -0,0 +1,48 @@ +package org.opentripplanner.routing.api.request.preference; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class RelaxTest { + + @Test + void testConstructor() { + new Relax(0.9951, 0); + new Relax(4.0, 2_000_000_000); + assertThrows(IllegalArgumentException.class, () -> new Relax(0.9949, 0)); + assertThrows(IllegalArgumentException.class, () -> new Relax(4.01, 300)); + assertThrows(IllegalArgumentException.class, () -> new Relax(2.1, -1)); + } + + @Test + void hasNoEffect() { + assertTrue(new Relax(1.0049, 0).hasNoEffect()); + assertFalse(new Relax(1.01, 0).hasNoEffect()); + } + + @Test + void hasEffect() { + assertTrue(new Relax(1.01, 0).hasEffect()); + assertFalse(new Relax(1.0049, 0).hasEffect()); + } + + @Test + void testToString() { + assertEquals("0 + 1.00 * x", Relax.NORMAL.toString()); + assertEquals("300 + 2.10 * x", new Relax(2.1, 300).toString()); + } + + @Test + void ratio() { + assertEquals(2.0, new Relax(2.0, 333).ratio()); + } + + @Test + void slack() { + assertEquals(333, new Relax(2.0, 333).slack()); + } +} diff --git a/src/test/java/org/opentripplanner/routing/api/request/preference/TransitPreferencesTest.java b/src/test/java/org/opentripplanner/routing/api/request/preference/TransitPreferencesTest.java index 1a761dd05a9..811c4a70b29 100644 --- a/src/test/java/org/opentripplanner/routing/api/request/preference/TransitPreferencesTest.java +++ b/src/test/java/org/opentripplanner/routing/api/request/preference/TransitPreferencesTest.java @@ -9,6 +9,7 @@ import java.time.Duration; import java.util.Map; import org.junit.jupiter.api.Test; +import org.opentripplanner.framework.model.Cost; import org.opentripplanner.raptor.api.model.SearchDirection; import org.opentripplanner.routing.api.request.framework.CostLinearFunction; import org.opentripplanner.transit.model.basic.TransitMode; @@ -26,7 +27,10 @@ class TransitPreferencesTest { private static final Duration D25m = Duration.ofMinutes(25); private static final Duration D35m = Duration.ofMinutes(35); private static final SearchDirection RAPTOR_SEARCH_DIRECTION = SearchDirection.REVERSE; - private static final Relax TRANSIT_GROUP_PRIORITY_RELAX = new Relax(1.5, 300); + private static final CostLinearFunction TRANSIT_GROUP_PRIORITY_RELAX = CostLinearFunction.of( + Cost.costOfSeconds(300), + 1.5 + ); private static final boolean IGNORE_REALTIME_UPDATES = true; private static final boolean INCLUDE_PLANNED_CANCELLATIONS = true; private static final boolean INCLUDE_REALTIME_CANCELLATIONS = true; @@ -121,7 +125,7 @@ void testToString() { "reluctanceForMode: {AIRPLANE=2.1}, " + "otherThanPreferredRoutesPenalty: $350, " + "unpreferredCost: 5m + 1.15 t, " + - "relaxTransitPriorityGroup: Relax[ratio=1.5, slack=300], " + + "relaxTransitPriorityGroup: 5m + 1.50 t, " + "ignoreRealtimeUpdates, " + "includePlannedCancellations, " + "includeRealtimeCancellations, " + From 486be00e9f18bf6a1ce87bcecc2c4718f64aee96 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 3 Nov 2023 16:12:49 +0100 Subject: [PATCH 07/85] refactor: Change c-style doc into JavaDoc for J01_PassThroughTest --- .../moduletests/J01_PassThroughTest.java | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/J01_PassThroughTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/J01_PassThroughTest.java index 4cc776674da..b15a0a46aaf 100644 --- a/src/test/java/org/opentripplanner/raptor/moduletests/J01_PassThroughTest.java +++ b/src/test/java/org/opentripplanner/raptor/moduletests/J01_PassThroughTest.java @@ -28,35 +28,35 @@ import org.opentripplanner.raptor.api.request.RaptorRequestBuilder; import org.opentripplanner.raptor.configure.RaptorConfig; -/* -FEATURE UNDER TEST - -Raptor should be able to handle route request with a specified pass-through point. -If a stop point is specified as pass-through point in the request then all the results returned -from raptor should include this stop point either as alight or board point for a trip or as an -intermediate point in the trip. - -It should be possible to specify more than one pass through point. The result should include stop -points in the order as they were specified in the request. Only alternatives that passes through -all the stop point should be included in the result. - -In order to support stop areas raptor should also support multiple stop points in the same pass-through -group. It should be possible to define both stop A and B as a pass-through. Then alternatives that -pass either stop A or B should not be dropped. +/** + * FEATURE UNDER TEST + * + * Raptor should be able to handle route request with a specified pass-through point. + * If a stop point is specified as pass-through point in the request then all the results returned + * from raptor should include this stop point either as alight or board point for a trip or as an + * intermediate point in the trip. + * + * It should be possible to specify more than one pass through point. The result should include + * stop points in the order as they were specified in the request. Only alternatives that pass + * through all the stop point should be included in the result. + * + * In order to support stop areas raptor should also support multiple stop points in the same + * pass-through group. It should be possible to define both stop A and B as a pass-through. Then + * alternatives that pass either stop A or B should not be dropped. */ -public class J01_PassThroughTest { +class J01_PassThroughTest { - public static final List PASS_THROUGH_STOP_A = List.of(point("A", STOP_A)); - public static final List PASS_THROUGH_STOP_C = List.of(point("C", STOP_C)); - public static final List PASS_THROUGH_STOP_D = List.of(point("D", STOP_D)); - public static final List PASS_THROUGH_STOP_B_OR_C = List.of( + static final List PASS_THROUGH_STOP_A = List.of(point("A", STOP_A)); + static final List PASS_THROUGH_STOP_C = List.of(point("C", STOP_C)); + static final List PASS_THROUGH_STOP_D = List.of(point("D", STOP_D)); + static final List PASS_THROUGH_STOP_B_OR_C = List.of( point("B&C", STOP_B, STOP_C) ); - public static final List PASS_THROUGH_STOP_B_THEN_C = List.of( + static final List PASS_THROUGH_STOP_B_THEN_C = List.of( point("B", STOP_B), point("C", STOP_C) ); - public static final List PASS_THROUGH_STOP_B_THEN_D = List.of( + static final List PASS_THROUGH_STOP_B_THEN_D = List.of( point("B", STOP_B), point("D", STOP_D) ); @@ -86,7 +86,7 @@ private RaptorRequestBuilder prepareRequest() { @Test @DisplayName("Pass-through stop point as a last point in the journey.") - public void passThroughPointOnEgress() { + void passThroughPointOnEgress() { var data = new TestTransitData(); // Create two routes. @@ -117,7 +117,7 @@ public void passThroughPointOnEgress() { @Test @DisplayName("Pass-through stop point as a first point in the journey.") - public void passThroughPointOnAccess() { + void passThroughPointOnAccess() { var data = new TestTransitData(); // Create two routes. @@ -151,7 +151,7 @@ public void passThroughPointOnAccess() { @Test @DisplayName("Pass-through stop point as an intermediate point in the journey.") - public void passThroughPointInTheMiddle() { + void passThroughPointInTheMiddle() { var data = new TestTransitData(); // Create two routes. @@ -182,7 +182,7 @@ public void passThroughPointInTheMiddle() { @Test @DisplayName("Multiple pass-through stop points") - public void multiplePassThroughPoints() { + void multiplePassThroughPoints() { var data = new TestTransitData(); // Create two routes. @@ -215,7 +215,7 @@ public void multiplePassThroughPoints() { @Test @DisplayName("Pass-through order") - public void passThroughOrder() { + void passThroughOrder() { var data = new TestTransitData(); // Create two routes. @@ -244,7 +244,7 @@ public void passThroughOrder() { @Test @DisplayName("Multiple stops in same pass-through group") - public void passThroughGroup() { + void passThroughGroup() { var data = new TestTransitData(); // Create two routes. From 616950a8d451de1e3052cbdc99379ccaa279aa86 Mon Sep 17 00:00:00 2001 From: Johan Torin Date: Wed, 8 Nov 2023 10:01:07 +0100 Subject: [PATCH 08/85] Improve Azure ServiceBus subscriptions removal. Call close() instead of stop() on the event processor to make sure it has stopped using the subscription before removal. Otherwise errors may lead to stale subscriptions lingering. --- .../ext/siri/updater/azure/AbstractAzureSiriUpdater.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ext/java/org/opentripplanner/ext/siri/updater/azure/AbstractAzureSiriUpdater.java b/src/ext/java/org/opentripplanner/ext/siri/updater/azure/AbstractAzureSiriUpdater.java index 6f69ca99216..926eca38497 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/updater/azure/AbstractAzureSiriUpdater.java +++ b/src/ext/java/org/opentripplanner/ext/siri/updater/azure/AbstractAzureSiriUpdater.java @@ -163,7 +163,7 @@ public void run() { @Override public void teardown() { - eventProcessor.stop(); + eventProcessor.close(); serviceBusAdmin.deleteSubscription(topicName, subscriptionName).block(); LOG.info("Subscription {} deleted on topic {}", subscriptionName, topicName); } From 64866231678ef8d9b569f317acfb2ad57f551a17 Mon Sep 17 00:00:00 2001 From: Johan Torin Date: Wed, 8 Nov 2023 10:06:12 +0100 Subject: [PATCH 09/85] Don't execute teardown logic twice on shutdown. At least in the case of the AzureSiriUpdater this leads to errors the second time. * Provide an alternative API for adding system shutdown hooks that takes a Runnable directly. * State the shutdown logic directly to the shutdown hook, remove the teardown method. * Handle the execute-hook-directly-if-shutting-down logic internally in ApplicationShutdownSupport. --- .../azure/AbstractAzureSiriUpdater.java | 24 +++++--------- .../ApplicationShutdownSupport.java | 32 +++++++++++++++++++ 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/src/ext/java/org/opentripplanner/ext/siri/updater/azure/AbstractAzureSiriUpdater.java b/src/ext/java/org/opentripplanner/ext/siri/updater/azure/AbstractAzureSiriUpdater.java index 926eca38497..0d36233f22b 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/updater/azure/AbstractAzureSiriUpdater.java +++ b/src/ext/java/org/opentripplanner/ext/siri/updater/azure/AbstractAzureSiriUpdater.java @@ -149,23 +149,15 @@ public void run() { subscriptionName ); - Thread shutdownHook = new Thread(this::teardown, "azur-siri-updater-shutdown"); - boolean added = ApplicationShutdownSupport.addShutdownHook( - shutdownHook, - shutdownHook.getName() + ApplicationShutdownSupport.addShutdownHook( + "azure-siri-updater-shutdown", + () -> { + LOG.info("Calling shutdownHook on AbstractAzureSiriUpdater"); + eventProcessor.close(); + serviceBusAdmin.deleteSubscription(topicName, subscriptionName).block(); + LOG.info("Subscription '{}' deleted on topic '{}'.", subscriptionName, topicName); + } ); - if (!added) { - // Handling corner case when instance is being shut down before it has been initialized - LOG.info("Instance is already shutting down - cleaning up immediately."); - teardown(); - } - } - - @Override - public void teardown() { - eventProcessor.close(); - serviceBusAdmin.deleteSubscription(topicName, subscriptionName).block(); - LOG.info("Subscription {} deleted on topic {}", subscriptionName, topicName); } @Override diff --git a/src/main/java/org/opentripplanner/framework/application/ApplicationShutdownSupport.java b/src/main/java/org/opentripplanner/framework/application/ApplicationShutdownSupport.java index 7f349fc0146..3729060b992 100644 --- a/src/main/java/org/opentripplanner/framework/application/ApplicationShutdownSupport.java +++ b/src/main/java/org/opentripplanner/framework/application/ApplicationShutdownSupport.java @@ -1,5 +1,6 @@ package org.opentripplanner.framework.application; +import java.util.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,4 +32,35 @@ public static boolean addShutdownHook(Thread shutdownHook, String shutdownHookNa return false; } } + + /** + * Attempt to add a shutdown hook. If the application is already shutting down, the shutdown hook + * will be executed immediately. + * + * @param hookName the name of the thread + * @param shutdownHook the payload to be executed in the thread + * @return an Optional possibly containing the created thread, needed to un-schedule the shutdown hook + */ + public static Optional addShutdownHook(String hookName, Runnable shutdownHook) { + final Thread shutdownThread = new Thread(shutdownHook, hookName); + try { + LOG.info("Adding shutdown hook '{}'.", hookName); + Runtime.getRuntime().addShutdownHook(shutdownThread); + return Optional.of(shutdownThread); + } catch (IllegalStateException ignore) { + LOG.info("OTP is already shutting down, running shutdown hook '{}' immediately.", hookName); + shutdownThread.start(); + } + return Optional.empty(); + } + + /** + * Remove a previously scheduled shutdown hook. + * + * @param shutdownThread an Optional possibly containing a thread + */ + public static void removeShutdownHook(Thread shutdownThread) { + LOG.info("Removing shutdown hook '{}'.", shutdownThread.getName()); + Runtime.getRuntime().removeShutdownHook(shutdownThread); + } } From 4598eaf2140491cd4fbfe2e5016e8b32535c1d0f Mon Sep 17 00:00:00 2001 From: Johan Torin Date: Wed, 15 Nov 2023 13:54:36 +0100 Subject: [PATCH 10/85] Move teardown logic directly to where the shutdown hook is added. This avoids running it twice on shutdown. --- .../updater/SiriETGooglePubsubUpdater.java | 28 ++++++------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETGooglePubsubUpdater.java b/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETGooglePubsubUpdater.java index cb664632443..55fddc18a4d 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETGooglePubsubUpdater.java +++ b/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETGooglePubsubUpdater.java @@ -195,15 +195,6 @@ public void run() { } } - @Override - public void teardown() { - if (subscriber != null) { - LOG.info("Stopping SIRI-ET PubSub subscriber {}", subscriptionName); - subscriber.stopAsync(); - } - deleteSubscription(); - } - @Override public boolean isPrimed() { return this.primed; @@ -215,17 +206,16 @@ public String getConfigRef() { } private void addShutdownHook() { - // TODO: This should probably be on a higher level? - Thread shutdownHook = new Thread(this::teardown, "siri-et-google-pubsub-shutdown"); - boolean added = ApplicationShutdownSupport.addShutdownHook( - shutdownHook, - shutdownHook.getName() + ApplicationShutdownSupport.addShutdownHook( + "siri-et-google-pubsub-shutdown", + () -> { + if (subscriber != null) { + LOG.info("Stopping SIRI-ET PubSub subscriber '{}'.", subscriptionName); + subscriber.stopAsync(); + } + deleteSubscription(); + } ); - if (!added) { - // Handling corner case when instance is being shut down before it has been initialized - LOG.info("Instance is already shutting down - cleaning up immediately."); - teardown(); - } } /** From cc32fc1b29b5e5cd5028706ef95e4a2bd8c904cd Mon Sep 17 00:00:00 2001 From: Johan Torin Date: Wed, 15 Nov 2023 13:54:42 +0100 Subject: [PATCH 11/85] Use ApplicationShutdownSupport to schedule the shutdown hook. Notice that this adds immediate execution in the case that the JVM is already shutting down. --- src/main/java/org/opentripplanner/standalone/OTPMain.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/opentripplanner/standalone/OTPMain.java b/src/main/java/org/opentripplanner/standalone/OTPMain.java index 65b40dc3630..e63e9734878 100644 --- a/src/main/java/org/opentripplanner/standalone/OTPMain.java +++ b/src/main/java/org/opentripplanner/standalone/OTPMain.java @@ -221,7 +221,8 @@ private static void registerShutdownHookToGracefullyShutDownServer( TransitModel transitModel, RaptorConfig raptorConfig ) { - var hook = new Thread( + ApplicationShutdownSupport.addShutdownHook( + "server-shutdown", () -> { LOG.info("OTP shutdown started..."); UpdaterConfigurator.shutdownGraph(transitModel); @@ -229,10 +230,8 @@ private static void registerShutdownHookToGracefullyShutDownServer( WeakCollectionCleaner.DEFAULT.exit(); DeferredAuthorityFactory.exit(); LOG.info("OTP shutdown: resources released..."); - }, - "server-shutdown" + } ); - ApplicationShutdownSupport.addShutdownHook(hook, hook.getName()); } private static void setOtpConfigVersionsOnServerInfo(ConstructApplication app) { From 046ad228b28f226d41b6721fa0cc8ec9f5387c44 Mon Sep 17 00:00:00 2001 From: Johan Torin Date: Wed, 15 Nov 2023 13:54:48 +0100 Subject: [PATCH 12/85] Improve shutdown of HTTP server. If the JVM is already shutting down, the graceful shutdown of the HTTP server should be executed immediately. Previously the server could potentially be started anyway. --- .../standalone/server/GrizzlyServer.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/opentripplanner/standalone/server/GrizzlyServer.java b/src/main/java/org/opentripplanner/standalone/server/GrizzlyServer.java index a0a2228529e..c422e9c24f3 100644 --- a/src/main/java/org/opentripplanner/standalone/server/GrizzlyServer.java +++ b/src/main/java/org/opentripplanner/standalone/server/GrizzlyServer.java @@ -1,10 +1,14 @@ package org.opentripplanner.standalone.server; +import static org.opentripplanner.framework.application.ApplicationShutdownSupport.addShutdownHook; +import static org.opentripplanner.framework.application.ApplicationShutdownSupport.removeShutdownHook; + import com.google.common.util.concurrent.ThreadFactoryBuilder; import jakarta.ws.rs.core.Application; import java.io.IOException; import java.net.BindException; import java.time.Duration; +import java.util.Optional; import org.glassfish.grizzly.http.CompressionConfig; import org.glassfish.grizzly.http.server.CLStaticHttpHandler; import org.glassfish.grizzly.http.server.HttpHandler; @@ -13,7 +17,6 @@ import org.glassfish.grizzly.http.server.StaticHttpHandler; import org.glassfish.grizzly.threadpool.ThreadPoolConfig; import org.glassfish.jersey.server.ContainerFactory; -import org.opentripplanner.framework.application.ApplicationShutdownSupport; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.standalone.config.CommandLineParameters; import org.slf4j.Logger; @@ -126,10 +129,11 @@ public void run() { // Graph graph = gs.getGraph(); // httpServer.getServerConfiguration().addHttpHandler(new OTPHttpHandler(graph), "/test/*"); - // Add shutdown hook to gracefully shut down Grizzly. - // Signal handling (sun.misc.Signal) is potentially not available on all JVMs. - Thread shutdownThread = new Thread(httpServer::shutdown, "grizzly-shutdown"); - ApplicationShutdownSupport.addShutdownHook(shutdownThread, shutdownThread.getName()); + // Add shutdown hook to gracefully shut down Grizzly. If no thread is returned then shutdown is already in progress. + Optional shutdownThread = addShutdownHook("grizzly-shutdown", httpServer::shutdown); + if (!shutdownThread.isPresent()) { + return; + } /* RELINQUISH CONTROL TO THE SERVER THREAD */ try { @@ -144,8 +148,7 @@ public void run() { LOG.info("Interrupted, shutting down."); } - // Clean up graceful shutdown hook before shutting down Grizzly. - Runtime.getRuntime().removeShutdownHook(shutdownThread); + shutdownThread.ifPresent(thread -> removeShutdownHook(thread)); httpServer.shutdown(); } From db2f52cdba43b06cc0a1231483e189b25c7b2029 Mon Sep 17 00:00:00 2001 From: Johan Torin Date: Wed, 15 Nov 2023 14:02:53 +0100 Subject: [PATCH 13/85] Remove old API. --- .../ApplicationShutdownSupport.java | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/src/main/java/org/opentripplanner/framework/application/ApplicationShutdownSupport.java b/src/main/java/org/opentripplanner/framework/application/ApplicationShutdownSupport.java index 3729060b992..cd962a79d66 100644 --- a/src/main/java/org/opentripplanner/framework/application/ApplicationShutdownSupport.java +++ b/src/main/java/org/opentripplanner/framework/application/ApplicationShutdownSupport.java @@ -13,26 +13,6 @@ public final class ApplicationShutdownSupport { private ApplicationShutdownSupport() {} - /** - * Attempt to add a shutdown hook. If the application is already shutting down, the shutdown hook - * will not be added. - * - * @return true if the shutdown hook is successfully added, false otherwise. - */ - public static boolean addShutdownHook(Thread shutdownHook, String shutdownHookName) { - try { - LOG.info("Adding shutdown hook {}", shutdownHookName); - Runtime.getRuntime().addShutdownHook(shutdownHook); - return true; - } catch (IllegalStateException ignore) { - LOG.info( - "OTP is already shutting down, the shutdown hook {} will not be added", - shutdownHookName - ); - return false; - } - } - /** * Attempt to add a shutdown hook. If the application is already shutting down, the shutdown hook * will be executed immediately. From eafd577a7874a1e35002a771667a9a0258dce3e0 Mon Sep 17 00:00:00 2001 From: Johan Torin Date: Wed, 15 Nov 2023 14:14:56 +0100 Subject: [PATCH 14/85] Improve logging on shutdown. If the JVM is already shutting down when the hook is scheduled, the message informing of this is logged immediately, previously it could potentially be lost. --- .../opentripplanner/standalone/OtpStartupInfo.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/opentripplanner/standalone/OtpStartupInfo.java b/src/main/java/org/opentripplanner/standalone/OtpStartupInfo.java index 0b84c361fb4..c601846d889 100644 --- a/src/main/java/org/opentripplanner/standalone/OtpStartupInfo.java +++ b/src/main/java/org/opentripplanner/standalone/OtpStartupInfo.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.stream.Collectors; +import org.opentripplanner.framework.application.ApplicationShutdownSupport; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,14 +38,10 @@ public static void logInfo() { // This is good when aggregating logs across multiple load balanced instances of OTP // Hint: a regexp filter like "^OTP (START|SHUTTING)" will list nodes going up/down LOG.info("OTP STARTING UP ({}) using Java {}", projectInfo().getVersionString(), javaVersion()); - Runtime - .getRuntime() - .addShutdownHook( - new Thread( - () -> LOG.info("OTP SHUTTING DOWN ({})", projectInfo().getVersionString()), - "server-shutdown-info" - ) - ); + ApplicationShutdownSupport.addShutdownHook( + "server-shutdown-info", + () -> LOG.info("OTP SHUTTING DOWN ({})", projectInfo().getVersionString()) + ); LOG.info(NEW_LINE + "{}", info()); } From 35238618df0aceaec4140d817d58f9a74c824a68 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Sat, 11 Nov 2023 22:50:30 +0100 Subject: [PATCH 15/85] Token serializer Signed-off-by: Thomas Gran --- .../framework/lang/ObjectUtils.java | 10 +- .../framework/token/Deserializer.java | 99 ++++++++++ .../framework/token/FieldDefinition.java | 54 ++++++ .../framework/token/Serializer.java | 64 ++++++ .../framework/token/Token.java | 61 ++++++ .../framework/token/TokenBuilder.java | 55 ++++++ .../framework/token/TokenDefinition.java | 107 ++++++++++ .../token/TokenDefinitionBuilder.java | 88 +++++++++ .../framework/token/TokenSchema.java | 81 ++++++++ .../framework/token/TokenType.java | 20 ++ .../framework/lang/ObjectUtilsTest.java | 12 +- .../token/AdvancedTokenSchemaTest.java | 182 ++++++++++++++++++ .../framework/token/TokenSchemaConstants.java | 27 +++ .../framework/token/TokenSchemaTest.java | 140 ++++++++++++++ 14 files changed, 997 insertions(+), 3 deletions(-) create mode 100644 src/main/java/org/opentripplanner/framework/token/Deserializer.java create mode 100644 src/main/java/org/opentripplanner/framework/token/FieldDefinition.java create mode 100644 src/main/java/org/opentripplanner/framework/token/Serializer.java create mode 100644 src/main/java/org/opentripplanner/framework/token/Token.java create mode 100644 src/main/java/org/opentripplanner/framework/token/TokenBuilder.java create mode 100644 src/main/java/org/opentripplanner/framework/token/TokenDefinition.java create mode 100644 src/main/java/org/opentripplanner/framework/token/TokenDefinitionBuilder.java create mode 100644 src/main/java/org/opentripplanner/framework/token/TokenSchema.java create mode 100644 src/main/java/org/opentripplanner/framework/token/TokenType.java create mode 100644 src/test/java/org/opentripplanner/framework/token/AdvancedTokenSchemaTest.java create mode 100644 src/test/java/org/opentripplanner/framework/token/TokenSchemaConstants.java create mode 100644 src/test/java/org/opentripplanner/framework/token/TokenSchemaTest.java diff --git a/src/main/java/org/opentripplanner/framework/lang/ObjectUtils.java b/src/main/java/org/opentripplanner/framework/lang/ObjectUtils.java index ed1ed992cd2..1e67b8f253e 100644 --- a/src/main/java/org/opentripplanner/framework/lang/ObjectUtils.java +++ b/src/main/java/org/opentripplanner/framework/lang/ObjectUtils.java @@ -34,9 +34,17 @@ public static T ifNotNull( } public static T requireNotInitialized(T oldValue, T newValue) { + return requireNotInitialized(null, oldValue, newValue); + } + + public static T requireNotInitialized(@Nullable String name, T oldValue, T newValue) { if (oldValue != null) { throw new IllegalStateException( - "Field is already set! Old value: " + oldValue + ", new value: " + newValue + "Field%s is already set! Old value: %s, new value: %s.".formatted( + (name == null ? "" : " " + name), + oldValue, + newValue + ) ); } return newValue; diff --git a/src/main/java/org/opentripplanner/framework/token/Deserializer.java b/src/main/java/org/opentripplanner/framework/token/Deserializer.java new file mode 100644 index 00000000000..1da783b239d --- /dev/null +++ b/src/main/java/org/opentripplanner/framework/token/Deserializer.java @@ -0,0 +1,99 @@ +package org.opentripplanner.framework.token; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; +import javax.annotation.Nullable; +import org.opentripplanner.framework.time.DurationUtils; + +class Deserializer { + + private final ByteArrayInputStream input; + + Deserializer(String token) { + this.input = new ByteArrayInputStream(Base64.getUrlDecoder().decode(token)); + } + + List deserialize(TokenDefinition definition) throws IOException { + try { + // Assume deprecated fields are included in the token + return readFields(definition, false); + } catch (IOException ignore) { + // If the token is the next version, then deprecated field are removed. Try + // skipping the deprecated tokens + return readFields(definition, true); + } + } + + private List readFields(TokenDefinition definition, boolean matchNewVersionPlusOne) + throws IOException { + input.reset(); + List result = new ArrayList<>(); + + var in = new ObjectInputStream(input); + + readAndMatchVersion(in, definition, matchNewVersionPlusOne); + + for (FieldDefinition field : definition.listFields()) { + if (matchNewVersionPlusOne && field.deprecated()) { + continue; + } + var v = read(in, field); + if (!field.deprecated()) { + result.add(v); + } + } + return result; + } + + private void readAndMatchVersion( + ObjectInputStream in, + TokenDefinition definition, + boolean matchVersionPlusOne + ) throws IOException { + int matchVersion = (matchVersionPlusOne ? 1 : 0) + definition.version(); + + int v = readInt(in); + if (v != matchVersion) { + throw new IOException( + "Version does not match. Token version: " + v + ", schema version: " + definition.version() + ); + } + } + + private Object read(ObjectInputStream in, FieldDefinition field) throws IOException { + return switch (field.type()) { + case BYTE -> readByte(in); + case DURATION -> readDuration(in); + case INT -> readInt(in); + case STRING -> readString(in); + case TIME_INSTANT -> readTimeInstant(in); + }; + } + + private static byte readByte(ObjectInputStream in) throws IOException { + return in.readByte(); + } + + private static int readInt(ObjectInputStream in) throws IOException { + return Integer.parseInt(in.readUTF()); + } + + private static String readString(ObjectInputStream in) throws IOException { + return in.readUTF(); + } + + private static Duration readDuration(ObjectInputStream in) throws IOException { + return DurationUtils.duration(in.readUTF()); + } + + @Nullable + private static Instant readTimeInstant(ObjectInputStream in) throws IOException { + return Instant.parse(in.readUTF()); + } +} diff --git a/src/main/java/org/opentripplanner/framework/token/FieldDefinition.java b/src/main/java/org/opentripplanner/framework/token/FieldDefinition.java new file mode 100644 index 00000000000..12f529c1b2f --- /dev/null +++ b/src/main/java/org/opentripplanner/framework/token/FieldDefinition.java @@ -0,0 +1,54 @@ +package org.opentripplanner.framework.token; + +import java.util.Objects; + +class FieldDefinition { + + private final String name; + private final TokenType type; + private final boolean deprecated; + + private FieldDefinition(String name, TokenType type, boolean deprecated) { + this.name = Objects.requireNonNull(name); + this.type = Objects.requireNonNull(type); + this.deprecated = deprecated; + } + + public FieldDefinition(String name, TokenType type) { + this(name, type, false); + } + + public String name() { + return name; + } + + public TokenType type() { + return type; + } + + public boolean deprecated() { + return deprecated; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + FieldDefinition that = (FieldDefinition) o; + return deprecated == that.deprecated && Objects.equals(name, that.name) && type == that.type; + } + + @Override + public int hashCode() { + return Objects.hash(name, type, deprecated); + } + + @Override + public String toString() { + return (deprecated ? "@deprecated " : "") + name + ":" + type; + } + + public FieldDefinition deprecate() { + return new FieldDefinition(name, type, true); + } +} diff --git a/src/main/java/org/opentripplanner/framework/token/Serializer.java b/src/main/java/org/opentripplanner/framework/token/Serializer.java new file mode 100644 index 00000000000..289d4b283d8 --- /dev/null +++ b/src/main/java/org/opentripplanner/framework/token/Serializer.java @@ -0,0 +1,64 @@ +package org.opentripplanner.framework.token; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.time.Duration; +import java.time.Instant; +import java.util.Base64; +import org.opentripplanner.framework.time.DurationUtils; + +class Serializer { + + private final TokenDefinition definition; + private final ByteArrayOutputStream buf = new ByteArrayOutputStream(); + + Serializer(TokenDefinition definition) { + this.definition = definition; + } + + String serialize(Object[] values) throws IOException { + try (var out = new ObjectOutputStream(buf)) { + writeInt(out, definition.version()); + + for (var fieldName : definition.fieldNames()) { + var value = values[definition.index(fieldName)]; + write(out, fieldName, value); + } + out.flush(); + } + return Base64.getUrlEncoder().encodeToString(buf.toByteArray()); + } + + private void write(ObjectOutputStream out, String fieldName, Object value) throws IOException { + var type = definition.type(fieldName); + switch (type) { + case BYTE -> writeByte(out, (byte) value); + case DURATION -> writeDuration(out, (Duration) value); + case INT -> writeInt(out, (int) value); + case STRING -> writeString(out, (String) value); + case TIME_INSTANT -> writeTimeInstant(out, (Instant) value); + default -> throw new IllegalArgumentException("Unknown type: " + type); + } + } + + private static void writeByte(ObjectOutputStream out, byte value) throws IOException { + out.writeByte(value); + } + + private static void writeInt(ObjectOutputStream out, int value) throws IOException { + out.writeUTF(Integer.toString(value)); + } + + private static void writeString(ObjectOutputStream out, String value) throws IOException { + out.writeUTF(value); + } + + private static void writeDuration(ObjectOutputStream out, Duration duration) throws IOException { + out.writeUTF(DurationUtils.durationToStr(duration)); + } + + private static void writeTimeInstant(ObjectOutputStream out, Instant time) throws IOException { + out.writeUTF(time.toString()); + } +} diff --git a/src/main/java/org/opentripplanner/framework/token/Token.java b/src/main/java/org/opentripplanner/framework/token/Token.java new file mode 100644 index 00000000000..0d527a16cbe --- /dev/null +++ b/src/main/java/org/opentripplanner/framework/token/Token.java @@ -0,0 +1,61 @@ +package org.opentripplanner.framework.token; + +import java.time.Duration; +import java.time.Instant; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * Given a schema definition and a token version this class holds the values for + * all fields in a token. + */ +public class Token { + + private final TokenDefinition definition; + private final List fieldValues; + + Token(TokenDefinition definition, List fieldValues) { + this.definition = Objects.requireNonNull(definition); + this.fieldValues = Objects.requireNonNull(fieldValues); + } + + public int version() { + return definition.version(); + } + + public byte getByte(String fieldName) { + return (byte) get(fieldName, TokenType.BYTE); + } + + public Duration getDuration(String fieldName) { + return (Duration) get(fieldName, TokenType.DURATION); + } + + public int getInt(String fieldName) { + return (int) get(fieldName, TokenType.INT); + } + + public String getString(String fieldName) { + return (String) get(fieldName, TokenType.STRING); + } + + public Instant getTimeInstant(String fieldName) { + return (Instant) get(fieldName, TokenType.TIME_INSTANT); + } + + private Object get(String fieldName, TokenType type) { + return fieldValues.get(definition.getIndex(fieldName, type)); + } + + @Override + public String toString() { + return ( + "(v" + + version() + + ", " + + fieldValues.stream().map(Objects::toString).collect(Collectors.joining(", ")) + + ')' + ); + } +} diff --git a/src/main/java/org/opentripplanner/framework/token/TokenBuilder.java b/src/main/java/org/opentripplanner/framework/token/TokenBuilder.java new file mode 100644 index 00000000000..da9e9af8b7e --- /dev/null +++ b/src/main/java/org/opentripplanner/framework/token/TokenBuilder.java @@ -0,0 +1,55 @@ +package org.opentripplanner.framework.token; + +import java.io.IOException; +import java.time.Duration; +import java.time.Instant; +import org.opentripplanner.framework.lang.ObjectUtils; + +/** + * This class is used to create a {@link Token} before encoding it. + */ +public class TokenBuilder { + + private final TokenDefinition definition; + private final Object[] values; + + public TokenBuilder(TokenDefinition definition) { + this.definition = definition; + this.values = new Object[definition.size()]; + } + + public TokenBuilder withByte(String fieldName, byte v) { + return with(fieldName, TokenType.BYTE, v); + } + + public TokenBuilder withDuration(String fieldName, Duration v) { + return with(fieldName, TokenType.DURATION, v); + } + + public TokenBuilder withInt(String fieldName, int v) { + return with(fieldName, TokenType.INT, v); + } + + public TokenBuilder withString(String fieldName, String v) { + return with(fieldName, TokenType.STRING, v); + } + + public TokenBuilder withTimeInstant(String fieldName, Instant v) { + return with(fieldName, TokenType.TIME_INSTANT, v); + } + + public String build() { + try { + return new Serializer(definition).serialize(values); + } catch (IOException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + private TokenBuilder with(String fieldName, TokenType type, Object value) { + int index = definition.getIndex(fieldName, type); + ObjectUtils.requireNotInitialized(fieldName, values[index], value); + values[index] = value; + return this; + } +} diff --git a/src/main/java/org/opentripplanner/framework/token/TokenDefinition.java b/src/main/java/org/opentripplanner/framework/token/TokenDefinition.java new file mode 100644 index 00000000000..a61bf769e76 --- /dev/null +++ b/src/main/java/org/opentripplanner/framework/token/TokenDefinition.java @@ -0,0 +1,107 @@ +package org.opentripplanner.framework.token; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import org.opentripplanner.framework.tostring.ToStringBuilder; + +/** + * A token definition is an ordered list of files. A field has a name and a type. The + * definition is used to encode/decode a token. + */ +public class TokenDefinition { + + private final int version; + private final List fieldNames; + private final Map fields; + + TokenDefinition(int version, List fields) { + this.version = version; + this.fieldNames = fields.stream().map(FieldDefinition::name).toList(); + this.fields = immutableMapOf(fields); + } + + public int version() { + return version; + } + + public List fieldNames() { + return fieldNames; + } + + public TokenType type(String fieldName) { + return fields.get(fieldName).type(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TokenDefinition that = (TokenDefinition) o; + return version == that.version && listFields().equals(that.listFields()); + } + + @Override + public int hashCode() { + return Objects.hash(version, listFields()); + } + + @Override + public String toString() { + return ToStringBuilder + .of(TokenDefinition.class) + .addNum("version", version, 0) + .addCol("fields", listFields()) + .toString(); + } + + int size() { + return fieldNames.size(); + } + + int getIndex(String name, TokenType assertType) { + assertType(name, assertType); + return index(name); + } + + int index(String name) { + for (int i = 0; i < fieldNames.size(); ++i) { + if (fieldNames.get(i).equals(name)) { + return i; + } + } + throw unknownFieldNameException(name); + } + + List listNoneDeprecatedFields() { + return listFields().stream().filter(it -> !it.deprecated()).toList(); + } + + List listFields() { + return fieldNames.stream().map(fields::get).toList(); + } + + private void assertType(String name, TokenType assertType) { + Objects.requireNonNull(name); + var field = fields.get(name); + + if (field == null) { + throw unknownFieldNameException(name); + } + + if (field.type().isNot(assertType)) { + throw new IllegalArgumentException( + "The defined type for '" + name + "' is " + field.type() + " not " + assertType + "." + ); + } + } + + private IllegalArgumentException unknownFieldNameException(String name) { + return new IllegalArgumentException("Unknown field: '" + name + "'"); + } + + private static Map immutableMapOf(List fields) { + return Map.copyOf(fields.stream().collect(Collectors.toMap(FieldDefinition::name, it -> it))); + } +} diff --git a/src/main/java/org/opentripplanner/framework/token/TokenDefinitionBuilder.java b/src/main/java/org/opentripplanner/framework/token/TokenDefinitionBuilder.java new file mode 100644 index 00000000000..513fd2e61d3 --- /dev/null +++ b/src/main/java/org/opentripplanner/framework/token/TokenDefinitionBuilder.java @@ -0,0 +1,88 @@ +package org.opentripplanner.framework.token; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; +import org.opentripplanner.framework.lang.IntUtils; + +public class TokenDefinitionBuilder { + + private final int version; + private final List tokensHead = new ArrayList<>(); + private final List fields = new ArrayList<>(); + + TokenDefinitionBuilder(int version) { + this.version = IntUtils.requireInRange(version, 1, 1_000_000); + } + + TokenDefinitionBuilder(List head, TokenDefinition last) { + this(last.version() + 1); + this.fields.addAll(last.listNoneDeprecatedFields()); + this.tokensHead.addAll(head); + this.tokensHead.add(last); + } + + public TokenDefinitionBuilder addByte(String fieldName) { + return add(fieldName, TokenType.BYTE); + } + + public TokenDefinitionBuilder addDuration(String fieldName) { + return add(fieldName, TokenType.DURATION); + } + + public TokenDefinitionBuilder addInt(String fieldName) { + return add(fieldName, TokenType.INT); + } + + public TokenDefinitionBuilder addString(String fieldName) { + return add(fieldName, TokenType.STRING); + } + + public TokenDefinitionBuilder addTimeInstant(String fieldName) { + return add(fieldName, TokenType.TIME_INSTANT); + } + + /** + * A deprecated field will be removed from the *next* token. A value must be provided for the + * deprecated field when encoding the current version. But, you can not read it! This make sure + * that the previous version sees the deprecated value, while this version will still work with + * a token provided with the next version. The deprecated field is automatically removed from + * the next version. + */ + public TokenDefinitionBuilder deprecate(String fieldName) { + int index = indexOfField(fieldName); + if (index < 0) { + throw new IllegalArgumentException( + "The field '" + fieldName + "' does not exist! Deprecation failed." + ); + } + fields.set(index, fields.get(index).deprecate()); + return this; + } + + private int indexOfField(String fieldName) { + for (int i = 0; i < fields.size(); i++) { + if (fields.get(i).name().equals(fieldName)) { + return i; + } + } + return -1; + } + + public TokenDefinitionBuilder newVersion() { + return new TokenDefinitionBuilder(tokensHead, buildIt()); + } + + public TokenSchema build() { + return new TokenSchema(Stream.concat(tokensHead.stream(), Stream.of(buildIt())).toList()); + } + + private TokenDefinition buildIt() { + return new TokenDefinition(version, fields); + } + + private TokenDefinitionBuilder add(String fieldName, TokenType type) { + fields.add(new FieldDefinition(fieldName, type)); + return this; + } +} diff --git a/src/main/java/org/opentripplanner/framework/token/TokenSchema.java b/src/main/java/org/opentripplanner/framework/token/TokenSchema.java new file mode 100644 index 00000000000..33300126fdb --- /dev/null +++ b/src/main/java/org/opentripplanner/framework/token/TokenSchema.java @@ -0,0 +1,81 @@ +package org.opentripplanner.framework.token; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.opentripplanner.framework.tostring.ToStringBuilder; + +/** + * A token schema contains a set of token definitions, one for each version. This is + * used to decode a token - the same version used to encode a token is used to + * decode it. When encodeing a token the latest version is always used. + *

+ * OTP only need to be backward compatible with the last version of otp. So, for each release of + * OTP the schema that is older than the previous version can be merged. By doing so, you do not + * need to keep code around to support very old versions. + */ +public class TokenSchema { + + private final List definitions; + + TokenSchema(List definitions) { + // Reverse the list so the newest version is first and the oldest version is last + var list = new ArrayList<>(definitions); + Collections.reverse(list); + this.definitions = List.copyOf(list); + } + + /** + * Define a set of versioned tokens. The version number for each will be auto-incremented. + * The provided {@code baseVersion} specify the version for the first token defined. + *

+ * Old unused tokens definitions can merged into the first used definition. When this is done + * the "new" base should be given the exact same version number as it had before. The best way to + * ensure this is to add unit tests for all active version. If done right, the tests should not + * change when merging the versions. + *

+ * Take a look at the unit tests to see an example on merging a schema. + *

+ * @param baseVersion The initial version for the first definition. The version number is + * automatically incremented when new definitions are added. If there is many + * definitions, the oldest definitions can be merged into one. The new + * definition base version should match the highest version number of the + * definitions merged into one. For example, if version 3, 4 and 5 is merged, + * the new merged base version is 5. Legal range is [1, 1_000_000] + */ + public static TokenDefinitionBuilder ofVersion(int baseVersion) { + return new TokenDefinitionBuilder(baseVersion); + } + + public Token decode(String token) { + var deserializer = new Deserializer(token); + for (TokenDefinition definition : definitions) { + try { + return new Token(definition, deserializer.deserialize(definition)); + } catch (Exception ignore) {} + } + throw new IllegalArgumentException( + "Token is not valid. Unable to parse token: '" + token + "'." + ); + } + + public TokenBuilder encode() { + return new TokenBuilder(currentDefinition()); + } + + /** + * We iterate over definitions in REVERSE order, because we want to use the + * latest version. + */ + public TokenDefinition currentDefinition() { + return definitions.get(0); + } + + @Override + public String toString() { + return ToStringBuilder + .of(TokenSchema.class) + .addObj("definition", currentDefinition()) + .toString(); + } +} diff --git a/src/main/java/org/opentripplanner/framework/token/TokenType.java b/src/main/java/org/opentripplanner/framework/token/TokenType.java new file mode 100644 index 00000000000..0a890a44005 --- /dev/null +++ b/src/main/java/org/opentripplanner/framework/token/TokenType.java @@ -0,0 +1,20 @@ +package org.opentripplanner.framework.token; + +/** + * List of types we can store in a token. + *

+ * Enums are not safe, so do not add support for them. The reason is that new values can be added + * to the enum and the previous version will fail to read the new version - it is no longer forward + * compatible with the new value of the enum. + */ +public enum TokenType { + BYTE, + DURATION, + INT, + STRING, + TIME_INSTANT; + + boolean isNot(TokenType other) { + return this != other; + } +} diff --git a/src/test/java/org/opentripplanner/framework/lang/ObjectUtilsTest.java b/src/test/java/org/opentripplanner/framework/lang/ObjectUtilsTest.java index 47389348c76..9237b221fe5 100644 --- a/src/test/java/org/opentripplanner/framework/lang/ObjectUtilsTest.java +++ b/src/test/java/org/opentripplanner/framework/lang/ObjectUtilsTest.java @@ -34,10 +34,18 @@ void ifNotNullFunctional() { @Test void requireNotInitialized() { assertEquals("new", ObjectUtils.requireNotInitialized(null, "new")); - assertThrows( + var ex = assertThrows( IllegalStateException.class, - () -> ObjectUtils.requireNotInitialized("old", "new") + () -> ObjectUtils.requireNotInitialized("foo", "old", "new") ); + assertEquals("Field foo is already set! Old value: old, new value: new.", ex.getMessage()); + + ex = + assertThrows( + IllegalStateException.class, + () -> ObjectUtils.requireNotInitialized("old", "new") + ); + assertEquals("Field is already set! Old value: old, new value: new.", ex.getMessage()); } @Test diff --git a/src/test/java/org/opentripplanner/framework/token/AdvancedTokenSchemaTest.java b/src/test/java/org/opentripplanner/framework/token/AdvancedTokenSchemaTest.java new file mode 100644 index 00000000000..263b4bdb844 --- /dev/null +++ b/src/test/java/org/opentripplanner/framework/token/AdvancedTokenSchemaTest.java @@ -0,0 +1,182 @@ +package org.opentripplanner.framework.token; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.framework.token.AdvancedTokenSchemaTest.TestCase.testCase; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +class AdvancedTokenSchemaTest implements TokenSchemaConstants { + + private static final List TEST_CASES = new ArrayList<>(); + + static { + // Version 1: [BYTE] + var builder = TokenSchema.ofVersion(1).addByte(BYTE_FIELD); + TEST_CASES.add( + TestCase.testCase(builder, "(v1, 17)", it -> it.encode().withByte(BYTE_FIELD, BYTE_VALUE)) + ); + + // Version 2: [BYTE, DURATION, INT] + builder = builder.newVersion().addDuration(DURATION_FIELD).addInt(INT_FIELD); + TEST_CASES.add( + testCase( + builder, + "(v2, 17, PT2M13S, 31)", + // We can add named fields in any order(token order: byte,Duration,Int) + it -> + it + .encode() + .withInt(INT_FIELD, INT_VALUE) + .withByte(BYTE_FIELD, BYTE_VALUE) + .withDuration(DURATION_FIELD, DURATION_VALUE) + ) + ); + + // Version 3 - BYTE, @deprecated DURATION, INT + builder = builder.newVersion().deprecate(DURATION_FIELD); + TEST_CASES.add( + testCase( + builder, + "(v3, 17, 31)", + it -> + it + .encode() + .withInt(INT_FIELD, INT_VALUE) + .withByte(BYTE_FIELD, BYTE_VALUE) + .withDuration(DURATION_FIELD, DURATION_VALUE) + ) + ); + + // Version 4 - BYTE, INT, STRING + builder = builder.newVersion().addString(STRING_FIELD); + TEST_CASES.add( + testCase( + builder, + "(v4, 17, 31, text)", + it -> + it + .encode() + .withInt(INT_FIELD, INT_VALUE) + .withByte(BYTE_FIELD, BYTE_VALUE) + .withString(STRING_FIELD, STRING_VALUE) + ) + ); + + // Version 5 - @deprecated BYTE, INT, STRING, TIME_INSTANT + builder = builder.newVersion().deprecate(BYTE_FIELD).addTimeInstant(TIME_INSTANT_FIELD); + TEST_CASES.add( + testCase( + builder, + "(v5, 31, text, 2023-10-23T10:00:59Z)", + it -> + it + .encode() + .withInt(INT_FIELD, INT_VALUE) + .withByte(BYTE_FIELD, BYTE_VALUE) + .withTimeInstant(TIME_INSTANT_FIELD, TIME_INSTANT_VALUE) + .withString(STRING_FIELD, STRING_VALUE) + ) + ); + // Version 6 - INT, STRING, TIME_INSTANT + builder = builder.newVersion(); + TEST_CASES.add( + testCase( + builder, + "(v6, 31, text, 2023-10-23T10:00:59Z)", + it -> + it + .encode() + .withInt(INT_FIELD, INT_VALUE) + .withTimeInstant(TIME_INSTANT_FIELD, TIME_INSTANT_VALUE) + .withString(STRING_FIELD, STRING_VALUE) + ) + ); + } + + private static List testCases() { + return TEST_CASES; + } + + @ParameterizedTest + @MethodSource(value = "testCases") + void testDecodeBackwardsCompatibility(TestCase testCase) { + allTestCasesFrom(testCase) + .forEach(s -> assertEquals(testCase.expected(), s.decode(testCase.token()).toString())); + } + + @ParameterizedTest + @MethodSource(value = "testCases") + void testDecodeForwardCompatibility(TestCase testCase) { + nextTestCase(testCase) + .map(TestCase::token) + .ifPresent(nextVersionToken -> + assertEquals(testCase.expected(), testCase.subject().decode(nextVersionToken).toString()) + ); + } + + @Test + void testMerge() { + var merged = TokenSchema + .ofVersion(6) + .addInt(INT_FIELD) + .addString(STRING_FIELD) + .addTimeInstant(TIME_INSTANT_FIELD) + .build(); + + var subjectV6 = TEST_CASES.get(5).subject(); + assertEquals(merged.currentDefinition(), subjectV6.currentDefinition()); + } + + @Test + void testDefinitionToString() { + var expected = List.of( + "TokenDefinition{version: 1, fields: [AByte:BYTE]}", + "TokenDefinition{version: 2, fields: [AByte:BYTE, ADur:DURATION, ANum:INT]}", + "TokenDefinition{version: 3, fields: [AByte:BYTE, @deprecated ADur:DURATION, ANum:INT]}", + "TokenDefinition{version: 4, fields: [AByte:BYTE, ANum:INT, AStr:STRING]}", + "TokenDefinition{version: 5, fields: [@deprecated AByte:BYTE, ANum:INT, AStr:STRING, ATime:TIME_INSTANT]}", + "TokenDefinition{version: 6, fields: [ANum:INT, AStr:STRING, ATime:TIME_INSTANT]}" + ); + for (int i = 0; i < TEST_CASES.size(); i++) { + assertEquals(expected.get(i), TEST_CASES.get(i).subject().currentDefinition().toString()); + } + } + + /** + * List of all test-cases including the given test-case until the end of all test-cases + */ + private static Stream allTestCasesFrom(TestCase testCase) { + return TEST_CASES + .subList(TEST_CASES.indexOf(testCase), TEST_CASES.size() - 1) + .stream() + .map(TestCase::subject); + } + + private static Optional nextTestCase(TestCase testCase) { + int index = TEST_CASES.indexOf(testCase) + 1; + return index < TEST_CASES.size() ? Optional.of(TEST_CASES.get(index)) : Optional.empty(); + } + + record TestCase(TokenSchema subject, String expected, String token) { + static TestCase testCase( + TokenDefinitionBuilder definitionBuilder, + String expected, + Function builder + ) { + var schema = definitionBuilder.build(); + return new TestCase(schema, expected, builder.apply(schema).build()); + } + + @Override + public String toString() { + return subject.currentDefinition().toString(); + } + } +} diff --git a/src/test/java/org/opentripplanner/framework/token/TokenSchemaConstants.java b/src/test/java/org/opentripplanner/framework/token/TokenSchemaConstants.java new file mode 100644 index 00000000000..fd6c38461b3 --- /dev/null +++ b/src/test/java/org/opentripplanner/framework/token/TokenSchemaConstants.java @@ -0,0 +1,27 @@ +package org.opentripplanner.framework.token; + +import java.time.Duration; +import java.time.Instant; +import org.opentripplanner.framework.time.DurationUtils; + +public interface TokenSchemaConstants { + // Token field names. These are used to reference a specific field value in theString BYTE_FIELD = "AByte"; + // token to avoid index errors. They are not part of the serialized token.String DURATION_FIELD = "ADur"; + String BYTE_FIELD = "AByte"; + String DURATION_FIELD = "ADur"; + String INT_FIELD = "ANum"; + String STRING_FIELD = "AStr"; + String TIME_INSTANT_FIELD = "ATime"; + + byte BYTE_VALUE = 17; + Duration DURATION_VALUE = DurationUtils.duration("2m13s"); + int INT_VALUE = 31; + String STRING_VALUE = "text"; + Instant TIME_INSTANT_VALUE = Instant.parse("2023-10-23T10:00:59Z"); + + String BYTE_ENCODED = "rO0ABXcEAAExEQ=="; + String DURATION_ENCODED = "rO0ABXcKAAEyAAUybTEzcw=="; + String INT_ENCODED = "rO0ABXcHAAEzAAIzMQ=="; + String STRING_ENCODED = "rO0ABXcJAAE3AAR0ZXh0"; + String TIME_INSTANT_ENCODED = "rO0ABXcaAAIxMwAUMjAyMy0xMC0yM1QxMDowMDo1OVo="; +} diff --git a/src/test/java/org/opentripplanner/framework/token/TokenSchemaTest.java b/src/test/java/org/opentripplanner/framework/token/TokenSchemaTest.java new file mode 100644 index 00000000000..4a6d00c5e68 --- /dev/null +++ b/src/test/java/org/opentripplanner/framework/token/TokenSchemaTest.java @@ -0,0 +1,140 @@ +package org.opentripplanner.framework.token; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class TokenSchemaTest implements TokenSchemaConstants { + + // Token field names. These are used to reference a specific field value in the + // token to avoid index errors. They are not part of the serialized token. + + private static final TokenSchema BYTE_SCHEMA = TokenSchema + .ofVersion(1) + .addByte(BYTE_FIELD) + .build(); + private static final TokenSchema DURATION_SCHEMA = TokenSchema + .ofVersion(2) + .addDuration(DURATION_FIELD) + .build(); + private static final TokenSchema INT_SCHEMA = TokenSchema.ofVersion(3).addInt(INT_FIELD).build(); + private static final TokenSchema STRING_SCHEMA = TokenSchema + .ofVersion(7) + .addString(STRING_FIELD) + .build(); + private static final TokenSchema TIME_INSTANT_SCHEMA = TokenSchema + .ofVersion(13) + .addTimeInstant(TIME_INSTANT_FIELD) + .build(); + + @Test + public void encodeByte() { + var token = BYTE_SCHEMA.encode().withByte(BYTE_FIELD, (byte) 17).build(); + assertEquals(BYTE_ENCODED, token); + assertEquals(BYTE_VALUE, BYTE_SCHEMA.decode(token).getByte(BYTE_FIELD)); + } + + @Test + public void testDuration() { + var token = DURATION_SCHEMA.encode().withDuration(DURATION_FIELD, DURATION_VALUE).build(); + assertEquals(DURATION_ENCODED, token); + assertEquals(DURATION_VALUE, DURATION_SCHEMA.decode(token).getDuration(DURATION_FIELD)); + } + + @Test + public void testInt() { + var token = INT_SCHEMA.encode().withInt(INT_FIELD, INT_VALUE).build(); + assertEquals(INT_ENCODED, token); + assertEquals(INT_VALUE, INT_SCHEMA.decode(token).getInt(INT_FIELD)); + } + + @Test + public void testString() { + var token = STRING_SCHEMA.encode().withString(STRING_FIELD, STRING_VALUE).build(); + assertEquals(STRING_ENCODED, token); + assertEquals(STRING_VALUE, STRING_SCHEMA.decode(token).getString(STRING_FIELD)); + } + + @Test + public void encodeTimeInstant() { + var token = TIME_INSTANT_SCHEMA + .encode() + .withTimeInstant(TIME_INSTANT_FIELD, TIME_INSTANT_VALUE) + .build(); + assertEquals(TIME_INSTANT_ENCODED, token); + assertEquals( + TIME_INSTANT_VALUE, + TIME_INSTANT_SCHEMA.decode(token).getTimeInstant(TIME_INSTANT_FIELD) + ); + } + + @Test + public void encodeUndefinedFields() { + var ex = Assertions.assertThrows( + IllegalArgumentException.class, + () -> INT_SCHEMA.encode().withString("foo", "A") + ); + assertEquals("Unknown field: 'foo'", ex.getMessage()); + + Assertions.assertThrows( + NullPointerException.class, + () -> BYTE_SCHEMA.encode().withString(null, "A") + ); + } + + @Test + public void encodeFieldValueWithTypeMismatch() { + var ex = Assertions.assertThrows( + IllegalArgumentException.class, + () -> STRING_SCHEMA.encode().withByte(STRING_FIELD, (byte) 12) + ); + assertEquals("The defined type for 'AStr' is STRING not BYTE.", ex.getMessage()); + } + + @Test + public void decodeUndefinedToken() { + var ex = Assertions.assertThrows( + IllegalArgumentException.class, + () -> INT_SCHEMA.decode("foo") + ); + assertEquals("Token is not valid. Unable to parse token: 'foo'.", ex.getMessage()); + } + + @Test + public void testToString() { + assertEquals( + "TokenSchema{definition: TokenDefinition{version: 1, fields: [AByte:BYTE]}}", + BYTE_SCHEMA.toString() + ); + assertEquals( + "TokenSchema{definition: TokenDefinition{version: 3, fields: [ANum:INT]}}", + INT_SCHEMA.toString() + ); + assertEquals( + "TokenSchema{definition: TokenDefinition{version: 7, fields: [AStr:STRING]}}", + STRING_SCHEMA.toString() + ); + assertEquals( + "TokenSchema{definition: TokenDefinition{version: 2, fields: [ADur:DURATION]}}", + DURATION_SCHEMA.toString() + ); + assertEquals( + "TokenSchema{definition: TokenDefinition{version: 13, fields: [ATime:TIME_INSTANT]}}", + TIME_INSTANT_SCHEMA.toString() + ); + } + + @Test + void testDefinitionEqualsAndHashCode() { + var subject = BYTE_SCHEMA.currentDefinition(); + var same = TokenSchema.ofVersion(1).addByte(BYTE_FIELD).build().currentDefinition(); + + assertEquals(subject, same); + assertEquals(subject.hashCode(), same.hashCode()); + + assertNotEquals(subject, INT_SCHEMA.currentDefinition()); + assertNotEquals(subject.hashCode(), INT_SCHEMA.currentDefinition().hashCode()); + } +} From aabd51508ac8a3e48fcb6b315df40e5d44ab1b18 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 3 Nov 2023 17:48:42 +0100 Subject: [PATCH 16/85] test: Add transit-priority-group test to Raptor module tests --- .../raptor/_data/transit/TestTripPattern.java | 11 +- .../moduletests/K01_TransitPriorityTest.java | 135 ++++++++++++++++++ 2 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 src/test/java/org/opentripplanner/raptor/moduletests/K01_TransitPriorityTest.java diff --git a/src/test/java/org/opentripplanner/raptor/_data/transit/TestTripPattern.java b/src/test/java/org/opentripplanner/raptor/_data/transit/TestTripPattern.java index 1b68063259f..740010a8a4f 100644 --- a/src/test/java/org/opentripplanner/raptor/_data/transit/TestTripPattern.java +++ b/src/test/java/org/opentripplanner/raptor/_data/transit/TestTripPattern.java @@ -20,6 +20,8 @@ public class TestTripPattern implements DefaultTripPattern { private int patternIndex = 0; + private int priorityGroupId = 0; + /** *

    * 0 - 000 : No restriction
@@ -57,6 +59,12 @@ TestTripPattern withPatternIndex(int index) {
     return this;
   }
 
+  public TestTripPattern withPriorityGroup(int priorityGroupId) {
+    this.priorityGroupId = priorityGroupId;
+    return this;
+  }
+
+
   public TestTripPattern withRoute(Route route) {
     this.route = route;
     return this;
@@ -119,8 +127,7 @@ public int slackIndex() {
 
   @Override
   public int priorityGroupId() {
-    // TODO C2 - Add routing tests for priority groups later ...
-    throw new UnsupportedOperationException();
+    return priorityGroupId;
   }
 
   @Override
diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/K01_TransitPriorityTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/K01_TransitPriorityTest.java
new file mode 100644
index 00000000000..216b3428f7a
--- /dev/null
+++ b/src/test/java/org/opentripplanner/raptor/moduletests/K01_TransitPriorityTest.java
@@ -0,0 +1,135 @@
+package org.opentripplanner.raptor.moduletests;
+
+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 static org.opentripplanner.raptor._data.RaptorTestConstants.STOP_B;
+import static org.opentripplanner.raptor._data.RaptorTestConstants.STOP_C;
+import static org.opentripplanner.raptor._data.RaptorTestConstants.T00_00;
+import static org.opentripplanner.raptor._data.RaptorTestConstants.T01_00;
+import static org.opentripplanner.raptor._data.api.PathUtils.pathsToString;
+import static org.opentripplanner.raptor._data.transit.TestAccessEgress.walk;
+import static org.opentripplanner.raptor._data.transit.TestRoute.route;
+import static org.opentripplanner.raptor._data.transit.TestTripPattern.pattern;
+import static org.opentripplanner.raptor._data.transit.TestTripSchedule.schedule;
+
+import java.time.Duration;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.opentripplanner.raptor.RaptorService;
+import org.opentripplanner.raptor._data.transit.TestTransitData;
+import org.opentripplanner.raptor._data.transit.TestTripSchedule;
+import org.opentripplanner.raptor.api.model.DominanceFunction;
+import org.opentripplanner.raptor.api.request.RaptorProfile;
+import org.opentripplanner.raptor.api.request.RaptorRequestBuilder;
+import org.opentripplanner.raptor.api.request.RaptorTransitPriorityGroupCalculator;
+import org.opentripplanner.raptor.configure.RaptorConfig;
+import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.RaptorCostConverter;
+
+/**
+ * FEATURE UNDER TEST
+ *
+ * Raptor should be able to handle route request with transit-priority.
+ */
+public class K01_TransitPriorityTest {
+
+  private static final RaptorTransitPriorityGroupCalculator PRIORITY_GROUP_CALCULATOR = new RaptorTransitPriorityGroupCalculator() {
+    @Override
+    public int mergeTransitPriorityGroupIds(int currentGroupIds, int boardingGroupId) {
+      return currentGroupIds | boardingGroupId;
+    }
+
+    /**
+     * Left dominate right, if right has at least one priority group not in left.
+     */
+    @Override
+    public DominanceFunction dominanceFunction() {
+      return (l, r) -> ((l ^ r) & r) != 0;
+    }
+  };
+
+  private static final int GROUP_A = 0x01;
+  private static final int GROUP_B = 0x02;
+  private static final int GROUP_C = 0x04;
+  private static final int GROUP_AB = PRIORITY_GROUP_CALCULATOR.mergeTransitPriorityGroupIds(
+    GROUP_A,
+    GROUP_B
+  );
+  private static final int GROUP_AC = PRIORITY_GROUP_CALCULATOR.mergeTransitPriorityGroupIds(
+    GROUP_A,
+    GROUP_C
+  );
+  private static final int COST_SLACK_90s = RaptorCostConverter.toRaptorCost(90);
+
+  private final TestTransitData data = new TestTransitData();
+  private final RaptorRequestBuilder requestBuilder = new RaptorRequestBuilder<>();
+  private final RaptorService raptorService = new RaptorService<>(
+    RaptorConfig.defaultConfigForTest()
+  );
+
+  @BeforeEach
+  private void prepareRequest() {
+    // Each pattern depart at the same time, but arrive with 60s between them.
+    // Given a slack on the cost equals to ~90s make both L1 and L2 optimal, but no L3
+    data.withRoutes(
+      route(pattern("L1", STOP_B, STOP_C).withPriorityGroup(GROUP_A))
+        .withTimetable(schedule("00:02 00:12")),
+      route(pattern("L2", STOP_B, STOP_C).withPriorityGroup(GROUP_B))
+        .withTimetable(schedule("00:02 00:13")),
+      route(pattern("L3", STOP_B, STOP_C).withPriorityGroup(GROUP_C))
+        .withTimetable(schedule("00:02 00:14"))
+    );
+
+    requestBuilder
+      .profile(RaptorProfile.MULTI_CRITERIA)
+      // TODO: 2023-07-24 Currently heuristics does not work with pass-through so we
+      //  have to turn them off. Make sure to re-enable optimization later when it's fixed
+      .clearOptimizations();
+
+    requestBuilder
+      .searchParams()
+      .earliestDepartureTime(T00_00)
+      .latestArrivalTime(T01_00)
+      .searchWindow(Duration.ofMinutes(2))
+      .timetable(true);
+
+    requestBuilder.withMultiCriteria(mc ->
+      // Raptor cost 9000 ~= 90 seconds slack
+      mc
+        .withRelaxC1(value -> value + COST_SLACK_90s)
+        .withTransitPriorityCalculator(PRIORITY_GROUP_CALCULATOR)
+    );
+    // Add 1 second access/egress paths
+    requestBuilder.searchParams().addAccessPaths(walk(STOP_B, 1)).addEgressPaths(walk(STOP_C, 1));
+    assetGroupCalculatorIsSetupCorrect();
+  }
+
+  @Test
+  public void transitPriority() {
+    // We expect L1 & L2 but not L3, since the cost of L3 is > $90.00.
+    assertEquals(
+      """
+      Walk 1s ~ B ~ BUS L1 0:02 0:12 ~ C ~ Walk 1s [0:01:59 0:12:01 10m2s 0tx $1204]
+      Walk 1s ~ B ~ BUS L2 0:02 0:13 ~ C ~ Walk 1s [0:01:59 0:13:01 11m2s 0tx $1264]
+      """.trim(),
+      pathsToString(raptorService.route(requestBuilder.build(), data))
+    );
+  }
+
+  /**
+   * Make sure the calculator and group setup is done correct.
+   */
+  void assetGroupCalculatorIsSetupCorrect() {
+    var d = PRIORITY_GROUP_CALCULATOR.dominanceFunction();
+
+    assertTrue(d.leftDominateRight(GROUP_A, GROUP_B));
+    assertTrue(d.leftDominateRight(GROUP_B, GROUP_A));
+    assertFalse(d.leftDominateRight(GROUP_A, GROUP_A));
+    // 3 = 1&2, 5 = 1&4
+    assertTrue(d.leftDominateRight(GROUP_A, GROUP_AB));
+    assertFalse(d.leftDominateRight(GROUP_AB, GROUP_A));
+    assertFalse(d.leftDominateRight(GROUP_AB, GROUP_AB));
+    assertTrue(d.leftDominateRight(GROUP_AB, GROUP_AC));
+    assertTrue(d.leftDominateRight(GROUP_AC, GROUP_AB));
+  }
+}

From 30e406772cf930fb182dbcc74c1de7364fc35ea3 Mon Sep 17 00:00:00 2001
From: Thomas Gran 
Date: Thu, 9 Nov 2023 14:51:55 +0100
Subject: [PATCH 17/85] Apply suggestions from code review

Co-authored-by: Leonard Ehrenfried 
---
 .../ext/transmodelapi/model/plan/TripQuery.java        |  4 ++--
 .../org/opentripplanner/api/mapping/RelaxMapper.java   |  6 +++---
 .../transit/request/PriorityGroupConfigurator.java     |  2 +-
 .../transit/request/PriorityGroupMatcher.java          |  2 +-
 .../config/routerequest/RouteRequestConfig.java        |  2 +-
 .../routerequest/TransitPriorityGroupConfig.java       | 10 +++++-----
 6 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java
index 073de2e9003..a64438e481a 100644
--- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java
+++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java
@@ -282,10 +282,10 @@ public static GraphQLFieldDefinition create(
             """
             Relax generalized-cost when comparing trips with a different set of
             transit-priority-groups. The groups are set server side for service-journey and
-            can not be configured in the API. This mainly help return competition neutral
+            can not be configured in the API. This mainly helps to return competition neutral
             services. Long distance authorities are put in different transit-priority-groups.
             
-            This relax the comparison inside the routing engine for each stop-arrival. If two
+            This relaxes the comparison inside the routing engine for each stop-arrival. If two
             paths have a different set of transit-priority-groups, then the generalized-cost
             comparison is relaxed. The final set of paths are filtered through the normal
             itinerary-filters.
diff --git a/src/main/java/org/opentripplanner/api/mapping/RelaxMapper.java b/src/main/java/org/opentripplanner/api/mapping/RelaxMapper.java
index 1586ff55ee0..1a510174a66 100644
--- a/src/main/java/org/opentripplanner/api/mapping/RelaxMapper.java
+++ b/src/main/java/org/opentripplanner/api/mapping/RelaxMapper.java
@@ -8,7 +8,7 @@
 import org.opentripplanner.routing.api.request.preference.Relax;
 
 /**
- * Map a text to a Relax instance. The patter used is:
+ * Map a text to a Relax instance. The pattern used is:
  * 
  *   NNN.NN '*' [variable-placeholder] '+' NNN
  * 
@@ -23,11 +23,11 @@ public class RelaxMapper { private static final String SEP = "\\s*"; private static final String NUM = "(\\d+(?:\\.\\d+)?+)"; private static final String INT = "(\\d+)"; - private static final String ALFA = "[a-zA-Z]+"; + private static final String ALPHA = "[a-zA-Z]+"; private static final String PLUS = SEP + Pattern.quote("+") + SEP; private static final String TIMES = Pattern.quote("*"); - private static final String RATIO = NUM + SEP + TIMES + "?" + SEP + ALFA; + private static final String RATIO = NUM + SEP + TIMES + "?" + SEP + ALPHA; private static final String SLACK = INT; private static final Pattern RELAX_PATTERN_1 = Pattern.compile(RATIO + PLUS + SLACK); diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfigurator.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfigurator.java index f8ba2882325..87fc3f222b1 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfigurator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfigurator.java @@ -11,7 +11,7 @@ import org.opentripplanner.transit.model.network.RoutingTripPattern; /** - * This class dynamically build an index of transit-group-ids from the + * This class dynamically builds an index of transit-group-ids from the * provided {@link TransitPriorityGroupSelect}s while serving the caller with * group-ids for each requested pattern. It is made for optimal * performance, since it is used in request scope. diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcher.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcher.java index d0bda5802df..33e29347b32 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcher.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcher.java @@ -17,7 +17,7 @@ * This class turn a {@link TransitPriorityGroupSelect} into a matcher. *

* Design: It uses the composite design pattern. A matcher is created for each - * value in the "select", then the list of none empty matchers are merged into + * value in the "select", then the list of non-empty matchers is merged into * a `CompositeMatcher`. So, a new matcher is only created if the field in the * select is present. */ diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java index b682d36ee30..3dfe472137a 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java @@ -343,7 +343,7 @@ The board time is added to the time when going from the stop (offboard) to onboa .summary("The relax function for transit-priority-groups") .description( """ - A path is considered optimal if it the generalized-cost is smaller than an other the + A path is considered optimal if it the generalized-cost is less than the generalized-cost of another path. If this parameter is set, the comparison is relaxed further if they belong to different transit-priority-groups. """ diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/TransitPriorityGroupConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/TransitPriorityGroupConfig.java index 2b1f02e1c62..56730a35110 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/TransitPriorityGroupConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/TransitPriorityGroupConfig.java @@ -19,8 +19,8 @@ public static void mapTransitRequest(NodeAdapter root, TransitRequest transit) { .summary("Transit priority groups configuration") .description( """ - Use this to group transit patterns into groups. Each group will be given priority - over others groups in the trip search. Hence; To paths with a different different + Use this to separate transit patterns into groups. Each group will be given priority + over other groups in the trip search. Hence, two paths with a different different set of groups will BOTH be returned unless the cost is worse then the relaxation specified in the `` parameter . @@ -33,7 +33,7 @@ public static void mapTransitRequest(NodeAdapter root, TransitRequest transit) { c, "base", "All groups in base get the same group-id(GROUP_ZERO). Normally you will put all " + - "local-traffic and other 'none-problematic' services here." + "local-traffic and other 'non-problematic' services here." ) ); transit.addPriorityGroupsByAgency( @@ -74,14 +74,14 @@ private static TransitPriorityGroupSelect mapTransitGroupSelect(NodeAdapter c) { .addModes( c .of("modes") - .since(V2_3) + .since(V2_5) .summary("List all modes to select for this group.") .asEnumSet(TransitMode.class) ) .addSubModeRegexp( c .of("subModes") - .since(V2_3) + .since(V2_5) .summary("List a set of regular expressions for matching sub-modes.") .asStringList(List.of()) ) From 23c52d48d079b9cdf0c43e16549f211aa3c1e752 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 17 Nov 2023 12:31:20 +0100 Subject: [PATCH 18/85] refactor: Add ArrayUtils#hasContent Signed-off-by: Thomas Gran --- .../framework/lang/ArrayUtils.java | 14 ++++++++++++++ .../framework/lang/ArrayUtilsTest.java | 17 +++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 src/main/java/org/opentripplanner/framework/lang/ArrayUtils.java create mode 100644 src/test/java/org/opentripplanner/framework/lang/ArrayUtilsTest.java diff --git a/src/main/java/org/opentripplanner/framework/lang/ArrayUtils.java b/src/main/java/org/opentripplanner/framework/lang/ArrayUtils.java new file mode 100644 index 00000000000..a3a67cb67cd --- /dev/null +++ b/src/main/java/org/opentripplanner/framework/lang/ArrayUtils.java @@ -0,0 +1,14 @@ +package org.opentripplanner.framework.lang; + +import javax.annotation.Nullable; + +public class ArrayUtils { + + /** + * Return {@code true} if array has at least one element. Return {@code false} is array is + * {@code null} or has zero length. + */ + public static boolean hasContent(@Nullable T[] array) { + return array != null && array.length > 0; + } +} diff --git a/src/test/java/org/opentripplanner/framework/lang/ArrayUtilsTest.java b/src/test/java/org/opentripplanner/framework/lang/ArrayUtilsTest.java new file mode 100644 index 00000000000..8933555e75c --- /dev/null +++ b/src/test/java/org/opentripplanner/framework/lang/ArrayUtilsTest.java @@ -0,0 +1,17 @@ +package org.opentripplanner.framework.lang; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.framework.lang.ArrayUtils.hasContent; + +import org.junit.jupiter.api.Test; + +class ArrayUtilsTest { + + @Test + void testHasContent() { + assertFalse(hasContent(null)); + assertFalse(hasContent(new String[] {})); + assertTrue(hasContent(new Double[] { 0.0 })); + } +} From 04630522381af630ec1b04f860078b34d14df5c0 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 17 Nov 2023 12:47:30 +0100 Subject: [PATCH 19/85] feature: Add support for tagging config with "experimentalFeature" documentation. Signed-off-by: Thomas Gran --- .../config/framework/json/NodeInfo.java | 3 +++ .../framework/json/NodeInfoBuilder.java | 19 ++++++++++++++++++- .../framework/json/ParameterBuilder.java | 7 +++++-- .../config/framework/json/NodeInfoTest.java | 9 +++++++++ 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/opentripplanner/standalone/config/framework/json/NodeInfo.java b/src/main/java/org/opentripplanner/standalone/config/framework/json/NodeInfo.java index 18b4c0280a6..f7910a491a9 100644 --- a/src/main/java/org/opentripplanner/standalone/config/framework/json/NodeInfo.java +++ b/src/main/java/org/opentripplanner/standalone/config/framework/json/NodeInfo.java @@ -41,6 +41,9 @@ public record NodeInfo( boolean skipChild ) implements Comparable { + static final String EXPERIMENTAL_FEATURE = + "THIS IS STILL AN EXPERIMENTAL FEATURE - IT MAY CHANGE WITHOUT ANY NOTICE!"; + static final String TYPE_QUALIFIER = "type"; static final String SOURCETYPE_QUALIFIER = "sourceType"; diff --git a/src/main/java/org/opentripplanner/standalone/config/framework/json/NodeInfoBuilder.java b/src/main/java/org/opentripplanner/standalone/config/framework/json/NodeInfoBuilder.java index f836e174e81..9c9303e790e 100644 --- a/src/main/java/org/opentripplanner/standalone/config/framework/json/NodeInfoBuilder.java +++ b/src/main/java/org/opentripplanner/standalone/config/framework/json/NodeInfoBuilder.java @@ -5,6 +5,7 @@ import static org.opentripplanner.standalone.config.framework.json.ConfigType.ENUM_MAP; import static org.opentripplanner.standalone.config.framework.json.ConfigType.ENUM_SET; import static org.opentripplanner.standalone.config.framework.json.ConfigType.MAP; +import static org.opentripplanner.standalone.config.framework.json.NodeInfo.EXPERIMENTAL_FEATURE; import java.util.EnumSet; @@ -18,6 +19,7 @@ class NodeInfoBuilder { private OtpVersion since = OtpVersion.NA; private String summary = "TODO: Add short summary."; private String description = null; + private boolean experimentalFeature = false; private String defaultValue = null; private boolean required = true; private boolean skipChildren = false; @@ -63,6 +65,21 @@ NodeInfoBuilder withDescription(String description) { return this; } + String description() { + if (!experimentalFeature) { + return description; + } + if (description == null) { + return EXPERIMENTAL_FEATURE; + } + return description + "\n\n" + EXPERIMENTAL_FEATURE; + } + + NodeInfoBuilder withExperimentalFeature() { + this.experimentalFeature = true; + return this; + } + NodeInfoBuilder withOptional(String defaultValue) { this.defaultValue = defaultValue; return withOptional(); @@ -119,7 +136,7 @@ NodeInfo build() { return new NodeInfo( name, summary, - description, + description(), type, enumType, elementType, diff --git a/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java b/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java index 0302fce4d3a..f1d90d0ec40 100644 --- a/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java +++ b/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java @@ -4,7 +4,6 @@ import static org.opentripplanner.standalone.config.framework.json.ConfigType.COST_LINEAR_FUNCTION; import static org.opentripplanner.standalone.config.framework.json.ConfigType.DOUBLE; import static org.opentripplanner.standalone.config.framework.json.ConfigType.DURATION; -import static org.opentripplanner.standalone.config.framework.json.ConfigType.ENUM; import static org.opentripplanner.standalone.config.framework.json.ConfigType.FEED_SCOPED_ID; import static org.opentripplanner.standalone.config.framework.json.ConfigType.INTEGER; import static org.opentripplanner.standalone.config.framework.json.ConfigType.LOCALE; @@ -23,7 +22,6 @@ import java.time.LocalDate; import java.time.ZoneId; import java.time.format.DateTimeParseException; -import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Collection; import java.util.EnumMap; @@ -92,6 +90,11 @@ public ParameterBuilder description(String description) { return this; } + public ParameterBuilder experimentalFeature() { + this.info.withExperimentalFeature(); + return this; + } + /** * Add documentation for optional field with default value to a parameter. *

diff --git a/src/test/java/org/opentripplanner/standalone/config/framework/json/NodeInfoTest.java b/src/test/java/org/opentripplanner/standalone/config/framework/json/NodeInfoTest.java index 38d7be14f1e..3227e43b23d 100644 --- a/src/test/java/org/opentripplanner/standalone/config/framework/json/NodeInfoTest.java +++ b/src/test/java/org/opentripplanner/standalone/config/framework/json/NodeInfoTest.java @@ -78,6 +78,15 @@ void typeDescription() { assertEquals("enum set", createBuilder().withEnumSet(AnEnum.class).build().typeDescription()); } + @Test + void experimentalFeature() { + var subject = createBuilder().withExperimentalFeature().build(); + assertEquals(NodeInfo.EXPERIMENTAL_FEATURE, subject.description()); + + subject = createBuilder().withDescription("Description").withExperimentalFeature().build(); + assertEquals("Description\n\n" + NodeInfo.EXPERIMENTAL_FEATURE, subject.description()); + } + private NodeInfoBuilder createBuilder() { return NodeInfo .of() From c403878be3241a4daca76e2d33fe4185780534f9 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 17 Nov 2023 12:53:45 +0100 Subject: [PATCH 20/85] doc: Improve Transit priority groups configuration doc Signed-off-by: Thomas Gran --- docs/RouteRequest.md | 11 ++++++----- src/ext/graphql/transmodelapi/schema.graphql | 4 ++-- .../routerequest/TransitPriorityGroupConfig.java | 13 +++++++------ 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/docs/RouteRequest.md b/docs/RouteRequest.md index 3d420e5922a..853b6787b66 100644 --- a/docs/RouteRequest.md +++ b/docs/RouteRequest.md @@ -254,7 +254,7 @@ We return number of seconds that we are willing to wait for preferred route. The relax function for transit-priority-groups -A path is considered optimal if it the generalized-cost is smaller than an other the +A path is considered optimal if it the generalized-cost is less than the generalized-cost of another path. If this parameter is set, the comparison is relaxed further if they belong to different transit-priority-groups. @@ -810,12 +810,13 @@ If not enabled generalizedCost function is used to pick the optimal transfer poi Transit priority groups configuration -Use this to group transit patterns into groups. Each group will be given priority -over others groups in the trip search. Hence; To paths with a different different -set of groups will BOTH be returned unless the cost is worse then the relaxation -specified in the `` parameter . +Use this to separate transit patterns into groups. Each group will be given a priority +when compared with other groups. Hence, two paths with a different set of groups will BOTH +be returned unless the cost is worse then the relaxation specified in the +`relaxTransitPriorityGroup` parameter. Only available in TransmodelAPI for now. +THIS IS STILL AN EXPERIMENTAL FEATURE - IT MAY CHANGE WITHOUT ANY NOTICE!

transitReluctanceForMode

diff --git a/src/ext/graphql/transmodelapi/schema.graphql b/src/ext/graphql/transmodelapi/schema.graphql index 38bd44ffe27..afc9ba7e2e3 100644 --- a/src/ext/graphql/transmodelapi/schema.graphql +++ b/src/ext/graphql/transmodelapi/schema.graphql @@ -826,10 +826,10 @@ type QueryType { """ Relax generalized-cost when comparing trips with a different set of transit-priority-groups. The groups are set server side for service-journey and - can not be configured in the API. This mainly help return competition neutral + can not be configured in the API. This mainly helps to return competition neutral services. Long distance authorities are put in different transit-priority-groups. - This relax the comparison inside the routing engine for each stop-arrival. If two + This relaxes the comparison inside the routing engine for each stop-arrival. If two paths have a different set of transit-priority-groups, then the generalized-cost comparison is relaxed. The final set of paths are filtered through the normal itinerary-filters. diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/TransitPriorityGroupConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/TransitPriorityGroupConfig.java index 56730a35110..82f3347f2af 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/TransitPriorityGroupConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/TransitPriorityGroupConfig.java @@ -1,6 +1,7 @@ package org.opentripplanner.standalone.config.routerequest; import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_3; +import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_5; import java.util.Collection; import java.util.List; @@ -19,13 +20,13 @@ public static void mapTransitRequest(NodeAdapter root, TransitRequest transit) { .summary("Transit priority groups configuration") .description( """ - Use this to separate transit patterns into groups. Each group will be given priority - over other groups in the trip search. Hence, two paths with a different different - set of groups will BOTH be returned unless the cost is worse then the relaxation - specified in the `` parameter . - - """.stripIndent() + Use this to separate transit patterns into groups. Each group will be given a priority + when compared with other groups. Hence, two paths with a different set of groups will BOTH + be returned unless the cost is worse then the relaxation specified in the + `relaxTransitPriorityGroup` parameter. This is only available in the TransmodelAPI for now. + """ ) + .experimentalFeature() .asObject(); transit.addPriorityGroupsBase( From 1b8520f844b582dd9322d495d1876443b35cfe56 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 17 Nov 2023 12:54:12 +0100 Subject: [PATCH 21/85] review: Clean code PriorityGroupMatcher Signed-off-by: Thomas Gran --- .../request/PriorityGroupConfigurator.java | 25 ++++--------------- .../transit/request/PriorityGroupMatcher.java | 9 +++++++ 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfigurator.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfigurator.java index 87fc3f222b1..c4406680b5e 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfigurator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfigurator.java @@ -3,8 +3,8 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; -import java.util.function.Predicate; import java.util.stream.Stream; +import org.opentripplanner.framework.lang.ArrayUtils; import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.grouppriority.TransitPriorityGroup32n; import org.opentripplanner.routing.api.request.request.filter.TransitPriorityGroupSelect; import org.opentripplanner.transit.model.framework.FeedScopedId; @@ -41,26 +41,11 @@ private PriorityGroupConfigurator( Collection byAgency, Collection global ) { - this.baseMatchers = - base - .stream() - .map(PriorityGroupMatcher::of) - .filter(Predicate.not(PriorityGroupMatcher::isEmpty)) - .toArray(PriorityGroupMatcher[]::new); - this.agencyMatchers = - byAgency - .stream() - .map(PriorityGroupMatcher::of) - .filter(Predicate.not(PriorityGroupMatcher::isEmpty)) - .toArray(PriorityGroupMatcher[]::new); - this.globalMatchers = - global - .stream() - .map(PriorityGroupMatcher::of) - .filter(Predicate.not(PriorityGroupMatcher::isEmpty)) - .toArray(PriorityGroupMatcher[]::new); + this.baseMatchers = PriorityGroupMatcher.of(base); + this.agencyMatchers = PriorityGroupMatcher.of(byAgency); + this.globalMatchers = PriorityGroupMatcher.of(global); this.enabled = - (baseMatchers.length > 0) || (agencyMatchers.length > 0) || (globalMatchers.length > 0); + Stream.of(baseMatchers, agencyMatchers, globalMatchers).anyMatch(ArrayUtils::hasContent); } public static PriorityGroupConfigurator empty() { diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcher.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcher.java index 33e29347b32..2ae9b543d24 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcher.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcher.java @@ -1,6 +1,7 @@ package org.opentripplanner.routing.algorithm.raptoradapter.transit.request; import java.util.ArrayList; +import java.util.Collection; import java.util.EnumSet; import java.util.HashSet; import java.util.List; @@ -56,6 +57,14 @@ public static PriorityGroupMatcher of(TransitPriorityGroupSelect select) { return compositeOf(list); } + static PriorityGroupMatcher[] of(Collection selectors) { + return selectors + .stream() + .map(PriorityGroupMatcher::of) + .filter(Predicate.not(PriorityGroupMatcher::isEmpty)) + .toArray(PriorityGroupMatcher[]::new); + } + private static PriorityGroupMatcher compositeOf(List list) { // Remove empty/noop matchers list = list.stream().filter(Predicate.not(PriorityGroupMatcher::isEmpty)).toList(); From 20c40d4bf2f89a006126ca9fa084dc3068ca1ecd Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 17 Nov 2023 13:00:12 +0100 Subject: [PATCH 22/85] refactor: reformat code --- docs/RouteRequest.md | 2 +- .../opentripplanner/raptor/_data/transit/TestTripPattern.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/RouteRequest.md b/docs/RouteRequest.md index 853b6787b66..6b8808f1bff 100644 --- a/docs/RouteRequest.md +++ b/docs/RouteRequest.md @@ -813,7 +813,7 @@ Transit priority groups configuration Use this to separate transit patterns into groups. Each group will be given a priority when compared with other groups. Hence, two paths with a different set of groups will BOTH be returned unless the cost is worse then the relaxation specified in the -`relaxTransitPriorityGroup` parameter. Only available in TransmodelAPI for now. +`relaxTransitPriorityGroup` parameter. This is only available in the TransmodelAPI for now. THIS IS STILL AN EXPERIMENTAL FEATURE - IT MAY CHANGE WITHOUT ANY NOTICE! diff --git a/src/test/java/org/opentripplanner/raptor/_data/transit/TestTripPattern.java b/src/test/java/org/opentripplanner/raptor/_data/transit/TestTripPattern.java index 740010a8a4f..63dcbeb5863 100644 --- a/src/test/java/org/opentripplanner/raptor/_data/transit/TestTripPattern.java +++ b/src/test/java/org/opentripplanner/raptor/_data/transit/TestTripPattern.java @@ -64,7 +64,6 @@ public TestTripPattern withPriorityGroup(int priorityGroupId) { return this; } - public TestTripPattern withRoute(Route route) { this.route = route; return this; From c160ec476fd886c5d728036d7c30ee5820f258a9 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 17 Nov 2023 13:02:26 +0100 Subject: [PATCH 23/85] refactor: Delete unused RelaxMapper --- .../api/mapping/RelaxMapper.java | 78 ------------------- 1 file changed, 78 deletions(-) delete mode 100644 src/main/java/org/opentripplanner/api/mapping/RelaxMapper.java diff --git a/src/main/java/org/opentripplanner/api/mapping/RelaxMapper.java b/src/main/java/org/opentripplanner/api/mapping/RelaxMapper.java deleted file mode 100644 index 1a510174a66..00000000000 --- a/src/main/java/org/opentripplanner/api/mapping/RelaxMapper.java +++ /dev/null @@ -1,78 +0,0 @@ -package org.opentripplanner.api.mapping; - -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.Optional; -import java.util.regex.Pattern; -import org.opentripplanner.routing.api.request.preference.Relax; - -/** - * Map a text to a Relax instance. The pattern used is: - *
- *   NNN.NN '*' [variable-placeholder] '+' NNN
- * 
- * {@code NNN.NN} is any positive decimal number. - * {@code NNN} is any positive integer number. - * {@code variable-placeholder} is any alphabetical variable name - this is just a placeholder - * to make it clear what is the ratio/factor and what is the - * constant/slack. - */ -public class RelaxMapper { - - private static final String SEP = "\\s*"; - private static final String NUM = "(\\d+(?:\\.\\d+)?+)"; - private static final String INT = "(\\d+)"; - private static final String ALPHA = "[a-zA-Z]+"; - private static final String PLUS = SEP + Pattern.quote("+") + SEP; - private static final String TIMES = Pattern.quote("*"); - - private static final String RATIO = NUM + SEP + TIMES + "?" + SEP + ALPHA; - private static final String SLACK = INT; - - private static final Pattern RELAX_PATTERN_1 = Pattern.compile(RATIO + PLUS + SLACK); - private static final Pattern RELAX_PATTERN_2 = Pattern.compile(SLACK + PLUS + RATIO); - - public static Relax mapRelax(String input) { - if (input == null || input.isBlank()) { - return null; - } - String inputTrimmed = input.trim(); - List errors = new ArrayList<>(); - - return parse(RELAX_PATTERN_1, inputTrimmed, 1, 2, errors) - .or(() -> parse(RELAX_PATTERN_2, inputTrimmed, 2, 1, errors)) - .orElseThrow(() -> - new IllegalArgumentException( - "Unable to parse function: '" + - input + - "'. Use: '2.0 * x + 100'." + - (errors.isEmpty() ? "" : " Details: " + errors) - ) - ); - } - - public static String mapRelaxToString(Relax domain) { - return String.format(Locale.ROOT, "%.2f * x + %d", domain.ratio(), domain.slack()); - } - - private static Optional parse( - Pattern pattern, - String input, - int ratioIdx, - int slackIdx, - List errors - ) { - var m = pattern.matcher(input); - if (m.matches()) { - try { - return Optional.of( - new Relax(Double.parseDouble(m.group(ratioIdx)), Integer.parseInt(m.group(slackIdx))) - ); - } catch (Exception e) { - errors.add(e); - } - } - return Optional.empty(); - } -} From 85994c23b3797b10e30c198c07297a01985fe239 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Mon, 20 Nov 2023 22:54:23 +0100 Subject: [PATCH 24/85] fix: SearchWindow for arriveBy search is of by one In the current version the dynamic search-window is set 1 minute too early to include a trip if both the minimum travel time can be calculated exact, and the journey arrival-time is withing 59s of the LAT. --- .../transit/RaptorSearchWindowCalculator.java | 9 ++- .../RaptorSearchWindowCalculatorTest.java | 71 +++++++++++++++---- 2 files changed, 62 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/RaptorSearchWindowCalculator.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/RaptorSearchWindowCalculator.java index 31608622bc5..bbb5a25c11e 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/RaptorSearchWindowCalculator.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/RaptorSearchWindowCalculator.java @@ -82,7 +82,7 @@ public RaptorSearchWindowCalculator calculate() { } // TravelWindow is the time from the earliest-departure-time to the latest-arrival-time - int travelWindow = searchWindowSeconds + roundUpToNearestMinute(heuristicMinTransitTime); + int travelWindow = searchWindowSeconds + roundDownToNearestMinute(heuristicMinTransitTime); if (!params.isEarliestDepartureTimeSet()) { earliestDepartureTime = latestArrivalTime - travelWindow; @@ -90,15 +90,14 @@ public RaptorSearchWindowCalculator calculate() { return this; } - int roundUpToNearestMinute(int minTravelTimeInSeconds) { + int roundDownToNearestMinute(int minTravelTimeInSeconds) { if (minTravelTimeInSeconds < 0) { throw new IllegalArgumentException( "This operation is not defined for negative numbers: " + minTravelTimeInSeconds ); } - // See the UnitTest for verification of this: - // We want: 0 -> 0, 1 -> 60, 59 -> 60 ... - return ((minTravelTimeInSeconds + 59) / 60) * 60; + // We want: 0 -> 0, 59 -> 0, 60 -> 60 ... + return (minTravelTimeInSeconds / 60) * 60; } /** diff --git a/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/RaptorSearchWindowCalculatorTest.java b/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/RaptorSearchWindowCalculatorTest.java index 4605d4bfc28..4898c5e2ae9 100644 --- a/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/RaptorSearchWindowCalculatorTest.java +++ b/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/RaptorSearchWindowCalculatorTest.java @@ -2,9 +2,13 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.time.Duration; +import java.util.List; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.raptor._data.transit.TestTripSchedule; import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.raptor.api.request.DynamicSearchWindowCoefficients; @@ -58,19 +62,48 @@ public void calcEarliestDeparture() { subject.withHeuristics(minTransitTime, minWaitTime).withSearchParams(searchParams).calculate(); /* - search-window: round_N(C + T * minTransitTime + W * minWaitTime) + search-window: round_60(C + T * minTransitTime + W * minWaitTime) = round_60(600 + 0.6 * 500 + 0.4 * 200) = round_60(980) = 960 EDT = LAT - (search-window + minTripTime) - EDT = 3000 - (960s + roundUp_60(500)) - EDT = 3000 - (960s + 540s) - EDT = 1500 + EDT = 3000 - (960s + round_60(500)) + EDT = 3000 - (960s + 480s) + EDT = 1560 */ assertEquals(960, subject.getSearchWindowSeconds()); - assertEquals(1_500, subject.getEarliestDepartureTime()); + assertEquals(1_560, subject.getEarliestDepartureTime()); // Given - verify not changed assertEquals(3_000, subject.getLatestArrivalTime()); + assertTrue( + minTransitTime > + subject.getLatestArrivalTime() - + (subject.getSearchWindowSeconds() + subject.getEarliestDepartureTime()) + ); + } + + @Test + public void calcEarliestDepartureExact() { + SearchParams searchParams = new RaptorRequestBuilder() + .searchParams() + .latestArrivalTime(3_000) + .buildSearchParam(); + + int minTransitTime = 600; + int minWaitTime = 0; + + subject.withHeuristics(minTransitTime, minWaitTime).withSearchParams(searchParams).calculate(); + + assertEquals(960, subject.getSearchWindowSeconds()); + assertEquals(1_440, subject.getEarliestDepartureTime()); + // Given - verify not changed + assertEquals(3_000, subject.getLatestArrivalTime()); + + assertEquals( + minTransitTime, + subject.getLatestArrivalTime() - + (subject.getSearchWindowSeconds() + subject.getEarliestDepartureTime()) + ); } @Test @@ -166,17 +199,23 @@ public void calculateNotDefinedIfMinTravelTimeNotSet() { assertThrows(IllegalArgumentException.class, subject::calculate); } - @Test - public void roundUpToNearestMinute() { - assertEquals(0, subject.roundUpToNearestMinute(0)); - assertEquals(60, subject.roundUpToNearestMinute(1)); - assertEquals(60, subject.roundUpToNearestMinute(60)); - assertEquals(120, subject.roundUpToNearestMinute(61)); + static List roundUpToNearestMinuteTestCase() { + return List.of(V2.of(0, 0), V2.of(0, 59), V2.of(60, 60), V2.of(60, 119), V2.of(120, 120)); + } + + @ParameterizedTest + @MethodSource("roundUpToNearestMinuteTestCase") + void roundUpToNearestMinute(V2 v2) { + assertEquals(v2.expected(), subject.roundDownToNearestMinute(v2.value())); } @Test - public void roundUpToNearestMinuteNotDefinedForNegativeNumbers() { - assertThrows(IllegalArgumentException.class, () -> subject.roundUpToNearestMinute(-1)); + void roundUpToNearestMinuteNotDefinedForNegativeNumbers() { + var ex = assertThrows( + IllegalArgumentException.class, + () -> subject.roundDownToNearestMinute(-1) + ); + assertEquals("This operation is not defined for negative numbers: -1", ex.getMessage()); } @Test @@ -188,4 +227,10 @@ public void roundStep() { assertEquals(60, subject.roundStep(30f)); assertEquals(480, subject.roundStep(450f)); } + + record V2(int expected, int value) { + static V2 of(int expected, int value) { + return new V2(expected, value); + } + } } From 9137a0796f4026925a23f3b955083cc97c27b8df Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 21 Nov 2023 15:02:17 +0100 Subject: [PATCH 25/85] Add module documentation for the token component --- .../framework/token/package.md | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 src/main/java/org/opentripplanner/framework/token/package.md diff --git a/src/main/java/org/opentripplanner/framework/token/package.md b/src/main/java/org/opentripplanner/framework/token/package.md new file mode 100644 index 00000000000..fb68e4919d1 --- /dev/null +++ b/src/main/java/org/opentripplanner/framework/token/package.md @@ -0,0 +1,71 @@ +# Summary + +This adds a component to specify a very simple schema for a token. The schema is a list of +versioned token definitions and the encode/decode methods enforce versioning, and forward and +backward compatibility. It also allows definitions to be merged. We only need to support one OTP +version. So after every release of OTP we can merge the definitions older than the last release. + +# Issue +[Issue Create a reusable token generator #5451](https://github.com/opentripplanner/OpenTripPlanner/issues/5451) + +# Example + +## Define Schema +```Java +// v1: (mode: byte) +var builder = TokenSchema.ofVersion(1).addByte("mode"); + +// v2: (mode: byte, searchWindow : Duration, numOfItineraries : int) +builder.newVersion().addDuration("searchWindow").addInt("numOfItineraries"); + +// v3: (mode: byte, @deprecated searchWindow : Duration, numOfItineraries : int, name : String) +builder = builder.newVersion().deprecate("searchWindow").addString("name"); + +// v4: (@deprecated mode: byte, numOfItineraries : int, name : String, dateTime : Instant) +builder = builder.newVersion().deprecate("mode").addTimeInstant("dateTime"); + +var schema = builder.build(); +``` + +## Merge Schema + +The merging is provided to simplify the code when old versions of the schema is no longer needed. +For example after releasing OTP `v2.5`, everything older than `v2.4` can be merged - we only +support backwards compatibility with the previous version. + +```Java +// v3 - v1, v2 and v3 merged +var builder = TokenSchema + .ofVersion(3) + .addByte("mode") + .addInt("numOfItineraries") + .addString("name"); +``` + +## Encode token + +Create a new token with latest version/definition(v4). Deprecated fields need to be inserted to +be forward compatible. + +```Java +var token = schema.encode() + .withInt("numOfItineraries", 4) + .withByte("mode", BUS_CODE) + .withTimeInstant("dateTime", Instant.now()) + .withString("name", "Oslo - Bergen") + .build(); +``` + +## Decode token + +The token returned is parsed using the schema version that was used to generate the token. When +acting on this, a mapping into the existing code must be provided for each supported version. If an +old version can not be supported anymore, then merging the Schema is an option. + +```Java +var token = schema.decode("rO0ABXcaAAIxMwAUMjAyMy0xMC0yM1QxMDowMDo1OVo="); + +if(token.version() == 1) { token.getByte("mode") ... } +if(token.version() == 2) { ... } +if(token.version() == 3) { ... } +``` From 64dbe1ade1bd6e932caaaef6fbce9c295b81cc42 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 21 Nov 2023 15:36:57 +0100 Subject: [PATCH 26/85] review: Remove @Nullable and cleanup Serializer --- .../framework/token/Deserializer.java | 2 - .../framework/token/Serializer.java | 55 ++++++++++++------- .../framework/token/TokenBuilder.java | 2 +- 3 files changed, 37 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/opentripplanner/framework/token/Deserializer.java b/src/main/java/org/opentripplanner/framework/token/Deserializer.java index 1da783b239d..6ca2a7977f0 100644 --- a/src/main/java/org/opentripplanner/framework/token/Deserializer.java +++ b/src/main/java/org/opentripplanner/framework/token/Deserializer.java @@ -8,7 +8,6 @@ import java.util.ArrayList; import java.util.Base64; import java.util.List; -import javax.annotation.Nullable; import org.opentripplanner.framework.time.DurationUtils; class Deserializer { @@ -92,7 +91,6 @@ private static Duration readDuration(ObjectInputStream in) throws IOException { return DurationUtils.duration(in.readUTF()); } - @Nullable private static Instant readTimeInstant(ObjectInputStream in) throws IOException { return Instant.parse(in.readUTF()); } diff --git a/src/main/java/org/opentripplanner/framework/token/Serializer.java b/src/main/java/org/opentripplanner/framework/token/Serializer.java index 289d4b283d8..d65f0b8983d 100644 --- a/src/main/java/org/opentripplanner/framework/token/Serializer.java +++ b/src/main/java/org/opentripplanner/framework/token/Serializer.java @@ -1,6 +1,7 @@ package org.opentripplanner.framework.token; import java.io.ByteArrayOutputStream; +import java.io.Closeable; import java.io.IOException; import java.io.ObjectOutputStream; import java.time.Duration; @@ -8,57 +9,73 @@ import java.util.Base64; import org.opentripplanner.framework.time.DurationUtils; -class Serializer { +class Serializer implements Closeable { private final TokenDefinition definition; + private final Object[] values; + private final ObjectOutputStream out; private final ByteArrayOutputStream buf = new ByteArrayOutputStream(); - Serializer(TokenDefinition definition) { + private Serializer(TokenDefinition definition, Object[] values) throws IOException { this.definition = definition; + this.values = values; + this.out = new ObjectOutputStream(buf); } - String serialize(Object[] values) throws IOException { - try (var out = new ObjectOutputStream(buf)) { - writeInt(out, definition.version()); + @Override + public void close() throws IOException { + out.close(); + } + + static String serialize(TokenDefinition definition, Object[] values) throws IOException { + try (var s = new Serializer(definition, values)) { + s.writeInt(definition.version()); for (var fieldName : definition.fieldNames()) { - var value = values[definition.index(fieldName)]; - write(out, fieldName, value); + s.write(fieldName); } - out.flush(); + return s.serialize(); } + } + + private String serialize() throws IOException { + out.close(); return Base64.getUrlEncoder().encodeToString(buf.toByteArray()); } - private void write(ObjectOutputStream out, String fieldName, Object value) throws IOException { + private void write(String fieldName) throws IOException { + write(fieldName, values[definition.index(fieldName)]); + } + + private void write(String fieldName, Object value) throws IOException { var type = definition.type(fieldName); switch (type) { - case BYTE -> writeByte(out, (byte) value); - case DURATION -> writeDuration(out, (Duration) value); - case INT -> writeInt(out, (int) value); - case STRING -> writeString(out, (String) value); - case TIME_INSTANT -> writeTimeInstant(out, (Instant) value); + case BYTE -> writeByte((byte) value); + case DURATION -> writeDuration((Duration) value); + case INT -> writeInt((int) value); + case STRING -> writeString((String) value); + case TIME_INSTANT -> writeTimeInstant((Instant) value); default -> throw new IllegalArgumentException("Unknown type: " + type); } } - private static void writeByte(ObjectOutputStream out, byte value) throws IOException { + private void writeByte(byte value) throws IOException { out.writeByte(value); } - private static void writeInt(ObjectOutputStream out, int value) throws IOException { + private void writeInt(int value) throws IOException { out.writeUTF(Integer.toString(value)); } - private static void writeString(ObjectOutputStream out, String value) throws IOException { + private void writeString(String value) throws IOException { out.writeUTF(value); } - private static void writeDuration(ObjectOutputStream out, Duration duration) throws IOException { + private void writeDuration(Duration duration) throws IOException { out.writeUTF(DurationUtils.durationToStr(duration)); } - private static void writeTimeInstant(ObjectOutputStream out, Instant time) throws IOException { + private void writeTimeInstant(Instant time) throws IOException { out.writeUTF(time.toString()); } } diff --git a/src/main/java/org/opentripplanner/framework/token/TokenBuilder.java b/src/main/java/org/opentripplanner/framework/token/TokenBuilder.java index da9e9af8b7e..d5f1b043818 100644 --- a/src/main/java/org/opentripplanner/framework/token/TokenBuilder.java +++ b/src/main/java/org/opentripplanner/framework/token/TokenBuilder.java @@ -40,7 +40,7 @@ public TokenBuilder withTimeInstant(String fieldName, Instant v) { public String build() { try { - return new Serializer(definition).serialize(values); + return Serializer.serialize(definition, values); } catch (IOException e) { throw new RuntimeException(e.getMessage(), e); } From af1db755296b5be26f0a7cad8f94917c9ec09240 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 21 Nov 2023 15:42:11 +0100 Subject: [PATCH 27/85] Apply suggestions from code review Co-authored-by: Henrik Abrahamsson <127481124+habrahamsson-skanetrafiken@users.noreply.github.com> Co-authored-by: Leonard Ehrenfried --- .../org/opentripplanner/framework/token/TokenDefinition.java | 2 +- .../framework/token/TokenDefinitionBuilder.java | 4 ++-- .../java/org/opentripplanner/framework/token/TokenSchema.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/opentripplanner/framework/token/TokenDefinition.java b/src/main/java/org/opentripplanner/framework/token/TokenDefinition.java index a61bf769e76..4cacd388517 100644 --- a/src/main/java/org/opentripplanner/framework/token/TokenDefinition.java +++ b/src/main/java/org/opentripplanner/framework/token/TokenDefinition.java @@ -7,7 +7,7 @@ import org.opentripplanner.framework.tostring.ToStringBuilder; /** - * A token definition is an ordered list of files. A field has a name and a type. The + * A token definition is an ordered list of fields. A field has a name and a type. The * definition is used to encode/decode a token. */ public class TokenDefinition { diff --git a/src/main/java/org/opentripplanner/framework/token/TokenDefinitionBuilder.java b/src/main/java/org/opentripplanner/framework/token/TokenDefinitionBuilder.java index 513fd2e61d3..d50dbc1d129 100644 --- a/src/main/java/org/opentripplanner/framework/token/TokenDefinitionBuilder.java +++ b/src/main/java/org/opentripplanner/framework/token/TokenDefinitionBuilder.java @@ -2,7 +2,7 @@ import java.util.ArrayList; import java.util.List; -import java.util.stream.Stream; +import org.opentripplanner.framework.collection.ListUtils; import org.opentripplanner.framework.lang.IntUtils; public class TokenDefinitionBuilder { @@ -74,7 +74,7 @@ public TokenDefinitionBuilder newVersion() { } public TokenSchema build() { - return new TokenSchema(Stream.concat(tokensHead.stream(), Stream.of(buildIt())).toList()); + return new TokenSchema(ListUtils.combine(tokensHead, List.of(buildIt()))); } private TokenDefinition buildIt() { diff --git a/src/main/java/org/opentripplanner/framework/token/TokenSchema.java b/src/main/java/org/opentripplanner/framework/token/TokenSchema.java index 33300126fdb..7ac3aa63c9f 100644 --- a/src/main/java/org/opentripplanner/framework/token/TokenSchema.java +++ b/src/main/java/org/opentripplanner/framework/token/TokenSchema.java @@ -8,7 +8,7 @@ /** * A token schema contains a set of token definitions, one for each version. This is * used to decode a token - the same version used to encode a token is used to - * decode it. When encodeing a token the latest version is always used. + * decode it. When encoding a token the latest version is always used. *

* OTP only need to be backward compatible with the last version of otp. So, for each release of * OTP the schema that is older than the previous version can be merged. By doing so, you do not From 94027e3d251026b933d47c9aa40abb97cc63cc0d Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 21 Nov 2023 15:43:36 +0100 Subject: [PATCH 28/85] review: listNoneDeprecatedFields renamed to listNonDeprecatedFields --- .../org/opentripplanner/framework/token/TokenDefinition.java | 2 +- .../opentripplanner/framework/token/TokenDefinitionBuilder.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/framework/token/TokenDefinition.java b/src/main/java/org/opentripplanner/framework/token/TokenDefinition.java index 4cacd388517..d4ac7a61aec 100644 --- a/src/main/java/org/opentripplanner/framework/token/TokenDefinition.java +++ b/src/main/java/org/opentripplanner/framework/token/TokenDefinition.java @@ -74,7 +74,7 @@ int index(String name) { throw unknownFieldNameException(name); } - List listNoneDeprecatedFields() { + List listNonDeprecatedFields() { return listFields().stream().filter(it -> !it.deprecated()).toList(); } diff --git a/src/main/java/org/opentripplanner/framework/token/TokenDefinitionBuilder.java b/src/main/java/org/opentripplanner/framework/token/TokenDefinitionBuilder.java index d50dbc1d129..f4d3014128e 100644 --- a/src/main/java/org/opentripplanner/framework/token/TokenDefinitionBuilder.java +++ b/src/main/java/org/opentripplanner/framework/token/TokenDefinitionBuilder.java @@ -17,7 +17,7 @@ public class TokenDefinitionBuilder { TokenDefinitionBuilder(List head, TokenDefinition last) { this(last.version() + 1); - this.fields.addAll(last.listNoneDeprecatedFields()); + this.fields.addAll(last.listNonDeprecatedFields()); this.tokensHead.addAll(head); this.tokensHead.add(last); } From 37efdaf11c28430446882989f0fad73511b4c543 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 21 Nov 2023 21:20:49 +0100 Subject: [PATCH 29/85] Apply suggestions from code review Co-authored-by: Johan Torin Co-authored-by: Leonard Ehrenfried --- docs/RouteRequest.md | 12 ++++++------ .../cost/grouppriority/TransitPriorityGroup32n.java | 4 ++-- .../transit/request/PriorityGroupMatcher.java | 4 ++-- .../routing/api/request/request/TransitRequest.java | 4 ++-- .../standalone/config/framework/json/NodeInfo.java | 2 +- .../config/routerequest/RouteRequestConfig.java | 5 +++-- .../routerequest/TransitPriorityGroupConfig.java | 4 ++-- .../grouppriority/TransitPriorityGroup32nTest.java | 3 ++- 8 files changed, 20 insertions(+), 18 deletions(-) diff --git a/docs/RouteRequest.md b/docs/RouteRequest.md index 6b8808f1bff..28c49764810 100644 --- a/docs/RouteRequest.md +++ b/docs/RouteRequest.md @@ -57,7 +57,7 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe | numItineraries | `integer` | The maximum number of itineraries to return. | *Optional* | `50` | 2.0 | | [optimize](#rd_optimize) | `enum` | The set of characteristics that the user wants to optimize for. | *Optional* | `"safe"` | 2.0 | | [otherThanPreferredRoutesPenalty](#rd_otherThanPreferredRoutesPenalty) | `integer` | Penalty added for using every route that is not preferred if user set any route as preferred. | *Optional* | `300` | 2.0 | -| [relaxTransitPriorityGroup](#rd_relaxTransitPriorityGroup) | `string` | The relax function for transit-priority-groups | *Optional* | `"0s + 1.00 t"` | 2.3 | +| [relaxTransitPriorityGroup](#rd_relaxTransitPriorityGroup) | `string` | The relax function for transit-priority-groups | *Optional* | `"0s + 1.00 t"` | 2.5 | | [relaxTransitSearchGeneralizedCostAtDestination](#rd_relaxTransitSearchGeneralizedCostAtDestination) | `double` | Whether non-optimal transit paths at the destination should be returned | *Optional* | | 2.3 | | [searchWindow](#rd_searchWindow) | `duration` | The duration of the search-window. | *Optional* | | 2.0 | | stairsReluctance | `double` | Used instead of walkReluctance for stairs. | *Optional* | `2.0` | 2.0 | @@ -108,7 +108,7 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe |    [extraStopBoardAlightCostsFactor](#rd_to_extraStopBoardAlightCostsFactor) | `double` | Add an extra board- and alight-cost for prioritized stops. | *Optional* | `0.0` | 2.1 | |    [minSafeWaitTimeFactor](#rd_to_minSafeWaitTimeFactor) | `double` | Used to set a maximum wait-time cost, base on min-safe-transfer-time. | *Optional* | `5.0` | 2.1 | |    [optimizeTransferWaitTime](#rd_to_optimizeTransferWaitTime) | `boolean` | This enables the transfer wait time optimization. | *Optional* | `true` | 2.1 | -| [transitPriorityGroups](#rd_transitPriorityGroups) | `object` | Transit priority groups configuration | *Optional* | | 2.3 | +| [transitPriorityGroups](#rd_transitPriorityGroups) | `object` | Transit priority groups configuration | *Optional* | | 2.5 | | [transitReluctanceForMode](#rd_transitReluctanceForMode) | `enum map of double` | Transit reluctance for a given transport mode | *Optional* | | 2.1 | | [unpreferred](#rd_unpreferred) | `object` | Parameters listing authorities or lines that preferably should not be used in trip patters. | *Optional* | | 2.2 | |    [agencies](#rd_unpreferred_agencies) | `feed-scoped-id[]` | The ids of the agencies that incur an extra cost when being used. Format: `FeedId:AgencyId` | *Optional* | | 2.2 | @@ -249,12 +249,12 @@ We return number of seconds that we are willing to wait for preferred route.

relaxTransitPriorityGroup

-**Since version:** `2.3` ∙ **Type:** `string` ∙ **Cardinality:** `Optional` ∙ **Default value:** `"0s + 1.00 t"` +**Since version:** `2.5` ∙ **Type:** `string` ∙ **Cardinality:** `Optional` ∙ **Default value:** `"0s + 1.00 t"` **Path:** /routingDefaults The relax function for transit-priority-groups -A path is considered optimal if it the generalized-cost is less than the +A path is considered optimal if the generalized-cost is less than the generalized-cost of another path. If this parameter is set, the comparison is relaxed further if they belong to different transit-priority-groups. @@ -805,7 +805,7 @@ If not enabled generalizedCost function is used to pick the optimal transfer poi

transitPriorityGroups

-**Since version:** `2.3` ∙ **Type:** `object` ∙ **Cardinality:** `Optional` +**Since version:** `2.5` ∙ **Type:** `object` ∙ **Cardinality:** `Optional` **Path:** /routingDefaults Transit priority groups configuration @@ -816,7 +816,7 @@ be returned unless the cost is worse then the relaxation specified in the `relaxTransitPriorityGroup` parameter. This is only available in the TransmodelAPI for now. -THIS IS STILL AN EXPERIMENTAL FEATURE - IT MAY CHANGE WITHOUT ANY NOTICE! +**THIS IS STILL AN EXPERIMENTAL FEATURE - IT MAY CHANGE WITHOUT ANY NOTICE!**

transitReluctanceForMode

diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitPriorityGroup32n.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitPriorityGroup32n.java index befea6a511d..9b744932b8b 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitPriorityGroup32n.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitPriorityGroup32n.java @@ -4,7 +4,7 @@ import org.opentripplanner.raptor.api.request.RaptorTransitPriorityGroupCalculator; /** - * This is a "BitSet" implementation for groupId. It can store until 32 groups, + * This is a "BitSet" implementation for groupId. It can store upto 32 groups, * a set with few elements does NOT dominate a set with more elements. */ public class TransitPriorityGroup32n { @@ -33,7 +33,7 @@ public String toString() { } /** - * Left dominate right, if right contains a group witch does not exist in left. Left + * Left dominate right, if right contains a group which does not exist in left. Left * do NOT dominate right if they are equals or left is a super set of right. */ public static boolean dominate(int left, int right) { diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcher.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcher.java index 2ae9b543d24..f13a44e8a41 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcher.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcher.java @@ -15,7 +15,7 @@ import org.opentripplanner.transit.model.network.TripPattern; /** - * This class turn a {@link TransitPriorityGroupSelect} into a matcher. + * This class turns a {@link TransitPriorityGroupSelect} into a matcher. *

* Design: It uses the composite design pattern. A matcher is created for each * value in the "select", then the list of non-empty matchers is merged into @@ -138,7 +138,7 @@ boolean match(TripPattern pattern) { /** * Take a list of matchers and provide a single interface. At least one matcher in the - * list must match for the composite mather to return a match. + * list must match for the composite matcher to return a match. */ private static final class CompositeMatcher extends PriorityGroupMatcher { diff --git a/src/main/java/org/opentripplanner/routing/api/request/request/TransitRequest.java b/src/main/java/org/opentripplanner/routing/api/request/request/TransitRequest.java index 35a0fabaafb..fcb9b4bdabf 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/request/TransitRequest.java +++ b/src/main/java/org/opentripplanner/routing/api/request/request/TransitRequest.java @@ -60,8 +60,8 @@ public void setFilters(List filters) { /** * All transit patterns matching one of the {@link TransitPriorityGroupSelect}s is assigned the - * BASE-GROUP-ID. This is normally EVERYTHING including local-traffic, that does not - * need to be threaded in a special way. + * BASE-GROUP-ID. This is normally EVERYTHING, including local-traffic, that does not + * need to be treated in a special way. *

* Note! Entities that do not mach any of the three sets({@code #priorityGroupsBase()}, * {@link #priorityGroupsByAgency} and {@link #priorityGroupsGlobal()}) diff --git a/src/main/java/org/opentripplanner/standalone/config/framework/json/NodeInfo.java b/src/main/java/org/opentripplanner/standalone/config/framework/json/NodeInfo.java index f7910a491a9..296c07805f9 100644 --- a/src/main/java/org/opentripplanner/standalone/config/framework/json/NodeInfo.java +++ b/src/main/java/org/opentripplanner/standalone/config/framework/json/NodeInfo.java @@ -42,7 +42,7 @@ public record NodeInfo( ) implements Comparable { static final String EXPERIMENTAL_FEATURE = - "THIS IS STILL AN EXPERIMENTAL FEATURE - IT MAY CHANGE WITHOUT ANY NOTICE!"; + "**THIS IS STILL AN EXPERIMENTAL FEATURE - IT MAY CHANGE WITHOUT ANY NOTICE!**"; static final String TYPE_QUALIFIER = "type"; static final String SOURCETYPE_QUALIFIER = "sourceType"; diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java index 3dfe472137a..66f92da04eb 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java @@ -5,6 +5,7 @@ import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_2; import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_3; import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_4; +import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_5; import static org.opentripplanner.standalone.config.routerequest.ItineraryFiltersConfig.mapItineraryFilterParams; import static org.opentripplanner.standalone.config.routerequest.TransferConfig.mapTransferPreferences; import static org.opentripplanner.standalone.config.routerequest.VehicleRentalConfig.setVehicleRental; @@ -339,11 +340,11 @@ The board time is added to the time when going from the stop (offboard) to onboa String relaxTransitPriorityGroupValue = c .of("relaxTransitPriorityGroup") - .since(V2_3) + .since(V2_5) .summary("The relax function for transit-priority-groups") .description( """ - A path is considered optimal if it the generalized-cost is less than the + A path is considered optimal if the generalized-cost is less than the generalized-cost of another path. If this parameter is set, the comparison is relaxed further if they belong to different transit-priority-groups. """ diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/TransitPriorityGroupConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/TransitPriorityGroupConfig.java index 82f3347f2af..a1f648667ee 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/TransitPriorityGroupConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/TransitPriorityGroupConfig.java @@ -16,7 +16,7 @@ public class TransitPriorityGroupConfig { public static void mapTransitRequest(NodeAdapter root, TransitRequest transit) { var c = root .of("transitPriorityGroups") - .since(OtpVersion.V2_3) + .since(OtpVersion.V2_5) .summary("Transit priority groups configuration") .description( """ @@ -63,7 +63,7 @@ private static Collection mapList( ) { return root .of(parameterName) - .since(V2_3) + .since(V2_5) .summary("Configuration for transit priority groups.") .description(description + " The max total number of group-ids are 32, so be careful.") .asObjects(TransitPriorityGroupConfig::mapTransitGroupSelect); diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitPriorityGroup32nTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitPriorityGroup32nTest.java index 7b989c4d6dd..85083a3ee6a 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitPriorityGroup32nTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitPriorityGroup32nTest.java @@ -13,12 +13,13 @@ class TransitPriorityGroup32nTest { private static final int GROUP_INDEX_0 = 0; private static final int GROUP_INDEX_1 = 1; private static final int GROUP_INDEX_2 = 2; + private static final int GROUP_INDEX_30 = 30; private static final int GROUP_INDEX_31 = 31; private static final int GROUP_0 = TransitPriorityGroup32n.groupId(GROUP_INDEX_0); private static final int GROUP_1 = TransitPriorityGroup32n.groupId(GROUP_INDEX_1); private static final int GROUP_2 = TransitPriorityGroup32n.groupId(GROUP_INDEX_2); - private static final int GROUP_30 = TransitPriorityGroup32n.groupId(GROUP_INDEX_31); + private static final int GROUP_30 = TransitPriorityGroup32n.groupId(GROUP_INDEX_30); private static final int GROUP_31 = TransitPriorityGroup32n.groupId(GROUP_INDEX_31); private static final RaptorTransitPriorityGroupCalculator subjct = TransitPriorityGroup32n.priorityCalculator(); From cb1e67723058949cfd5878fb72924530e93d4930 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 21 Nov 2023 22:31:53 +0100 Subject: [PATCH 30/85] refactor: fix map problem with hash-code and equals in PriorityGroupConfigurator --- .../request/PriorityGroupConfigurator.java | 55 +++++++++++++++---- 1 file changed, 43 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfigurator.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfigurator.java index c4406680b5e..9564bfa0fd3 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfigurator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfigurator.java @@ -1,8 +1,11 @@ package org.opentripplanner.routing.algorithm.raptoradapter.transit.request; +import gnu.trove.impl.Constants; +import gnu.trove.map.TObjectIntMap; +import gnu.trove.map.hash.TObjectIntHashMap; +import java.util.Arrays; import java.util.Collection; -import java.util.HashMap; -import java.util.Map; +import java.util.List; import java.util.stream.Stream; import org.opentripplanner.framework.lang.ArrayUtils; import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.grouppriority.TransitPriorityGroup32n; @@ -26,14 +29,18 @@ public class PriorityGroupConfigurator { private final PriorityGroupMatcher[] baseMatchers; private final PriorityGroupMatcher[] agencyMatchers; private final PriorityGroupMatcher[] globalMatchers; - private final Map> agencyMatchersIds = new HashMap<>(); - private final Map globalMatchersIds = new HashMap<>(); + + // Index matchers and ids + private final List agencyMatchersIds; + private final List globalMatchersIds; private PriorityGroupConfigurator() { this.enabled = false; this.baseMatchers = null; this.agencyMatchers = null; this.globalMatchers = null; + this.agencyMatchersIds = List.of(); + this.globalMatchersIds = List.of(); } private PriorityGroupConfigurator( @@ -46,6 +53,10 @@ private PriorityGroupConfigurator( this.globalMatchers = PriorityGroupMatcher.of(global); this.enabled = Stream.of(baseMatchers, agencyMatchers, globalMatchers).anyMatch(ArrayUtils::hasContent); + this.globalMatchersIds = + Arrays.stream(globalMatchers).map(m -> new MatcherAndId(m, nextGroupId())).toList(); + // We need to populate this dynamically + this.agencyMatchersIds = Arrays.stream(agencyMatchers).map(MatcherAgencyAndIds::new).toList(); } public static PriorityGroupConfigurator empty() { @@ -69,7 +80,7 @@ public static PriorityGroupConfigurator of( * @throws IllegalArgumentException if more than 32 group-ids are requested. */ public int lookupTransitPriorityGroupId(RoutingTripPattern tripPattern) { - if (!enabled) { + if (!enabled || tripPattern == null) { return BASE_GROUP_ID; } @@ -80,15 +91,22 @@ public int lookupTransitPriorityGroupId(RoutingTripPattern tripPattern) { return BASE_GROUP_ID; } } - for (var matcher : agencyMatchers) { - if (matcher.match(p)) { - var agencyIds = agencyMatchersIds.computeIfAbsent(matcher, m -> new HashMap<>()); - return agencyIds.computeIfAbsent(p.getRoute().getAgency().getId(), id -> nextGroupId()); + for (var it : agencyMatchersIds) { + if (it.matcher().match(p)) { + var agencyId = p.getRoute().getAgency().getId(); + int groupId = it.ids().get(agencyId); + + if (groupId < 0) { + groupId = nextGroupId(); + it.ids.put(agencyId, groupId); + } + return groupId; } } - for (var matcher : globalMatchers) { - if (matcher.match(p)) { - return globalMatchersIds.computeIfAbsent(matcher, it -> nextGroupId()); + + for (var it : globalMatchersIds) { + if (it.matcher.match(p)) { + return it.groupId(); } } // Fallback to base-group-id @@ -98,4 +116,17 @@ public int lookupTransitPriorityGroupId(RoutingTripPattern tripPattern) { private int nextGroupId() { return TransitPriorityGroup32n.groupId(++groupIndexCounter); } + + /** Pair of matcher and groupId. Used only inside this class. */ + record MatcherAndId(PriorityGroupMatcher matcher, int groupId) {} + + /** Matcher with map of ids by agency. */ + record MatcherAgencyAndIds(PriorityGroupMatcher matcher, TObjectIntMap ids) { + MatcherAgencyAndIds(PriorityGroupMatcher matcher) { + this( + matcher, + new TObjectIntHashMap<>(Constants.DEFAULT_CAPACITY, Constants.DEFAULT_LOAD_FACTOR, -1) + ); + } + } } From f5dc552d179d6409f0e766441f35376b817d04ea Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Wed, 22 Nov 2023 10:11:37 +0100 Subject: [PATCH 31/85] refactor:Add PriorityGroupMatcher#toString --- .../transit/request/PriorityGroupMatcher.java | 55 +++++++++++++++++-- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcher.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcher.java index f13a44e8a41..dd7c1b46636 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcher.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcher.java @@ -1,14 +1,17 @@ package org.opentripplanner.routing.algorithm.raptoradapter.transit.request; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.EnumSet; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; import java.util.regex.Pattern; +import java.util.stream.Collectors; import org.opentripplanner.routing.api.request.request.filter.TransitPriorityGroupSelect; import org.opentripplanner.transit.model.basic.TransitMode; import org.opentripplanner.transit.model.framework.FeedScopedId; @@ -46,13 +49,15 @@ public static PriorityGroupMatcher of(TransitPriorityGroupSelect select) { list.add(new ModeMatcher(select.modes())); } if (!select.subModeRegexp().isEmpty()) { - list.add(new RegExpMatcher(select.subModeRegexp(), p -> p.getNetexSubmode().name())); + list.add( + new RegExpMatcher("SubMode", select.subModeRegexp(), p -> p.getNetexSubmode().name()) + ); } if (!select.agencyIds().isEmpty()) { - list.add(new IdMatcher(select.agencyIds(), p -> p.getRoute().getAgency().getId())); + list.add(new IdMatcher("Agency", select.agencyIds(), p -> p.getRoute().getAgency().getId())); } if (!select.routeIds().isEmpty()) { - list.add(new IdMatcher(select.agencyIds(), p -> p.getRoute().getId())); + list.add(new IdMatcher("Route", select.routeIds(), p -> p.getRoute().getId())); } return compositeOf(list); } @@ -65,6 +70,14 @@ static PriorityGroupMatcher[] of(Collection selector .toArray(PriorityGroupMatcher[]::new); } + private static String arrayToString(T[] values) { + return colToString(Arrays.asList(values)); + } + + private static String colToString(Collection values) { + return values.stream().map(Objects::toString).collect(Collectors.joining(" | ")); + } + private static PriorityGroupMatcher compositeOf(List list) { // Remove empty/noop matchers list = list.stream().filter(Predicate.not(PriorityGroupMatcher::isEmpty)).toList(); @@ -96,14 +109,25 @@ public ModeMatcher(List modes) { boolean match(TripPattern pattern) { return modes.contains(pattern.getMode()); } + + @Override + public String toString() { + return "Mode(" + colToString(modes) + ')'; + } } private static final class RegExpMatcher extends PriorityGroupMatcher { + private final String typeName; private final Pattern[] subModeRegexp; private final Function toValue; - public RegExpMatcher(List subModeRegexp, Function toValue) { + public RegExpMatcher( + String typeName, + List subModeRegexp, + Function toValue + ) { + this.typeName = typeName; this.subModeRegexp = subModeRegexp.stream().map(Pattern::compile).toArray(Pattern[]::new); this.toValue = toValue; } @@ -118,14 +142,25 @@ boolean match(TripPattern pattern) { } return false; } + + @Override + public String toString() { + return typeName + "Regexp(" + arrayToString(subModeRegexp) + ')'; + } } private static final class IdMatcher extends PriorityGroupMatcher { + private final String typeName; private final Set ids; private final Function idProvider; - public IdMatcher(List ids, Function idProvider) { + public IdMatcher( + String typeName, + List ids, + Function idProvider + ) { + this.typeName = typeName; this.ids = new HashSet<>(ids); this.idProvider = idProvider; } @@ -134,6 +169,11 @@ public IdMatcher(List ids, Function idP boolean match(TripPattern pattern) { return ids.contains(idProvider.apply(pattern)); } + + @Override + public String toString() { + return typeName + "Id(" + colToString(ids) + ')'; + } } /** @@ -157,5 +197,10 @@ boolean match(TripPattern pattern) { } return false; } + + @Override + public String toString() { + return "(" + arrayToString(matchers) + ')'; + } } } From f0bf669b2af9cf033c79b9be10d927513f2e27f9 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Wed, 22 Nov 2023 10:13:00 +0100 Subject: [PATCH 32/85] refactor:Add unit tests on PriorityGroupMatcher and PriorityGroupConfigurator --- .../ConstrainedBoardingSearchTest.java | 4 +- .../PriorityGroupConfiguratorTest.java | 89 +++++++++++++ .../request/PriorityGroupMatcherTest.java | 118 ++++++++++++++++++ .../transit/request/TestRouteData.java | 111 +++++++++++++++- 4 files changed, 315 insertions(+), 7 deletions(-) create mode 100644 src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfiguratorTest.java create mode 100644 src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcherTest.java diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/constrainedtransfer/ConstrainedBoardingSearchTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/constrainedtransfer/ConstrainedBoardingSearchTest.java index ec7edf3bda6..214ea55eb3c 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/constrainedtransfer/ConstrainedBoardingSearchTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/constrainedtransfer/ConstrainedBoardingSearchTest.java @@ -110,7 +110,7 @@ public class ConstrainedBoardingSearchTest { @BeforeEach void setup() { route1 = - new TestRouteData( + TestRouteData.of( "R1", TransitMode.RAIL, List.of(STOP_A, STOP_B, STOP_C), @@ -119,7 +119,7 @@ void setup() { ); route2 = - new TestRouteData( + TestRouteData.of( "R2", TransitMode.BUS, List.of(STOP_B, STOP_C, STOP_D), diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfiguratorTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfiguratorTest.java new file mode 100644 index 00000000000..6ca2e999c35 --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfiguratorTest.java @@ -0,0 +1,89 @@ +package org.opentripplanner.routing.algorithm.raptoradapter.transit.request; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.routing.algorithm.raptoradapter.transit.request.TestTransitCaseData.STOP_A; +import static org.opentripplanner.routing.algorithm.raptoradapter.transit.request.TestTransitCaseData.STOP_B; +import static org.opentripplanner.routing.algorithm.raptoradapter.transit.request.TestTransitCaseData.STOP_D; + +import java.util.List; +import org.junit.jupiter.api.Test; +import org.opentripplanner.routing.api.request.request.filter.TransitPriorityGroupSelect; +import org.opentripplanner.transit.model.basic.TransitMode; +import org.opentripplanner.transit.model.network.RoutingTripPattern; + +class PriorityGroupConfiguratorTest { + + private final TestRouteData routeA = TestRouteData.of( + "R1", + TransitMode.RAIL, + List.of(STOP_A, STOP_B), + "10:00 10:10" + ); + private final TestRouteData routeB = TestRouteData.of( + "B2", + TransitMode.BUS, + List.of(STOP_B, STOP_D), + "10:15 10:40" + ); + private final TestRouteData routeC = TestRouteData.of( + "R3", + TransitMode.RAIL, + List.of(STOP_A, STOP_B), + "10:00 10:10" + ); + private final TestRouteData routeD = TestRouteData.of( + "R3", + TransitMode.FERRY, + List.of(STOP_A, STOP_B), + "10:00 10:10" + ); + + private final RoutingTripPattern railA = routeA.getTripPattern().getRoutingTripPattern(); + private final RoutingTripPattern busB = routeB.getTripPattern().getRoutingTripPattern(); + private final RoutingTripPattern railC = routeC.getTripPattern().getRoutingTripPattern(); + private final RoutingTripPattern ferryC = routeD.getTripPattern().getRoutingTripPattern(); + + @Test + void emptyConfigurationShouldReturnGroupZero() { + var subject = PriorityGroupConfigurator.of(List.of(), List.of(), List.of()); + assertEquals(0, subject.lookupTransitPriorityGroupId(railA)); + assertEquals(0, subject.lookupTransitPriorityGroupId(busB)); + assertEquals(0, subject.lookupTransitPriorityGroupId(null)); + } + + @Test + void lookupTransitPriorityGroupIdBySameAgency() { + var subject = PriorityGroupConfigurator.of( + List.of(), + List.of( + TransitPriorityGroupSelect.of().addModes(List.of(TransitMode.BUS)).build(), + TransitPriorityGroupSelect.of().addModes(List.of(TransitMode.RAIL)).build() + ), + List.of() + ); + + assertEquals(0, subject.lookupTransitPriorityGroupId(null)); + assertEquals(0, subject.lookupTransitPriorityGroupId(ferryC)); + assertEquals(1, subject.lookupTransitPriorityGroupId(railA)); + assertEquals(2, subject.lookupTransitPriorityGroupId(busB)); + assertEquals(1, subject.lookupTransitPriorityGroupId(railC)); + } + + @Test + void lookupTransitPriorityGroupIdByGlobalMode() { + var subject = PriorityGroupConfigurator.of( + List.of(), + List.of(), + List.of( + TransitPriorityGroupSelect.of().addModes(List.of(TransitMode.BUS)).build(), + TransitPriorityGroupSelect.of().addModes(List.of(TransitMode.RAIL)).build() + ) + ); + + assertEquals(0, subject.lookupTransitPriorityGroupId(null)); + assertEquals(0, subject.lookupTransitPriorityGroupId(ferryC)); + assertEquals(2, subject.lookupTransitPriorityGroupId(railA)); + assertEquals(1, subject.lookupTransitPriorityGroupId(busB)); + assertEquals(2, subject.lookupTransitPriorityGroupId(railC)); + } +} diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcherTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcherTest.java new file mode 100644 index 00000000000..1205b6b2205 --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcherTest.java @@ -0,0 +1,118 @@ +package org.opentripplanner.routing.algorithm.raptoradapter.transit.request; + +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 java.util.List; +import org.junit.jupiter.api.Test; +import org.opentripplanner.routing.api.request.request.filter.TransitPriorityGroupSelect; +import org.opentripplanner.transit.model.basic.TransitMode; +import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.model.network.TripPattern; + +class PriorityGroupMatcherTest { + + private final TestRouteData r1 = TestRouteData.rail("R1").withAgency("A1").build(); + private final TestRouteData b1 = TestRouteData.bus("B2").withAgency("A2").build(); + private final TestRouteData f1 = TestRouteData + .ferry("F1") + .withAgency("A1") + .withSubmode("localFerry") + .build(); + + private final TripPattern rail1 = r1.getTripPattern(); + private final TripPattern bus = b1.getTripPattern(); + private final TripPattern ferry = f1.getTripPattern(); + private final FeedScopedId r1agencyId = rail1.getRoute().getAgency().getId(); + private final FeedScopedId r1routeId = rail1.getRoute().getId(); + private final FeedScopedId anyId = new FeedScopedId("F", "ANY"); + + @Test + void testMode() { + var m = PriorityGroupMatcher.of( + TransitPriorityGroupSelect.of().addModes(List.of(TransitMode.BUS, TransitMode.TRAM)).build() + ); + assertEquals("Mode(BUS | TRAM)", m.toString()); + assertFalse(m.isEmpty()); + assertTrue(m.match(bus)); + assertFalse(m.match(rail1)); + assertFalse(m.match(ferry)); + } + + @Test + void testAgencyIds() { + var matchers = List.of( + PriorityGroupMatcher.of( + TransitPriorityGroupSelect.of().addAgencyIds(List.of(r1agencyId)).build() + ), + PriorityGroupMatcher.of( + TransitPriorityGroupSelect.of().addAgencyIds(List.of(r1agencyId, anyId)).build() + ) + ); + + assertEquals("AgencyId(F:A1)", matchers.get(0).toString()); + assertEquals("AgencyId(F:A1 | F:ANY)", matchers.get(1).toString()); + + for (PriorityGroupMatcher m : matchers) { + assertFalse(m.isEmpty()); + assertTrue(m.match(rail1)); + assertTrue(m.match(ferry)); + assertFalse(m.match(bus)); + } + } + + @Test + void routeIds() { + var matchers = List.of( + PriorityGroupMatcher.of( + TransitPriorityGroupSelect.of().addRouteIds(List.of(r1routeId)).build() + ), + PriorityGroupMatcher.of( + TransitPriorityGroupSelect.of().addRouteIds(List.of(r1routeId, anyId)).build() + ) + ); + + assertEquals("RouteId(F:R1)", matchers.get(0).toString()); + assertEquals("RouteId(F:R1 | F:ANY)", matchers.get(1).toString()); + + for (PriorityGroupMatcher m : matchers) { + assertFalse(m.isEmpty()); + assertTrue(m.match(rail1)); + assertFalse(m.match(ferry)); + assertFalse(m.match(bus)); + } + } + + @Test + void testSubMode() { + var subject = PriorityGroupMatcher.of( + TransitPriorityGroupSelect.of().addSubModeRegexp(List.of(".*local.*")).build() + ); + + assertEquals("SubModeRegexp(.*local.*)", subject.toString()); + + assertFalse(subject.isEmpty()); + assertFalse(subject.match(rail1)); + assertTrue(subject.match(ferry)); + assertFalse(subject.match(bus)); + } + + @Test + void testToString() { + var m = PriorityGroupMatcher.of( + TransitPriorityGroupSelect + .of() + .addModes(List.of(TransitMode.BUS, TransitMode.TRAM)) + .addAgencyIds(List.of(anyId, r1agencyId)) + .addRouteIds(List.of(r1routeId)) + .addSubModeRegexp(List.of(".*local.*")) + .build() + ); + + assertEquals( + "(Mode(BUS | TRAM) | SubModeRegexp(.*local.*) | AgencyId(F:A1 | F:ANY) | RouteId(F:R1))", + m.toString() + ); + } +} diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TestRouteData.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TestRouteData.java index fc5ceb350c9..598918bcf01 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TestRouteData.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TestRouteData.java @@ -2,6 +2,8 @@ import static org.opentripplanner.routing.algorithm.raptoradapter.transit.request.TestTransitCaseData.DATE; import static org.opentripplanner.routing.algorithm.raptoradapter.transit.request.TestTransitCaseData.OFFSET; +import static org.opentripplanner.routing.algorithm.raptoradapter.transit.request.TestTransitCaseData.STOP_A; +import static org.opentripplanner.routing.algorithm.raptoradapter.transit.request.TestTransitCaseData.STOP_B; import java.util.ArrayList; import java.util.Arrays; @@ -38,13 +40,13 @@ public class TestRouteData { private final TripPattern tripPattern; private Trip currentTrip; - public TestRouteData(String route, TransitMode mode, List stops, String... times) { + public TestRouteData(Route route, List stops, List times) { final Deduplicator deduplicator = new Deduplicator(); - this.route = TransitModelForTest.route(route).withMode(mode).withShortName(route).build(); + this.route = route; this.trips = - Arrays - .stream(times) - .map(it -> parseTripInfo(route, it, stops, deduplicator)) + times + .stream() + .map(it -> parseTripInfo(route.getName(), it, stops, deduplicator)) .collect(Collectors.toList()); List stopTimesFistTrip = firstTrip().getStopTimes(); @@ -84,6 +86,35 @@ public TestRouteData(String route, TransitMode mode, List stops, St this.timetable = patternForDates; } + public static TestRouteData of( + String route, + TransitMode mode, + List stops, + String... times + ) { + return new TestRouteData.Builder(route) + .withMode(mode) + .withStops(stops) + .withTimes(Arrays.asList(times)) + .build(); + } + + public static TestRouteData.Builder of(String route, TransitMode mode) { + return new TestRouteData.Builder(route).withMode(mode); + } + + public static TestRouteData.Builder bus(String route) { + return of(route, TransitMode.BUS); + } + + public static TestRouteData.Builder rail(String route) { + return of(route, TransitMode.RAIL); + } + + public static TestRouteData.Builder ferry(String route) { + return of(route, TransitMode.FERRY); + } + public Route getRoute() { return route; } @@ -168,4 +199,74 @@ private StopTime stopTime(Trip trip, RegularStop stop, int time, int seq) { s.setRouteShortName("NA"); return s; } + + public static class Builder { + + private final String route; + private String agency; + private TransitMode mode = TransitMode.BUS; + private String submode; + private List stops; + private List times; + + public Builder(String route) { + this.route = route; + } + + public Builder withAgency(String agency) { + this.agency = agency; + return this; + } + + public Builder withMode(TransitMode mode) { + this.mode = mode; + return this; + } + + public Builder withStops(List stops) { + this.stops = stops; + return this; + } + + public List stops() { + if (stops == null) { + withStops(List.of(STOP_A, STOP_B)); + } + return stops; + } + + public Builder withTimes(List times) { + this.times = times; + return this; + } + + public List times() { + if (times == null) { + var buf = new StringBuilder(); + int t = TimeUtils.time("10:00"); + for (var ignore : stops()) { + t += 600; + buf.append(" ").append(TimeUtils.timeToStrLong(t)); + } + this.times = List.of(buf.substring(1)); + } + return times; + } + + public Builder withSubmode(String submode) { + this.submode = submode; + return this; + } + + public TestRouteData build() { + var routeBuilder = TransitModelForTest.route(route).withMode(mode).withShortName(route); + if (agency != null) { + routeBuilder.withAgency(TransitModelForTest.agency(agency)); + } + if (submode != null) { + routeBuilder.withNetexSubmode(submode); + } + return new TestRouteData(routeBuilder.build(), stops(), times()); + } + } } From 834f9ea61325f441a87925a9c86e9b3c224de5b5 Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Wed, 22 Nov 2023 12:12:32 +0000 Subject: [PATCH 33/85] Add changelog entry for #5520 [ci skip] --- docs/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Changelog.md b/docs/Changelog.md index a2eece5d664..e9af1cb2090 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -49,6 +49,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Check transport mode when mapping GroupStops [#5518](https://github.com/opentripplanner/OpenTripPlanner/pull/5518) - Cleanup trip times - Part A [#5437](https://github.com/opentripplanner/OpenTripPlanner/pull/5437) - Transfer cost limit [#5516](https://github.com/opentripplanner/OpenTripPlanner/pull/5516) +- Fix missed trip when arrive-by search-window is off by one minute [#5520](https://github.com/opentripplanner/OpenTripPlanner/pull/5520) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.4.0 (2023-09-13) From df412960be4b778d12e80e0264d045da7cb8c7dd Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Wed, 22 Nov 2023 14:06:16 +0100 Subject: [PATCH 34/85] feature: Add validation to ScheduledTripTimes (not enabled, FLEX fails) --- .../model/timetable/ScheduledTripTimes.java | 119 ++++++++----- .../timetable/ScheduledTripTimesBuilder.java | 80 +++++++-- .../model/timetable/TripTimesFactory.java | 4 +- ...rRoutingRequestTransitDataCreatorTest.java | 22 +-- .../timetable/ScheduledTripTimesTest.java | 160 ++++++++++++++++++ .../model/timetable/TripTimesTest.java | 76 +++------ 6 files changed, 340 insertions(+), 121 deletions(-) create mode 100644 src/test/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesTest.java diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java index c5a5d8f097c..39d4e58c7b6 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java @@ -1,23 +1,30 @@ package org.opentripplanner.transit.model.timetable; -import static org.opentripplanner.transit.model.timetable.ValidationError.ErrorCode.NEGATIVE_DWELL_TIME; -import static org.opentripplanner.transit.model.timetable.ValidationError.ErrorCode.NEGATIVE_HOP_TIME; - import java.io.Serializable; import java.util.Arrays; import java.util.BitSet; import java.util.List; -import java.util.Optional; +import java.util.Objects; import java.util.OptionalInt; import java.util.function.Supplier; import javax.annotation.Nullable; import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.framework.lang.IntUtils; +import org.opentripplanner.framework.time.DurationUtils; import org.opentripplanner.model.BookingInfo; import org.opentripplanner.transit.model.basic.Accessibility; import org.opentripplanner.transit.model.framework.Deduplicator; -final class ScheduledTripTimes implements Serializable, Comparable { +public final class ScheduledTripTimes implements Serializable, Comparable { + + /** + * When time-shifting from one time-zone to another negative times may occur. + */ + private static final int MIN_TIME = DurationUtils.durationInSeconds("-12h"); + /** + * We allow a trip to last for maximum 10 days. In Norway the longest trip is 6 days. + */ + private static final int MAX_TIME = DurationUtils.durationInSeconds("10d"); /** * Implementation notes: This allows re-using the same scheduled arrival and departure time @@ -48,17 +55,22 @@ final class ScheduledTripTimes implements Serializable, Comparable validateNonIncreasingTimes() { - final int nStops = arrivalTimes.length; - int prevDep = -9_999_999; - for (int s = 0; s < nStops; s++) { - final int arr = getArrivalTime(s); - final int dep = getDepartureTime(s); - - if (dep < arr) { - return Optional.of(new ValidationError(NEGATIVE_DWELL_TIME, s)); - } - if (prevDep > arr) { - return Optional.of(new ValidationError(NEGATIVE_HOP_TIME, s)); - } - prevDep = dep; - } - return Optional.empty(); - } - /** Sort trips based on first departure time. */ @Override public int compareTo(final ScheduledTripTimes other) { @@ -343,4 +330,52 @@ int[] copyDepartureTimes() { I18NString[] copyHeadsigns(Supplier defaultValue) { return headsigns == null ? defaultValue.get() : Arrays.copyOf(headsigns, headsigns.length); } + + /* private methods */ + + private void validate() { + int lastStop = departureTimes.length - 1; + IntUtils.requireInRange(departureTimes[0], MIN_TIME, MAX_TIME); + IntUtils.requireInRange(arrivalTimes[lastStop], MIN_TIME, MAX_TIME); + // TODO: This class is used by FLEX, so we can not validate increasing TripTimes + // validateNonIncreasingTimes(); + } + + /** + * When creating scheduled trip times we could potentially imply negative running or dwell times. + * We really don't want those being used in routing. This method checks that all times are + * increasing. The first stop arrival time and the last stops depature time is NOT checked - + * these should be ignored by raptor. + */ + private void validateNonIncreasingTimes() { + final int lastStop = arrivalTimes.length - 1; + + // This check is currently used since Flex trips may have only one stop. This class should + // not be used to represent FLEX, so remove this check and create new data classes for FLEX + // trips. + if (lastStop < 1) { + return; + } + int prevDep = getDepartureTime(0); + + for (int i = 1; true; ++i) { + final int arr = getArrivalTime(i); + final int dep = getDepartureTime(i); + + if (prevDep > arr) { + throw new IllegalArgumentException( + "Negative hop time for stop position " + i + " for " + trip + "." + ); + } + if (i == lastStop) { + return; + } + if (dep < arr) { + throw new IllegalArgumentException( + "Negative dwell time for stop position " + i + " for " + trip + "." + ); + } + prevDep = dep; + } + } } diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesBuilder.java b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesBuilder.java index d6515321f74..6fc5f968826 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesBuilder.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesBuilder.java @@ -4,6 +4,7 @@ import java.util.List; import javax.annotation.Nullable; import org.opentripplanner.framework.i18n.I18NString; +import org.opentripplanner.framework.time.TimeUtils; import org.opentripplanner.model.BookingInfo; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.framework.DeduplicatorService; @@ -11,18 +12,19 @@ public class ScheduledTripTimesBuilder { private final int NOT_SET = -1; - - int timeShift; - int serviceCode = NOT_SET; - int[] arrivalTimes; - int[] departureTimes; - BitSet timepoints; - Trip trip; - List dropOffBookingInfos; - List pickupBookingInfos; - I18NString[] headsigns; - String[][] headsignVias; - int[] originalGtfsStopSequence; + private final BitSet EMPTY_BIT_SET = new BitSet(0); + + private int timeShift; + private int serviceCode = NOT_SET; + private int[] arrivalTimes; + private int[] departureTimes; + private BitSet timepoints; + private Trip trip; + private List dropOffBookingInfos; + private List pickupBookingInfos; + private I18NString[] headsigns; + private String[][] headsignVias; + private int[] originalGtfsStopSequence; private final DeduplicatorService deduplicator; ScheduledTripTimesBuilder(@Nullable DeduplicatorService deduplicator) { @@ -57,6 +59,10 @@ public class ScheduledTripTimesBuilder { this.originalGtfsStopSequence = originalGtfsStopSequence; } + public int timeShift() { + return timeShift; + } + public ScheduledTripTimesBuilder withTimeShift(int timeShift) { this.timeShift = timeShift; return this; @@ -71,53 +77,103 @@ public ScheduledTripTimesBuilder plusTimeShift(int delta) { return this; } + public int serviceCode() { + return serviceCode; + } + public ScheduledTripTimesBuilder withServiceCode(int serviceCode) { this.serviceCode = serviceCode; return this; } + public int[] arrivalTimes() { + return arrivalTimes == null ? departureTimes : arrivalTimes; + } + public ScheduledTripTimesBuilder withArrivalTimes(int[] arrivalTimes) { this.arrivalTimes = deduplicator.deduplicateIntArray(arrivalTimes); return this; } + /** For unit testing, uses {@link TimeUtils#time(java.lang.String)}. */ + public ScheduledTripTimesBuilder withArrivalTimes(String arrivalTimes) { + return withArrivalTimes(TimeUtils.times(arrivalTimes)); + } + + public int[] departureTimes() { + return departureTimes == null ? arrivalTimes : departureTimes; + } + public ScheduledTripTimesBuilder withDepartureTimes(int[] departureTimes) { this.departureTimes = deduplicator.deduplicateIntArray(departureTimes); return this; } + /** For unit testing, uses {@link TimeUtils#time(java.lang.String)}. */ + public ScheduledTripTimesBuilder withDepartureTimes(String departureTimes) { + return withDepartureTimes(TimeUtils.times(departureTimes)); + } + + public BitSet timepoints() { + return timepoints == null ? EMPTY_BIT_SET : timepoints; + } + public ScheduledTripTimesBuilder withTimepoints(BitSet timepoints) { this.timepoints = deduplicator.deduplicateBitSet(timepoints); return this; } + public Trip trip() { + return trip; + } + public ScheduledTripTimesBuilder withTrip(Trip trip) { this.trip = trip; return this; } + public List dropOffBookingInfos() { + return dropOffBookingInfos == null ? List.of() : dropOffBookingInfos; + } + public ScheduledTripTimesBuilder withDropOffBookingInfos(List dropOffBookingInfos) { this.dropOffBookingInfos = deduplicator.deduplicateImmutableList(BookingInfo.class, dropOffBookingInfos); return this; } + public List pickupBookingInfos() { + return pickupBookingInfos == null ? List.of() : pickupBookingInfos; + } + public ScheduledTripTimesBuilder withPickupBookingInfos(List pickupBookingInfos) { this.pickupBookingInfos = deduplicator.deduplicateImmutableList(BookingInfo.class, pickupBookingInfos); return this; } + public I18NString[] headsigns() { + return headsigns; + } + public ScheduledTripTimesBuilder withHeadsigns(I18NString[] headsigns) { this.headsigns = deduplicator.deduplicateObjectArray(I18NString.class, headsigns); return this; } + public String[][] headsignVias() { + return headsignVias; + } + public ScheduledTripTimesBuilder withHeadsignVias(String[][] headsignVias) { this.headsignVias = deduplicator.deduplicateString2DArray(headsignVias); return this; } + public int[] originalGtfsStopSequence() { + return originalGtfsStopSequence; + } + public ScheduledTripTimesBuilder withOriginalGtfsStopSequence(int[] originalGtfsStopSequence) { this.originalGtfsStopSequence = deduplicator.deduplicateIntArray(originalGtfsStopSequence); return this; diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimesFactory.java b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimesFactory.java index 1bb953b81c5..c868fda6582 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimesFactory.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimesFactory.java @@ -1,6 +1,6 @@ package org.opentripplanner.transit.model.timetable; -import java.util.Collection; +import java.util.List; import org.opentripplanner.model.StopTime; import org.opentripplanner.transit.model.framework.Deduplicator; @@ -20,7 +20,7 @@ public class TripTimesFactory { */ public static TripTimes tripTimes( Trip trip, - Collection stopTimes, + List stopTimes, Deduplicator deduplicator ) { return new TripTimes(StopTimeToScheduledTripTimesMapper.map(trip, stopTimes, deduplicator)); diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java index 20ca21a50a6..e59a5360808 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java @@ -6,7 +6,6 @@ import java.time.LocalDate; import java.time.ZonedDateTime; import java.util.ArrayList; -import java.util.Arrays; import java.util.BitSet; import java.util.List; import org.junit.jupiter.api.Test; @@ -16,13 +15,12 @@ import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripPatternForDate; import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.basic.TransitMode; -import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.network.RoutingTripPattern; import org.opentripplanner.transit.model.network.StopPattern; import org.opentripplanner.transit.model.network.TripPattern; +import org.opentripplanner.transit.model.timetable.ScheduledTripTimes; import org.opentripplanner.transit.model.timetable.TripTimes; -import org.opentripplanner.transit.model.timetable.TripTimesFactory; public class RaptorRoutingRequestTransitDataCreatorTest { @@ -98,21 +96,17 @@ private static TripPatternForDates findTripPatternForDate( } private TripTimes createTripTimesForTest() { - StopTime stopTime1 = new StopTime(); - StopTime stopTime2 = new StopTime(); - - stopTime1.setDepartureTime(0); - stopTime2.setArrivalTime(7200); - - return TripTimesFactory.tripTimes( - TransitModelForTest.trip("Test").build(), - Arrays.asList(stopTime1, stopTime2), - new Deduplicator() + return TripTimes.of( + ScheduledTripTimes + .of() + .withTrip(TransitModelForTest.trip("Test").build()) + .withDepartureTimes("00:00 02:00") + .build() ); } /** - * Utility function to create bare minimum of valid StopTime with no interesting attributes + * Utility function to create bare minimum of valid StopTime * * @return StopTime instance */ diff --git a/src/test/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesTest.java b/src/test/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesTest.java new file mode 100644 index 00000000000..d548be2309b --- /dev/null +++ b/src/test/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesTest.java @@ -0,0 +1,160 @@ +package org.opentripplanner.transit.model.timetable; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +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 static org.opentripplanner.transit.model._data.TransitModelForTest.id; + +import java.util.BitSet; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.opentripplanner.framework.time.TimeUtils; +import org.opentripplanner.transit.model._data.TransitModelForTest; +import org.opentripplanner.transit.model.basic.Accessibility; +import org.opentripplanner.transit.model.framework.FeedScopedId; + +class ScheduledTripTimesTest { + + private static final Trip TRIP = TransitModelForTest.trip("Trip-1").build(); + + private static final List STOP_IDS = List.of(id("A"), id("B"), id("C")); + private static final int SERVICE_CODE = 5; + private static final BitSet TIMEPOINTS = new BitSet(3); + private static final int T10_00 = TimeUtils.time("10:00"); + private static final int T10_01 = T10_00 + 60; + private static final int T11_00 = TimeUtils.time("11:00"); + private static final int T11_02 = T11_00 + 120; + private static final int T12_00 = TimeUtils.time("12:00"); + private static final int T12_03 = T12_00 + 180; + public static final int STOP_POS_0 = 0; + public static final int STOP_POS_1 = 1; + public static final int STOP_POS_2 = 2; + + static { + TIMEPOINTS.set(1); + } + + private final ScheduledTripTimes subject = ScheduledTripTimes + .of(null) + .withArrivalTimes("10:00 11:00 12:00") + .withDepartureTimes("10:01 11:02 12:03") + .withServiceCode(SERVICE_CODE) + .withTrip(TRIP) + .withTimepoints(TIMEPOINTS) + .build(); + + @Test + void getServiceCode() { + assertEquals(SERVICE_CODE, subject.getServiceCode()); + } + + @Test + void getScheduledArrivalTime() { + assertEquals(T10_00, subject.getScheduledArrivalTime(STOP_POS_0)); + assertEquals(T11_00, subject.getScheduledArrivalTime(STOP_POS_1)); + assertEquals(T12_00, subject.getScheduledArrivalTime(STOP_POS_2)); + } + + @Test + void getArrivalTime() { + assertEquals(T10_00, subject.getArrivalTime(STOP_POS_0)); + assertEquals(T11_00, subject.getArrivalTime(STOP_POS_1)); + assertEquals(T12_00, subject.getArrivalTime(STOP_POS_2)); + } + + @Test + void getArrivalDelay() { + assertEquals(0, subject.getArrivalDelay(STOP_POS_0)); + assertEquals(0, subject.getArrivalDelay(STOP_POS_1)); + assertEquals(0, subject.getArrivalDelay(STOP_POS_2)); + } + + @Test + void getScheduledDepartureTime() { + assertEquals(T10_01, subject.getScheduledDepartureTime(STOP_POS_0)); + assertEquals(T11_02, subject.getScheduledDepartureTime(STOP_POS_1)); + assertEquals(T12_03, subject.getScheduledDepartureTime(STOP_POS_2)); + } + + @Test + void getDepartureTime() { + assertEquals(T10_01, subject.getDepartureTime(STOP_POS_0)); + assertEquals(T11_02, subject.getDepartureTime(STOP_POS_1)); + assertEquals(T12_03, subject.getDepartureTime(STOP_POS_2)); + } + + @Test + void getDepartureDelay() { + assertEquals(0, subject.getDepartureDelay(STOP_POS_0)); + assertEquals(0, subject.getDepartureDelay(STOP_POS_1)); + assertEquals(0, subject.getDepartureDelay(STOP_POS_2)); + } + + @Test + void isTimepoint() { + assertFalse(subject.isTimepoint(STOP_POS_0)); + assertTrue(subject.isTimepoint(STOP_POS_1)); + assertFalse(subject.isTimepoint(STOP_POS_2)); + } + + @Test + void getTrip() { + assertEquals(TRIP, subject.getTrip()); + } + + @Test + void sortIndex() { + assertEquals(T10_01, subject.sortIndex()); + } + + @Test + void isScheduled() { + assertTrue(subject.isScheduled()); + } + + @Test + void isCanceledOrDeleted() { + assertFalse(subject.isCanceledOrDeleted()); + } + + @Test + void isCanceled() { + assertFalse(subject.isCanceled()); + } + + @Test + void isDeleted() { + assertFalse(subject.isDeleted()); + } + + @Test + void getRealTimeState() { + assertEquals(RealTimeState.SCHEDULED, subject.getRealTimeState()); + } + + @Test + void getNumStops() { + assertEquals(3, subject.getNumStops()); + } + + @Test + void getWheelchairAccessibility() { + assertEquals(Accessibility.NO_INFORMATION, subject.getWheelchairAccessibility()); + } + + @Test + void getOccupancyStatus() { + assertEquals(OccupancyStatus.NO_DATA_AVAILABLE, subject.getOccupancyStatus(0)); + } + + @Test + void copyArrivalTimes() { + assertArrayEquals(new int[] { T10_00, T11_00, T12_00 }, subject.copyArrivalTimes()); + } + + @Test + void copyDepartureTimes() { + assertArrayEquals(new int[] { T10_01, T11_02, T12_03 }, subject.copyDepartureTimes()); + } +} diff --git a/src/test/java/org/opentripplanner/transit/model/timetable/TripTimesTest.java b/src/test/java/org/opentripplanner/transit/model/timetable/TripTimesTest.java index 4c2cc41cef2..af37c812667 100644 --- a/src/test/java/org/opentripplanner/transit/model/timetable/TripTimesTest.java +++ b/src/test/java/org/opentripplanner/transit/model/timetable/TripTimesTest.java @@ -8,7 +8,6 @@ import static org.opentripplanner.transit.model.timetable.ValidationError.ErrorCode.NEGATIVE_DWELL_TIME; import static org.opentripplanner.transit.model.timetable.ValidationError.ErrorCode.NEGATIVE_HOP_TIME; -import java.util.Collection; import java.util.LinkedList; import java.util.List; import org.junit.jupiter.api.Nested; @@ -27,7 +26,7 @@ class TripTimesTest { private static final String TRIP_ID = "testTripId"; - private static final List stops = List.of( + private static final List stopIds = List.of( id("A"), id("B"), id("C"), @@ -43,10 +42,10 @@ static TripTimes createInitialTripTimes() { List stopTimes = new LinkedList<>(); - for (int i = 0; i < stops.size(); ++i) { + for (int i = 0; i < stopIds.size(); ++i) { StopTime stopTime = new StopTime(); - RegularStop stop = TEST_MODEL.stop(stops.get(i).getId(), 0.0, 0.0).build(); + RegularStop stop = TEST_MODEL.stop(stopIds.get(i).getId(), 0.0, 0.0).build(); stopTime.setStop(stop); stopTime.setArrivalTime(i * 60); stopTime.setDepartureTime(i * 60); @@ -69,7 +68,7 @@ class Headsign { @Test void shouldHandleBothNullScenario() { Trip trip = TransitModelForTest.trip("TRIP").build(); - Collection stopTimes = List.of(EMPTY_STOPPOINT, EMPTY_STOPPOINT, EMPTY_STOPPOINT); + List stopTimes = List.of(EMPTY_STOPPOINT, EMPTY_STOPPOINT, EMPTY_STOPPOINT); TripTimes tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator()); @@ -80,7 +79,7 @@ void shouldHandleBothNullScenario() { @Test void shouldHandleTripOnlyHeadSignScenario() { Trip trip = TransitModelForTest.trip("TRIP").withHeadsign(DIRECTION).build(); - Collection stopTimes = List.of(EMPTY_STOPPOINT, EMPTY_STOPPOINT, EMPTY_STOPPOINT); + List stopTimes = List.of(EMPTY_STOPPOINT, EMPTY_STOPPOINT, EMPTY_STOPPOINT); TripTimes tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator()); @@ -93,11 +92,7 @@ void shouldHandleStopsOnlyHeadSignScenario() { Trip trip = TransitModelForTest.trip("TRIP").build(); StopTime stopWithHeadsign = new StopTime(); stopWithHeadsign.setStopHeadsign(STOP_TEST_DIRECTION); - Collection stopTimes = List.of( - stopWithHeadsign, - stopWithHeadsign, - stopWithHeadsign - ); + List stopTimes = List.of(stopWithHeadsign, stopWithHeadsign, stopWithHeadsign); TripTimes tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator()); @@ -110,11 +105,7 @@ void shouldHandleStopsEqualToTripHeadSignScenario() { Trip trip = TransitModelForTest.trip("TRIP").withHeadsign(DIRECTION).build(); StopTime stopWithHeadsign = new StopTime(); stopWithHeadsign.setStopHeadsign(DIRECTION); - Collection stopTimes = List.of( - stopWithHeadsign, - stopWithHeadsign, - stopWithHeadsign - ); + List stopTimes = List.of(stopWithHeadsign, stopWithHeadsign, stopWithHeadsign); TripTimes tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator()); @@ -127,7 +118,7 @@ void shouldHandleDifferingTripAndStopHeadSignScenario() { Trip trip = TransitModelForTest.trip("TRIP").withHeadsign(DIRECTION).build(); StopTime stopWithHeadsign = new StopTime(); stopWithHeadsign.setStopHeadsign(STOP_TEST_DIRECTION); - Collection stopTimes = List.of(stopWithHeadsign, EMPTY_STOPPOINT, EMPTY_STOPPOINT); + List stopTimes = List.of(stopWithHeadsign, EMPTY_STOPPOINT, EMPTY_STOPPOINT); TripTimes tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator()); @@ -433,46 +424,29 @@ void unknownGtfsSequence() { } @Test - public void testApply() { - Trip trip = TransitModelForTest.trip(TRIP_ID).build(); - - List stopTimes = new LinkedList<>(); - - StopTime stopTime0 = new StopTime(); - StopTime stopTime1 = new StopTime(); - StopTime stopTime2 = new StopTime(); - - RegularStop stop0 = TEST_MODEL.stop(stops.get(0).getId(), 0.0, 0.0).build(); - RegularStop stop1 = TEST_MODEL.stop(stops.get(1).getId(), 0.0, 0.0).build(); - RegularStop stop2 = TEST_MODEL.stop(stops.get(2).getId(), 0.0, 0.0).build(); - - stopTime0.setStop(stop0); - stopTime0.setDepartureTime(0); - stopTime0.setStopSequence(0); - - stopTime1.setStop(stop1); - stopTime1.setArrivalTime(30); - stopTime1.setDepartureTime(60); - stopTime1.setStopSequence(1); + public void validateNegativeDwellTime() { + var tt = createInitialTripTimes(); + var updatedTt = tt.copyOfScheduledTimes(); - stopTime2.setStop(stop2); - stopTime2.setArrivalTime(90); - stopTime2.setStopSequence(2); + updatedTt.updateArrivalTime(2, 69); + updatedTt.updateDepartureTime(2, 68); - stopTimes.add(stopTime0); - stopTimes.add(stopTime1); - stopTimes.add(stopTime2); - - TripTimes differingTripTimes = TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator()); + var validationResult = updatedTt.validateNonIncreasingTimes(); + assertTrue(validationResult.isPresent()); + assertEquals(2, validationResult.get().stopIndex()); + assertEquals(NEGATIVE_DWELL_TIME, validationResult.get().code()); + } - TripTimes updatedTripTimesA = differingTripTimes.copyOfScheduledTimes(); + @Test + public void validateNegativeHopTime() { + var tt = createInitialTripTimes(); + var updatedTt = tt.copyOfScheduledTimes(); - updatedTripTimesA.updateArrivalTime(1, 89); - updatedTripTimesA.updateDepartureTime(1, 98); + updatedTt.updateArrivalTime(2, 59); - var validationResult = updatedTripTimesA.validateNonIncreasingTimes(); + var validationResult = updatedTt.validateNonIncreasingTimes(); assertTrue(validationResult.isPresent()); assertEquals(2, validationResult.get().stopIndex()); - assertEquals(NEGATIVE_DWELL_TIME, validationResult.get().code()); + assertEquals(NEGATIVE_HOP_TIME, validationResult.get().code()); } } From 7ff79bf2c3890146707e48ac9d4773b3122e545d Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Mon, 2 Oct 2023 12:10:24 +0200 Subject: [PATCH 35/85] refactor: Move normalization of trip-times from mapper to builder. This ensures the normalization is performed even if the mapper is no used. --- .../model/timetable/ScheduledTripTimes.java | 16 +++--- .../timetable/ScheduledTripTimesBuilder.java | 51 +++++++++++++++---- .../StopTimeToScheduledTripTimesMapper.java | 7 +-- 3 files changed, 51 insertions(+), 23 deletions(-) diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java index 39d4e58c7b6..310397ba7ba 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java @@ -69,22 +69,20 @@ public final class ScheduledTripTimes implements Serializable, Comparable - * Always provide a deduplicator when building the graph. No deduplication is ok when changing - * simple fields like {@code timeShift} and {@code serviceCode} or even the prefered way in a - * unittest. - */ - public ScheduledTripTimesBuilder copyOf(@Nullable Deduplicator deduplicator) { + public ScheduledTripTimesBuilder copyOf(Deduplicator deduplicator) { return new ScheduledTripTimesBuilder( timeShift, serviceCode, diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesBuilder.java b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesBuilder.java index 6fc5f968826..0eee5d3e183 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesBuilder.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesBuilder.java @@ -6,16 +6,15 @@ import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.framework.time.TimeUtils; import org.opentripplanner.model.BookingInfo; -import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.framework.DeduplicatorService; public class ScheduledTripTimesBuilder { - private final int NOT_SET = -1; - private final BitSet EMPTY_BIT_SET = new BitSet(0); + private static final int NOT_SET = -1; + private static final BitSet EMPTY_BIT_SET = new BitSet(0); private int timeShift; - private int serviceCode = NOT_SET; + private int serviceCode; private int[] arrivalTimes; private int[] departureTimes; private BitSet timepoints; @@ -28,7 +27,7 @@ public class ScheduledTripTimesBuilder { private final DeduplicatorService deduplicator; ScheduledTripTimesBuilder(@Nullable DeduplicatorService deduplicator) { - this.deduplicator = deduplicator == null ? DeduplicatorService.NOOP : deduplicator; + this(0, NOT_SET, null, null, null, null, null, null, null, null, null, deduplicator); } ScheduledTripTimesBuilder( @@ -43,9 +42,8 @@ public class ScheduledTripTimesBuilder { I18NString[] headsigns, String[][] headsignVias, int[] originalGtfsStopSequence, - Deduplicator deduplicator + DeduplicatorService deduplicator ) { - this(deduplicator); this.timeShift = timeShift; this.serviceCode = serviceCode; this.arrivalTimes = arrivalTimes; @@ -57,6 +55,7 @@ public class ScheduledTripTimesBuilder { this.headsigns = headsigns; this.headsignVias = headsignVias; this.originalGtfsStopSequence = originalGtfsStopSequence; + this.deduplicator = deduplicator == null ? DeduplicatorService.NOOP : deduplicator; } public int timeShift() { @@ -87,7 +86,7 @@ public ScheduledTripTimesBuilder withServiceCode(int serviceCode) { } public int[] arrivalTimes() { - return arrivalTimes == null ? departureTimes : arrivalTimes; + return arrivalTimes; } public ScheduledTripTimesBuilder withArrivalTimes(int[] arrivalTimes) { @@ -101,7 +100,7 @@ public ScheduledTripTimesBuilder withArrivalTimes(String arrivalTimes) { } public int[] departureTimes() { - return departureTimes == null ? arrivalTimes : departureTimes; + return departureTimes; } public ScheduledTripTimesBuilder withDepartureTimes(int[] departureTimes) { @@ -180,6 +179,40 @@ public ScheduledTripTimesBuilder withOriginalGtfsStopSequence(int[] originalGtfs } public ScheduledTripTimes build() { + normalizeTimes(); return new ScheduledTripTimes(this); } + + /** + * Times are always shifted to zero based on the first departure time. This is essential for + * frequencies and deduplication. + */ + private void normalizeTimes() { + if (departureTimes == null) { + this.departureTimes = arrivalTimes; + } + if (arrivalTimes == null) { + this.arrivalTimes = departureTimes; + } + + int shift = departureTimes[0]; + if (shift == 0) { + return; + } + this.departureTimes = timeShift(departureTimes, shift); + if (arrivalTimes != departureTimes) { + this.arrivalTimes = timeShift(arrivalTimes, shift); + } + this.timeShift += shift; + } + + int[] timeShift(int[] a, int shift) { + if (shift == 0) { + return a; + } + for (int i = 0; i < a.length; i++) { + a[i] -= shift; + } + return a; + } } diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/StopTimeToScheduledTripTimesMapper.java b/src/main/java/org/opentripplanner/transit/model/timetable/StopTimeToScheduledTripTimesMapper.java index bf98c6e77b8..22bb77b073e 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/StopTimeToScheduledTripTimesMapper.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/StopTimeToScheduledTripTimesMapper.java @@ -40,16 +40,13 @@ private ScheduledTripTimes doMap(Collection stopTimes) { final int[] arrivals = new int[nStops]; final int[] sequences = new int[nStops]; final BitSet timepoints = new BitSet(nStops); - // Times are always shifted to zero. This is essential for frequencies and deduplication. - int timeShift = stopTimes.iterator().next().getArrivalTime(); - builder.withTimeShift(timeShift); final List dropOffBookingInfos = new ArrayList<>(); final List pickupBookingInfos = new ArrayList<>(); int s = 0; for (final StopTime st : stopTimes) { - departures[s] = st.getDepartureTime() - timeShift; - arrivals[s] = st.getArrivalTime() - timeShift; + departures[s] = st.getDepartureTime(); + arrivals[s] = st.getArrivalTime(); sequences[s] = st.getStopSequence(); timepoints.set(s, st.getTimepoint() == 1); From 1728c29de73b706fb6c640e28200c4bdb2a776df Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 16 Nov 2023 19:44:44 +0100 Subject: [PATCH 36/85] refactor: Use DataValidationException to signal TripTime errors. --- .../ext/siri/AddedTripBuilder.java | 10 +- .../ext/siri/ModifiedTripBuilder.java | 22 +-- .../ext/siri/SiriTimetableSnapshotSource.java | 4 + .../framework/error/OtpError.java | 37 ++++ .../issue/api/DataImportIssueStore.java | 4 + .../issue/api/NoopDataImportIssueStore.java | 4 + .../service/DefaultDataImportIssueStore.java | 6 + .../gtfs/GenerateTripPatternsOperation.java | 17 +- .../org/opentripplanner/model/Timetable.java | 15 +- .../netex/mapping/TripPatternMapper.java | 17 +- .../framework/DataValidationException.java | 35 ++++ .../transit/model/framework/Result.java | 15 ++ .../model/timetable/ScheduledTripTimes.java | 15 +- .../StopTimeToScheduledTripTimesMapper.java | 6 +- .../timetable/TimetableValidationError.java | 29 ++++ .../transit/model/timetable/TripTimes.java | 26 ++- .../model/timetable/ValidationError.java | 11 -- .../spi/DataValidationExceptionMapper.java | 36 ++++ .../spi/TripTimesValidationMapper.java | 25 --- .../updater/trip/TimetableSnapshotSource.java | 58 ++++--- .../OtpArchitectureModules.java | 1 + .../TimetableValidationErrorTest.java | 24 +++ .../model/timetable/TripTimesTest.java | 163 ++++++++++-------- 23 files changed, 382 insertions(+), 198 deletions(-) create mode 100644 src/main/java/org/opentripplanner/framework/error/OtpError.java create mode 100644 src/main/java/org/opentripplanner/transit/model/framework/DataValidationException.java create mode 100644 src/main/java/org/opentripplanner/transit/model/timetable/TimetableValidationError.java delete mode 100644 src/main/java/org/opentripplanner/transit/model/timetable/ValidationError.java create mode 100644 src/main/java/org/opentripplanner/updater/spi/DataValidationExceptionMapper.java delete mode 100644 src/main/java/org/opentripplanner/updater/spi/TripTimesValidationMapper.java create mode 100644 src/test/java/org/opentripplanner/transit/model/timetable/TimetableValidationErrorTest.java diff --git a/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java b/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java index 2d7789422ea..74d84ee397e 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java +++ b/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java @@ -18,6 +18,7 @@ import org.opentripplanner.framework.time.ServiceDateUtils; import org.opentripplanner.model.StopTime; import org.opentripplanner.transit.model.basic.TransitMode; +import org.opentripplanner.transit.model.framework.DataValidationException; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.framework.Result; import org.opentripplanner.transit.model.network.Route; @@ -31,7 +32,7 @@ import org.opentripplanner.transit.model.timetable.TripTimes; import org.opentripplanner.transit.model.timetable.TripTimesFactory; import org.opentripplanner.transit.service.TransitModel; -import org.opentripplanner.updater.spi.TripTimesValidationMapper; +import org.opentripplanner.updater.spi.DataValidationExceptionMapper; import org.opentripplanner.updater.spi.UpdateError; import org.rutebanken.netex.model.BusSubmodeEnumeration; import org.rutebanken.netex.model.RailSubmodeEnumeration; @@ -231,9 +232,10 @@ Result build() { } /* Validate */ - var validityResult = updatedTripTimes.validateNonIncreasingTimes(); - if (validityResult.isPresent()) { - return TripTimesValidationMapper.toResult(tripId, validityResult.get()); + try { + updatedTripTimes.validateNonIncreasingTimes(); + } catch (DataValidationException e) { + return DataValidationExceptionMapper.toResult(e); } // Adding trip to index necessary to include values in graphql-queries diff --git a/src/ext/java/org/opentripplanner/ext/siri/ModifiedTripBuilder.java b/src/ext/java/org/opentripplanner/ext/siri/ModifiedTripBuilder.java index f56d3c6a75f..ce21823b9b3 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/ModifiedTripBuilder.java +++ b/src/ext/java/org/opentripplanner/ext/siri/ModifiedTripBuilder.java @@ -12,7 +12,7 @@ import java.util.Set; import org.opentripplanner.ext.siri.mapper.PickDropMapper; import org.opentripplanner.framework.time.ServiceDateUtils; -import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.model.framework.DataValidationException; import org.opentripplanner.transit.model.framework.Result; import org.opentripplanner.transit.model.network.StopPattern; import org.opentripplanner.transit.model.network.TripPattern; @@ -20,7 +20,7 @@ import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.model.timetable.RealTimeState; import org.opentripplanner.transit.model.timetable.TripTimes; -import org.opentripplanner.updater.spi.TripTimesValidationMapper; +import org.opentripplanner.updater.spi.DataValidationExceptionMapper; import org.opentripplanner.updater.spi.UpdateError; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -114,16 +114,16 @@ public Result build() { newTimes.setRealTimeState(RealTimeState.MODIFIED); } - var error = newTimes.validateNonIncreasingTimes(); - final FeedScopedId id = newTimes.getTrip().getId(); - if (error.isPresent()) { - var updateError = error.get(); + // TODO - Handle DataValidationException at the outemost level(pr trip) + try { + newTimes.validateNonIncreasingTimes(); + } catch (DataValidationException e) { LOG.info( - "Invalid SIRI-ET data for trip {} - TripTimes are non-increasing after applying SIRI delay propagation at stop index {}", - id, - updateError.stopIndex() + "Invalid SIRI-ET data for trip {} - TripTimes failed to validate after applying SIRI delay propagation. {}", + newTimes.getTrip().getId(), + e.getMessage() ); - return TripTimesValidationMapper.toResult(id, updateError); + return DataValidationExceptionMapper.toResult(e); } int numStopsInUpdate = newTimes.getNumStops(); @@ -131,7 +131,7 @@ public Result build() { if (numStopsInUpdate != numStopsInPattern) { LOG.info( "Invalid SIRI-ET data for trip {} - Inconsistent number of updated stops ({}) and stops in pattern ({})", - id, + newTimes.getTrip().getId(), numStopsInUpdate, numStopsInPattern ); diff --git a/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java b/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java index 0cedc491f79..e6efb3ec3d9 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java +++ b/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java @@ -18,6 +18,7 @@ import org.opentripplanner.model.TimetableSnapshot; import org.opentripplanner.model.TimetableSnapshotProvider; import org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.TransitLayerUpdater; +import org.opentripplanner.transit.model.framework.DataValidationException; import org.opentripplanner.transit.model.framework.Result; import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.timetable.Trip; @@ -26,6 +27,7 @@ import org.opentripplanner.transit.service.TransitModel; import org.opentripplanner.transit.service.TransitService; import org.opentripplanner.updater.TimetableSnapshotSourceParameters; +import org.opentripplanner.updater.spi.DataValidationExceptionMapper; import org.opentripplanner.updater.spi.UpdateError; import org.opentripplanner.updater.spi.UpdateResult; import org.opentripplanner.updater.spi.UpdateSuccess; @@ -215,6 +217,8 @@ private Result apply( /* commit */ return addTripToGraphAndBuffer(result.successValue()); + } catch (DataValidationException e) { + return DataValidationExceptionMapper.toResult(e); } catch (Exception e) { LOG.warn( "{} EstimatedJourney {} failed.", diff --git a/src/main/java/org/opentripplanner/framework/error/OtpError.java b/src/main/java/org/opentripplanner/framework/error/OtpError.java new file mode 100644 index 00000000000..6b2ff37945e --- /dev/null +++ b/src/main/java/org/opentripplanner/framework/error/OtpError.java @@ -0,0 +1,37 @@ +package org.opentripplanner.framework.error; + +import java.util.Locale; + +/** + * A generic representation of an error. The error should have a code used to group the same + * type of errors together. To avoid filling up memory with error strings during graph build + * we store errors in memory "decomposed". The {@link #messageTemplate()} and + * {@link #messageArguments()} is used to construct the message. Use the {@link Locale#ROOT} + * when constructing the message - we only support english with SI formatting. + */ +public interface OtpError { + /** + * An error code used to identify the error type. This is NOT an enum, but feel free + * to use an inum in the implementation, then use the enum NAME as the code or + * enum TYPE:NAME. Then name should be unique within OTP. + */ + String errorCode(); + + /** + * The string template used to create a human-readable error message. Use the + * {@link String#format(Locale, String, Object...)} format. + */ + String messageTemplate(); + + /** + * The arguments to inject into the message. + */ + Object[] messageArguments(); + + /** + * Construct a message. + */ + default String message() { + return String.format(Locale.ROOT, messageTemplate(), messageArguments()); + } +} diff --git a/src/main/java/org/opentripplanner/graph_builder/issue/api/DataImportIssueStore.java b/src/main/java/org/opentripplanner/graph_builder/issue/api/DataImportIssueStore.java index 1e28cec72f4..bd158436747 100644 --- a/src/main/java/org/opentripplanner/graph_builder/issue/api/DataImportIssueStore.java +++ b/src/main/java/org/opentripplanner/graph_builder/issue/api/DataImportIssueStore.java @@ -1,6 +1,7 @@ package org.opentripplanner.graph_builder.issue.api; import java.util.List; +import org.opentripplanner.framework.error.OtpError; /** * This service is used to store issued during data import. When the import is complete @@ -19,6 +20,9 @@ public interface DataImportIssueStore { /** Add an issue to the issue report. */ void add(DataImportIssue issue); + /** Add an issue to the issue report. */ + void add(OtpError issue); + /** Add an issue to the issue report without the need of creating an issue class. */ void add(String type, String message); diff --git a/src/main/java/org/opentripplanner/graph_builder/issue/api/NoopDataImportIssueStore.java b/src/main/java/org/opentripplanner/graph_builder/issue/api/NoopDataImportIssueStore.java index 504980d74a5..a3adfe7127c 100644 --- a/src/main/java/org/opentripplanner/graph_builder/issue/api/NoopDataImportIssueStore.java +++ b/src/main/java/org/opentripplanner/graph_builder/issue/api/NoopDataImportIssueStore.java @@ -1,6 +1,7 @@ package org.opentripplanner.graph_builder.issue.api; import java.util.List; +import org.opentripplanner.framework.error.OtpError; /** * A no-op implementation of the issue store, convenient for unit testing. No issues are @@ -11,6 +12,9 @@ class NoopDataImportIssueStore implements DataImportIssueStore { @Override public void add(DataImportIssue issue) {} + @Override + public void add(OtpError issue) {} + @Override public void add(String type, String message) {} diff --git a/src/main/java/org/opentripplanner/graph_builder/issue/service/DefaultDataImportIssueStore.java b/src/main/java/org/opentripplanner/graph_builder/issue/service/DefaultDataImportIssueStore.java index 53f8c65d10c..3cdcf9d0db6 100644 --- a/src/main/java/org/opentripplanner/graph_builder/issue/service/DefaultDataImportIssueStore.java +++ b/src/main/java/org/opentripplanner/graph_builder/issue/service/DefaultDataImportIssueStore.java @@ -3,6 +3,7 @@ import jakarta.inject.Singleton; import java.util.ArrayList; import java.util.List; +import org.opentripplanner.framework.error.OtpError; import org.opentripplanner.graph_builder.issue.api.DataImportIssue; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.graph_builder.issue.api.Issue; @@ -30,6 +31,11 @@ public void add(DataImportIssue issue) { } } + @Override + public void add(OtpError issue) { + add(issue.errorCode(), issue.messageTemplate(), issue.messageArguments()); + } + @Override public void add(String type, String message) { add(Issue.issue(type, message)); diff --git a/src/main/java/org/opentripplanner/gtfs/GenerateTripPatternsOperation.java b/src/main/java/org/opentripplanner/gtfs/GenerateTripPatternsOperation.java index 1cb3c4c0186..bf26f9c6d7b 100644 --- a/src/main/java/org/opentripplanner/gtfs/GenerateTripPatternsOperation.java +++ b/src/main/java/org/opentripplanner/gtfs/GenerateTripPatternsOperation.java @@ -9,6 +9,7 @@ import java.util.Map; import java.util.Set; import org.opentripplanner.ext.flex.trip.FlexTrip; +import org.opentripplanner.framework.logging.ProgressTracker; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.graph_builder.issues.TripDegenerate; import org.opentripplanner.graph_builder.issues.TripUndefinedService; @@ -16,6 +17,7 @@ import org.opentripplanner.model.Frequency; import org.opentripplanner.model.StopTime; import org.opentripplanner.model.impl.OtpTransitServiceBuilder; +import org.opentripplanner.transit.model.framework.DataValidationException; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.network.Route; @@ -47,7 +49,6 @@ public class GenerateTripPatternsOperation { private final Multimap tripPatterns; private final ListMultimap frequenciesForTrip = ArrayListMultimap.create(); - private int tripCount = 0; private int freqCount = 0; private int scheduledCount = 0; @@ -70,17 +71,21 @@ public void run() { collectFrequencyByTrip(); final Collection trips = transitDaoBuilder.getTripsById().values(); - final int tripsSize = trips.size(); + var progressLogger = ProgressTracker.track("build trip patterns", 50_000, trips.size()); + LOG.info(progressLogger.startMessage()); /* Loop over all trips, handling each one as a frequency-based or scheduled trip. */ for (Trip trip : trips) { - if (++tripCount % 100000 == 0) { - LOG.debug("build trip patterns {}/{}", tripCount, tripsSize); + try { + buildTripPatternForTrip(trip); + //noinspection Convert2MethodRef + progressLogger.step(m -> LOG.info(m)); + } catch (DataValidationException e) { + issueStore.add(e.error()); } - - buildTripPatternForTrip(trip); } + LOG.info(progressLogger.completeMessage()); LOG.info( "Added {} frequency-based and {} single-trip timetable entries.", freqCount, diff --git a/src/main/java/org/opentripplanner/model/Timetable.java b/src/main/java/org/opentripplanner/model/Timetable.java index 170ed49092d..0bcdb9fd1ab 100644 --- a/src/main/java/org/opentripplanner/model/Timetable.java +++ b/src/main/java/org/opentripplanner/model/Timetable.java @@ -25,6 +25,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.opentripplanner.framework.time.ServiceDateUtils; +import org.opentripplanner.transit.model.framework.DataValidationException; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.framework.Result; import org.opentripplanner.transit.model.network.TripPattern; @@ -33,7 +34,7 @@ import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.TripTimes; import org.opentripplanner.updater.GtfsRealtimeMapper; -import org.opentripplanner.updater.spi.TripTimesValidationMapper; +import org.opentripplanner.updater.spi.DataValidationExceptionMapper; import org.opentripplanner.updater.spi.UpdateError; import org.opentripplanner.updater.trip.BackwardsDelayPropagationType; import org.slf4j.Logger; @@ -360,14 +361,10 @@ public Result createUpdatedTripTimesFromGTFSRT( } // Validate for non-increasing times. Log error if present. - var error = newTimes.validateNonIncreasingTimes(); - if (error.isPresent()) { - LOG.debug( - "TripTimes are non-increasing after applying GTFS-RT delay propagation to trip {} after stop index {}.", - tripId, - error.get().stopIndex() - ); - return TripTimesValidationMapper.toResult(newTimes.getTrip().getId(), error.get()); + try { + newTimes.validateNonIncreasingTimes(); + } catch (DataValidationException e) { + return DataValidationExceptionMapper.toResult(e); } if (tripUpdate.hasVehicle()) { diff --git a/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java b/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java index 675b9eab5c9..a00912264ea 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java @@ -16,6 +16,7 @@ import org.opentripplanner.netex.mapping.support.FeedScopedIdFactory; import org.opentripplanner.transit.model.basic.SubMode; import org.opentripplanner.transit.model.basic.TransitMode; +import org.opentripplanner.transit.model.framework.DataValidationException; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.framework.EntityById; import org.opentripplanner.transit.model.framework.FeedScopedId; @@ -367,12 +368,16 @@ private void createTripTimes(List trips, TripPattern tripPattern) { trip.getId() ); } else { - TripTimes tripTimes = TripTimesFactory.tripTimes( - trip, - result.tripStopTimes.get(trip), - deduplicator - ); - tripPattern.add(tripTimes); + try { + TripTimes tripTimes = TripTimesFactory.tripTimes( + trip, + result.tripStopTimes.get(trip), + deduplicator + ); + tripPattern.add(tripTimes); + } catch (DataValidationException e) { + issueStore.add(e.error()); + } } } } diff --git a/src/main/java/org/opentripplanner/transit/model/framework/DataValidationException.java b/src/main/java/org/opentripplanner/transit/model/framework/DataValidationException.java new file mode 100644 index 00000000000..d1c985e6db0 --- /dev/null +++ b/src/main/java/org/opentripplanner/transit/model/framework/DataValidationException.java @@ -0,0 +1,35 @@ +package org.opentripplanner.transit.model.framework; + +import org.opentripplanner.framework.error.OtpError; + +/** + * This class is used to throw a data validation exception. It holds an error witch can be + * inserted into build issue store, into the updater logs or returned to the APIs. The framework + * to catch and handle this is NOT IN PLACE, see + * Error code design #5070. + *

+ * MORE WORK ON THIS IS NEEDED! + */ +public class DataValidationException extends RuntimeException { + + private final OtpError error; + + public DataValidationException(OtpError error) { + super(); + this.error = error; + } + + public OtpError error() { + return error; + } + + @Override + public String getMessage() { + return error.message(); + } + + @Override + public String toString() { + return getMessage(); + } +} diff --git a/src/main/java/org/opentripplanner/transit/model/framework/Result.java b/src/main/java/org/opentripplanner/transit/model/framework/Result.java index e23bdca83ca..621ce7569f2 100644 --- a/src/main/java/org/opentripplanner/transit/model/framework/Result.java +++ b/src/main/java/org/opentripplanner/transit/model/framework/Result.java @@ -9,7 +9,22 @@ * A type for containing either a success or a failure type as the result of a computation. *

* It's very similar to the Either or Validation type found in functional programming languages. + * + * @deprecated This not possible to use inside a constructor - can only return one thing. Witch, + * then makes encapsulation harder and leaves the door open to forget. Also, having to create + * error codes, mapping and handling code for hundreds of different possible validation error + * types make changes harder, and cleanup impossible. This is a nice way to design APIs, but + * faced with hundreds or even thousands of different validation error types and the same + * amount of code branches this breaks. + *

+ * I will use the {@link DataValidationException} for now, but we need to make an error + * handling strategy witch take all use-cases and goals into account, also pragmatic goals + * like maintainability. The {@link DataValidationException} is not a solution, but it + * at least it allows me to omit returning all possible error on every method ... + *

+ * See https://github.com/opentripplanner/OpenTripPlanner/issues/5070 */ +@Deprecated public abstract sealed class Result { private Result() {} diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java index 310397ba7ba..1f6aecc3ac5 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java @@ -1,5 +1,8 @@ package org.opentripplanner.transit.model.timetable; +import static org.opentripplanner.transit.model.timetable.TimetableValidationError.ErrorCode.NEGATIVE_DWELL_TIME; +import static org.opentripplanner.transit.model.timetable.TimetableValidationError.ErrorCode.NEGATIVE_HOP_TIME; + import java.io.Serializable; import java.util.Arrays; import java.util.BitSet; @@ -13,7 +16,9 @@ import org.opentripplanner.framework.time.DurationUtils; import org.opentripplanner.model.BookingInfo; import org.opentripplanner.transit.model.basic.Accessibility; +import org.opentripplanner.transit.model.framework.DataValidationException; import org.opentripplanner.transit.model.framework.Deduplicator; +import org.opentripplanner.transit.model.framework.DeduplicatorService; public final class ScheduledTripTimes implements Serializable, Comparable { @@ -78,7 +83,7 @@ public static ScheduledTripTimesBuilder of() { return new ScheduledTripTimesBuilder(null); } - public static ScheduledTripTimesBuilder of(Deduplicator deduplicator) { + public static ScheduledTripTimesBuilder of(DeduplicatorService deduplicator) { return new ScheduledTripTimesBuilder(deduplicator); } @@ -361,16 +366,14 @@ private void validateNonIncreasingTimes() { final int dep = getDepartureTime(i); if (prevDep > arr) { - throw new IllegalArgumentException( - "Negative hop time for stop position " + i + " for " + trip + "." - ); + throw new DataValidationException(new TimetableValidationError(NEGATIVE_HOP_TIME, i, trip)); } if (i == lastStop) { return; } if (dep < arr) { - throw new IllegalArgumentException( - "Negative dwell time for stop position " + i + " for " + trip + "." + throw new DataValidationException( + new TimetableValidationError(NEGATIVE_DWELL_TIME, i, trip) ); } prevDep = dep; diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/StopTimeToScheduledTripTimesMapper.java b/src/main/java/org/opentripplanner/transit/model/timetable/StopTimeToScheduledTripTimesMapper.java index 22bb77b073e..bd21905f7d1 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/StopTimeToScheduledTripTimesMapper.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/StopTimeToScheduledTripTimesMapper.java @@ -7,7 +7,7 @@ import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.model.BookingInfo; import org.opentripplanner.model.StopTime; -import org.opentripplanner.transit.model.framework.Deduplicator; +import org.opentripplanner.transit.model.framework.DeduplicatorService; class StopTimeToScheduledTripTimesMapper { @@ -16,7 +16,7 @@ class StopTimeToScheduledTripTimesMapper { private static final String[] EMPTY_STRING_ARRAY = new String[0]; - private StopTimeToScheduledTripTimesMapper(Trip trip, Deduplicator deduplicator) { + private StopTimeToScheduledTripTimesMapper(Trip trip, DeduplicatorService deduplicator) { this.trip = trip; this.builder = ScheduledTripTimes.of(deduplicator).withTrip(trip); } @@ -29,7 +29,7 @@ private StopTimeToScheduledTripTimesMapper(Trip trip, Deduplicator deduplicator) public static ScheduledTripTimes map( Trip trip, Collection stopTimes, - Deduplicator deduplicator + DeduplicatorService deduplicator ) { return new StopTimeToScheduledTripTimesMapper(trip, deduplicator).doMap(stopTimes); } diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/TimetableValidationError.java b/src/main/java/org/opentripplanner/transit/model/timetable/TimetableValidationError.java new file mode 100644 index 00000000000..a69fd87e93b --- /dev/null +++ b/src/main/java/org/opentripplanner/transit/model/timetable/TimetableValidationError.java @@ -0,0 +1,29 @@ +package org.opentripplanner.transit.model.timetable; + +import org.opentripplanner.framework.error.OtpError; + +/** + * Details about why a {@link TripTimes} instance is invalid. + */ +public record TimetableValidationError(ErrorCode code, int stopIndex, Trip trip) + implements OtpError { + @Override + public String errorCode() { + return code.name(); + } + + @Override + public String messageTemplate() { + return "%s for stop position %d in trip %s."; + } + + @Override + public Object[] messageArguments() { + return new Object[] { code, stopIndex, trip }; + } + + public enum ErrorCode { + NEGATIVE_DWELL_TIME, + NEGATIVE_HOP_TIME, + } +} diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java index b4ca4850a18..ecb0df38127 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java @@ -1,19 +1,19 @@ package org.opentripplanner.transit.model.timetable; -import static org.opentripplanner.transit.model.timetable.ValidationError.ErrorCode.NEGATIVE_DWELL_TIME; -import static org.opentripplanner.transit.model.timetable.ValidationError.ErrorCode.NEGATIVE_HOP_TIME; +import static org.opentripplanner.transit.model.timetable.TimetableValidationError.ErrorCode.NEGATIVE_DWELL_TIME; +import static org.opentripplanner.transit.model.timetable.TimetableValidationError.ErrorCode.NEGATIVE_HOP_TIME; import java.io.Serializable; import java.time.Duration; import java.util.Arrays; import java.util.List; -import java.util.Optional; import java.util.OptionalInt; import java.util.function.IntUnaryOperator; import javax.annotation.Nullable; import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.model.BookingInfo; import org.opentripplanner.transit.model.basic.Accessibility; +import org.opentripplanner.transit.model.framework.DataValidationException; /** * A TripTimes represents the arrival and departure times for a single trip in an Timetable. It is @@ -33,13 +33,6 @@ public final class TripTimes implements Serializable, Comparable { private OccupancyStatus[] occupancyStatus; private Accessibility wheelchairAccessibility; - private TripTimes(final TripTimes original, int timeShiftDelta) { - this( - original, - original.scheduledTripTimes.copyOfNoDuplication().plusTimeShift(timeShiftDelta).build() - ); - } - TripTimes(ScheduledTripTimes scheduledTripTimes) { this( scheduledTripTimes, @@ -278,9 +271,9 @@ public void setRealTimeState(final RealTimeState realTimeState) { * checks that all internal times are increasing. Thus, this check should be used at the end of * updating trip times, after any propagating or interpolating delay operations. * - * @return empty if times were found to be increasing, the first validation error otherwise + * @throws org.opentripplanner.transit.model.framework.DataValidationException on the first validation error found. */ - public Optional validateNonIncreasingTimes() { + public void validateNonIncreasingTimes() { final int nStops = arrivalTimes.length; int prevDep = -9_999_999; for (int s = 0; s < nStops; s++) { @@ -288,14 +281,17 @@ public Optional validateNonIncreasingTimes() { final int dep = getDepartureTime(s); if (dep < arr) { - return Optional.of(new ValidationError(NEGATIVE_DWELL_TIME, s)); + throw new DataValidationException( + new TimetableValidationError(NEGATIVE_DWELL_TIME, s, getTrip()) + ); } if (prevDep > arr) { - return Optional.of(new ValidationError(NEGATIVE_HOP_TIME, s)); + throw new DataValidationException( + new TimetableValidationError(NEGATIVE_HOP_TIME, s, getTrip()) + ); } prevDep = dep; } - return Optional.empty(); } /** diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/ValidationError.java b/src/main/java/org/opentripplanner/transit/model/timetable/ValidationError.java deleted file mode 100644 index 1e150218dc5..00000000000 --- a/src/main/java/org/opentripplanner/transit/model/timetable/ValidationError.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.opentripplanner.transit.model.timetable; - -/** - * Details about why a {@link TripTimes} instance is invalid. - */ -public record ValidationError(ErrorCode code, int stopIndex) { - public enum ErrorCode { - NEGATIVE_DWELL_TIME, - NEGATIVE_HOP_TIME, - } -} diff --git a/src/main/java/org/opentripplanner/updater/spi/DataValidationExceptionMapper.java b/src/main/java/org/opentripplanner/updater/spi/DataValidationExceptionMapper.java new file mode 100644 index 00000000000..dee103bc385 --- /dev/null +++ b/src/main/java/org/opentripplanner/updater/spi/DataValidationExceptionMapper.java @@ -0,0 +1,36 @@ +package org.opentripplanner.updater.spi; + +import org.opentripplanner.transit.model.framework.DataValidationException; +import org.opentripplanner.transit.model.framework.Result; +import org.opentripplanner.transit.model.timetable.TimetableValidationError; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Converts a {@link TimetableValidationError} to the model of the updater ready to be consumed + * by the metrics APIS and logs. + */ +public class DataValidationExceptionMapper { + + private static final Logger LOG = LoggerFactory.getLogger(DataValidationExceptionMapper.class); + + public static Result toResult(DataValidationException error) { + if (error.error() instanceof TimetableValidationError tt) { + return Result.failure( + new UpdateError(tt.trip().getId(), mapTimeTableError(tt.code()), tt.stopIndex()) + ); + } + // The mapper should handle all possible errors + LOG.error("Unhandled error: " + error.getMessage(), error); + return Result.failure(UpdateError.noTripId(UpdateError.UpdateErrorType.UNKNOWN)); + } + + private static UpdateError.UpdateErrorType mapTimeTableError( + TimetableValidationError.ErrorCode code + ) { + return switch (code) { + case NEGATIVE_DWELL_TIME -> UpdateError.UpdateErrorType.NEGATIVE_DWELL_TIME; + case NEGATIVE_HOP_TIME -> UpdateError.UpdateErrorType.NEGATIVE_HOP_TIME; + }; + } +} diff --git a/src/main/java/org/opentripplanner/updater/spi/TripTimesValidationMapper.java b/src/main/java/org/opentripplanner/updater/spi/TripTimesValidationMapper.java deleted file mode 100644 index 127eddeabeb..00000000000 --- a/src/main/java/org/opentripplanner/updater/spi/TripTimesValidationMapper.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.opentripplanner.updater.spi; - -import org.opentripplanner.transit.model.framework.FeedScopedId; -import org.opentripplanner.transit.model.framework.Result; -import org.opentripplanner.transit.model.timetable.ValidationError; - -/** - * Converts a {@link ValidationError} to the model of the updater ready to be consumed - * by the metrics APIS and logs. - */ -public class TripTimesValidationMapper { - - public static Result toResult( - FeedScopedId tripId, - ValidationError validationError - ) { - var type = - switch (validationError.code()) { - case NEGATIVE_DWELL_TIME -> UpdateError.UpdateErrorType.NEGATIVE_DWELL_TIME; - case NEGATIVE_HOP_TIME -> UpdateError.UpdateErrorType.NEGATIVE_HOP_TIME; - }; - - return Result.failure(new UpdateError(tripId, type, validationError.stopIndex())); - } -} diff --git a/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java b/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java index 6cda95eb983..8e782ebe934 100644 --- a/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java +++ b/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java @@ -44,6 +44,7 @@ import org.opentripplanner.model.TimetableSnapshotProvider; import org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.TransitLayerUpdater; import org.opentripplanner.transit.model.basic.TransitMode; +import org.opentripplanner.transit.model.framework.DataValidationException; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.framework.Result; @@ -62,6 +63,7 @@ import org.opentripplanner.updater.GtfsRealtimeFuzzyTripMatcher; import org.opentripplanner.updater.GtfsRealtimeMapper; import org.opentripplanner.updater.TimetableSnapshotSourceParameters; +import org.opentripplanner.updater.spi.DataValidationExceptionMapper; import org.opentripplanner.updater.spi.ResultLogger; import org.opentripplanner.updater.spi.UpdateError; import org.opentripplanner.updater.spi.UpdateResult; @@ -279,31 +281,36 @@ public UpdateResult applyTripUpdates( tripDescriptor ); - Result result = - switch (tripScheduleRelationship) { - case SCHEDULED -> handleScheduledTrip( - tripUpdate, - tripId, - serviceDate, - backwardsDelayPropagationType - ); - case ADDED -> validateAndHandleAddedTrip( - tripUpdate, - tripDescriptor, - tripId, - serviceDate - ); - case CANCELED -> handleCanceledTrip(tripId, serviceDate, CancelationType.CANCEL); - case DELETED -> handleCanceledTrip(tripId, serviceDate, CancelationType.DELETE); - case REPLACEMENT -> validateAndHandleModifiedTrip( - tripUpdate, - tripDescriptor, - tripId, - serviceDate - ); - case UNSCHEDULED -> UpdateError.result(tripId, NOT_IMPLEMENTED_UNSCHEDULED); - case DUPLICATED -> UpdateError.result(tripId, NOT_IMPLEMENTED_DUPLICATED); - }; + Result result; + try { + result = + switch (tripScheduleRelationship) { + case SCHEDULED -> handleScheduledTrip( + tripUpdate, + tripId, + serviceDate, + backwardsDelayPropagationType + ); + case ADDED -> validateAndHandleAddedTrip( + tripUpdate, + tripDescriptor, + tripId, + serviceDate + ); + case CANCELED -> handleCanceledTrip(tripId, serviceDate, CancelationType.CANCEL); + case DELETED -> handleCanceledTrip(tripId, serviceDate, CancelationType.DELETE); + case REPLACEMENT -> validateAndHandleModifiedTrip( + tripUpdate, + tripDescriptor, + tripId, + serviceDate + ); + case UNSCHEDULED -> UpdateError.result(tripId, NOT_IMPLEMENTED_UNSCHEDULED); + case DUPLICATED -> UpdateError.result(tripId, NOT_IMPLEMENTED_DUPLICATED); + }; + } catch (DataValidationException e) { + result = DataValidationExceptionMapper.toResult(e); + } results.add(result); if (result.isFailure()) { @@ -708,7 +715,6 @@ private Result handleAddedTrip( // Just use first service id of set tripBuilder.withServiceId(serviceIds.iterator().next()); } - return addTripToGraphAndBuffer( tripBuilder.build(), tripUpdate.getVehicle(), diff --git a/src/test/java/org/opentripplanner/OtpArchitectureModules.java b/src/test/java/org/opentripplanner/OtpArchitectureModules.java index e4d08427c71..72520acaa1c 100644 --- a/src/test/java/org/opentripplanner/OtpArchitectureModules.java +++ b/src/test/java/org/opentripplanner/OtpArchitectureModules.java @@ -39,6 +39,7 @@ public interface OtpArchitectureModules { */ Module FRAMEWORK_UTILS = Module.of( FRAMEWORK.subPackage("application"), + FRAMEWORK.subPackage("error"), FRAMEWORK.subPackage("i18n"), FRAMEWORK.subPackage("lang"), FRAMEWORK.subPackage("logging"), diff --git a/src/test/java/org/opentripplanner/transit/model/timetable/TimetableValidationErrorTest.java b/src/test/java/org/opentripplanner/transit/model/timetable/TimetableValidationErrorTest.java new file mode 100644 index 00000000000..7dd67665e92 --- /dev/null +++ b/src/test/java/org/opentripplanner/transit/model/timetable/TimetableValidationErrorTest.java @@ -0,0 +1,24 @@ +package org.opentripplanner.transit.model.timetable; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; +import org.opentripplanner.transit.model._data.TransitModelForTest; +import org.opentripplanner.transit.model.basic.TransitMode; + +class TimetableValidationErrorTest { + + private TimetableValidationError subject = new TimetableValidationError( + TimetableValidationError.ErrorCode.NEGATIVE_HOP_TIME, + 3, + TransitModelForTest.trip("A").withMode(TransitMode.BUS).withShortName("Line A").build() + ); + + @Test + void message() { + assertEquals( + "NEGATIVE_HOP_TIME for stop position 3 in trip Trip{F:A Line A}.", + subject.message() + ); + } +} diff --git a/src/test/java/org/opentripplanner/transit/model/timetable/TripTimesTest.java b/src/test/java/org/opentripplanner/transit/model/timetable/TripTimesTest.java index af37c812667..ee73bd6856c 100644 --- a/src/test/java/org/opentripplanner/transit/model/timetable/TripTimesTest.java +++ b/src/test/java/org/opentripplanner/transit/model/timetable/TripTimesTest.java @@ -3,10 +3,11 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opentripplanner.transit.model._data.TransitModelForTest.id; -import static org.opentripplanner.transit.model.timetable.ValidationError.ErrorCode.NEGATIVE_DWELL_TIME; -import static org.opentripplanner.transit.model.timetable.ValidationError.ErrorCode.NEGATIVE_HOP_TIME; +import static org.opentripplanner.transit.model.timetable.TimetableValidationError.ErrorCode.NEGATIVE_DWELL_TIME; +import static org.opentripplanner.transit.model.timetable.TimetableValidationError.ErrorCode.NEGATIVE_HOP_TIME; import java.util.LinkedList; import java.util.List; @@ -16,6 +17,7 @@ import org.opentripplanner.framework.i18n.NonLocalizedString; import org.opentripplanner.model.StopTime; import org.opentripplanner.transit.model._data.TransitModelForTest; +import org.opentripplanner.transit.model.framework.DataValidationException; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.site.RegularStop; @@ -155,32 +157,6 @@ public void testPassedUpdate() { assertEquals(60, updatedTripTimesA.getArrivalTime(1)); } - @Test - public void testNegativeDwellTime() { - TripTimes updatedTripTimesA = createInitialTripTimes().copyOfScheduledTimes(); - - updatedTripTimesA.updateArrivalTime(1, 60); - updatedTripTimesA.updateDepartureTime(1, 59); - - var error = updatedTripTimesA.validateNonIncreasingTimes(); - assertTrue(error.isPresent()); - assertEquals(1, error.get().stopIndex()); - assertEquals(NEGATIVE_DWELL_TIME, error.get().code()); - } - - @Test - public void testNegativeHopTime() { - TripTimes updatedTripTimesB = createInitialTripTimes().copyOfScheduledTimes(); - - updatedTripTimesB.updateDepartureTime(6, 421); - updatedTripTimesB.updateArrivalTime(7, 420); - - var error = updatedTripTimesB.validateNonIncreasingTimes(); - assertTrue(error.isPresent()); - assertEquals(7, error.get().stopIndex()); - assertEquals(NEGATIVE_HOP_TIME, error.get().code()); - } - /** * Test negative hop time with stop cancellations. * Scheduled: 5 at 300, 6 at 360, 7 at 420 @@ -199,14 +175,14 @@ public void testNegativeHopTimeWithStopCancellations() { updatedTripTimes.setCancelled(6); updatedTripTimes.updateArrivalTime(7, 420); - var error = updatedTripTimes.validateNonIncreasingTimes(); - assertTrue(error.isPresent()); - assertEquals(NEGATIVE_HOP_TIME, error.get().code()); - - assertTrue(updatedTripTimes.interpolateMissingTimes()); - error = updatedTripTimes.validateNonIncreasingTimes(); - assertTrue(error.isPresent()); - assertEquals(NEGATIVE_HOP_TIME, error.get().code()); + var error = assertThrows( + DataValidationException.class, + updatedTripTimes::validateNonIncreasingTimes + ); + assertEquals( + "NEGATIVE_HOP_TIME for stop position 7 in trip Trip{F:testTripId RRtestTripId}.", + error.error().message() + ); } /** @@ -226,13 +202,18 @@ public void testPositiveHopTimeWithStopCancellationsLate() { updatedTripTimes.setCancelled(6); updatedTripTimes.updateArrivalTime(7, 420); - var error = updatedTripTimes.validateNonIncreasingTimes(); - assertTrue(error.isPresent()); - assertEquals(NEGATIVE_HOP_TIME, error.get().code()); + var error = assertThrows( + DataValidationException.class, + updatedTripTimes::validateNonIncreasingTimes + ); + assertEquals( + "NEGATIVE_HOP_TIME for stop position 7 in trip Trip{F:testTripId RRtestTripId}.", + error.error().message() + ); assertTrue(updatedTripTimes.interpolateMissingTimes()); - error = updatedTripTimes.validateNonIncreasingTimes(); - assertFalse(error.isPresent()); + + updatedTripTimes.validateNonIncreasingTimes(); } /** @@ -250,13 +231,17 @@ public void testPositiveHopTimeWithStopCancellationsEarly() { updatedTripTimes.setCancelled(6); updatedTripTimes.updateArrivalTime(7, 320); - var error = updatedTripTimes.validateNonIncreasingTimes(); - assertTrue(error.isPresent()); - assertEquals(NEGATIVE_HOP_TIME, error.get().code()); + var error = assertThrows( + DataValidationException.class, + updatedTripTimes::validateNonIncreasingTimes + ); + assertEquals( + "NEGATIVE_HOP_TIME for stop position 7 in trip Trip{F:testTripId RRtestTripId}.", + error.error().message() + ); assertTrue(updatedTripTimes.interpolateMissingTimes()); - error = updatedTripTimes.validateNonIncreasingTimes(); - assertFalse(error.isPresent()); + updatedTripTimes.validateNonIncreasingTimes(); } /** @@ -276,14 +261,22 @@ public void testPositiveHopTimeWithTerminalCancellation() { updatedTripTimes.updateArrivalTime(2, 0); updatedTripTimes.updateDepartureTime(2, 10); - var error = updatedTripTimes.validateNonIncreasingTimes(); - assertTrue(error.isPresent()); - assertEquals(NEGATIVE_HOP_TIME, error.get().code()); + var error = assertThrows( + DataValidationException.class, + updatedTripTimes::validateNonIncreasingTimes + ); + assertEquals( + "NEGATIVE_HOP_TIME for stop position 2 in trip Trip{F:testTripId RRtestTripId}.", + error.error().message() + ); assertFalse(updatedTripTimes.interpolateMissingTimes()); - error = updatedTripTimes.validateNonIncreasingTimes(); - assertTrue(error.isPresent()); - assertEquals(NEGATIVE_HOP_TIME, error.get().code()); + error = + assertThrows(DataValidationException.class, updatedTripTimes::validateNonIncreasingTimes); + assertEquals( + "NEGATIVE_HOP_TIME for stop position 2 in trip Trip{F:testTripId RRtestTripId}.", + error.error().message() + ); } /** @@ -301,8 +294,8 @@ public void testInterpolationWithTerminalCancellation() { updatedTripTimes.setCancelled(7); assertFalse(updatedTripTimes.interpolateMissingTimes()); - var error = updatedTripTimes.validateNonIncreasingTimes(); - assertFalse(error.isPresent()); + + updatedTripTimes.validateNonIncreasingTimes(); } /** @@ -325,13 +318,18 @@ public void testInterpolationWithMultipleStopCancellations() { updatedTripTimes.updateArrivalTime(7, 350); updatedTripTimes.updateDepartureTime(7, 350); - var error = updatedTripTimes.validateNonIncreasingTimes(); - assertTrue(error.isPresent()); - assertEquals(NEGATIVE_HOP_TIME, error.get().code()); + var error = assertThrows( + DataValidationException.class, + updatedTripTimes::validateNonIncreasingTimes + ); + assertEquals( + "NEGATIVE_HOP_TIME for stop position 7 in trip Trip{F:testTripId RRtestTripId}.", + error.error().message() + ); assertTrue(updatedTripTimes.interpolateMissingTimes()); - error = updatedTripTimes.validateNonIncreasingTimes(); - assertFalse(error.isPresent()); + + updatedTripTimes.validateNonIncreasingTimes(); } /** @@ -355,13 +353,17 @@ public void testInterpolationWithMultipleStopCancellations2() { updatedTripTimes.updateArrivalTime(7, 240); updatedTripTimes.updateDepartureTime(7, 240); - var error = updatedTripTimes.validateNonIncreasingTimes(); - assertTrue(error.isPresent()); - assertEquals(NEGATIVE_HOP_TIME, error.get().code()); + var error = assertThrows( + DataValidationException.class, + updatedTripTimes::validateNonIncreasingTimes + ); + assertEquals( + "NEGATIVE_HOP_TIME for stop position 3 in trip Trip{F:testTripId RRtestTripId}.", + error.error().message() + ); assertTrue(updatedTripTimes.interpolateMissingTimes()); - error = updatedTripTimes.validateNonIncreasingTimes(); - assertFalse(error.isPresent()); + updatedTripTimes.validateNonIncreasingTimes(); } @Test @@ -371,7 +373,7 @@ public void testNonIncreasingUpdateCrossingMidnight() { updatedTripTimesA.updateArrivalTime(0, -300); //"Yesterday" updatedTripTimesA.updateDepartureTime(0, 50); - assertTrue(updatedTripTimesA.validateNonIncreasingTimes().isEmpty()); + updatedTripTimesA.validateNonIncreasingTimes(); } @Test @@ -425,28 +427,37 @@ void unknownGtfsSequence() { @Test public void validateNegativeDwellTime() { + var expMsg = "NEGATIVE_DWELL_TIME for stop position 3 in trip Trip{F:testTripId RRtestTripId}."; var tt = createInitialTripTimes(); var updatedTt = tt.copyOfScheduledTimes(); - updatedTt.updateArrivalTime(2, 69); - updatedTt.updateDepartureTime(2, 68); + updatedTt.updateArrivalTime(3, 69); + updatedTt.updateDepartureTime(3, 68); + + var ex = assertThrows(DataValidationException.class, updatedTt::validateNonIncreasingTimes); + var error = (TimetableValidationError) ex.error(); - var validationResult = updatedTt.validateNonIncreasingTimes(); - assertTrue(validationResult.isPresent()); - assertEquals(2, validationResult.get().stopIndex()); - assertEquals(NEGATIVE_DWELL_TIME, validationResult.get().code()); + assertEquals(3, error.stopIndex()); + assertEquals(NEGATIVE_DWELL_TIME, error.code()); + assertEquals(expMsg, error.message()); + assertEquals(expMsg, ex.getMessage()); } @Test public void validateNegativeHopTime() { + var expMsg = "NEGATIVE_HOP_TIME for stop position 2 in trip Trip{F:testTripId RRtestTripId}."; var tt = createInitialTripTimes(); var updatedTt = tt.copyOfScheduledTimes(); - updatedTt.updateArrivalTime(2, 59); + updatedTt.updateDepartureTime(1, 100); + updatedTt.updateArrivalTime(2, 99); + + var ex = assertThrows(DataValidationException.class, updatedTt::validateNonIncreasingTimes); + var error = (TimetableValidationError) ex.error(); - var validationResult = updatedTt.validateNonIncreasingTimes(); - assertTrue(validationResult.isPresent()); - assertEquals(2, validationResult.get().stopIndex()); - assertEquals(NEGATIVE_HOP_TIME, validationResult.get().code()); + assertEquals(2, error.stopIndex()); + assertEquals(NEGATIVE_HOP_TIME, error.code()); + assertEquals(expMsg, error.message()); + assertEquals(expMsg, ex.getMessage()); } } From ebbe2fd4798d0fe65adc34439ea5aa92200b224c Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 16 Nov 2023 20:33:23 +0100 Subject: [PATCH 37/85] refactor: Extract TripTimes interface. This creates a new interface TripTimes and rename the old class to RealTimeTripTimes and let the ScheduledTripTimes also implement this interface. This will allow us to wait for an update before an accusal RealTime object is created. --- .../RealtimeResolverTest.java | 2 +- .../ext/siri/TimetableHelperTest.java | 4 +- .../ext/siri/AddedTripBuilder.java | 6 +- .../ext/siri/ModifiedTripBuilder.java | 5 +- .../ext/siri/SiriTimetableSnapshotSource.java | 3 +- .../ext/siri/TimetableHelper.java | 4 +- .../ext/siri/TripTimesAndStopPattern.java | 6 - .../org/opentripplanner/model/Timetable.java | 7 +- .../opentripplanner/model/TripTimesPatch.java | 7 +- .../netex/mapping/TripPatternMapper.java | 10 +- .../model/timetable/FrequencyEntry.java | 8 +- .../model/timetable/RealTimeTripTimes.java | 584 +++++++++++++++++ .../model/timetable/ScheduledTripTimes.java | 141 ++-- .../transit/model/timetable/TripTimes.java | 600 +++--------------- .../model/timetable/TripTimesFactory.java | 6 +- .../updater/trip/TimetableSnapshotSource.java | 16 +- ...rRoutingRequestTransitDataCreatorTest.java | 12 +- ...eRequestTransitDataProviderFilterTest.java | 7 +- .../stoptimes/StopTimesHelperTest.java | 4 +- ...esTest.java => RealTimeTripTimesTest.java} | 32 +- 20 files changed, 794 insertions(+), 670 deletions(-) delete mode 100644 src/ext/java/org/opentripplanner/ext/siri/TripTimesAndStopPattern.java create mode 100644 src/main/java/org/opentripplanner/transit/model/timetable/RealTimeTripTimes.java rename src/test/java/org/opentripplanner/transit/model/timetable/{TripTimesTest.java => RealTimeTripTimesTest.java} (93%) diff --git a/src/ext-test/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolverTest.java b/src/ext-test/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolverTest.java index 43be9c6d735..af415b5e883 100644 --- a/src/ext-test/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolverTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolverTest.java @@ -137,7 +137,7 @@ private static TripPattern delay(TripPattern pattern1, int seconds) { } private static TripTimes delay(TripTimes tt, int seconds) { - var delayed = tt.copyOfScheduledTimes(); + var delayed = tt.copyScheduledTimes(); IntStream .range(0, delayed.getNumStops()) .forEach(i -> { diff --git a/src/ext-test/java/org/opentripplanner/ext/siri/TimetableHelperTest.java b/src/ext-test/java/org/opentripplanner/ext/siri/TimetableHelperTest.java index 97bd3be2398..dde2677a952 100644 --- a/src/ext-test/java/org/opentripplanner/ext/siri/TimetableHelperTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/siri/TimetableHelperTest.java @@ -19,8 +19,8 @@ import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.model.site.Station; import org.opentripplanner.transit.model.timetable.OccupancyStatus; +import org.opentripplanner.transit.model.timetable.RealTimeTripTimes; import org.opentripplanner.transit.model.timetable.Trip; -import org.opentripplanner.transit.model.timetable.TripTimes; import org.opentripplanner.transit.model.timetable.TripTimesFactory; import org.opentripplanner.transit.service.StopModel; import uk.org.siri.siri20.OccupancyEnumeration; @@ -46,7 +46,7 @@ public class TimetableHelperTest { ZoneIds.CET ); - private TripTimes tripTimes; + private RealTimeTripTimes tripTimes; @BeforeEach public void setUp() { diff --git a/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java b/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java index 74d84ee397e..cb49c6ea50e 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java +++ b/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java @@ -28,8 +28,8 @@ import org.opentripplanner.transit.model.organization.Operator; import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.model.timetable.RealTimeState; +import org.opentripplanner.transit.model.timetable.RealTimeTripTimes; import org.opentripplanner.transit.model.timetable.Trip; -import org.opentripplanner.transit.model.timetable.TripTimes; import org.opentripplanner.transit.model.timetable.TripTimesFactory; import org.opentripplanner.transit.service.TransitModel; import org.opentripplanner.updater.spi.DataValidationExceptionMapper; @@ -203,14 +203,14 @@ Result build() { .withStopPattern(stopPattern) .build(); - TripTimes tripTimes = TripTimesFactory.tripTimes( + RealTimeTripTimes tripTimes = TripTimesFactory.tripTimes( trip, aimedStopTimes, transitModel.getDeduplicator() ); tripTimes.setServiceCode(transitModel.getServiceCodes().get(trip.getServiceId())); pattern.add(tripTimes); - TripTimes updatedTripTimes = tripTimes.copyOfScheduledTimes(); + RealTimeTripTimes updatedTripTimes = tripTimes.copyScheduledTimes(); // Loop through calls again and apply updates for (int stopSequence = 0; stopSequence < calls.size(); stopSequence++) { diff --git a/src/ext/java/org/opentripplanner/ext/siri/ModifiedTripBuilder.java b/src/ext/java/org/opentripplanner/ext/siri/ModifiedTripBuilder.java index ce21823b9b3..56cc28d82f4 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/ModifiedTripBuilder.java +++ b/src/ext/java/org/opentripplanner/ext/siri/ModifiedTripBuilder.java @@ -19,6 +19,7 @@ import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.model.timetable.RealTimeState; +import org.opentripplanner.transit.model.timetable.RealTimeTripTimes; import org.opentripplanner.transit.model.timetable.TripTimes; import org.opentripplanner.updater.spi.DataValidationExceptionMapper; import org.opentripplanner.updater.spi.UpdateError; @@ -94,7 +95,7 @@ public ModifiedTripBuilder( * in form the SIRI-ET update. */ public Result build() { - TripTimes newTimes = existingTripTimes.copyOfScheduledTimes(); + RealTimeTripTimes newTimes = existingTripTimes.copyScheduledTimes(); StopPattern stopPattern = createStopPattern(pattern, calls, entityResolver); @@ -145,7 +146,7 @@ public Result build() { /** * Applies real-time updates from the calls into newTimes. */ - private void applyUpdates(TripTimes newTimes) { + private void applyUpdates(RealTimeTripTimes newTimes) { ZonedDateTime startOfService = ServiceDateUtils.asStartOfService(serviceDate, zoneId); Set alreadyVisited = new HashSet<>(); diff --git a/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java b/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java index e6efb3ec3d9..0dd935361d6 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java +++ b/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java @@ -21,6 +21,7 @@ import org.opentripplanner.transit.model.framework.DataValidationException; import org.opentripplanner.transit.model.framework.Result; import org.opentripplanner.transit.model.network.TripPattern; +import org.opentripplanner.transit.model.timetable.RealTimeTripTimes; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.TripTimes; import org.opentripplanner.transit.service.DefaultTransitService; @@ -399,7 +400,7 @@ private boolean markScheduledTripAsDeleted(Trip trip, final LocalDate serviceDat if (tripTimes == null) { LOG.warn("Could not mark scheduled trip as deleted {}", trip.getId()); } else { - final TripTimes newTripTimes = tripTimes.copyOfScheduledTimes(); + final RealTimeTripTimes newTripTimes = tripTimes.copyScheduledTimes(); newTripTimes.deleteTrip(); buffer.update(pattern, newTripTimes, serviceDate); success = true; diff --git a/src/ext/java/org/opentripplanner/ext/siri/TimetableHelper.java b/src/ext/java/org/opentripplanner/ext/siri/TimetableHelper.java index ade320db298..e7063730e87 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/TimetableHelper.java +++ b/src/ext/java/org/opentripplanner/ext/siri/TimetableHelper.java @@ -7,7 +7,7 @@ import org.opentripplanner.ext.siri.mapper.OccupancyMapper; import org.opentripplanner.framework.i18n.NonLocalizedString; import org.opentripplanner.framework.time.ServiceDateUtils; -import org.opentripplanner.transit.model.timetable.TripTimes; +import org.opentripplanner.transit.model.timetable.RealTimeTripTimes; import uk.org.siri.siri20.NaturalLanguageStringStructure; import uk.org.siri.siri20.OccupancyEnumeration; @@ -52,7 +52,7 @@ private static int handleMissingRealtime(int... times) { public static void applyUpdates( ZonedDateTime departureDate, - TripTimes tripTimes, + RealTimeTripTimes tripTimes, int index, boolean isLastStop, boolean isJourneyPredictionInaccurate, diff --git a/src/ext/java/org/opentripplanner/ext/siri/TripTimesAndStopPattern.java b/src/ext/java/org/opentripplanner/ext/siri/TripTimesAndStopPattern.java deleted file mode 100644 index 70d9e034188..00000000000 --- a/src/ext/java/org/opentripplanner/ext/siri/TripTimesAndStopPattern.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.opentripplanner.ext.siri; - -import org.opentripplanner.transit.model.network.StopPattern; -import org.opentripplanner.transit.model.timetable.TripTimes; - -public record TripTimesAndStopPattern(TripTimes times, StopPattern pattern) {} diff --git a/src/main/java/org/opentripplanner/model/Timetable.java b/src/main/java/org/opentripplanner/model/Timetable.java index 0bcdb9fd1ab..a8f0f1bbf44 100644 --- a/src/main/java/org/opentripplanner/model/Timetable.java +++ b/src/main/java/org/opentripplanner/model/Timetable.java @@ -31,6 +31,7 @@ import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.timetable.Direction; import org.opentripplanner.transit.model.timetable.FrequencyEntry; +import org.opentripplanner.transit.model.timetable.RealTimeTripTimes; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.TripTimes; import org.opentripplanner.updater.GtfsRealtimeMapper; @@ -204,7 +205,7 @@ public Result createUpdatedTripTimesFromGTFSRT( LOG.trace("tripId {} found at index {} in timetable.", tripId, tripIndex); } - TripTimes newTimes = getTripTimes(tripIndex).copyOfScheduledTimes(); + RealTimeTripTimes newTimes = getTripTimes(tripIndex).copyScheduledTimes(); List skippedStopIndices = new ArrayList<>(); // The GTFS-RT reference specifies that StopTimeUpdates are sorted by stop_sequence. @@ -429,12 +430,12 @@ public boolean isValidFor(LocalDate serviceDate) { // TODO maybe put this is a more appropriate place public void setServiceCodes(Map serviceCodes) { for (TripTimes tt : this.tripTimes) { - tt.setServiceCode(serviceCodes.get(tt.getTrip().getServiceId())); + ((RealTimeTripTimes) tt).setServiceCode(serviceCodes.get(tt.getTrip().getServiceId())); } // Repeated code... bad sign... for (FrequencyEntry freq : this.frequencyEntries) { TripTimes tt = freq.tripTimes; - tt.setServiceCode(serviceCodes.get(tt.getTrip().getServiceId())); + ((RealTimeTripTimes) tt).setServiceCode(serviceCodes.get(tt.getTrip().getServiceId())); } } diff --git a/src/main/java/org/opentripplanner/model/TripTimesPatch.java b/src/main/java/org/opentripplanner/model/TripTimesPatch.java index f804a502d51..25afaf81eae 100644 --- a/src/main/java/org/opentripplanner/model/TripTimesPatch.java +++ b/src/main/java/org/opentripplanner/model/TripTimesPatch.java @@ -1,6 +1,7 @@ package org.opentripplanner.model; import java.util.List; +import org.opentripplanner.transit.model.timetable.RealTimeTripTimes; import org.opentripplanner.transit.model.timetable.TripTimes; /** @@ -9,15 +10,15 @@ */ public class TripTimesPatch { - private final TripTimes tripTimes; + private final RealTimeTripTimes tripTimes; private final List skippedStopIndices; - public TripTimesPatch(TripTimes tripTimes, List skippedStopIndices) { + public TripTimesPatch(RealTimeTripTimes tripTimes, List skippedStopIndices) { this.tripTimes = tripTimes; this.skippedStopIndices = skippedStopIndices; } - public TripTimes getTripTimes() { + public RealTimeTripTimes getTripTimes() { return this.tripTimes; } diff --git a/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java b/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java index a00912264ea..4babac12e03 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java @@ -11,6 +11,7 @@ import java.util.Objects; import java.util.stream.Collectors; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; +import org.opentripplanner.model.StopTime; import org.opentripplanner.netex.index.api.ReadOnlyHierarchicalMap; import org.opentripplanner.netex.index.api.ReadOnlyHierarchicalMapById; import org.opentripplanner.netex.mapping.support.FeedScopedIdFactory; @@ -361,7 +362,8 @@ private org.opentripplanner.transit.model.network.Route lookupRoute( private void createTripTimes(List trips, TripPattern tripPattern) { for (Trip trip : trips) { - if (result.tripStopTimes.get(trip).size() == 0) { + List stopTimes = result.tripStopTimes.get(trip); + if (stopTimes.size() == 0) { issueStore.add( "TripWithoutTripTimes", "Trip %s does not contain any trip times.", @@ -369,11 +371,7 @@ private void createTripTimes(List trips, TripPattern tripPattern) { ); } else { try { - TripTimes tripTimes = TripTimesFactory.tripTimes( - trip, - result.tripStopTimes.get(trip), - deduplicator - ); + TripTimes tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, deduplicator); tripPattern.add(tripTimes); } catch (DataValidationException e) { issueStore.add(e.error()); diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/FrequencyEntry.java b/src/main/java/org/opentripplanner/transit/model/timetable/FrequencyEntry.java index fc34b08e0b2..61658f6b3e0 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/FrequencyEntry.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/FrequencyEntry.java @@ -6,6 +6,8 @@ /** * Uses a TripTimes to represent multiple trips following the same template at regular intervals. * (see GTFS frequencies.txt) + *

+ * Refactor this to inherit {@link TripTimes} */ public class FrequencyEntry implements Serializable { @@ -116,11 +118,13 @@ public int prevArrivalTime(int stop, int t) { * Returns a disposable TripTimes for this frequency entry in which the vehicle passes the given * stop index (not stop sequence number) at the given time. This allows us to separate the * departure/arrival search process from actually instantiating a TripTimes, to avoid making too - * many short-lived clones. This delegation is a sign that maybe FrequencyEntry should subclass + * many short-lived clones. This delegation is a sign that maybe FrequencyEntry should implement * TripTimes. */ public TripTimes materialize(int stop, int time, boolean depart) { - return tripTimes.timeShift(stop, time, depart); + // TODO: The cast should be changed, the internal type should be scheduledTripTimes and the + // shift method should partially be inlined here + return ((RealTimeTripTimes) tripTimes).timeShift(stop, time, depart); } /** Used in debugging / dumping times. */ diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/RealTimeTripTimes.java b/src/main/java/org/opentripplanner/transit/model/timetable/RealTimeTripTimes.java new file mode 100644 index 00000000000..a62da95e79a --- /dev/null +++ b/src/main/java/org/opentripplanner/transit/model/timetable/RealTimeTripTimes.java @@ -0,0 +1,584 @@ +package org.opentripplanner.transit.model.timetable; + +import static org.opentripplanner.transit.model.timetable.TimetableValidationError.ErrorCode.NEGATIVE_DWELL_TIME; +import static org.opentripplanner.transit.model.timetable.TimetableValidationError.ErrorCode.NEGATIVE_HOP_TIME; + +import java.time.Duration; +import java.util.Arrays; +import java.util.List; +import java.util.OptionalInt; +import java.util.function.IntUnaryOperator; +import javax.annotation.Nullable; +import org.opentripplanner.framework.i18n.I18NString; +import org.opentripplanner.model.BookingInfo; +import org.opentripplanner.transit.model.basic.Accessibility; +import org.opentripplanner.transit.model.framework.DataValidationException; + +/** + * A TripTimes represents the arrival and departure times for a single trip in an Timetable. It is + * carried along by States when routing to ensure that they have a consistent, fast view of the trip + * when realtime updates have been applied. All times are expressed as seconds since midnight (as in + * GTFS). + */ +public final class RealTimeTripTimes implements TripTimes { + + private ScheduledTripTimes scheduledTripTimes; + + private int[] arrivalTimes; + private int[] departureTimes; + private RealTimeState realTimeState; + private StopRealTimeState[] stopRealTimeStates; + private I18NString[] headsigns; + private OccupancyStatus[] occupancyStatus; + private Accessibility wheelchairAccessibility; + + RealTimeTripTimes(ScheduledTripTimes scheduledTripTimes) { + this( + scheduledTripTimes, + scheduledTripTimes.getRealTimeState(), + null, + null, + null, + scheduledTripTimes.getWheelchairAccessibility() + ); + } + + private RealTimeTripTimes(RealTimeTripTimes original, ScheduledTripTimes scheduledTripTimes) { + this( + scheduledTripTimes, + original.realTimeState, + original.stopRealTimeStates, + original.headsigns, + original.occupancyStatus, + original.wheelchairAccessibility + ); + } + + private RealTimeTripTimes( + ScheduledTripTimes scheduledTripTimes, + RealTimeState realTimeState, + StopRealTimeState[] stopRealTimeStates, + I18NString[] headsigns, + OccupancyStatus[] occupancyStatus, + Accessibility wheelchairAccessibility + ) { + this.scheduledTripTimes = scheduledTripTimes; + this.realTimeState = realTimeState; + this.stopRealTimeStates = stopRealTimeStates; + this.headsigns = headsigns; + this.occupancyStatus = occupancyStatus; + this.wheelchairAccessibility = wheelchairAccessibility; + + // We set these to null to indicate that this is a non-updated/scheduled TripTimes. + // We cannot point to the scheduled times because we do not want to make an unnecessary copy. + this.arrivalTimes = null; + this.departureTimes = null; + } + + public static RealTimeTripTimes of(ScheduledTripTimes scheduledTripTimes) { + return new RealTimeTripTimes(scheduledTripTimes); + } + + @Override + public RealTimeTripTimes copyScheduledTimes() { + return new RealTimeTripTimes(this, scheduledTripTimes); + } + + /** + * Both trip_headsign and stop_headsign (per stop on a particular trip) are optional GTFS fields. + * A trip may not have a headsign, in which case we should fall back on a Timetable or + * Pattern-level headsign. Such a string will be available when we give TripPatterns or + * StopPatterns unique human readable route variant names, but a TripTimes currently does not have + * a pointer to its enclosing timetable or pattern. + */ + @Nullable + public I18NString getHeadsign(final int stop) { + return (headsigns != null && headsigns[stop] != null) + ? headsigns[stop] + : scheduledTripTimes.getHeadsign(stop); + } + + /** + * Return list of via names per particular stop. This field provides info about intermediate stops + * between current stop and final trip destination. Mapped from NeTEx DestinationDisplay.vias. No + * GTFS mapping at the moment. + * + * @return Empty list if there are no vias registered for a stop. + */ + @Override + public List getHeadsignVias(final int stop) { + return scheduledTripTimes.getHeadsignVias(stop); + } + + /** + * @return the whole trip's headsign. Individual stops can have different headsigns. + */ + @Override + public I18NString getTripHeadsign() { + return scheduledTripTimes.getTripHeadsign(); + } + + /** + * The time in seconds after midnight at which the vehicle should arrive at the given stop + * according to the original schedule. + */ + @Override + public int getScheduledArrivalTime(final int stop) { + return scheduledTripTimes.getScheduledArrivalTime(stop); + } + + /** + * The time in seconds after midnight at which the vehicle should leave the given stop according + * to the original schedule. + */ + @Override + public int getScheduledDepartureTime(final int stop) { + return scheduledTripTimes.getScheduledDepartureTime(stop); + } + + /** + * The time in seconds after midnight at which the vehicle arrives at each stop, accounting for + * any real-time updates. + */ + @Override + public int getArrivalTime(final int stop) { + return getOrElse(stop, arrivalTimes, scheduledTripTimes::getScheduledArrivalTime); + } + + /** + * The time in seconds after midnight at which the vehicle leaves each stop, accounting for any + * real-time updates. + */ + @Override + public int getDepartureTime(final int stop) { + return getOrElse(stop, departureTimes, scheduledTripTimes::getScheduledDepartureTime); + } + + /** @return the difference between the scheduled and actual arrival times at this stop. */ + @Override + public int getArrivalDelay(final int stop) { + return getArrivalTime(stop) - scheduledTripTimes.getScheduledArrivalTime(stop); + } + + /** @return the difference between the scheduled and actual departure times at this stop. */ + @Override + public int getDepartureDelay(final int stop) { + return getDepartureTime(stop) - scheduledTripTimes.getScheduledDepartureTime(stop); + } + + public void setRecorded(int stop) { + setStopRealTimeStates(stop, StopRealTimeState.RECORDED); + } + + public void setCancelled(int stop) { + setStopRealTimeStates(stop, StopRealTimeState.CANCELLED); + } + + public void setNoData(int stop) { + setStopRealTimeStates(stop, StopRealTimeState.NO_DATA); + } + + public void setPredictionInaccurate(int stop) { + setStopRealTimeStates(stop, StopRealTimeState.INACCURATE_PREDICTIONS); + } + + public boolean isCancelledStop(int stop) { + return isStopRealTimeStates(stop, StopRealTimeState.CANCELLED); + } + + public boolean isRecordedStop(int stop) { + return isStopRealTimeStates(stop, StopRealTimeState.RECORDED); + } + + public boolean isNoDataStop(int stop) { + return isStopRealTimeStates(stop, StopRealTimeState.NO_DATA); + } + + public boolean isPredictionInaccurate(int stop) { + return isStopRealTimeStates(stop, StopRealTimeState.INACCURATE_PREDICTIONS); + } + + public void setOccupancyStatus(int stop, OccupancyStatus occupancyStatus) { + prepareForRealTimeUpdates(); + this.occupancyStatus[stop] = occupancyStatus; + } + + /** + * This is only for API-purposes (does not affect routing). + */ + @Override + public OccupancyStatus getOccupancyStatus(int stop) { + if (this.occupancyStatus == null) { + return OccupancyStatus.NO_DATA_AVAILABLE; + } + return this.occupancyStatus[stop]; + } + + @Override + public BookingInfo getDropOffBookingInfo(int stop) { + return scheduledTripTimes.getDropOffBookingInfo(stop); + } + + @Override + public BookingInfo getPickupBookingInfo(int stop) { + return scheduledTripTimes.getPickupBookingInfo(stop); + } + + @Override + public boolean isScheduled() { + return realTimeState == RealTimeState.SCHEDULED; + } + + @Override + public boolean isCanceledOrDeleted() { + return isCanceled() || isDeleted(); + } + + @Override + public boolean isCanceled() { + return realTimeState == RealTimeState.CANCELED; + } + + @Override + public boolean isDeleted() { + return realTimeState == RealTimeState.DELETED; + } + + @Override + public RealTimeState getRealTimeState() { + return realTimeState; + } + + public void setRealTimeState(final RealTimeState realTimeState) { + this.realTimeState = realTimeState; + } + + /** + * When creating a scheduled TripTimes or wrapping it in updates, we could potentially imply + * negative running or dwell times. We really don't want those being used in routing. This method + * checks that all internal times are increasing. Thus, this check should be used at the end of + * updating trip times, after any propagating or interpolating delay operations. + * + * @throws org.opentripplanner.transit.model.framework.DataValidationException of the first error + * found. + */ + public void validateNonIncreasingTimes() { + final int nStops = arrivalTimes.length; + int prevDep = -9_999_999; + for (int s = 0; s < nStops; s++) { + final int arr = getArrivalTime(s); + final int dep = getDepartureTime(s); + + if (dep < arr) { + throw new DataValidationException( + new TimetableValidationError(NEGATIVE_DWELL_TIME, s, getTrip()) + ); + } + if (prevDep > arr) { + throw new DataValidationException( + new TimetableValidationError(NEGATIVE_HOP_TIME, s, getTrip()) + ); + } + prevDep = dep; + } + } + + /** Cancel this entire trip */ + public void cancelTrip() { + realTimeState = RealTimeState.CANCELED; + } + + /** Soft delete the entire trip */ + public void deleteTrip() { + realTimeState = RealTimeState.DELETED; + } + + public void updateDepartureTime(final int stop, final int time) { + prepareForRealTimeUpdates(); + departureTimes[stop] = time; + } + + public void updateDepartureDelay(final int stop, final int delay) { + prepareForRealTimeUpdates(); + departureTimes[stop] = scheduledTripTimes.getScheduledDepartureTime(stop) + delay; + } + + public void updateArrivalTime(final int stop, final int time) { + prepareForRealTimeUpdates(); + arrivalTimes[stop] = time; + } + + public void updateArrivalDelay(final int stop, final int delay) { + prepareForRealTimeUpdates(); + arrivalTimes[stop] = scheduledTripTimes.getScheduledArrivalTime(stop) + delay; + } + + @Nullable + public Accessibility getWheelchairAccessibility() { + // No need to fall back to scheduled state, since it is copied over in the constructor + return wheelchairAccessibility; + } + + public void updateWheelchairAccessibility(Accessibility wheelchairAccessibility) { + this.wheelchairAccessibility = wheelchairAccessibility; + } + + public int getNumStops() { + return scheduledTripTimes.getNumStops(); + } + + /** + * Returns a time-shifted copy of this TripTimes in which the vehicle passes the given stop index + * (not stop sequence number) at the given time. We only have a mechanism to shift the scheduled + * stoptimes, not the real-time stoptimes. Therefore, this only works on trips without updates for + * now (frequency trips don't have updates). + */ + public TripTimes timeShift(final int stop, final int time, final boolean depart) { + if (arrivalTimes != null || departureTimes != null) { + return null; + } + // Adjust 0-based times to match desired stoptime. + final int shift = time - (depart ? getDepartureTime(stop) : getArrivalTime(stop)); + + return new RealTimeTripTimes( + this, + scheduledTripTimes.copyOfNoDuplication().plusTimeShift(shift).build() + ); + } + + /** + * Time-shift all times on this trip. This is used when updating the time zone for the trip. + */ + @Override + public TripTimes adjustTimesToGraphTimeZone(Duration shiftDelta) { + return new RealTimeTripTimes( + this, + scheduledTripTimes.copyOfNoDuplication().plusTimeShift((int) shiftDelta.toSeconds()).build() + ); + } + + @Override + public int gtfsSequenceOfStopIndex(final int stop) { + return scheduledTripTimes.gtfsSequenceOfStopIndex(stop); + } + + @Override + public OptionalInt stopIndexOfGtfsSequence(int stopSequence) { + return scheduledTripTimes.stopIndexOfGtfsSequence(stopSequence); + } + + @Override + public boolean isTimepoint(final int stopIndex) { + return scheduledTripTimes.isTimepoint(stopIndex); + } + + @Override + public int getServiceCode() { + return scheduledTripTimes.getServiceCode(); + } + + public void setServiceCode(int serviceCode) { + this.scheduledTripTimes = + scheduledTripTimes.copyOfNoDuplication().withServiceCode(serviceCode).build(); + } + + @Override + public Trip getTrip() { + return scheduledTripTimes.getTrip(); + } + + /** + * Note: This method only applies for GTFS, not SIRI! + * This method interpolates the times for SKIPPED stops in between regular stops since GTFS-RT + * does not require arrival and departure times for these stops. This method ensures the internal + * time representations in OTP for SKIPPED stops are between the regular stop times immediately + * before and after the cancellation in GTFS-RT. This is to meet the OTP requirement that stop + * times should be increasing and to support the trip search flag `includeRealtimeCancellations`. + * Terminal stop cancellations can be handled by backward and forward propagations, and are + * outside the scope of this method. + * + * @return true if there is interpolated times, false if there is no interpolation. + */ + public boolean interpolateMissingTimes() { + boolean hasInterpolatedTimes = false; + final int numStops = getNumStops(); + boolean startInterpolate = false; + boolean hasPrevTimes = false; + int prevDeparture = 0; + int prevScheduledDeparture = 0; + int prevStopIndex = -1; + + // Loop through all stops + for (int s = 0; s < numStops; s++) { + final boolean isCancelledStop = isCancelledStop(s); + final int scheduledArrival = getScheduledArrivalTime(s); + final int scheduledDeparture = getScheduledDepartureTime(s); + final int arrival = getArrivalTime(s); + final int departure = getDepartureTime(s); + + if (!isCancelledStop && !startInterpolate) { + // Regular stop, could be used for interpolation for future cancellation, keep track. + prevDeparture = departure; + prevScheduledDeparture = scheduledDeparture; + prevStopIndex = s; + hasPrevTimes = true; + } else if (isCancelledStop && !startInterpolate && hasPrevTimes) { + // First cancelled stop, keep track. + startInterpolate = true; + } else if (!isCancelledStop && startInterpolate && hasPrevTimes) { + // First regular stop after cancelled stops, interpolate. + // Calculate necessary info for interpolation. + int numCancelledStops = s - prevStopIndex - 1; + int scheduledTravelTime = scheduledArrival - prevScheduledDeparture; + int realTimeTravelTime = arrival - prevDeparture; + double travelTimeRatio = (double) realTimeTravelTime / scheduledTravelTime; + + // Fill out interpolated time for cancelled stops, using the calculated ratio. + for (int cancelledIndex = prevStopIndex + 1; cancelledIndex < s; cancelledIndex++) { + final int scheduledArrivalCancelled = getScheduledArrivalTime(cancelledIndex); + final int scheduledDepartureCancelled = getScheduledDepartureTime(cancelledIndex); + + // Interpolate + int scheduledArrivalDiff = scheduledArrivalCancelled - prevScheduledDeparture; + double interpolatedArrival = prevDeparture + travelTimeRatio * scheduledArrivalDiff; + int scheduledDepartureDiff = scheduledDepartureCancelled - prevScheduledDeparture; + double interpolatedDeparture = prevDeparture + travelTimeRatio * scheduledDepartureDiff; + + // Set Interpolated Times + updateArrivalTime(cancelledIndex, (int) interpolatedArrival); + updateDepartureTime(cancelledIndex, (int) interpolatedDeparture); + } + + // Set tracking variables + prevDeparture = departure; + prevScheduledDeparture = scheduledDeparture; + prevStopIndex = s; + startInterpolate = false; + hasPrevTimes = true; + + // Set return variable + hasInterpolatedTimes = true; + } + } + + return hasInterpolatedTimes; + } + + /** + * Adjusts arrival time for the stop at the firstUpdatedIndex if no update was given for it and + * arrival/departure times for the stops before that stop. Returns {@code true} if times have been + * adjusted. + */ + public boolean adjustTimesBeforeAlways(int firstUpdatedIndex) { + boolean hasAdjustedTimes = false; + int delay = getDepartureDelay(firstUpdatedIndex); + if (getArrivalDelay(firstUpdatedIndex) == 0) { + updateArrivalDelay(firstUpdatedIndex, delay); + hasAdjustedTimes = true; + } + delay = getArrivalDelay(firstUpdatedIndex); + if (delay == 0) { + return false; + } + for (int i = firstUpdatedIndex - 1; i >= 0; i--) { + hasAdjustedTimes = true; + updateDepartureDelay(i, delay); + updateArrivalDelay(i, delay); + } + return hasAdjustedTimes; + } + + /** + * Adjusts arrival and departure times for the stops before the stop at firstUpdatedIndex when + * required to ensure that the times are increasing. Can set NO_DATA flag on the updated previous + * stops. Returns {@code true} if times have been adjusted. + */ + public boolean adjustTimesBeforeWhenRequired(int firstUpdatedIndex, boolean setNoData) { + if (getArrivalTime(firstUpdatedIndex) > getDepartureTime(firstUpdatedIndex)) { + // The given trip update has arrival time after departure time for the first updated stop. + // This method doesn't try to fix issues in the given data, only for the missing part + return false; + } + int nextStopArrivalTime = getArrivalTime(firstUpdatedIndex); + int delay = getArrivalDelay(firstUpdatedIndex); + boolean hasAdjustedTimes = false; + boolean adjustTimes = true; + for (int i = firstUpdatedIndex - 1; i >= 0; i--) { + if (setNoData && !isCancelledStop(i)) { + setNoData(i); + } + if (adjustTimes) { + if (getDepartureTime(i) < nextStopArrivalTime) { + adjustTimes = false; + continue; + } else { + hasAdjustedTimes = true; + updateDepartureDelay(i, delay); + } + if (getArrivalTime(i) < getDepartureTime(i)) { + adjustTimes = false; + } else { + updateArrivalDelay(i, delay); + nextStopArrivalTime = getArrivalTime(i); + } + } + } + return hasAdjustedTimes; + } + + /* private member methods */ + + private void setStopRealTimeStates(int stop, StopRealTimeState state) { + prepareForRealTimeUpdates(); + this.stopRealTimeStates[stop] = state; + } + + /** + * The real-time states for a given stops. If the state is DEFAULT for a stop, + * the {@link #getRealTimeState()} should determine the realtime state of the stop. + *

+ * This is only for API-purposes (does not affect routing). + */ + private boolean isStopRealTimeStates(int stop, StopRealTimeState state) { + return stopRealTimeStates != null && stopRealTimeStates[stop] == state; + } + + public void setHeadsign(int index, I18NString headsign) { + if (headsigns == null) { + if (headsign.equals(getTrip().getHeadsign())) { + return; + } + this.headsigns = scheduledTripTimes.copyHeadsigns(() -> new I18NString[getNumStops()]); + this.headsigns[index] = headsign; + return; + } + + prepareForRealTimeUpdates(); + headsigns[index] = headsign; + } + + private static int getOrElse(int index, int[] array, IntUnaryOperator defaultValue) { + return array != null ? array[index] : defaultValue.applyAsInt(index); + } + + /** + * If they don't already exist, create arrays for updated arrival and departure times that are + * just time-shifted copies of the zero-based scheduled departure times. + *

+ * Also sets the realtime state to UPDATED. + */ + private void prepareForRealTimeUpdates() { + if (arrivalTimes == null) { + this.arrivalTimes = scheduledTripTimes.copyArrivalTimes(); + this.departureTimes = scheduledTripTimes.copyDepartureTimes(); + // Update the real-time state + this.realTimeState = RealTimeState.UPDATED; + this.stopRealTimeStates = new StopRealTimeState[arrivalTimes.length]; + Arrays.fill(stopRealTimeStates, StopRealTimeState.DEFAULT); + this.headsigns = scheduledTripTimes.copyHeadsigns(() -> null); + this.occupancyStatus = new OccupancyStatus[arrivalTimes.length]; + Arrays.fill(occupancyStatus, OccupancyStatus.NO_DATA_AVAILABLE); + // skip immutable types: scheduledTripTimes & wheelchairAccessibility + } + } +} diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java index 1f6aecc3ac5..0d1efeea7c7 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java @@ -3,7 +3,7 @@ import static org.opentripplanner.transit.model.timetable.TimetableValidationError.ErrorCode.NEGATIVE_DWELL_TIME; import static org.opentripplanner.transit.model.timetable.TimetableValidationError.ErrorCode.NEGATIVE_HOP_TIME; -import java.io.Serializable; +import java.time.Duration; import java.util.Arrays; import java.util.BitSet; import java.util.List; @@ -20,7 +20,7 @@ import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.framework.DeduplicatorService; -public final class ScheduledTripTimes implements Serializable, Comparable { +public final class ScheduledTripTimes implements TripTimes { /** * When time-shifting from one time-zone to another negative times may occur. @@ -111,132 +111,127 @@ public ScheduledTripTimesBuilder copyOfNoDuplication() { return copyOf(null); } - /** The code for the service on which this trip runs. For departure search optimizations. */ + @Override + public RealTimeTripTimes copyScheduledTimes() { + return RealTimeTripTimes.of(this); + } + + @Override + public TripTimes adjustTimesToGraphTimeZone(Duration shiftDelta) { + return copyOfNoDuplication().plusTimeShift((int) shiftDelta.toSeconds()).build(); + } + + @Override public int getServiceCode() { return serviceCode; } - /** - * The time in seconds after midnight at which the vehicle should arrive at the given stop - * according to the original schedule. - */ + @Override public int getScheduledArrivalTime(final int stop) { return arrivalTimes[stop] + timeShift; } - /** - * The time in seconds after midnight at which the vehicle arrives at each stop, accounting for - * any real-time updates. - */ + @Override public int getArrivalTime(final int stop) { return getScheduledArrivalTime(stop); } - /** @return the difference between the scheduled and actual arrival times at this stop. */ + @Override public int getArrivalDelay(final int stop) { return getArrivalTime(stop) - (arrivalTimes[stop] + timeShift); } - /** - * The time in seconds after midnight at which the vehicle should leave the given stop according - * to the original schedule. - */ + @Override public int getScheduledDepartureTime(final int stop) { return departureTimes[stop] + timeShift; } - /** - * The time in seconds after midnight at which the vehicle leaves each stop, accounting for any - * real-time updates. - */ + @Override public int getDepartureTime(final int stop) { return getScheduledDepartureTime(stop); } - /** @return the difference between the scheduled and actual departure times at this stop. */ + @Override public int getDepartureDelay(final int stop) { return getDepartureTime(stop) - (departureTimes[stop] + timeShift); } - /** - * Whether or not stopIndex is considered a GTFS timepoint. - */ + @Override public boolean isTimepoint(final int stopIndex) { return timepoints.get(stopIndex); } - /** The trips whose arrivals and departures are represented by this class */ + @Override public Trip getTrip() { return trip; } - /** - * Return an integer which can be used to sort TripTimes in order of departure/arrivals. - *

- * This sorted trip times is used to search for trips. OTP assume one trip do NOT pass another - * trip down the line. - */ + @Override public int sortIndex() { return getDepartureTime(0); } + @Override public BookingInfo getDropOffBookingInfo(int stop) { return dropOffBookingInfos.get(stop); } + @Override public BookingInfo getPickupBookingInfo(int stop) { return pickupBookingInfos.get(stop); } - /** - * Return {@code true} if the trip is unmodified, a scheduled trip from a published timetable. - * Return {@code false} if the trip is an updated, cancelled, or otherwise modified one. This - * method differs from {@link #getRealTimeState()} in that it checks whether real-time - * information is actually available. - */ + @Override public boolean isScheduled() { return true; } - /** - * Return {@code true} if canceled or soft-deleted - */ + @Override public boolean isCanceledOrDeleted() { return false; } - /** - * Return {@code true} if canceled - */ + @Override public boolean isCanceled() { return false; } - /** - * Return true if trip is soft-deleted, and should not be visible to the user - */ + @Override public boolean isDeleted() { return false; } + @Override public RealTimeState getRealTimeState() { return RealTimeState.SCHEDULED; } - /** - * @return the whole trip's headsign. Individual stops can have different headsigns. - */ + @Override + public boolean isCancelledStop(int stop) { + return false; + } + + @Override + public boolean isRecordedStop(int stop) { + return false; + } + + @Override + public boolean isNoDataStop(int stop) { + return false; + } + + @Override + public boolean isPredictionInaccurate(int stop) { + return false; + } + + @Override public I18NString getTripHeadsign() { return trip.getHeadsign(); } - /** - * Both trip_headsign and stop_headsign (per stop on a particular trip) are optional GTFS fields. - * A trip may not have a headsign, in which case we should fall back on a Timetable or - * Pattern-level headsign. Such a string will be available when we give TripPatterns or - * StopPatterns unique human-readable route variant names, but a ScheduledTripTimes currently - * does not have a pointer to its enclosing timetable or pattern. - */ + @Override @Nullable public I18NString getHeadsign(final int stop) { return (headsigns != null && headsigns[stop] != null) @@ -244,13 +239,7 @@ public I18NString getHeadsign(final int stop) { : getTrip().getHeadsign(); } - /** - * Return list of via names per particular stop. This field provides info about intermediate stops - * between current stop and final trip destination. Mapped from NeTEx DestinationDisplay.vias. No - * GTFS mapping at the moment. - * - * @return Empty list if there are no vias registered for a stop. - */ + @Override public List getHeadsignVias(final int stop) { if (headsignVias == null || headsignVias[stop] == null) { return List.of(); @@ -258,45 +247,27 @@ public List getHeadsignVias(final int stop) { return List.of(headsignVias[stop]); } + @Override public int getNumStops() { return arrivalTimes.length; } + @Override public Accessibility getWheelchairAccessibility() { return trip.getWheelchairBoarding(); } - /** - * This is only for API-purposes (does not affect routing). - */ + @Override public OccupancyStatus getOccupancyStatus(int ignore) { return OccupancyStatus.NO_DATA_AVAILABLE; } - /** Sort trips based on first departure time. */ @Override - public int compareTo(final ScheduledTripTimes other) { - return this.getDepartureTime(0) - other.getDepartureTime(0); - } - - /** - * Returns the GTFS sequence number of the given 0-based stop position. - * - * These are the GTFS stop sequence numbers, which show the order in which the vehicle visits the - * stops. Despite the fact that the StopPattern or TripPattern enclosing this class provides - * an ordered list of Stops, the original stop sequence numbers may still be needed for matching - * with GTFS-RT update messages. Unfortunately, each individual trip can have totally different - * sequence numbers for the same stops, so we need to store them at the individual trip level. An - * effort is made to re-use the sequence number arrays when they are the same across different - * trips in the same pattern. - */ public int gtfsSequenceOfStopIndex(final int stop) { return originalGtfsStopSequence[stop]; } - /** - * Returns the 0-based stop index of the given GTFS sequence number. - */ + @Override public OptionalInt stopIndexOfGtfsSequence(int stopSequence) { if (originalGtfsStopSequence == null) { return OptionalInt.empty(); diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java index ecb0df38127..a7a5b34e7c2 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java @@ -1,614 +1,172 @@ package org.opentripplanner.transit.model.timetable; -import static org.opentripplanner.transit.model.timetable.TimetableValidationError.ErrorCode.NEGATIVE_DWELL_TIME; -import static org.opentripplanner.transit.model.timetable.TimetableValidationError.ErrorCode.NEGATIVE_HOP_TIME; - import java.io.Serializable; import java.time.Duration; -import java.util.Arrays; +import java.util.Comparator; import java.util.List; import java.util.OptionalInt; -import java.util.function.IntUnaryOperator; import javax.annotation.Nullable; import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.model.BookingInfo; import org.opentripplanner.transit.model.basic.Accessibility; -import org.opentripplanner.transit.model.framework.DataValidationException; - -/** - * A TripTimes represents the arrival and departure times for a single trip in an Timetable. It is - * carried along by States when routing to ensure that they have a consistent, fast view of the trip - * when realtime updates have been applied. All times are expressed as seconds since midnight (as in - * GTFS). - */ -public final class TripTimes implements Serializable, Comparable { - - private ScheduledTripTimes scheduledTripTimes; - - private int[] arrivalTimes; - private int[] departureTimes; - private RealTimeState realTimeState; - private StopRealTimeState[] stopRealTimeStates; - private I18NString[] headsigns; - private OccupancyStatus[] occupancyStatus; - private Accessibility wheelchairAccessibility; - - TripTimes(ScheduledTripTimes scheduledTripTimes) { - this( - scheduledTripTimes, - scheduledTripTimes.getRealTimeState(), - null, - null, - null, - scheduledTripTimes.getWheelchairAccessibility() - ); - } - - private TripTimes(TripTimes original, ScheduledTripTimes scheduledTripTimes) { - this( - scheduledTripTimes, - original.realTimeState, - original.stopRealTimeStates, - original.headsigns, - original.occupancyStatus, - original.wheelchairAccessibility - ); - } - - private TripTimes( - ScheduledTripTimes scheduledTripTimes, - RealTimeState realTimeState, - StopRealTimeState[] stopRealTimeStates, - I18NString[] headsigns, - OccupancyStatus[] occupancyStatus, - Accessibility wheelchairAccessibility - ) { - this.scheduledTripTimes = scheduledTripTimes; - this.realTimeState = realTimeState; - this.stopRealTimeStates = stopRealTimeStates; - this.headsigns = headsigns; - this.occupancyStatus = occupancyStatus; - this.wheelchairAccessibility = wheelchairAccessibility; - - // We set these to null to indicate that this is a non-updated/scheduled TripTimes. - // We cannot point to the scheduled times because we do not want to make an unnecessary copy. - this.arrivalTimes = null; - this.departureTimes = null; - } - - public static TripTimes of(ScheduledTripTimes scheduledTripTimes) { - return new TripTimes(scheduledTripTimes); - } +public interface TripTimes extends Serializable, Comparable { /** * Copy scheduled times, but not the actual times. */ - public TripTimes copyOfScheduledTimes() { - return new TripTimes(this, scheduledTripTimes); - } - - /** - * Both trip_headsign and stop_headsign (per stop on a particular trip) are optional GTFS fields. - * A trip may not have a headsign, in which case we should fall back on a Timetable or - * Pattern-level headsign. Such a string will be available when we give TripPatterns or - * StopPatterns unique human readable route variant names, but a TripTimes currently does not have - * a pointer to its enclosing timetable or pattern. - */ - @Nullable - public I18NString getHeadsign(final int stop) { - return (headsigns != null && headsigns[stop] != null) - ? headsigns[stop] - : scheduledTripTimes.getHeadsign(stop); - } - - /** - * Return list of via names per particular stop. This field provides info about intermediate stops - * between current stop and final trip destination. Mapped from NeTEx DestinationDisplay.vias. No - * GTFS mapping at the moment. - * - * @return Empty list if there are no vias registered for a stop. - */ - public List getHeadsignVias(final int stop) { - return scheduledTripTimes.getHeadsignVias(stop); - } + public RealTimeTripTimes copyScheduledTimes(); - /** - * @return the whole trip's headsign. Individual stops can have different headsigns. - */ - public I18NString getTripHeadsign() { - return scheduledTripTimes.getTripHeadsign(); - } + /** The code for the service on which this trip runs. For departure search optimizations. */ + int getServiceCode(); /** * The time in seconds after midnight at which the vehicle should arrive at the given stop * according to the original schedule. */ - public int getScheduledArrivalTime(final int stop) { - return scheduledTripTimes.getScheduledArrivalTime(stop); - } + int getScheduledArrivalTime(int stop); /** - * The time in seconds after midnight at which the vehicle should leave the given stop according - * to the original schedule. + * The time in seconds after midnight at which the vehicle arrives at each stop, accounting for + * any real-time updates. */ - public int getScheduledDepartureTime(final int stop) { - return scheduledTripTimes.getScheduledDepartureTime(stop); - } + int getArrivalTime(int stop); - /** - * Return an integer which can be used to sort TripTimes in order of departure/arrivals. - *

- * This sorted trip times is used to search for trips. OTP assume one trip do NOT pass another - * trip down the line. - */ - public int sortIndex() { - return getDepartureTime(0); - } + /** @return the difference between the scheduled and actual arrival times at this stop. */ + int getArrivalDelay(int stop); /** - * The time in seconds after midnight at which the vehicle arrives at each stop, accounting for - * any real-time updates. + * The time in seconds after midnight at which the vehicle should leave the given stop according + * to the original schedule. */ - public int getArrivalTime(final int stop) { - return getOrElse(stop, arrivalTimes, scheduledTripTimes::getScheduledArrivalTime); - } + int getScheduledDepartureTime(int stop); /** * The time in seconds after midnight at which the vehicle leaves each stop, accounting for any * real-time updates. */ - public int getDepartureTime(final int stop) { - return getOrElse(stop, departureTimes, scheduledTripTimes::getScheduledDepartureTime); - } - - /** @return the difference between the scheduled and actual arrival times at this stop. */ - public int getArrivalDelay(final int stop) { - return getArrivalTime(stop) - scheduledTripTimes.getScheduledArrivalTime(stop); - } + int getDepartureTime(int stop); /** @return the difference between the scheduled and actual departure times at this stop. */ - public int getDepartureDelay(final int stop) { - return getDepartureTime(stop) - scheduledTripTimes.getScheduledDepartureTime(stop); - } - - public void setRecorded(int stop) { - setStopRealTimeStates(stop, StopRealTimeState.RECORDED); - } - - public void setCancelled(int stop) { - setStopRealTimeStates(stop, StopRealTimeState.CANCELLED); - } - - public void setNoData(int stop) { - setStopRealTimeStates(stop, StopRealTimeState.NO_DATA); - } - - public void setPredictionInaccurate(int stop) { - setStopRealTimeStates(stop, StopRealTimeState.INACCURATE_PREDICTIONS); - } - - public boolean isCancelledStop(int stop) { - return isStopRealTimeStates(stop, StopRealTimeState.CANCELLED); - } + int getDepartureDelay(int stop); - public boolean isRecordedStop(int stop) { - return isStopRealTimeStates(stop, StopRealTimeState.RECORDED); - } - - public boolean isNoDataStop(int stop) { - return isStopRealTimeStates(stop, StopRealTimeState.NO_DATA); - } - - public boolean isPredictionInaccurate(int stop) { - return isStopRealTimeStates(stop, StopRealTimeState.INACCURATE_PREDICTIONS); - } + /** + * Whether or not stopIndex is considered a GTFS timepoint. + */ + boolean isTimepoint(int stopIndex); - public void setOccupancyStatus(int stop, OccupancyStatus occupancyStatus) { - prepareForRealTimeUpdates(); - this.occupancyStatus[stop] = occupancyStatus; - } + /** The trips whose arrivals and departures are represented by this class */ + Trip getTrip(); /** - * This is only for API-purposes (does not affect routing). + * Return an integer which can be used to sort TripTimes in order of departure/arrivals. + *

+ * This sorted trip times is used to search for trips. OTP assume one trip do NOT pass another + * trip down the line. */ - public OccupancyStatus getOccupancyStatus(int stop) { - if (this.occupancyStatus == null) { - return OccupancyStatus.NO_DATA_AVAILABLE; - } - return this.occupancyStatus[stop]; + default int sortIndex() { + return getDepartureTime(0); } - public BookingInfo getDropOffBookingInfo(int stop) { - return scheduledTripTimes.getDropOffBookingInfo(stop); + /** Sort trips based on first departure time. */ + default Comparator compare() { + return Comparator.comparingInt(TripTimes::sortIndex); } - public BookingInfo getPickupBookingInfo(int stop) { - return scheduledTripTimes.getPickupBookingInfo(stop); + /** Sort trips based on first departure time. */ + default int compareTo(TripTimes other) { + return sortIndex() - other.sortIndex(); } + BookingInfo getDropOffBookingInfo(int stop); + + BookingInfo getPickupBookingInfo(int stop); + /** * Return {@code true} if the trip is unmodified, a scheduled trip from a published timetable. * Return {@code false} if the trip is an updated, cancelled, or otherwise modified one. This - * method differs from {@link #getRealTimeState()} in that it checks whether real-time - * information is actually available. + * method differs from {@link #getRealTimeState()} in that it checks whether real-time information + * is actually available. */ - public boolean isScheduled() { - return realTimeState == RealTimeState.SCHEDULED; - } + boolean isScheduled(); /** * Return {@code true} if canceled or soft-deleted */ - public boolean isCanceledOrDeleted() { - return isCanceled() || isDeleted(); - } + boolean isCanceledOrDeleted(); /** * Return {@code true} if canceled */ - public boolean isCanceled() { - return realTimeState == RealTimeState.CANCELED; - } + boolean isCanceled(); /** * Return true if trip is soft-deleted, and should not be visible to the user */ - public boolean isDeleted() { - return realTimeState == RealTimeState.DELETED; - } + boolean isDeleted(); - public RealTimeState getRealTimeState() { - return realTimeState; - } + RealTimeState getRealTimeState(); - public void setRealTimeState(final RealTimeState realTimeState) { - this.realTimeState = realTimeState; - } + boolean isCancelledStop(int stop); + + boolean isRecordedStop(int stop); + + boolean isNoDataStop(int stop); + + boolean isPredictionInaccurate(int stop); /** - * When creating a scheduled TripTimes or wrapping it in updates, we could potentially imply - * negative running or dwell times. We really don't want those being used in routing. This method - * checks that all internal times are increasing. Thus, this check should be used at the end of - * updating trip times, after any propagating or interpolating delay operations. - * - * @throws org.opentripplanner.transit.model.framework.DataValidationException on the first validation error found. + * @return the whole trip's headsign. Individual stops can have different headsigns. */ - public void validateNonIncreasingTimes() { - final int nStops = arrivalTimes.length; - int prevDep = -9_999_999; - for (int s = 0; s < nStops; s++) { - final int arr = getArrivalTime(s); - final int dep = getDepartureTime(s); - - if (dep < arr) { - throw new DataValidationException( - new TimetableValidationError(NEGATIVE_DWELL_TIME, s, getTrip()) - ); - } - if (prevDep > arr) { - throw new DataValidationException( - new TimetableValidationError(NEGATIVE_HOP_TIME, s, getTrip()) - ); - } - prevDep = dep; - } - } + I18NString getTripHeadsign(); /** - * Note: This method only applies for GTFS, not SIRI! - * This method interpolates the times for SKIPPED stops in between regular stops since GTFS-RT - * does not require arrival and departure times for these stops. This method ensures the internal - * time representations in OTP for SKIPPED stops are between the regular stop times immediately - * before and after the cancellation in GTFS-RT. This is to meet the OTP requirement that stop - * times should be increasing and to support the trip search flag `includeRealtimeCancellations`. - * Terminal stop cancellations can be handled by backward and forward propagations, and are - * outside the scope of this method. - * - * @return true if there is interpolated times, false if there is no interpolation. + * Both trip_headsign and stop_headsign (per stop on a particular trip) are optional GTFS fields. + * A trip may not have a headsign, in which case we should fall back on a Timetable or + * Pattern-level headsign. Such a string will be available when we give TripPatterns or + * StopPatterns unique human-readable route variant names, but a ScheduledTripTimes currently does + * not have a pointer to its enclosing timetable or pattern. */ - public boolean interpolateMissingTimes() { - boolean hasInterpolatedTimes = false; - final int numStops = getNumStops(); - boolean startInterpolate = false; - boolean hasPrevTimes = false; - int prevDeparture = 0; - int prevScheduledDeparture = 0; - int prevStopIndex = -1; - - // Loop through all stops - for (int s = 0; s < numStops; s++) { - final boolean isCancelledStop = isCancelledStop(s); - final int scheduledArrival = getScheduledArrivalTime(s); - final int scheduledDeparture = getScheduledDepartureTime(s); - final int arrival = getArrivalTime(s); - final int departure = getDepartureTime(s); - - if (!isCancelledStop && !startInterpolate) { - // Regular stop, could be used for interpolation for future cancellation, keep track. - prevDeparture = departure; - prevScheduledDeparture = scheduledDeparture; - prevStopIndex = s; - hasPrevTimes = true; - } else if (isCancelledStop && !startInterpolate && hasPrevTimes) { - // First cancelled stop, keep track. - startInterpolate = true; - } else if (!isCancelledStop && startInterpolate && hasPrevTimes) { - // First regular stop after cancelled stops, interpolate. - // Calculate necessary info for interpolation. - int numCancelledStops = s - prevStopIndex - 1; - int scheduledTravelTime = scheduledArrival - prevScheduledDeparture; - int realTimeTravelTime = arrival - prevDeparture; - double travelTimeRatio = (double) realTimeTravelTime / scheduledTravelTime; - - // Fill out interpolated time for cancelled stops, using the calculated ratio. - for (int cancelledIndex = prevStopIndex + 1; cancelledIndex < s; cancelledIndex++) { - final int scheduledArrivalCancelled = getScheduledArrivalTime(cancelledIndex); - final int scheduledDepartureCancelled = getScheduledDepartureTime(cancelledIndex); - - // Interpolate - int scheduledArrivalDiff = scheduledArrivalCancelled - prevScheduledDeparture; - double interpolatedArrival = prevDeparture + travelTimeRatio * scheduledArrivalDiff; - int scheduledDepartureDiff = scheduledDepartureCancelled - prevScheduledDeparture; - double interpolatedDeparture = prevDeparture + travelTimeRatio * scheduledDepartureDiff; - - // Set Interpolated Times - updateArrivalTime(cancelledIndex, (int) interpolatedArrival); - updateDepartureTime(cancelledIndex, (int) interpolatedDeparture); - } - - // Set tracking variables - prevDeparture = departure; - prevScheduledDeparture = scheduledDeparture; - prevStopIndex = s; - startInterpolate = false; - hasPrevTimes = true; - - // Set return variable - hasInterpolatedTimes = true; - } - } - - return hasInterpolatedTimes; - } - - /** Cancel this entire trip */ - public void cancelTrip() { - realTimeState = RealTimeState.CANCELED; - } - - /** Soft delete the entire trip */ - public void deleteTrip() { - realTimeState = RealTimeState.DELETED; - } - - public void updateDepartureTime(final int stop, final int time) { - prepareForRealTimeUpdates(); - departureTimes[stop] = time; - } - - public void updateDepartureDelay(final int stop, final int delay) { - prepareForRealTimeUpdates(); - departureTimes[stop] = scheduledTripTimes.getScheduledDepartureTime(stop) + delay; - } - - public void updateArrivalTime(final int stop, final int time) { - prepareForRealTimeUpdates(); - arrivalTimes[stop] = time; - } - - public void updateArrivalDelay(final int stop, final int delay) { - prepareForRealTimeUpdates(); - arrivalTimes[stop] = scheduledTripTimes.getScheduledArrivalTime(stop) + delay; - } - @Nullable - public Accessibility getWheelchairAccessibility() { - // No need to fall back to scheduled state, since it is copied over in the constructor - return wheelchairAccessibility; - } - - public void updateWheelchairAccessibility(Accessibility wheelchairAccessibility) { - this.wheelchairAccessibility = wheelchairAccessibility; - } - - public int getNumStops() { - return scheduledTripTimes.getNumStops(); - } - - /** Sort trips based on first departure time. */ - @Override - public int compareTo(final TripTimes other) { - return this.getDepartureTime(0) - other.getDepartureTime(0); - } + I18NString getHeadsign(int stop); /** - * Returns a time-shifted copy of this TripTimes in which the vehicle passes the given stop index - * (not stop sequence number) at the given time. We only have a mechanism to shift the scheduled - * stoptimes, not the real-time stoptimes. Therefore, this only works on trips without updates for - * now (frequency trips don't have updates). + * Return list of via names per particular stop. This field provides info about intermediate stops + * between current stop and final trip destination. Mapped from NeTEx DestinationDisplay.vias. No + * GTFS mapping at the moment. + * + * @return Empty list if there are no vias registered for a stop. */ - public TripTimes timeShift(final int stop, final int time, final boolean depart) { - if (arrivalTimes != null || departureTimes != null) { - return null; - } - // Adjust 0-based times to match desired stoptime. - final int shift = time - (depart ? getDepartureTime(stop) : getArrivalTime(stop)); - - return new TripTimes( - this, - scheduledTripTimes.copyOfNoDuplication().plusTimeShift(shift).build() - ); - } + List getHeadsignVias(int stop); + + int getNumStops(); + + Accessibility getWheelchairAccessibility(); /** - * Time-shift all times on this trip. This is used when updating the time zone for the trip. + * This is only for API-purposes (does not affect routing). */ - public TripTimes adjustTimesToGraphTimeZone(Duration shiftDelta) { - return new TripTimes( - this, - scheduledTripTimes.copyOfNoDuplication().plusTimeShift((int) shiftDelta.toSeconds()).build() - ); - } + OccupancyStatus getOccupancyStatus(int stop); /** * Returns the GTFS sequence number of the given 0-based stop position. - * + *

* These are the GTFS stop sequence numbers, which show the order in which the vehicle visits the - * stops. Despite the fact that the StopPattern or TripPattern enclosing this TripTimes provides - * an ordered list of Stops, the original stop sequence numbers may still be needed for matching + * stops. Despite the fact that the StopPattern or TripPattern enclosing this class provides an + * ordered list of Stops, the original stop sequence numbers may still be needed for matching * with GTFS-RT update messages. Unfortunately, each individual trip can have totally different * sequence numbers for the same stops, so we need to store them at the individual trip level. An * effort is made to re-use the sequence number arrays when they are the same across different * trips in the same pattern. */ - public int gtfsSequenceOfStopIndex(final int stop) { - return scheduledTripTimes.gtfsSequenceOfStopIndex(stop); - } + int gtfsSequenceOfStopIndex(int stop); /** * Returns the 0-based stop index of the given GTFS sequence number. */ - public OptionalInt stopIndexOfGtfsSequence(int stopSequence) { - return scheduledTripTimes.stopIndexOfGtfsSequence(stopSequence); - } - - /** - * Whether or not stopIndex is considered a GTFS timepoint. - */ - public boolean isTimepoint(final int stopIndex) { - return scheduledTripTimes.isTimepoint(stopIndex); - } - - /** The code for the service on which this trip runs. For departure search optimizations. */ - public int getServiceCode() { - return scheduledTripTimes.getServiceCode(); - } - - public void setServiceCode(int serviceCode) { - this.scheduledTripTimes = - scheduledTripTimes.copyOfNoDuplication().withServiceCode(serviceCode).build(); - } - - /** The trips whose arrivals and departures are represented by this class */ - public Trip getTrip() { - return scheduledTripTimes.getTrip(); - } - - /** - * Adjusts arrival time for the stop at the firstUpdatedIndex if no update was given for it and - * arrival/departure times for the stops before that stop. Returns {@code true} if times have been - * adjusted. - */ - public boolean adjustTimesBeforeAlways(int firstUpdatedIndex) { - boolean hasAdjustedTimes = false; - int delay = getDepartureDelay(firstUpdatedIndex); - if (getArrivalDelay(firstUpdatedIndex) == 0) { - updateArrivalDelay(firstUpdatedIndex, delay); - hasAdjustedTimes = true; - } - delay = getArrivalDelay(firstUpdatedIndex); - if (delay == 0) { - return false; - } - for (int i = firstUpdatedIndex - 1; i >= 0; i--) { - hasAdjustedTimes = true; - updateDepartureDelay(i, delay); - updateArrivalDelay(i, delay); - } - return hasAdjustedTimes; - } - - /** - * Adjusts arrival and departure times for the stops before the stop at firstUpdatedIndex when - * required to ensure that the times are increasing. Can set NO_DATA flag on the updated previous - * stops. Returns {@code true} if times have been adjusted. - */ - public boolean adjustTimesBeforeWhenRequired(int firstUpdatedIndex, boolean setNoData) { - if (getArrivalTime(firstUpdatedIndex) > getDepartureTime(firstUpdatedIndex)) { - // The given trip update has arrival time after departure time for the first updated stop. - // This method doesn't try to fix issues in the given data, only for the missing part - return false; - } - int nextStopArrivalTime = getArrivalTime(firstUpdatedIndex); - int delay = getArrivalDelay(firstUpdatedIndex); - boolean hasAdjustedTimes = false; - boolean adjustTimes = true; - for (int i = firstUpdatedIndex - 1; i >= 0; i--) { - if (setNoData && !isCancelledStop(i)) { - setNoData(i); - } - if (adjustTimes) { - if (getDepartureTime(i) < nextStopArrivalTime) { - adjustTimes = false; - continue; - } else { - hasAdjustedTimes = true; - updateDepartureDelay(i, delay); - } - if (getArrivalTime(i) < getDepartureTime(i)) { - adjustTimes = false; - } else { - updateArrivalDelay(i, delay); - nextStopArrivalTime = getArrivalTime(i); - } - } - } - return hasAdjustedTimes; - } - - /* private member methods */ - - private void setStopRealTimeStates(int stop, StopRealTimeState state) { - prepareForRealTimeUpdates(); - this.stopRealTimeStates[stop] = state; - } - - /** - * The real-time states for a given stops. If the state is DEFAULT for a stop, - * the {@link #getRealTimeState()} should determine the realtime state of the stop. - *

- * This is only for API-purposes (does not affect routing). - */ - private boolean isStopRealTimeStates(int stop, StopRealTimeState state) { - return stopRealTimeStates != null && stopRealTimeStates[stop] == state; - } - - public void setHeadsign(int index, I18NString headsign) { - if (headsigns == null) { - if (headsign.equals(getTrip().getHeadsign())) { - return; - } - this.headsigns = scheduledTripTimes.copyHeadsigns(() -> new I18NString[getNumStops()]); - this.headsigns[index] = headsign; - return; - } - - prepareForRealTimeUpdates(); - headsigns[index] = headsign; - } - - private static int getOrElse(int index, int[] array, IntUnaryOperator defaultValue) { - return array != null ? array[index] : defaultValue.applyAsInt(index); - } + OptionalInt stopIndexOfGtfsSequence(int stopSequence); /** - * If they don't already exist, create arrays for updated arrival and departure times that are - * just time-shifted copies of the zero-based scheduled departure times. - *

- * Also sets the realtime state to UPDATED. + * Time-shift all times on this trip. This is used when updating the time zone for the trip. */ - private void prepareForRealTimeUpdates() { - if (arrivalTimes == null) { - this.arrivalTimes = scheduledTripTimes.copyArrivalTimes(); - this.departureTimes = scheduledTripTimes.copyDepartureTimes(); - // Update the real-time state - this.realTimeState = RealTimeState.UPDATED; - this.stopRealTimeStates = new StopRealTimeState[arrivalTimes.length]; - Arrays.fill(stopRealTimeStates, StopRealTimeState.DEFAULT); - this.headsigns = scheduledTripTimes.copyHeadsigns(() -> null); - this.occupancyStatus = new OccupancyStatus[arrivalTimes.length]; - Arrays.fill(occupancyStatus, OccupancyStatus.NO_DATA_AVAILABLE); - // skip immutable types: scheduledTripTimes & wheelchairAccessibility - } - } + TripTimes adjustTimesToGraphTimeZone(Duration shiftDelta); } diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimesFactory.java b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimesFactory.java index c868fda6582..611974904f0 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimesFactory.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimesFactory.java @@ -18,11 +18,13 @@ public class TripTimesFactory { * non-interpolated stoptimes should already be marked at timepoints by a previous filtering * step. */ - public static TripTimes tripTimes( + public static RealTimeTripTimes tripTimes( Trip trip, List stopTimes, Deduplicator deduplicator ) { - return new TripTimes(StopTimeToScheduledTripTimesMapper.map(trip, stopTimes, deduplicator)); + return new RealTimeTripTimes( + StopTimeToScheduledTripTimesMapper.map(trip, stopTimes, deduplicator) + ); } } diff --git a/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java b/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java index 8e782ebe934..d8954298123 100644 --- a/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java +++ b/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java @@ -54,8 +54,8 @@ import org.opentripplanner.transit.model.organization.Agency; import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.model.timetable.RealTimeState; +import org.opentripplanner.transit.model.timetable.RealTimeTripTimes; import org.opentripplanner.transit.model.timetable.Trip; -import org.opentripplanner.transit.model.timetable.TripTimes; import org.opentripplanner.transit.model.timetable.TripTimesFactory; import org.opentripplanner.transit.service.DefaultTransitService; import org.opentripplanner.transit.service.TransitEditorService; @@ -892,7 +892,11 @@ private Result addTripToGraphAndBuffer( ); // Create new trip times - final TripTimes newTripTimes = TripTimesFactory.tripTimes(trip, stopTimes, deduplicator); + final RealTimeTripTimes newTripTimes = TripTimesFactory.tripTimes( + trip, + stopTimes, + deduplicator + ); // Update all times to mark trip times as realtime // TODO: should we incorporate the delay field if present? @@ -952,7 +956,9 @@ private boolean cancelScheduledTrip( if (tripIndex == -1) { debug(tripId, "Could not cancel scheduled trip because it's not in the timetable"); } else { - final TripTimes newTripTimes = timetable.getTripTimes(tripIndex).copyOfScheduledTimes(); + final RealTimeTripTimes newTripTimes = timetable + .getTripTimes(tripIndex) + .copyScheduledTimes(); switch (cancelationType) { case CANCEL -> newTripTimes.cancelTrip(); case DELETE -> newTripTimes.deleteTrip(); @@ -992,7 +998,9 @@ private boolean cancelPreviouslyAddedTrip( if (tripIndex == -1) { debug(tripId, "Could not cancel previously added trip on {}", serviceDate); } else { - final TripTimes newTripTimes = timetable.getTripTimes(tripIndex).copyOfScheduledTimes(); + final RealTimeTripTimes newTripTimes = timetable + .getTripTimes(tripIndex) + .copyScheduledTimes(); switch (cancelationType) { case CANCEL -> newTripTimes.cancelTrip(); case DELETE -> newTripTimes.deleteTrip(); diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java index e59a5360808..5dd1175cd1c 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java @@ -96,13 +96,11 @@ private static TripPatternForDates findTripPatternForDate( } private TripTimes createTripTimesForTest() { - return TripTimes.of( - ScheduledTripTimes - .of() - .withTrip(TransitModelForTest.trip("Test").build()) - .withDepartureTimes("00:00 02:00") - .build() - ); + return ScheduledTripTimes + .of() + .withTrip(TransitModelForTest.trip("Test").build()) + .withDepartureTimes("00:00 02:00") + .build(); } /** diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RouteRequestTransitDataProviderFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RouteRequestTransitDataProviderFilterTest.java index 86d5f9404ed..a45c3f2b7a6 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RouteRequestTransitDataProviderFilterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RouteRequestTransitDataProviderFilterTest.java @@ -40,6 +40,7 @@ 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.RealTimeTripTimes; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.TripAlteration; import org.opentripplanner.transit.model.timetable.TripBuilder; @@ -513,7 +514,7 @@ void keepAccessibleTrip() { @Test void keepRealTimeAccessibleTrip() { - TripTimes realTimeWheelchairAccessibleTrip = createTestTripTimes( + RealTimeTripTimes realTimeWheelchairAccessibleTrip = createTestTripTimes( TRIP_ID, ROUTE, BikeAccess.NOT_ALLOWED, @@ -616,7 +617,7 @@ void includeRealtimeCancellationsTest() { TripAlteration.PLANNED ); - TripTimes tripTimesWithCancellation = createTestTripTimes( + RealTimeTripTimes tripTimesWithCancellation = createTestTripTimes( TRIP_ID, ROUTE, BikeAccess.NOT_ALLOWED, @@ -860,7 +861,7 @@ private List combinedFilterForModesAndBannedAgencies( ); } - private TripTimes createTestTripTimes( + private RealTimeTripTimes createTestTripTimes( FeedScopedId tripId, Route route, BikeAccess bikeAccess, diff --git a/src/test/java/org/opentripplanner/routing/stoptimes/StopTimesHelperTest.java b/src/test/java/org/opentripplanner/routing/stoptimes/StopTimesHelperTest.java index 54194696e71..1432c68fd49 100644 --- a/src/test/java/org/opentripplanner/routing/stoptimes/StopTimesHelperTest.java +++ b/src/test/java/org/opentripplanner/routing/stoptimes/StopTimesHelperTest.java @@ -38,7 +38,9 @@ public static void setUp() throws Exception { transitService.getTripForId(new FeedScopedId(feedId, "5.1")) ); var tt = transitService.getTimetableForTripPattern(pattern, LocalDate.now()); - tt.getTripTimes(0).cancelTrip(); + var newTripTimes = tt.getTripTimes(0).copyScheduledTimes(); + newTripTimes.cancelTrip(); + tt.setTripTimes(0, newTripTimes); } /** diff --git a/src/test/java/org/opentripplanner/transit/model/timetable/TripTimesTest.java b/src/test/java/org/opentripplanner/transit/model/timetable/RealTimeTripTimesTest.java similarity index 93% rename from src/test/java/org/opentripplanner/transit/model/timetable/TripTimesTest.java rename to src/test/java/org/opentripplanner/transit/model/timetable/RealTimeTripTimesTest.java index ee73bd6856c..5b3a8f76052 100644 --- a/src/test/java/org/opentripplanner/transit/model/timetable/TripTimesTest.java +++ b/src/test/java/org/opentripplanner/transit/model/timetable/RealTimeTripTimesTest.java @@ -22,7 +22,7 @@ import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.site.RegularStop; -class TripTimesTest { +class RealTimeTripTimesTest { private static final TransitModelForTest TEST_MODEL = TransitModelForTest.of(); @@ -134,7 +134,7 @@ void shouldHandleDifferingTripAndStopHeadSignScenario() { @Test public void testStopUpdate() { - TripTimes updatedTripTimesA = createInitialTripTimes().copyOfScheduledTimes(); + RealTimeTripTimes updatedTripTimesA = createInitialTripTimes().copyScheduledTimes(); updatedTripTimesA.updateArrivalTime(3, 190); updatedTripTimesA.updateDepartureTime(3, 190); @@ -149,7 +149,7 @@ public void testStopUpdate() { @Test public void testPassedUpdate() { - TripTimes updatedTripTimesA = createInitialTripTimes().copyOfScheduledTimes(); + RealTimeTripTimes updatedTripTimesA = createInitialTripTimes().copyScheduledTimes(); updatedTripTimesA.updateDepartureTime(0, 30); @@ -167,7 +167,7 @@ public void testPassedUpdate() { */ @Test public void testNegativeHopTimeWithStopCancellations() { - TripTimes updatedTripTimes = createInitialTripTimes(); + var updatedTripTimes = createInitialTripTimes().copyScheduledTimes(); updatedTripTimes.updateDepartureTime(5, 421); updatedTripTimes.updateArrivalTime(6, 481); @@ -194,7 +194,7 @@ public void testNegativeHopTimeWithStopCancellations() { */ @Test public void testPositiveHopTimeWithStopCancellationsLate() { - TripTimes updatedTripTimes = createInitialTripTimes(); + var updatedTripTimes = createInitialTripTimes().copyScheduledTimes(); updatedTripTimes.updateDepartureTime(5, 400); updatedTripTimes.updateArrivalTime(6, 460); @@ -225,7 +225,7 @@ public void testPositiveHopTimeWithStopCancellationsLate() { */ @Test public void testPositiveHopTimeWithStopCancellationsEarly() { - TripTimes updatedTripTimes = createInitialTripTimes(); + var updatedTripTimes = createInitialTripTimes().copyScheduledTimes(); updatedTripTimes.updateDepartureTime(5, 300); updatedTripTimes.setCancelled(6); @@ -254,7 +254,7 @@ public void testPositiveHopTimeWithStopCancellationsEarly() { */ @Test public void testPositiveHopTimeWithTerminalCancellation() { - TripTimes updatedTripTimes = createInitialTripTimes(); + var updatedTripTimes = createInitialTripTimes().copyScheduledTimes(); updatedTripTimes.setCancelled(0); updatedTripTimes.setCancelled(1); @@ -288,7 +288,7 @@ public void testPositiveHopTimeWithTerminalCancellation() { */ @Test public void testInterpolationWithTerminalCancellation() { - TripTimes updatedTripTimes = createInitialTripTimes(); + var updatedTripTimes = createInitialTripTimes().copyScheduledTimes(); updatedTripTimes.setCancelled(6); updatedTripTimes.setCancelled(7); @@ -307,7 +307,7 @@ public void testInterpolationWithTerminalCancellation() { */ @Test public void testInterpolationWithMultipleStopCancellations() { - TripTimes updatedTripTimes = createInitialTripTimes(); + var updatedTripTimes = createInitialTripTimes().copyScheduledTimes(); updatedTripTimes.setCancelled(1); updatedTripTimes.setCancelled(2); @@ -341,7 +341,7 @@ public void testInterpolationWithMultipleStopCancellations() { */ @Test public void testInterpolationWithMultipleStopCancellations2() { - TripTimes updatedTripTimes = createInitialTripTimes(); + var updatedTripTimes = createInitialTripTimes().copyScheduledTimes(); updatedTripTimes.setCancelled(1); updatedTripTimes.setCancelled(2); @@ -368,7 +368,7 @@ public void testInterpolationWithMultipleStopCancellations2() { @Test public void testNonIncreasingUpdateCrossingMidnight() { - TripTimes updatedTripTimesA = createInitialTripTimes().copyOfScheduledTimes(); + RealTimeTripTimes updatedTripTimesA = createInitialTripTimes().copyScheduledTimes(); updatedTripTimesA.updateArrivalTime(0, -300); //"Yesterday" updatedTripTimesA.updateDepartureTime(0, 50); @@ -378,7 +378,7 @@ public void testNonIncreasingUpdateCrossingMidnight() { @Test public void testDelay() { - TripTimes updatedTripTimesA = createInitialTripTimes().copyOfScheduledTimes(); + RealTimeTripTimes updatedTripTimesA = createInitialTripTimes().copyScheduledTimes(); updatedTripTimesA.updateDepartureDelay(0, 10); updatedTripTimesA.updateArrivalDelay(6, 13); @@ -388,14 +388,14 @@ public void testDelay() { @Test public void testCancel() { - TripTimes updatedTripTimesA = createInitialTripTimes().copyOfScheduledTimes(); + RealTimeTripTimes updatedTripTimesA = createInitialTripTimes().copyScheduledTimes(); updatedTripTimesA.cancelTrip(); assertEquals(RealTimeState.CANCELED, updatedTripTimesA.getRealTimeState()); } @Test public void testNoData() { - TripTimes updatedTripTimesA = createInitialTripTimes().copyOfScheduledTimes(); + RealTimeTripTimes updatedTripTimesA = createInitialTripTimes().copyScheduledTimes(); updatedTripTimesA.setNoData(1); assertFalse(updatedTripTimesA.isNoDataStop(0)); assertTrue(updatedTripTimesA.isNoDataStop(1)); @@ -429,7 +429,7 @@ void unknownGtfsSequence() { public void validateNegativeDwellTime() { var expMsg = "NEGATIVE_DWELL_TIME for stop position 3 in trip Trip{F:testTripId RRtestTripId}."; var tt = createInitialTripTimes(); - var updatedTt = tt.copyOfScheduledTimes(); + var updatedTt = tt.copyScheduledTimes(); updatedTt.updateArrivalTime(3, 69); updatedTt.updateDepartureTime(3, 68); @@ -447,7 +447,7 @@ public void validateNegativeDwellTime() { public void validateNegativeHopTime() { var expMsg = "NEGATIVE_HOP_TIME for stop position 2 in trip Trip{F:testTripId RRtestTripId}."; var tt = createInitialTripTimes(); - var updatedTt = tt.copyOfScheduledTimes(); + var updatedTt = tt.copyScheduledTimes(); updatedTt.updateDepartureTime(1, 100); updatedTt.updateArrivalTime(2, 99); From ed2e8585da4e6ade350b5396bfb5fb1250d3addf Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Wed, 22 Nov 2023 17:07:32 +0000 Subject: [PATCH 38/85] Add changelog entry for #4999 [ci skip] --- docs/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Changelog.md b/docs/Changelog.md index e9af1cb2090..bccd0c504d5 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -50,6 +50,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Cleanup trip times - Part A [#5437](https://github.com/opentripplanner/OpenTripPlanner/pull/5437) - Transfer cost limit [#5516](https://github.com/opentripplanner/OpenTripPlanner/pull/5516) - Fix missed trip when arrive-by search-window is off by one minute [#5520](https://github.com/opentripplanner/OpenTripPlanner/pull/5520) +- Transit group priority - Part 1 [#4999](https://github.com/opentripplanner/OpenTripPlanner/pull/4999) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.4.0 (2023-09-13) From 092a12f957f961a6b37f034263f11c1abbecf327 Mon Sep 17 00:00:00 2001 From: OTP Serialization Version Bot Date: Wed, 22 Nov 2023 17:07:51 +0000 Subject: [PATCH 39/85] Bump serialization version id for #4999 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2e74d9c6574..1632ab7fea7 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ - 130 + 131 30.0 2.48.1 From 6649bdd7dab4fc856e5609b30f46ef4787da43e2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 23 Nov 2023 10:06:05 +0000 Subject: [PATCH 40/85] fix(deps): update dependency org.apache.httpcomponents.client5:httpclient5 to v5.2.2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1632ab7fea7..cd13c7ff0f7 100644 --- a/pom.xml +++ b/pom.xml @@ -898,7 +898,7 @@ org.apache.httpcomponents.client5 httpclient5 - 5.2.1 + 5.2.2 commons-cli From 5cc33549156571775bf3e607c6feea5094de250e Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 22 Nov 2023 11:28:20 +0100 Subject: [PATCH 41/85] Remove BusRouteStreetMatcher --- docs/BuildConfiguration.md | 1 - .../graph_builder/GraphBuilder.java | 3 - .../module/configure/GraphBuilderFactory.java | 2 - .../module/map/BusRouteStreetMatcher.java | 99 ------- .../module/map/EndMatchState.java | 21 -- .../module/map/LinearIterator.java | 229 --------------- .../graph_builder/module/map/MatchState.java | 119 -------- .../module/map/MidblockMatchState.java | 265 ------------------ .../module/map/StreetMatcher.java | 168 ----------- .../standalone/config/BuildConfig.java | 10 - .../transit/model/network/TripPattern.java | 9 - .../module/map/StreetMatcherTest.java | 157 ----------- 12 files changed, 1083 deletions(-) delete mode 100644 src/main/java/org/opentripplanner/graph_builder/module/map/BusRouteStreetMatcher.java delete mode 100644 src/main/java/org/opentripplanner/graph_builder/module/map/EndMatchState.java delete mode 100644 src/main/java/org/opentripplanner/graph_builder/module/map/LinearIterator.java delete mode 100644 src/main/java/org/opentripplanner/graph_builder/module/map/MatchState.java delete mode 100644 src/main/java/org/opentripplanner/graph_builder/module/map/MidblockMatchState.java delete mode 100644 src/main/java/org/opentripplanner/graph_builder/module/map/StreetMatcher.java delete mode 100644 src/test/java/org/opentripplanner/graph_builder/module/map/StreetMatcherTest.java diff --git a/docs/BuildConfiguration.md b/docs/BuildConfiguration.md index b32f782aa32..05da4cf4d0a 100644 --- a/docs/BuildConfiguration.md +++ b/docs/BuildConfiguration.md @@ -28,7 +28,6 @@ Sections follow that describe particular settings in more depth. | [graph](#graph) | `uri` | URI to the graph object file for reading and writing. | *Optional* | | 2.0 | | [gsCredentials](#gsCredentials) | `string` | Local file system path to Google Cloud Platform service accounts credentials file. | *Optional* | | 2.0 | | [includeEllipsoidToGeoidDifference](#includeEllipsoidToGeoidDifference) | `boolean` | Include the Ellipsoid to Geoid difference in the calculations of every point along every StreetWithElevationEdge. | *Optional* | `false` | 2.0 | -| matchBusRoutesToStreets | `boolean` | Based on GTFS shape data, guess which OSM streets each bus runs on to improve stop linking. | *Optional* | `false` | 1.5 | | maxAreaNodes | `integer` | Visibility calculations for an area will not be done if there are more nodes than this limit. | *Optional* | `150` | 2.1 | | [maxDataImportIssuesPerFile](#maxDataImportIssuesPerFile) | `integer` | When to split the import report. | *Optional* | `1000` | 2.0 | | maxElevationPropagationMeters | `integer` | The maximum distance to propagate elevation to vertices which have no elevation. | *Optional* | `2000` | 1.5 | diff --git a/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java b/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java index e6aba29278b..3a2bb777f04 100644 --- a/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java +++ b/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java @@ -121,9 +121,6 @@ public static GraphBuilder create( } if (hasTransitData && (hasOsm || graphBuilder.graph.hasStreets)) { - if (config.matchBusRoutesToStreets) { - graphBuilder.addModule(factory.busRouteStreetMatcher()); - } graphBuilder.addModule(factory.osmBoardingLocationsModule()); } diff --git a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderFactory.java b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderFactory.java index 1f50c7a6327..9fd99d35ea0 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderFactory.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderFactory.java @@ -24,7 +24,6 @@ import org.opentripplanner.graph_builder.module.TripPatternNamer; import org.opentripplanner.graph_builder.module.geometry.CalculateWorldEnvelopeModule; import org.opentripplanner.graph_builder.module.islandpruning.PruneIslands; -import org.opentripplanner.graph_builder.module.map.BusRouteStreetMatcher; import org.opentripplanner.graph_builder.module.ned.ElevationModule; import org.opentripplanner.graph_builder.module.osm.OsmModule; import org.opentripplanner.gtfs.graphbuilder.GtfsModule; @@ -45,7 +44,6 @@ public interface GraphBuilderFactory { NetexModule netexModule(); TimeZoneAdjusterModule timeZoneAdjusterModule(); TripPatternNamer tripPatternNamer(); - BusRouteStreetMatcher busRouteStreetMatcher(); OsmBoardingLocationsModule osmBoardingLocationsModule(); StreetLinkerModule streetLinkerModule(); PruneIslands pruneIslands(); diff --git a/src/main/java/org/opentripplanner/graph_builder/module/map/BusRouteStreetMatcher.java b/src/main/java/org/opentripplanner/graph_builder/module/map/BusRouteStreetMatcher.java deleted file mode 100644 index 8fc77bb7662..00000000000 --- a/src/main/java/org/opentripplanner/graph_builder/module/map/BusRouteStreetMatcher.java +++ /dev/null @@ -1,99 +0,0 @@ -package org.opentripplanner.graph_builder.module.map; - -import jakarta.inject.Inject; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import org.locationtech.jts.geom.Coordinate; -import org.locationtech.jts.geom.LineString; -import org.opentripplanner.framework.geometry.GeometryUtils; -import org.opentripplanner.framework.logging.ProgressTracker; -import org.opentripplanner.graph_builder.model.GraphBuilderModule; -import org.opentripplanner.routing.graph.Graph; -import org.opentripplanner.street.model.edge.Edge; -import org.opentripplanner.transit.model.network.Route; -import org.opentripplanner.transit.model.network.TripPattern; -import org.opentripplanner.transit.service.TransitModel; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Uses the shapes from GTFS to determine which streets buses drive on. This is used to improve the - * quality of the shapes shown for the user. - *

- * GTFS provides a mapping from trips→shapes. This module provides a mapping from stops→trips and - * shapes→edges. Then transitively we get a mapping from stop→edges. - */ -public class BusRouteStreetMatcher implements GraphBuilderModule { - - private static final Logger log = LoggerFactory.getLogger(BusRouteStreetMatcher.class); - - private final Graph graph; - private final TransitModel transitModel; - - @Inject - public BusRouteStreetMatcher(Graph graph, TransitModel transitModel) { - this.graph = graph; - this.transitModel = transitModel; - } - - public void buildGraph() { - // Mapbuilder needs transit index - transitModel.index(); - graph.index(transitModel.getStopModel()); - - StreetMatcher matcher = new StreetMatcher(graph); - log.info("Finding corresponding street edges for trip patterns..."); - // Why do we need to iterate over the routes? Why not just patterns? - Collection allRoutes = transitModel.getTransitModelIndex().getAllRoutes(); - - // Track progress - ProgressTracker progress = ProgressTracker.track( - "Match route to street edges", - 10, - allRoutes.size() - ); - log.info(progress.startMessage()); - - for (Route route : allRoutes) { - for (TripPattern pattern : transitModel - .getTransitModelIndex() - .getPatternsForRoute() - .get(route)) { - if (pattern.getMode().onStreet()) { - /* we can only match geometry to streets on bus routes */ - log.debug("Matching {}", pattern); - //If there are no shapes in GTFS pattern geometry is generated - //generated geometry is useless for street matching - //that is why pattern.geometry is null in that case - if (pattern.getGeometry() == null) { - continue; - } - - for (int i = 0; i < pattern.numHopGeometries(); i++) { - LineString hopGeometry = pattern.getHopGeometry(i); - - List edges = matcher.match(hopGeometry); - if (edges == null || edges.isEmpty()) { - log.warn("Could not match to street network: {}", pattern); - continue; - } - List coordinates = new ArrayList<>(); - for (Edge e : edges) { - coordinates.addAll(Arrays.asList(e.getGeometry().getCoordinates())); - } - Coordinate[] coordinateArray = new Coordinate[coordinates.size()]; - LineString ls = GeometryUtils - .getGeometryFactory() - .createLineString(coordinates.toArray(coordinateArray)); - // Replace the hop's geometry from GTFS with that of the equivalent OSM edges. - pattern.setHopGeometry(i, ls); - } - } - } - progress.step(log::info); - } - log.info(progress.completeMessage()); - } -} diff --git a/src/main/java/org/opentripplanner/graph_builder/module/map/EndMatchState.java b/src/main/java/org/opentripplanner/graph_builder/module/map/EndMatchState.java deleted file mode 100644 index ccb90cf3b00..00000000000 --- a/src/main/java/org/opentripplanner/graph_builder/module/map/EndMatchState.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.opentripplanner.graph_builder.module.map; - -import java.util.List; - -/** - * The end of a route's geometry, meaning that the search can quit - * - * @author novalis - */ -public class EndMatchState extends MatchState { - - public EndMatchState(MatchState parent, double error, double distance) { - super(parent, null, distance); - this.currentError = error; - } - - @Override - public List getNextStates() { - return null; - } -} diff --git a/src/main/java/org/opentripplanner/graph_builder/module/map/LinearIterator.java b/src/main/java/org/opentripplanner/graph_builder/module/map/LinearIterator.java deleted file mode 100644 index 562e65e4757..00000000000 --- a/src/main/java/org/opentripplanner/graph_builder/module/map/LinearIterator.java +++ /dev/null @@ -1,229 +0,0 @@ -package org.opentripplanner.graph_builder.module.map; - -import java.util.Iterator; -import org.locationtech.jts.geom.Coordinate; -import org.locationtech.jts.geom.Geometry; -import org.locationtech.jts.geom.LineString; -import org.locationtech.jts.geom.Lineal; -import org.locationtech.jts.linearref.LinearLocation; - -/** - * I copied this class from JTS but made a few changes. - *

- * The JTS version of this class has several design decisions that don't work for me. In particular, - * hasNext() in the original should be "isValid", and if we start mid-segment, we should continue at - * the end of this segment rather than the end of the next segment. - */ -public class LinearIterator implements Iterable { - - private final Geometry linearGeom; - - private final int numLines; - - /** - * Invariant: currentLine <> null if the iterator is pointing at a valid coordinate - * - * @throws IllegalArgumentException if linearGeom is not lineal - */ - private LineString currentLine; - - private int componentIndex = 0; - - private int vertexIndex = 0; - - private double segmentFraction; - - /** - * Creates an iterator initialized to the start of a linear {@link Geometry} - * - * @param linear the linear geometry to iterate over - * @throws IllegalArgumentException if linearGeom is not lineal - */ - public LinearIterator(Geometry linear) { - this(linear, 0, 0); - } - - /** - * Creates an iterator starting at a {@link LinearLocation} on a linear {@link Geometry} - * - * @param linear the linear geometry to iterate over - * @param start the location to start at - * @throws IllegalArgumentException if linearGeom is not lineal - */ - public LinearIterator(Geometry linear, LinearLocation start) { - this(linear, start.getComponentIndex(), start.getSegmentIndex()); - this.segmentFraction = start.getSegmentFraction(); - } - - /** - * Creates an iterator starting at a specified component and vertex in a linear {@link Geometry} - * - * @param linearGeom the linear geometry to iterate over - * @param componentIndex the component to start at - * @param vertexIndex the vertex to start at - * @throws IllegalArgumentException if linearGeom is not lineal - */ - public LinearIterator(Geometry linearGeom, int componentIndex, int vertexIndex) { - if (!(linearGeom instanceof Lineal)) throw new IllegalArgumentException( - "Lineal geometry is required" - ); - this.linearGeom = linearGeom; - numLines = linearGeom.getNumGeometries(); - this.componentIndex = componentIndex; - this.vertexIndex = vertexIndex; - loadCurrentLine(); - } - - public static LinearLocation getEndLocation(Geometry linear) { - //the version in LinearLocation is broken - - int lastComponentIndex = linear.getNumGeometries() - 1; - LineString lastLine = (LineString) linear.getGeometryN(lastComponentIndex); - int lastSegmentIndex = lastLine.getNumPoints() - 1; - return new LinearLocation(lastComponentIndex, lastSegmentIndex, 0.0); - } - - /** - * Tests whether there are any vertices left to iterator over. - * - * @return true if there are more vertices to scan - */ - public boolean hasNext() { - if (componentIndex >= numLines) { - return false; - } - if (componentIndex == numLines - 1 && vertexIndex >= currentLine.getNumPoints() - 1) { - return false; - } - return true; - } - - public boolean isValidIndex() { - if (componentIndex >= numLines) { - return false; - } - if (componentIndex == numLines - 1 && vertexIndex >= currentLine.getNumPoints()) { - return false; - } - return true; - } - - /** - * Moves the iterator ahead to the next vertex and (possibly) linear component. - */ - public void next() { - if (!hasNext()) return; - segmentFraction = 0.0; - vertexIndex++; - if (vertexIndex >= currentLine.getNumPoints()) { - componentIndex++; - if (componentIndex < linearGeom.getNumGeometries() - 1) { - loadCurrentLine(); - vertexIndex = 0; - } - } - } - - /** - * Checks whether the iterator cursor is pointing to the endpoint of a linestring. - * - * @return true if the iterator is at an endpoint - */ - public boolean isEndOfLine() { - if (componentIndex >= numLines) { - return false; - } - // LineString currentLine = (LineString) linear.getGeometryN(componentIndex); - if (vertexIndex < currentLine.getNumPoints() - 1) { - return false; - } - return true; - } - - /** - * The component index of the vertex the iterator is currently at. - * - * @return the current component index - */ - public int getComponentIndex() { - return componentIndex; - } - - /** - * The vertex index of the vertex the iterator is currently at. - * - * @return the current vertex index - */ - public int getVertexIndex() { - return vertexIndex; - } - - /** - * Gets the {@link LineString} component the iterator is current at. - * - * @return a linestring - */ - public LineString getLine() { - return currentLine; - } - - /** - * Gets the first {@link Coordinate} of the current segment. (the coordinate of the current - * vertex). - * - * @return a {@link Coordinate} - */ - public Coordinate getSegmentStart() { - return currentLine.getCoordinateN(vertexIndex); - } - - /** - * Gets the second {@link Coordinate} of the current segment. (the coordinate of the next vertex). - * If the iterator is at the end of a line, null is returned. - * - * @return a {@link Coordinate} or null - */ - public Coordinate getSegmentEnd() { - if (vertexIndex < getLine().getNumPoints() - 1) { - return currentLine.getCoordinateN(vertexIndex + 1); - } - return null; - } - - public LinearLocation getLocation() { - return new LinearLocation(componentIndex, vertexIndex, segmentFraction); - } - - @Override - public Iterator iterator() { - return new LinearIteratorIterator(); - } - - private void loadCurrentLine() { - if (componentIndex >= numLines) { - currentLine = null; - return; - } - currentLine = (LineString) linearGeom.getGeometryN(componentIndex); - } - - class LinearIteratorIterator implements Iterator { - - @Override - public boolean hasNext() { - return LinearIterator.this.hasNext(); - } - - @Override - public LinearLocation next() { - LinearLocation result = getLocation(); - LinearIterator.this.next(); - return result; - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - } -} diff --git a/src/main/java/org/opentripplanner/graph_builder/module/map/MatchState.java b/src/main/java/org/opentripplanner/graph_builder/module/map/MatchState.java deleted file mode 100644 index 424655eb377..00000000000 --- a/src/main/java/org/opentripplanner/graph_builder/module/map/MatchState.java +++ /dev/null @@ -1,119 +0,0 @@ -package org.opentripplanner.graph_builder.module.map; - -import java.util.ArrayList; -import java.util.List; -import org.locationtech.jts.geom.Coordinate; -import org.locationtech.jts.geom.Geometry; -import org.locationtech.jts.linearref.LinearLocation; -import org.opentripplanner.framework.geometry.SphericalDistanceLibrary; -import org.opentripplanner.routing.api.request.StreetMode; -import org.opentripplanner.street.model.edge.Edge; -import org.opentripplanner.street.model.edge.StreetEdge; -import org.opentripplanner.street.model.vertex.Vertex; -import org.opentripplanner.street.search.request.StreetSearchRequest; -import org.opentripplanner.street.search.state.State; - -public abstract class MatchState { - - private static final StreetSearchRequest REQUEST = StreetSearchRequest - .of() - .withMode(StreetMode.CAR) - .build(); - - protected static final double NEW_SEGMENT_PENALTY = 0.1; - - protected static final double NO_TRAVERSE_PENALTY = 20; - - public double currentError; - - public double accumulatedError; - - public MatchState parent; - - protected Edge edge; - - private double distanceAlongRoute = 0; - - public MatchState(MatchState parent, Edge edge, double distanceAlongRoute) { - this.distanceAlongRoute = distanceAlongRoute; - this.parent = parent; - this.edge = edge; - if (parent != null) { - this.accumulatedError = parent.accumulatedError + parent.currentError; - this.distanceAlongRoute += parent.distanceAlongRoute; - } - } - - public abstract List getNextStates(); - - public Edge getEdge() { - return edge; - } - - public double getTotalError() { - return accumulatedError + currentError; - } - - public double getDistanceAlongRoute() { - return distanceAlongRoute; - } - - /* computes the distance, in meters, along a geometry */ - protected static double distanceAlongGeometry( - Geometry geometry, - LinearLocation startIndex, - LinearLocation endIndex - ) { - if (endIndex == null) { - endIndex = LinearLocation.getEndLocation(geometry); - } - double total = 0; - LinearIterator it = new LinearIterator(geometry, startIndex); - LinearLocation index = startIndex; - Coordinate previousCoordinate = startIndex.getCoordinate(geometry); - - it.next(); - index = it.getLocation(); - while (index.compareTo(endIndex) < 0) { - Coordinate thisCoordinate = index.getCoordinate(geometry); - double distance = SphericalDistanceLibrary.fastDistance(previousCoordinate, thisCoordinate); - total += distance; - previousCoordinate = thisCoordinate; - if (!it.hasNext()) { - break; - } - it.next(); - index = it.getLocation(); - } - //now, last bit of last segment - Coordinate finalCoordinate = endIndex.getCoordinate(geometry); - total += SphericalDistanceLibrary.distance(previousCoordinate, finalCoordinate); - - return total; - } - - protected static double distance(Coordinate from, Coordinate to) { - return SphericalDistanceLibrary.fastDistance(from, to); - } - - protected boolean carsCanTraverse(Edge edge) { - // should be done with a method on edge (canTraverse already exists on turnEdge) - State s0 = new State(edge.getFromVertex(), REQUEST); - var states = edge.traverse(s0); - return !State.isEmpty(states); - } - - protected List getOutgoingMatchableEdges(Vertex vertex) { - List edges = new ArrayList<>(); - for (Edge e : vertex.getOutgoing()) { - if (!(e instanceof StreetEdge)) { - continue; - } - if (e.getGeometry() == null) { - continue; - } - edges.add(e); - } - return edges; - } -} diff --git a/src/main/java/org/opentripplanner/graph_builder/module/map/MidblockMatchState.java b/src/main/java/org/opentripplanner/graph_builder/module/map/MidblockMatchState.java deleted file mode 100644 index 111817e5ed8..00000000000 --- a/src/main/java/org/opentripplanner/graph_builder/module/map/MidblockMatchState.java +++ /dev/null @@ -1,265 +0,0 @@ -package org.opentripplanner.graph_builder.module.map; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import org.locationtech.jts.geom.Coordinate; -import org.locationtech.jts.geom.Geometry; -import org.locationtech.jts.linearref.LinearLocation; -import org.locationtech.jts.linearref.LocationIndexedLine; -import org.locationtech.jts.util.AssertionFailedException; -import org.opentripplanner.street.model.edge.Edge; -import org.opentripplanner.street.model.vertex.Vertex; - -public class MidblockMatchState extends MatchState { - - private static final double MAX_ERROR = 1000; - - private final LinearLocation edgeIndex; - private final Geometry edgeGeometry; - private final LocationIndexedLine indexedEdge; - public LinearLocation routeIndex; - Geometry routeGeometry; - - public MidblockMatchState( - MatchState parent, - Geometry routeGeometry, - Edge edge, - LinearLocation routeIndex, - LinearLocation edgeIndex, - double error, - double distanceAlongRoute - ) { - super(parent, edge, distanceAlongRoute); - this.routeGeometry = routeGeometry; - this.routeIndex = routeIndex; - this.edgeIndex = edgeIndex; - - edgeGeometry = edge.getGeometry(); - indexedEdge = new LocationIndexedLine(edgeGeometry); - currentError = error; - } - - @Override - public List getNextStates() { - ArrayList nextStates = new ArrayList<>(); - if (routeIndex.getSegmentIndex() == routeGeometry.getNumPoints() - 1) { - // this has either hit the end, or gone off the end. It's not real clear which. - // for now, let's assume it means that the ending is somewhere along this edge, - // so we return an end state - Coordinate pt = routeIndex.getCoordinate(routeGeometry); - double error = distance(pt, edgeIndex.getCoordinate(edgeGeometry)); - nextStates.add(new EndMatchState(this, error, 0)); - return nextStates; - } - - LinearIterator it = new LinearIterator(routeGeometry, routeIndex); - if (it.hasNext()) { - it.next(); - LinearLocation routeSuccessor = it.getLocation(); - - // now we want to see where this new point is in terms of the edge's geometry - Coordinate newRouteCoord = routeSuccessor.getCoordinate(routeGeometry); - LinearLocation newEdgeIndex = indexedEdge.project(newRouteCoord); - - Coordinate edgeCoord = newEdgeIndex.getCoordinate(edgeGeometry); - if (newEdgeIndex.compareTo(edgeIndex) <= 0) { - // we must make forward progress along the edge... or go to the next edge - /* this should not require the try/catch, but there is a bug in JTS */ - try { - LinearLocation projected2 = indexedEdge.indexOfAfter(edgeCoord, edgeIndex); - //another bug in JTS - if (Double.isNaN(projected2.getSegmentFraction())) { - // we are probably moving backwards - return Collections.emptyList(); - } else { - newEdgeIndex = projected2; - if (newEdgeIndex.equals(edgeIndex)) { - return Collections.emptyList(); - } - } - edgeCoord = newEdgeIndex.getCoordinate(edgeGeometry); - } catch (AssertionFailedException e) { - // we are not making progress, so just return an empty list - return Collections.emptyList(); - } - } - - if (newEdgeIndex.getSegmentIndex() == edgeGeometry.getNumPoints() - 1) { - // we might choose to continue from the end of the edge and a point mid-way - // along this route segment - - // find nearest point that makes progress along the route - Vertex toVertex = edge.getToVertex(); - Coordinate endCoord = toVertex.getCoordinate(); - LocationIndexedLine indexedRoute = new LocationIndexedLine(routeGeometry); - - // FIXME: it would be better to do this project/indexOfAfter in one step - // as the two-step version could snap to a bad place and be unable to escape. - - LinearLocation routeProjectedEndIndex = indexedRoute.project(endCoord); - Coordinate routeProjectedEndCoord = routeProjectedEndIndex.getCoordinate(routeGeometry); - - if (routeProjectedEndIndex.compareTo(routeIndex) <= 0) { - try { - routeProjectedEndIndex = indexedRoute.indexOfAfter(routeProjectedEndCoord, routeIndex); - if (Double.isNaN(routeProjectedEndIndex.getSegmentFraction())) { - // can't go forward - routeProjectedEndIndex = routeIndex; // this is bad, but not terrible - // since we are advancing along the edge - } - } catch (AssertionFailedException e) { - routeProjectedEndIndex = routeIndex; - } - routeProjectedEndCoord = routeProjectedEndIndex.getCoordinate(routeGeometry); - } - - double positionError = distance(routeProjectedEndCoord, endCoord); - double travelAlongRoute = distanceAlongGeometry( - routeGeometry, - routeIndex, - routeProjectedEndIndex - ); - double travelAlongEdge = distanceAlongGeometry(edgeGeometry, edgeIndex, newEdgeIndex); - double travelError = Math.abs(travelAlongEdge - travelAlongRoute); - - double error = positionError + travelError; - - if (error > MAX_ERROR) { - // we're not going to bother with states which are - // totally wrong - return nextStates; - } - - for (Edge e : getOutgoingMatchableEdges(toVertex)) { - double cost = error + NEW_SEGMENT_PENALTY; - if (!carsCanTraverse(e)) { - cost += NO_TRAVERSE_PENALTY; - } - MatchState nextState = new MidblockMatchState( - this, - routeGeometry, - e, - routeProjectedEndIndex, - new LinearLocation(), - cost, - travelAlongRoute - ); - nextStates.add(nextState); - } - } else { - double travelAlongEdge = distanceAlongGeometry(edgeGeometry, edgeIndex, newEdgeIndex); - double travelAlongRoute = distanceAlongGeometry(routeGeometry, routeIndex, routeSuccessor); - double travelError = Math.abs(travelAlongRoute - travelAlongEdge); - - double positionError = distance(edgeCoord, newRouteCoord); - - double error = travelError + positionError; - - MatchState nextState = new MidblockMatchState( - this, - routeGeometry, - edge, - routeSuccessor, - newEdgeIndex, - error, - travelAlongRoute - ); - nextStates.add(nextState); - - // it's also possible that, although we have not yet reached the end of this edge, - // we are going to turn, because the route turns earlier than the edge. In that - // case, we jump to the corner, and our error is the distance from the route point - // and the corner - - Vertex toVertex = edge.getToVertex(); - double travelAlongOldEdge = distanceAlongGeometry(edgeGeometry, edgeIndex, null); - - for (Edge e : getOutgoingMatchableEdges(toVertex)) { - Geometry newEdgeGeometry = e.getGeometry(); - LocationIndexedLine newIndexedEdge = new LocationIndexedLine(newEdgeGeometry); - newEdgeIndex = newIndexedEdge.project(newRouteCoord); - Coordinate newEdgeCoord = newEdgeIndex.getCoordinate(newEdgeGeometry); - positionError = distance(newEdgeCoord, newRouteCoord); - travelAlongEdge = - travelAlongOldEdge + - distanceAlongGeometry(newEdgeGeometry, new LinearLocation(), newEdgeIndex); - travelError = Math.abs(travelAlongRoute - travelAlongEdge); - - error = travelError + positionError; - - if (error > MAX_ERROR) { - // we're not going to bother with states which are - // totally wrong - return nextStates; - } - - double cost = error + NEW_SEGMENT_PENALTY; - if (!carsCanTraverse(e)) { - cost += NO_TRAVERSE_PENALTY; - } - - nextState = - new MidblockMatchState( - this, - routeGeometry, - e, - routeSuccessor, - new LinearLocation(), - cost, - travelAlongRoute - ); - nextStates.add(nextState); - } - } - return nextStates; - } else { - Coordinate routeCoord = routeIndex.getCoordinate(routeGeometry); - LinearLocation projected = indexedEdge.project(routeCoord); - double locationError = distance(projected.getCoordinate(edgeGeometry), routeCoord); - - MatchState end = new EndMatchState(this, locationError, 0); - return Arrays.asList(end); - } - } - - public int hashCode() { - return (edge.hashCode() * 1337 + hashCode(edgeIndex)) * 1337 + hashCode(routeIndex); - } - - public boolean equals(Object o) { - if (!(o instanceof MidblockMatchState)) { - return false; - } - MidblockMatchState other = (MidblockMatchState) o; - return ( - other.edge == edge && - other.edgeIndex.compareTo(edgeIndex) == 0 && - other.routeIndex.compareTo(routeIndex) == 0 - ); - } - - public String toString() { - return ( - "MidblockMatchState(" + - edge + - ", " + - edgeIndex.getSegmentIndex() + - ", " + - edgeIndex.getSegmentFraction() + - ") - " + - currentError - ); - } - - private int hashCode(LinearLocation location) { - return ( - location.getComponentIndex() * - 1000000 + - location.getSegmentIndex() * - 37 + - Double.valueOf(location.getSegmentFraction()).hashCode() - ); - } -} diff --git a/src/main/java/org/opentripplanner/graph_builder/module/map/StreetMatcher.java b/src/main/java/org/opentripplanner/graph_builder/module/map/StreetMatcher.java deleted file mode 100644 index e8bc92cc46f..00000000000 --- a/src/main/java/org/opentripplanner/graph_builder/module/map/StreetMatcher.java +++ /dev/null @@ -1,168 +0,0 @@ -package org.opentripplanner.graph_builder.module.map; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import org.locationtech.jts.geom.Coordinate; -import org.locationtech.jts.geom.Envelope; -import org.locationtech.jts.geom.Geometry; -import org.locationtech.jts.index.strtree.STRtree; -import org.locationtech.jts.linearref.LinearLocation; -import org.locationtech.jts.linearref.LocationIndexedLine; -import org.locationtech.jts.simplify.DouglasPeuckerSimplifier; -import org.opentripplanner.astar.model.BinHeap; -import org.opentripplanner.routing.graph.Graph; -import org.opentripplanner.street.model.edge.Edge; -import org.opentripplanner.street.model.edge.StreetEdge; -import org.opentripplanner.street.model.vertex.Vertex; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * This Performs most of the work for the MapBuilder graph builder module. It determines which - * sequence of graph edges a GTFS shape probably corresponds to. Note that GTFS shapes are not in - * any way constrained to OSM edges or even roads. - */ -public class StreetMatcher { - - private static final Logger log = LoggerFactory.getLogger(StreetMatcher.class); - private static final double DISTANCE_THRESHOLD = 0.0002; - private final STRtree index; - Graph graph; - - public StreetMatcher(Graph graph) { - this.graph = graph; - index = createIndex(); - index.build(); - } - - @SuppressWarnings("unchecked") - public List match(Geometry routeGeometry) { - routeGeometry = removeDuplicatePoints(routeGeometry); - - if (routeGeometry == null) { - return null; - } - - routeGeometry = DouglasPeuckerSimplifier.simplify(routeGeometry, 0.00001); - - // initial state: start midway along a block. - LocationIndexedLine indexedLine = new LocationIndexedLine(routeGeometry); - - LinearLocation startIndex = indexedLine.getStartIndex(); - - Coordinate routeStartCoordinate = startIndex.getCoordinate(routeGeometry); - Envelope envelope = new Envelope(routeStartCoordinate); - double distanceThreshold = DISTANCE_THRESHOLD; - envelope.expandBy(distanceThreshold); - - BinHeap states = new BinHeap<>(); - List nearbyEdges = index.query(envelope); - while (nearbyEdges.isEmpty()) { - envelope.expandBy(distanceThreshold); - distanceThreshold *= 2; - nearbyEdges = index.query(envelope); - } - - // compute initial states - for (Edge initialEdge : nearbyEdges) { - Geometry edgeGeometry = initialEdge.getGeometry(); - - LocationIndexedLine indexedEdge = new LocationIndexedLine(edgeGeometry); - LinearLocation initialLocation = indexedEdge.project(routeStartCoordinate); - - double error = MatchState.distance( - initialLocation.getCoordinate(edgeGeometry), - routeStartCoordinate - ); - MidblockMatchState state = new MidblockMatchState( - null, - routeGeometry, - initialEdge, - startIndex, - initialLocation, - error, - 0.01 - ); - states.insert(state, 0); //make sure all initial states are visited by inserting them at 0 - } - - // search for best-matching path - int seen_count = 0, total = 0; - HashSet seen = new HashSet<>(); - while (!states.empty()) { - double k = states.peek_min_key(); - MatchState state = states.extract_min(); - if (++total % 50000 == 0) { - log.debug("seen / total: {} / {}", seen_count, total); - } - if (seen.contains(state)) { - ++seen_count; - continue; - } else { - if (k != 0) { - //but do not mark states as closed if we start at them - seen.add(state); - } - } - if (state instanceof EndMatchState) { - return toEdgeList(state); - } - for (MatchState next : state.getNextStates()) { - if (seen.contains(next)) { - continue; - } - states.insert(next, next.getTotalError() - next.getDistanceAlongRoute()); - } - } - return null; - } - - STRtree createIndex() { - STRtree edgeIndex = new STRtree(); - for (Vertex v : graph.getVertices()) { - for (Edge e : v.getOutgoing()) { - if (e instanceof StreetEdge) { - Envelope envelope; - Geometry geometry = e.getGeometry(); - envelope = geometry.getEnvelopeInternal(); - edgeIndex.insert(envelope, e); - } - } - } - log.debug("Created index"); - return edgeIndex; - } - - private Geometry removeDuplicatePoints(Geometry routeGeometry) { - List coords = new ArrayList<>(); - Coordinate last = null; - for (Coordinate c : routeGeometry.getCoordinates()) { - if (!c.equals(last)) { - last = c; - coords.add(c); - } - } - if (coords.size() < 2) { - return null; - } - Coordinate[] coordArray = new Coordinate[coords.size()]; - return routeGeometry.getFactory().createLineString(coords.toArray(coordArray)); - } - - private List toEdgeList(MatchState next) { - ArrayList edges = new ArrayList<>(); - Edge lastEdge = null; - while (next != null) { - Edge edge = next.getEdge(); - if (edge != lastEdge) { - edges.add(edge); - lastEdge = edge; - } - next = next.parent; - } - Collections.reverse(edges); - return edges; - } -} diff --git a/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java b/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java index 4a56a0722e4..d62aa1bf41f 100644 --- a/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java @@ -114,8 +114,6 @@ public class BuildConfig implements OtpDataStoreConfig { public final boolean platformEntriesLinking; - public final boolean matchBusRoutesToStreets; - /** See {@link S3BucketConfig}. */ public final S3BucketConfig elevationBucket; @@ -271,14 +269,6 @@ When set to true (it is false by default), the elevation module will include the islandPruning = IslandPruningConfig.fromConfig(root); - matchBusRoutesToStreets = - root - .of("matchBusRoutesToStreets") - .since(V1_5) - .summary( - "Based on GTFS shape data, guess which OSM streets each bus runs on to improve stop linking." - ) - .asBoolean(false); maxDataImportIssuesPerFile = root .of("maxDataImportIssuesPerFile") diff --git a/src/main/java/org/opentripplanner/transit/model/network/TripPattern.java b/src/main/java/org/opentripplanner/transit/model/network/TripPattern.java index e89ea1940f6..7057d9fd56e 100644 --- a/src/main/java/org/opentripplanner/transit/model/network/TripPattern.java +++ b/src/main/java/org/opentripplanner/transit/model/network/TripPattern.java @@ -159,11 +159,6 @@ public StopPattern getStopPattern() { return stopPattern; } - // TODO OTP2 this method modifies the state, it will be refactored in a subsequent step - public void setHopGeometry(int i, LineString hopGeometry) { - this.hopGeometries[i] = CompactLineStringUtils.compactLineString(hopGeometry, false); - } - public LineString getGeometry() { if (hopGeometries == null || hopGeometries.length == 0) { return null; @@ -176,10 +171,6 @@ public LineString getGeometry() { return GeometryUtils.concatenateLineStrings(lineStrings); } - public int numHopGeometries() { - return hopGeometries.length; - } - public int numberOfStops() { return stopPattern.getSize(); } diff --git a/src/test/java/org/opentripplanner/graph_builder/module/map/StreetMatcherTest.java b/src/test/java/org/opentripplanner/graph_builder/module/map/StreetMatcherTest.java deleted file mode 100644 index 92001fbf8a7..00000000000 --- a/src/test/java/org/opentripplanner/graph_builder/module/map/StreetMatcherTest.java +++ /dev/null @@ -1,157 +0,0 @@ -package org.opentripplanner.graph_builder.module.map; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import java.util.List; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.locationtech.jts.geom.Coordinate; -import org.locationtech.jts.geom.GeometryFactory; -import org.locationtech.jts.geom.LineString; -import org.opentripplanner.routing.graph.Graph; -import org.opentripplanner.street.model._data.StreetModelForTest; -import org.opentripplanner.street.model.edge.Edge; -import org.opentripplanner.street.model.vertex.SimpleVertex; -import org.opentripplanner.street.model.vertex.StreetVertex; -import org.opentripplanner.street.model.vertex.VertexLabel; - -public class StreetMatcherTest { - - static GeometryFactory gf = new GeometryFactory(); - - private Graph graph; - - @BeforeEach - public void before() { - graph = new Graph(); - - vertex("56th_24th", 47.669457, -122.387577); - vertex("56th_22nd", 47.669462, -122.384739); - vertex("56th_20th", 47.669457, -122.382106); - - vertex("market_24th", 47.668690, -122.387577); - vertex("market_ballard", 47.668683, -122.386096); - vertex("market_22nd", 47.668686, -122.384749); - vertex("market_leary", 47.668669, -122.384392); - vertex("market_russell", 47.668655, -122.382997); - vertex("market_20th", 47.668684, -122.382117); - - vertex("shilshole_24th", 47.668419, -122.387534); - vertex("shilshole_22nd", 47.666519, -122.384744); - vertex("shilshole_vernon", 47.665938, -122.384048); - vertex("shilshole_20th", 47.664356, -122.382192); - - vertex("ballard_turn", 47.668509, -122.386069); - vertex("ballard_22nd", 47.667624, -122.384744); - vertex("ballard_vernon", 47.666422, -122.383158); - vertex("ballard_20th", 47.665476, -122.382128); - - vertex("leary_vernon", 47.666863, -122.382353); - vertex("leary_20th", 47.666682, -122.382160); - - vertex("russell_20th", 47.667846, -122.382128); - - edges("56th_24th", "56th_22nd", "56th_20th"); - - edges("56th_24th", "market_24th"); - edges("56th_22nd", "market_22nd"); - edges("56th_20th", "market_20th"); - - edges( - "market_24th", - "market_ballard", - "market_22nd", - "market_leary", - "market_russell", - "market_20th" - ); - edges("market_24th", "shilshole_24th", "shilshole_22nd", "shilshole_vernon", "shilshole_20th"); - edges("market_ballard", "ballard_turn", "ballard_22nd", "ballard_vernon", "ballard_20th"); - edges("market_leary", "leary_vernon", "leary_20th"); - edges("market_russell", "russell_20th"); - - edges("market_22nd", "ballard_22nd", "shilshole_22nd"); - edges("leary_vernon", "ballard_vernon", "shilshole_vernon"); - edges("market_20th", "russell_20th", "leary_20th", "ballard_20th", "shilshole_20th"); - } - - @Test - public void testStreetMatcher() { - LineString geometry = geometry(-122.385689, 47.669484, -122.387384, 47.669470); - - StreetMatcher matcher = new StreetMatcher(graph); - - List match = matcher.match(geometry); - assertNotNull(match); - assertEquals(1, match.size()); - assertEquals("56th_24th", match.get(0).getToVertex().getLabelString()); - - geometry = geometry(-122.385689, 47.669484, -122.387384, 47.669470, -122.387588, 47.669325); - - match = matcher.match(geometry); - assertNotNull(match); - assertEquals(2, match.size()); - - geometry = - geometry( - -122.385689, - 47.669484, - -122.387384, - 47.669470, - -122.387588, - 47.669325, - -122.387255, - 47.668675 - ); - - match = matcher.match(geometry); - assertNotNull(match); - assertEquals(3, match.size()); - - geometry = - geometry( - -122.384756, - 47.669260, - -122.384777, - 47.667454, - -122.383554, - 47.666789, - -122.3825, - 47.666 - ); - match = matcher.match(geometry); - assertNotNull(match); - assertEquals(4, match.size()); - assertEquals("ballard_20th", match.get(3).getToVertex().getLabelString()); - } - - private LineString geometry(double... ordinates) { - Coordinate[] coords = new Coordinate[ordinates.length / 2]; - - for (int i = 0; i < ordinates.length; i += 2) { - coords[i / 2] = new Coordinate(ordinates[i], ordinates[i + 1]); - } - return gf.createLineString(coords); - } - - /**** - * Private Methods - ****/ - - private SimpleVertex vertex(String label, double lat, double lon) { - var v = new SimpleVertex(label, lat, lon); - graph.addVertex(v); - return v; - } - - private void edges(String... vLabels) { - for (int i = 0; i < vLabels.length - 1; i++) { - StreetVertex vA = (StreetVertex) graph.getVertex(VertexLabel.string(vLabels[i])); - StreetVertex vB = (StreetVertex) graph.getVertex(VertexLabel.string(vLabels[i + 1])); - - StreetModelForTest.streetEdge(vA, vB); - StreetModelForTest.streetEdge(vB, vA); - } - } -} From 515bd953eaf1c26c2fc625de5338fb21500b34d9 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 22 Nov 2023 13:06:23 +0100 Subject: [PATCH 42/85] Remove property from example files --- docs/examples/entur/build-config.json | 1 - test/performance/norway/build-config.json | 1 - 2 files changed, 2 deletions(-) diff --git a/docs/examples/entur/build-config.json b/docs/examples/entur/build-config.json index 3829e975f34..e9351882774 100644 --- a/docs/examples/entur/build-config.json +++ b/docs/examples/entur/build-config.json @@ -5,7 +5,6 @@ "embedRouterConfig": true, "areaVisibility": true, "platformEntriesLinking": true, - "matchBusRoutesToStreets": false, "staticParkAndRide": true, "staticBikeParkAndRide": true, "maxDataImportIssuesPerFile": 1000, diff --git a/test/performance/norway/build-config.json b/test/performance/norway/build-config.json index 5fa7f03ed58..83de34a027b 100644 --- a/test/performance/norway/build-config.json +++ b/test/performance/norway/build-config.json @@ -6,7 +6,6 @@ "embedRouterConfig": true, "areaVisibility": true, "platformEntriesLinking": true, - "matchBusRoutesToStreets": false, "staticParkAndRide": true, "staticBikeParkAndRide": true, "maxDataImportIssuesPerFile": 1000, From 5a98621ad19f55efeb221dd4c8c15f6f464b5f85 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 23 Nov 2023 16:37:05 +0100 Subject: [PATCH 43/85] Document language conventions --- CODE_CONVENTIONS.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CODE_CONVENTIONS.md b/CODE_CONVENTIONS.md index a9fd73a0497..c0d528726a1 100644 --- a/CODE_CONVENTIONS.md +++ b/CODE_CONVENTIONS.md @@ -30,6 +30,12 @@ Builder initStop(Stop stop) { ## Naming Conventions +In general, we use American English. We use the GTFS terminology inside OTP as the transit domain +specific language. In cases where GTFS does not provide an alternative we use NeTEx. The naming +should follow the Java standard naming conventions. For example a "real-time updater" class +is named `RealTimeUpdater`. If in doubt check the Oxford Dictionary(American). + + ### Packages Try to arrange code by domain functionality, not technology. The main structure of a package should From dd2717ed0a04f91f5446dec30ba6e5b407b81ffb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 24 Nov 2023 00:17:03 +0000 Subject: [PATCH 44/85] fix(deps): update geotools.version to v30.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1632ab7fea7..5e0d9307f22 100644 --- a/pom.xml +++ b/pom.xml @@ -58,7 +58,7 @@ 131 - 30.0 + 30.1 2.48.1 2.16.0 3.1.3 From 793758395da68fd9c1c417b742160ed5e9881f41 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 24 Nov 2023 10:23:06 +0100 Subject: [PATCH 45/85] Revert "fix(deps): update dependency org.apache.httpcomponents.client5:httpclient5 to v5.2.2" This reverts commit 6649bdd7dab4fc856e5609b30f46ef4787da43e2. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index cd13c7ff0f7..1632ab7fea7 100644 --- a/pom.xml +++ b/pom.xml @@ -898,7 +898,7 @@ org.apache.httpcomponents.client5 httpclient5 - 5.2.2 + 5.2.1 commons-cli From d05ac15a19da14909740cf4dd89e99ce82583fb3 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 24 Nov 2023 10:15:19 +0100 Subject: [PATCH 46/85] Remove container images that have not been pulled for over a year --- .github/workflows/prune-container-images.yml | 22 ++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/prune-container-images.yml diff --git a/.github/workflows/prune-container-images.yml b/.github/workflows/prune-container-images.yml new file mode 100644 index 00000000000..de5954b9ba9 --- /dev/null +++ b/.github/workflows/prune-container-images.yml @@ -0,0 +1,22 @@ +name: 'Delete unused snapshot container images' + +on: + schedule: + - cron: '0 12 1 * *' + workflow_dispatch: + +jobs: + container-image: + if: github.repository_owner == 'opentripplanner' + runs-on: ubuntu-latest + steps: + - name: Delete unused container images + env: + CONTAINER_REPO: opentripplanner/opentripplanner + CONTAINER_REGISTRY_USER: otpbot + CONTAINER_REGISTRY_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }} + run: | + # remove all snapshot container images that have not been pulled for over a year + # --keep-semver makes sure that any image with a x.y.z version scheme is unaffected by this + pip install prune-container-repo==0.0.3 + prune-container-repo -u ${CONTAINER_REGISTRY_USER} -r ${CONTAINER_REPO} --days=365 --keep-semver --activate From b0be55fd64c1cae6dd8f97ef67e227ca9fd468c9 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 24 Nov 2023 15:22:26 +0100 Subject: [PATCH 47/85] Correct spelling of 'which' [ci skip] --- ARCHITECTURE.md | 2 +- CODE_CONVENTIONS.md | 2 +- .../framework/concurrent/OtpRequestThreadFactory.java | 2 +- .../org/opentripplanner/framework/text/MarkdownFormatter.java | 2 +- src/main/java/org/opentripplanner/framework/text/Table.java | 2 +- .../java/org/opentripplanner/framework/text/TableBuilder.java | 2 +- .../org/opentripplanner/framework/time/DurationUtils.java | 2 +- .../java/org/opentripplanner/framework/time/TimeUtils.java | 4 ++-- .../graph_builder/module/configure/GraphBuilderModules.java | 2 +- src/main/java/org/opentripplanner/model/plan/Itinerary.java | 2 +- .../org/opentripplanner/model/transfer/TransferPoint.java | 4 ++-- .../org/opentripplanner/model/transfer/TransferPointMap.java | 2 +- .../org/opentripplanner/openstreetmap/model/OSMWithTags.java | 2 +- .../raptor/rangeraptor/SystemErrDebugLogger.java | 2 +- .../rangeraptor/standard/heuristics/HeuristicsAdapter.java | 2 +- .../rangeraptor/standard/stoparrivals/view/StopsCursor.java | 4 ++-- .../raptor/rangeraptor/transit/EgressPaths.java | 4 ++-- src/main/java/org/opentripplanner/raptor/spi/Flyweight.java | 2 +- .../opentripplanner/raptor/spi/RaptorBoardOrAlightEvent.java | 2 +- .../routing/algorithm/raptoradapter/router/TransitRouter.java | 2 +- .../transit/constrainedtransfer/TransferIndexGenerator.java | 2 +- .../raptoradapter/transit/request/TripSearchTimetable.java | 2 +- .../api/request/preference/TimeSlopeSafetyTriangle.java | 2 +- .../standalone/api/OtpServerRequestContext.java | 4 ++-- .../standalone/config/framework/json/EnumMapper.java | 2 +- .../transit/model/framework/TransitBuilder.java | 2 +- .../transit/model/network/RoutingTripPattern.java | 2 +- .../java/org/opentripplanner/_support/arch/ArchComponent.java | 2 +- .../moduletests/F05_OnBoardAccessEgressAndTransfersTest.java | 2 +- .../opentripplanner/transit/speed_test/SpeedTestRequest.java | 2 +- .../transit/speed_test/model/testcase/ExpectedResults.java | 2 +- 31 files changed, 36 insertions(+), 36 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index dd0f986e518..acd90b787e6 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -24,7 +24,7 @@ examples. The Transit model is more complex than the VehiclePosition model. a use-case or set of features. It may have an api with request/response classes. These are usually stateless; Hence the `Use Case Service` does normally not have a model. The implementing class has the same name as the interface with prefix `Default`. - - `Domain Model` A model witch encapsulate a business area. In the drawing two examples are shown, + - `Domain Model` A model which encapsulate a business area. In the drawing two examples are shown, the `transit` and `vhicleposition` domain model. The transit model is more complex so the implementation have a separate `Service` and `Repository`. Almost all http endpoints are , read-only so the `Service` can focus on serving the http API endpoints, while the repository diff --git a/CODE_CONVENTIONS.md b/CODE_CONVENTIONS.md index c0d528726a1..49b92fbe2f8 100644 --- a/CODE_CONVENTIONS.md +++ b/CODE_CONVENTIONS.md @@ -44,7 +44,7 @@ be `org.opentripplanner...`. | Package | Description | | ------------------------------- |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `o.o.` | At the top level we should divide OTP into "domain"s like `apis`, `framework`, `transit`, `street`, `astar`, `raptor`, `feeds`, `updaters`, and `application`. | -| `component` and `sub-component` | A group of packages/classes witch naturally belong together, think aggregate as in Domain Driven Design. | +| `component` and `sub-component` | A group of packages/classes which naturally belong together, think aggregate as in Domain Driven Design. | | `component.api` | Used for components to define the programing interface for the component. If present, (see Raptor) all outside dependencies to the component should be through the `api`. | | `component.model` | Used to create a model of a Entites, ValueObjects, ++. If exposed outside the component you should include an entry point like `xyz.model.XyzModel` and/or a Service (in api or component root package). | | `component.service` | Implementation of the service like `DefaultTransitService`, may also contain use-case specific code. Note, the Service interface goes into the component root or `api`, not in the service package. | diff --git a/src/main/java/org/opentripplanner/framework/concurrent/OtpRequestThreadFactory.java b/src/main/java/org/opentripplanner/framework/concurrent/OtpRequestThreadFactory.java index 1ad44728c80..e88bbb420cd 100644 --- a/src/main/java/org/opentripplanner/framework/concurrent/OtpRequestThreadFactory.java +++ b/src/main/java/org/opentripplanner/framework/concurrent/OtpRequestThreadFactory.java @@ -7,7 +7,7 @@ /** * This thread pool factory should be used to create all threads handling "user" requests in OTP. - * It is used to instrument new threads witch enable log information propagation and error handling, + * It is used to instrument new threads which enable log information propagation and error handling, * like thread interruption. By "user" we mean users of the query APIs like GTFS GraphQL API, * Transmodel GraphQL API and other http endpoints. *

diff --git a/src/main/java/org/opentripplanner/framework/text/MarkdownFormatter.java b/src/main/java/org/opentripplanner/framework/text/MarkdownFormatter.java index d74a14a3315..6fdb495a0d5 100644 --- a/src/main/java/org/opentripplanner/framework/text/MarkdownFormatter.java +++ b/src/main/java/org/opentripplanner/framework/text/MarkdownFormatter.java @@ -73,7 +73,7 @@ public static String escapeInTable(String text) { return text.replace("|", "¦"); } - /** Return whitespace witch can be used to indent inside a table cell. */ + /** Return whitespace which can be used to indent inside a table cell. */ public static String indentInTable(int level) { return level <= 0 ? "" : NBSP.repeat(3 * level); } diff --git a/src/main/java/org/opentripplanner/framework/text/Table.java b/src/main/java/org/opentripplanner/framework/text/Table.java index 9db832c9410..940868b82e2 100644 --- a/src/main/java/org/opentripplanner/framework/text/Table.java +++ b/src/main/java/org/opentripplanner/framework/text/Table.java @@ -48,7 +48,7 @@ public static TableBuilder of() { } /** - * Static method witch format a given table as valid Markdown table like: + * Static method which format a given table as valid Markdown table like: *

    * | A | B |
    * |---|---|
diff --git a/src/main/java/org/opentripplanner/framework/text/TableBuilder.java b/src/main/java/org/opentripplanner/framework/text/TableBuilder.java
index cf6623c2d99..12e80fd62a8 100644
--- a/src/main/java/org/opentripplanner/framework/text/TableBuilder.java
+++ b/src/main/java/org/opentripplanner/framework/text/TableBuilder.java
@@ -50,7 +50,7 @@ public TableBuilder withAlights(Collection aligns) {
   }
 
   /**
-   * Return the width needed for each column. The witch is calculated by taking
+   * Return the width needed for each column. The which is calculated by taking
    * the maximum of the {@code minWidth}, header width and the maximum width for all
    * cells in the column.
    */
diff --git a/src/main/java/org/opentripplanner/framework/time/DurationUtils.java b/src/main/java/org/opentripplanner/framework/time/DurationUtils.java
index fb30392cf9b..bf59964fbb2 100644
--- a/src/main/java/org/opentripplanner/framework/time/DurationUtils.java
+++ b/src/main/java/org/opentripplanner/framework/time/DurationUtils.java
@@ -124,7 +124,7 @@ public static Duration duration(String duration) {
   }
 
   /**
-   * This is used to parse a string witch may be a number {@code NNNN}(number of seconds) or a
+   * This is used to parse a string which may be a number {@code NNNN}(number of seconds) or a
    * duration with format {@code NhNmNs}, where {@code N} is a decimal number and
    * {@code h} is hours, {@code m} minutes and {@code s} seconds.
    * 

diff --git a/src/main/java/org/opentripplanner/framework/time/TimeUtils.java b/src/main/java/org/opentripplanner/framework/time/TimeUtils.java index afeeb77ff5d..61549eeced3 100644 --- a/src/main/java/org/opentripplanner/framework/time/TimeUtils.java +++ b/src/main/java/org/opentripplanner/framework/time/TimeUtils.java @@ -205,7 +205,7 @@ public static String msToString(long milliseconds) { * busy-wait again. *

* This method does a "busy" wait - it is not affected by a thread interrupt like - * {@link Thread#sleep(long)}; Hence do not interfere with timeout logic witch uses the interrupt + * {@link Thread#sleep(long)}; Hence do not interfere with timeout logic which uses the interrupt * flag. *

* THIS CODE IS NOT MEANT FOR PRODUCTION! @@ -226,7 +226,7 @@ public static long busyWaitOnce(int waitMs) { * number. *

* This method does a "busy" wait - it is not affected by a thread interrupt like - * {@link Thread#sleep(long)}; Hence do not interfere with timeout logic witch uses the interrupt + * {@link Thread#sleep(long)}; Hence do not interfere with timeout logic which uses the interrupt * flag. *

* THIS CODE IS NOT MEANT FOR PRODUCTION! diff --git a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java index 82f183a0bc4..444adb5b727 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java @@ -46,7 +46,7 @@ import org.opentripplanner.transit.service.TransitModel; /** - * Configure all modules witch is not simple enough to be injected. + * Configure all modules which is not simple enough to be injected. */ @Module public class GraphBuilderModules { diff --git a/src/main/java/org/opentripplanner/model/plan/Itinerary.java b/src/main/java/org/opentripplanner/model/plan/Itinerary.java index 0c111999912..58320bf1652 100644 --- a/src/main/java/org/opentripplanner/model/plan/Itinerary.java +++ b/src/main/java/org/opentripplanner/model/plan/Itinerary.java @@ -428,7 +428,7 @@ public void setLegs(List legs) { * accessible the itinerary is as a whole. This is not a very scientific method but just a rough * guidance that expresses certainty or uncertainty about the accessibility. *

- * An alternative to this is to use the `generalized-cost` and use that to indicate witch itineraries is the + * An alternative to this is to use the `generalized-cost` and use that to indicate which itineraries is the * best/most friendly with respect to making the journey in a wheelchair. The `generalized-cost` include, not * only a penalty for unknown and inaccessible boardings, but also a penalty for undesired uphill and downhill * street traversal. diff --git a/src/main/java/org/opentripplanner/model/transfer/TransferPoint.java b/src/main/java/org/opentripplanner/model/transfer/TransferPoint.java index b10e96e6f1c..dcfc7d381ad 100644 --- a/src/main/java/org/opentripplanner/model/transfer/TransferPoint.java +++ b/src/main/java/org/opentripplanner/model/transfer/TransferPoint.java @@ -52,7 +52,7 @@ */ public interface TransferPoint { /** - * Utility method witch can be used in APIs to get the trip, if it exists, from a transfer point. + * Utility method which can be used in APIs to get the trip, if it exists, from a transfer point. */ @Nullable static Trip getTrip(TransferPoint point) { @@ -60,7 +60,7 @@ static Trip getTrip(TransferPoint point) { } /** - * Utility method witch can be used in APIs to get the route, if it exists, from a transfer + * Utility method which can be used in APIs to get the route, if it exists, from a transfer * point. */ @Nullable diff --git a/src/main/java/org/opentripplanner/model/transfer/TransferPointMap.java b/src/main/java/org/opentripplanner/model/transfer/TransferPointMap.java index 3592153f080..9f231d06aab 100644 --- a/src/main/java/org/opentripplanner/model/transfer/TransferPointMap.java +++ b/src/main/java/org/opentripplanner/model/transfer/TransferPointMap.java @@ -73,7 +73,7 @@ E computeIfAbsent(TransferPoint point, Supplier creator) { } /** - * List all elements witch matches any of the transfer points added to the map. + * List all elements which matches any of the transfer points added to the map. */ List get(Trip trip, StopLocation stop, int stopPointInPattern) { return Stream diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java index 6e596e52076..37757823362 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java @@ -201,7 +201,7 @@ public Optional getTagOpt(String network) { /** * Get tag and convert it to an integer. If the tag exist, but can not be parsed into a number, - * then the error handler is called with the value witch failed to parse. + * then the error handler is called with the value which failed to parse. */ public OptionalInt getTagAsInt(String tag, Consumer errorHandler) { String value = getTag(tag); diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/SystemErrDebugLogger.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/SystemErrDebugLogger.java index b10783c830f..8c35f103106 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/SystemErrDebugLogger.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/SystemErrDebugLogger.java @@ -28,7 +28,7 @@ import org.opentripplanner.raptor.rangeraptor.transit.TripTimesSearch; /** - * A debug logger witch can be plugged into Raptor to do debug logging to standard error. This is + * A debug logger which can be plugged into Raptor to do debug logging to standard error. This is * used by the REST API, SpeedTest and in module tests. *

* See the Raptor design doc for a general description of the logging functionality. diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/heuristics/HeuristicsAdapter.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/heuristics/HeuristicsAdapter.java index 78aab07f21a..ff38fff6d40 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/heuristics/HeuristicsAdapter.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/heuristics/HeuristicsAdapter.java @@ -185,7 +185,7 @@ private static AggregatedResults create( ); for (RaptorAccessEgress it : list) { - // Prevent transfer(walking) and the egress witch start with walking + // Prevent transfer(walking) and the egress which start with walking if (!(it.stopReachedOnBoard() || stopReachedByTransit)) { continue; } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/view/StopsCursor.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/view/StopsCursor.java index d20f4ba5db8..f72047597a7 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/view/StopsCursor.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/view/StopsCursor.java @@ -59,7 +59,7 @@ public Access fictiveAccess(int round, RaptorAccessEgress accessPath, int arr /** * Return a fictive Transfer stop arrival view. The arrival does not exist in the state, but is - * linked with the previous arrival witch is a "real" arrival present in the state. This enables + * linked with the previous arrival which is a "real" arrival present in the state. This enables * path generation. */ public Transfer fictiveTransfer( @@ -76,7 +76,7 @@ public Transfer fictiveTransfer( /** * Return a fictive Transit stop arrival view. The arrival does not exist in the state, but is - * linked with the previous arrival witch is a "real" arrival present in the state. This enables + * linked with the previous arrival which is a "real" arrival present in the state. This enables * path generation. */ public Transit fictiveTransit( diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressPaths.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressPaths.java index fa4fc57dc84..2038ab543df 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressPaths.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressPaths.java @@ -54,7 +54,7 @@ public Collection listAll() { } /** - * List all stops with an egress path witch start by walking. These egress paths can only be used + * List all stops with an egress path which start by walking. These egress paths can only be used * if arriving at the stop by transit. */ public int[] egressesWitchStartByWalking() { @@ -62,7 +62,7 @@ public int[] egressesWitchStartByWalking() { } /** - * List all stops with an egress path witch start on-board a "transit" ride. These + * List all stops with an egress path which start on-board a "transit" ride. These * egress paths can be used when arriving at the stop with both transfer or transit. */ public int[] egressesWitchStartByARide() { diff --git a/src/main/java/org/opentripplanner/raptor/spi/Flyweight.java b/src/main/java/org/opentripplanner/raptor/spi/Flyweight.java index c2b0536c8ab..a3d9ee0db1c 100644 --- a/src/main/java/org/opentripplanner/raptor/spi/Flyweight.java +++ b/src/main/java/org/opentripplanner/raptor/spi/Flyweight.java @@ -6,7 +6,7 @@ import java.lang.annotation.Target; /** - * This interface is used to tag methods witch return flyweight objects. The implementation may + * This interface is used to tag methods which return flyweight objects. The implementation may * choose not to implement the return type as a flyweight object, but the Raptor implementation * is guaranteed to treat them as such - enabling the optimization. *

diff --git a/src/main/java/org/opentripplanner/raptor/spi/RaptorBoardOrAlightEvent.java b/src/main/java/org/opentripplanner/raptor/spi/RaptorBoardOrAlightEvent.java index da01e397c06..5913f950969 100644 --- a/src/main/java/org/opentripplanner/raptor/spi/RaptorBoardOrAlightEvent.java +++ b/src/main/java/org/opentripplanner/raptor/spi/RaptorBoardOrAlightEvent.java @@ -77,7 +77,7 @@ default int boardStopIndex() { /** * This is a helper method for the Raptor implementation to be able to board or execute * a alternativeBoardingFallback method depending on the event. This logic should ideally - * be put inside raptor, but due to performance(creating lambda instances, witch for some + * be put inside raptor, but due to performance(creating lambda instances, which for some * reason is not inlined) this need to be here. *

* @param boardCallback perform boarding if the event in none empty (or some other special diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java index b9f9685341c..f18aa056504 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java @@ -177,7 +177,7 @@ private AccessEgresses fetchAccessEgresses() { if (OTPFeature.ParallelRouting.isOn()) { try { - // TODO: This is not using {@link OtpRequestThreadFactory} witch mean we do not get + // TODO: This is not using {@link OtpRequestThreadFactory} which mean we do not get // log-trace-parameters-propagation and graceful timeout handling here. CompletableFuture .allOf( diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/constrainedtransfer/TransferIndexGenerator.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/constrainedtransfer/TransferIndexGenerator.java index 9fc2dc69194..7acddd9cea5 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/constrainedtransfer/TransferIndexGenerator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/constrainedtransfer/TransferIndexGenerator.java @@ -57,7 +57,7 @@ public ConstrainedTransfersForPatterns generateTransfers() { for (ConstrainedTransfer tx : constrainedTransfers) { var c = tx.getTransferConstraint(); - // Only add transfers witch have an effect on the Raptor routing here. + // Only add transfers which have an effect on the Raptor routing here. // Some transfers only have the priority set, and that is used in optimized- // transfers, but not in Raptor. if (!c.includeInRaptorRouting()) { diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripSearchTimetable.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripSearchTimetable.java index 88e9d52e878..147eddfc605 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripSearchTimetable.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripSearchTimetable.java @@ -9,7 +9,7 @@ * This interface add two methods the the {@link RaptorTimeTable} to optimize the terip search * inside the transit model. They were previously in Raptor, but the trip Search is moded outside * of Raptor; We have keep these methods in an interface to be able to reuse the complex TripSearch - * in tests, witch do not use the transit model {@link TripSchedule}; Hence also the generic type + * in tests, which do not use the transit model {@link TripSchedule}; Hence also the generic type * on this interface. */ public interface TripSearchTimetable extends RaptorTimeTable { diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/TimeSlopeSafetyTriangle.java b/src/main/java/org/opentripplanner/routing/api/request/preference/TimeSlopeSafetyTriangle.java index e93dd5f5a6c..b901d738213 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/TimeSlopeSafetyTriangle.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/TimeSlopeSafetyTriangle.java @@ -52,7 +52,7 @@ public TimeSlopeSafetyTriangle(double time, double slope, double safety) { } /** - * Creates a special builder, witch used together with the + * Creates a special builder, which used together with the * {@link Builder#buildOrDefault(TimeSlopeSafetyTriangle)} can return a new instance or the * default value if no values are set. This is useful in the APIs where we want to fall back to * the default {@link TimeSlopeSafetyTriangle}, if no values are set. If only one or two values diff --git a/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java b/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java index 8fa8069ba70..fa6ead99c5e 100644 --- a/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java +++ b/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java @@ -47,7 +47,7 @@ * *

* This class is not THREAD-SAFE, each HTTP request gets its own copy, but if there are multiple - * threads witch accesses this context within the HTTP Request, then the caller is responsible + * threads which accesses this context within the HTTP Request, then the caller is responsible * for the synchronization. Only request scoped components need to be synchronized - they are * potentially lazy initialized. */ @@ -107,7 +107,7 @@ public interface OtpServerRequestContext { TileRendererManager tileRendererManager(); /** - * Callback witch is injected into the {@code DirectStreetRouter}, used to visualize the + * Callback which is injected into the {@code DirectStreetRouter}, used to visualize the * search. */ @HttpRequestScoped diff --git a/src/main/java/org/opentripplanner/standalone/config/framework/json/EnumMapper.java b/src/main/java/org/opentripplanner/standalone/config/framework/json/EnumMapper.java index 1bad7033104..ce880058005 100644 --- a/src/main/java/org/opentripplanner/standalone/config/framework/json/EnumMapper.java +++ b/src/main/java/org/opentripplanner/standalone/config/framework/json/EnumMapper.java @@ -31,7 +31,7 @@ public static String kebabCase(String input) { } /** - * Used to create a list of all values with description of each value witch can be included + * Used to create a list of all values with description of each value which can be included * in documentation. The list will look like this: * ``` * - `on` Turn on. diff --git a/src/main/java/org/opentripplanner/transit/model/framework/TransitBuilder.java b/src/main/java/org/opentripplanner/transit/model/framework/TransitBuilder.java index bd18690d9a3..52fd68165bc 100644 --- a/src/main/java/org/opentripplanner/transit/model/framework/TransitBuilder.java +++ b/src/main/java/org/opentripplanner/transit/model/framework/TransitBuilder.java @@ -5,7 +5,7 @@ public interface TransitBuilder, B extends Transit * Build a new object based on the values set in the builder. This method is NOT context aware - * any context is not updated. Use the {@link TransitEntityBuilder#save()} method instead to * build an object and store it in the context. This method is useful if you need to build an - * object witch should be request scoped or used in a test. + * object which should be request scoped or used in a test. *

* For value objects are stored as "part of" an entity, but OTP tries to reuse objects using the * {@code Deduplicator}. This method may or may not be context aware, using a deduplicator to diff --git a/src/main/java/org/opentripplanner/transit/model/network/RoutingTripPattern.java b/src/main/java/org/opentripplanner/transit/model/network/RoutingTripPattern.java index 0d319af0529..00033b5f798 100644 --- a/src/main/java/org/opentripplanner/transit/model/network/RoutingTripPattern.java +++ b/src/main/java/org/opentripplanner/transit/model/network/RoutingTripPattern.java @@ -12,7 +12,7 @@ * - The RTP is accessed frequently during the Raptor search, and we want it to be as small as * possible to load/access it in the cache and CPU for performance reasons. * - Also, we deduplicate these so a RTP can be reused by more than one TP. - * - This also provide explicit documentation on witch fields are used during a search and which + * - This also provide explicit documentation on which fields are used during a search and which * are not. */ public class RoutingTripPattern implements DefaultTripPattern, Serializable { diff --git a/src/test/java/org/opentripplanner/_support/arch/ArchComponent.java b/src/test/java/org/opentripplanner/_support/arch/ArchComponent.java index a1261d09648..7273b3123e3 100644 --- a/src/test/java/org/opentripplanner/_support/arch/ArchComponent.java +++ b/src/test/java/org/opentripplanner/_support/arch/ArchComponent.java @@ -9,7 +9,7 @@ public interface ArchComponent { /** * ArchUnit cached set of classes in OTP. It takes a bit of time to build the set of * classes, so it is nice to avoid this for every test. ArchUnit also support JUnit5 - * with a @ArchTest annotation witch cache and inject the classes, but the test become + * with a @ArchTest annotation which cache and inject the classes, but the test become * slightly more complex by using it and chasing it here works fine. */ JavaClasses OTP_CLASSES = new ClassFileImporter() diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/F05_OnBoardAccessEgressAndTransfersTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/F05_OnBoardAccessEgressAndTransfersTest.java index 59d7dcd7d4e..72da3f71145 100644 --- a/src/test/java/org/opentripplanner/raptor/moduletests/F05_OnBoardAccessEgressAndTransfersTest.java +++ b/src/test/java/org/opentripplanner/raptor/moduletests/F05_OnBoardAccessEgressAndTransfersTest.java @@ -25,7 +25,7 @@ * FEATURE UNDER TEST *

* Raptor should be able to route Access arriving on-board and egress departing on-board connecting - * to transit by transfers. Access and egress witch arrive/depart at/from the same stops by + * to transit by transfers. Access and egress which arrive/depart at/from the same stops by * walking should not be possible. */ public class F05_OnBoardAccessEgressAndTransfersTest implements RaptorTestConstants { diff --git a/src/test/java/org/opentripplanner/transit/speed_test/SpeedTestRequest.java b/src/test/java/org/opentripplanner/transit/speed_test/SpeedTestRequest.java index 1bfe202f92f..2dfaad077c6 100644 --- a/src/test/java/org/opentripplanner/transit/speed_test/SpeedTestRequest.java +++ b/src/test/java/org/opentripplanner/transit/speed_test/SpeedTestRequest.java @@ -63,7 +63,7 @@ RouteRequest toRouteRequest() { request.setTo(input.toPlace()); // Filter the results inside the SpeedTest, not in the itineraries filter, - // when ignoring street results. This will use the default witch is 50. + // when ignoring street results. This will use the default which is 50. if (!config.ignoreStreetResults) { request.setNumItineraries(opts.numOfItineraries()); } diff --git a/src/test/java/org/opentripplanner/transit/speed_test/model/testcase/ExpectedResults.java b/src/test/java/org/opentripplanner/transit/speed_test/model/testcase/ExpectedResults.java index 1b14bc8fc12..eb66b3671e4 100644 --- a/src/test/java/org/opentripplanner/transit/speed_test/model/testcase/ExpectedResults.java +++ b/src/test/java/org/opentripplanner/transit/speed_test/model/testcase/ExpectedResults.java @@ -10,7 +10,7 @@ /** * This class contains the results for a given test case. A set of results * for each {@link SpeedTestProfile} is kept. A default set is also available, - * witch can be used if there is not set for a given profile. + * which can be used if there is not set for a given profile. */ public class ExpectedResults { From 8ce8061df23431709fac6968f1d740c930c51a7c Mon Sep 17 00:00:00 2001 From: Assad Riaz Date: Fri, 24 Nov 2023 20:25:54 +0100 Subject: [PATCH 48/85] Rename realtime to real-time in docs --- docs/Changelog.md | 36 +++++++++---------- docs/Configuration.md | 2 +- docs/Data-Sources.md | 2 +- docs/Deployments.md | 4 +-- docs/Netex-Norway.md | 10 +++--- docs/RouteRequest.md | 4 +-- docs/RouterConfiguration.md | 2 +- docs/SandboxExtension.md | 4 +-- docs/System-Requirements.md | 2 +- docs/examples/skanetrafiken/Readme.md | 10 +++--- .../examples/skanetrafiken/router-config.json | 2 +- docs/sandbox/MapboxVectorTilesApi.md | 8 ++--- 12 files changed, 43 insertions(+), 43 deletions(-) diff --git a/docs/Changelog.md b/docs/Changelog.md index bccd0c504d5..65b2650f38b 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -40,7 +40,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Interpolate increasing stop times for GTFS-RT cancelled trips [#5348](https://github.com/opentripplanner/OpenTripPlanner/pull/5348) - Remove itineraries outside the search window in arriveBy search [#5433](https://github.com/opentripplanner/OpenTripPlanner/pull/5433) - Add back walk-reluctance in Transmodel API [#5471](https://github.com/opentripplanner/OpenTripPlanner/pull/5471) -- Make `feedId` required for realtime updaters [#5502](https://github.com/opentripplanner/OpenTripPlanner/pull/5502) +- Make `feedId` required for real-time updaters [#5502](https://github.com/opentripplanner/OpenTripPlanner/pull/5502) - Fix serialization of `AtomicInteger` [#5508](https://github.com/opentripplanner/OpenTripPlanner/pull/5508) - Improve linking of fixed stops used by flex trips [#5503](https://github.com/opentripplanner/OpenTripPlanner/pull/5503) - Keep min transfer filter is not local to group-by-filters [#5436](https://github.com/opentripplanner/OpenTripPlanner/pull/5436) @@ -88,7 +88,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Graceful timeout error handling [#5130](https://github.com/opentripplanner/OpenTripPlanner/pull/5130) - Log http request headers - like correlationId [#5131](https://github.com/opentripplanner/OpenTripPlanner/pull/5131) - Fix vertex removal race condition [#5141](https://github.com/opentripplanner/OpenTripPlanner/pull/5141) -- Comment out replacing DSJ-ID from planned data with ID from realtime-data [#5140](https://github.com/opentripplanner/OpenTripPlanner/pull/5140) +- Comment out replacing DSJ-ID from planned data with ID from real-time-data [#5140](https://github.com/opentripplanner/OpenTripPlanner/pull/5140) - Remove San Francisco and vehicle rental fare calculators [#5145](https://github.com/opentripplanner/OpenTripPlanner/pull/5145) - Remove batch query from Transmodel API [#5147](https://github.com/opentripplanner/OpenTripPlanner/pull/5147) - Fix nullable absolute direction in GTFS GraphQL API [#5159](https://github.com/opentripplanner/OpenTripPlanner/pull/5159) @@ -186,7 +186,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Remove all edges from stop vertex in island pruning [#4846](https://github.com/opentripplanner/OpenTripPlanner/pull/4846) - Filter functionality for GroupOfLines/GroupOfRoutes in TransmodelAPI [#4812](https://github.com/opentripplanner/OpenTripPlanner/pull/4812) - Mapping for maxAccessEgressDurationPerMode in Transmodel API [#4829](https://github.com/opentripplanner/OpenTripPlanner/pull/4829) -- Use headsign from the original pattern in a realtime added pattern if the stop sequence is unchanged [#4845](https://github.com/opentripplanner/OpenTripPlanner/pull/4845) +- Use headsign from the original pattern in a real-time added pattern if the stop sequence is unchanged [#4845](https://github.com/opentripplanner/OpenTripPlanner/pull/4845) - Remove RouteMatcher [#4821](https://github.com/opentripplanner/OpenTripPlanner/pull/4821) - Improve boarding location linking on platforms [#4852](https://github.com/opentripplanner/OpenTripPlanner/pull/4852) - Always check allowed modes in VehicleRentalEdge [#4810](https://github.com/opentripplanner/OpenTripPlanner/pull/4810) @@ -234,7 +234,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Experimental support for GTFS Fares V2 [#4338](https://github.com/opentripplanner/OpenTripPlanner/pull/4338) - Document JVM configuration options [#4492](https://github.com/opentripplanner/OpenTripPlanner/pull/4492) - Support for HTTPS datasource for Graph Building [#4482](https://github.com/opentripplanner/OpenTripPlanner/pull/4482) -- Metrics for realtime trip updaters [#4471](https://github.com/opentripplanner/OpenTripPlanner/pull/4471) +- Metrics for real-time trip updaters [#4471](https://github.com/opentripplanner/OpenTripPlanner/pull/4471) - Configuration Documentation generated programmatically [#4478](https://github.com/opentripplanner/OpenTripPlanner/pull/4478) @@ -265,7 +265,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Enable overriding maxDirectStreetDuration per mode [#4104](https://github.com/opentripplanner/OpenTripPlanner/pull/4104) - Preserve language in SIRI/GTFS-RT alert messages [#4117](https://github.com/opentripplanner/OpenTripPlanner/pull/4117) - Use board/alight cost only for transits [#4079](https://github.com/opentripplanner/OpenTripPlanner/pull/4079) -- Improve SIRI realtime performance by reducing stopPattern duplicates [#4038](https://github.com/opentripplanner/OpenTripPlanner/pull/4038) +- Improve SIRI real-time performance by reducing stopPattern duplicates [#4038](https://github.com/opentripplanner/OpenTripPlanner/pull/4038) - Siri updaters for Azure ServiceBus [#4106](https://github.com/opentripplanner/OpenTripPlanner/pull/4106) - Fallback to recorded/expected arrival/departure time if other one is missing in SIRI-ET [#4055](https://github.com/opentripplanner/OpenTripPlanner/pull/4055) - Allow overriding GBFS system_id with configuration [#4147](https://github.com/opentripplanner/OpenTripPlanner/pull/4147) @@ -274,7 +274,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Don't indicate stop has been updated when NO_DATA is defined [#3962](https://github.com/opentripplanner/OpenTripPlanner/pull/3962) - Implement nearby searches for car and bicycle parking [#4165](https://github.com/opentripplanner/OpenTripPlanner/pull/4165) - Do not link cars to stop vertices in routing [#4166](https://github.com/opentripplanner/OpenTripPlanner/pull/4166) -- Add Siri realtime occupancy info [#4180](https://github.com/opentripplanner/OpenTripPlanner/pull/4180) +- Add Siri real-time occupancy info [#4180](https://github.com/opentripplanner/OpenTripPlanner/pull/4180) - Add gtfs stop description translations [#4158](https://github.com/opentripplanner/OpenTripPlanner/pull/4158) - Add option to discard min transfer times [#4195](https://github.com/opentripplanner/OpenTripPlanner/pull/4195) - Use negative delay from first stop in a GTFS RT update in previous stop times when required [#4035](https://github.com/opentripplanner/OpenTripPlanner/pull/4035) @@ -303,7 +303,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Account for boarding restrictions when calculating direct transfers [#4421](https://github.com/opentripplanner/OpenTripPlanner/pull/4421) - Configure the import of OSM extracts individually [#4419](https://github.com/opentripplanner/OpenTripPlanner/pull/4419) - Configure the import of elevation data individually [#4423](https://github.com/opentripplanner/OpenTripPlanner/pull/4423) -- Return typed errors from realtime updates, prepare for realtime statistics [#4424](https://github.com/opentripplanner/OpenTripPlanner/pull/4424) +- Return typed errors from real-time updates, prepare for real-time statistics [#4424](https://github.com/opentripplanner/OpenTripPlanner/pull/4424) - Add feature switch for matching ET messages on stops [#4347](https://github.com/opentripplanner/OpenTripPlanner/pull/4347) - Make safety defaults customizable for walking and cycling [#4438](https://github.com/opentripplanner/OpenTripPlanner/pull/4438) - Fix block-based interlining when importing several GTFS feeds [#4468](https://github.com/opentripplanner/OpenTripPlanner/pull/4468) @@ -341,7 +341,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle ### Detailed changes by Pull Request -- Fix NullPointerException when a RealTime update do not match an existing TripPattern [#3284](https://github.com/opentripplanner/OpenTripPlanner/issues/3284) +- Fix NullPointerException when a Real-Time update do not match an existing TripPattern [#3284](https://github.com/opentripplanner/OpenTripPlanner/issues/3284) - Support for versioning the configuration files [#3282](https://github.com/opentripplanner/OpenTripPlanner/issues/3282) - Prioritize "direct" routes over transfers in group-filters [#3309](https://github.com/opentripplanner/OpenTripPlanner/issues/3309) - Remove poor transit results for short trips, when walking is better [#3331](https://github.com/opentripplanner/OpenTripPlanner/issues/3331) @@ -428,7 +428,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Remove build parameter 'useTransfersTxt' [#3791](https://github.com/opentripplanner/OpenTripPlanner/pull/3791) - Add cursor-based paging [#3759](https://github.com/opentripplanner/OpenTripPlanner/pull/3759) - Data overlay sandbox feature [#3760](https://github.com/opentripplanner/OpenTripPlanner/pull/3760) -- Add support for sandboxed realtime vehicle parking updaters [#3796](https://github.com/opentripplanner/OpenTripPlanner/pull/3796) +- Add support for sandboxed real-time vehicle parking updaters [#3796](https://github.com/opentripplanner/OpenTripPlanner/pull/3796) - Add reading and exposing of Netex submodes [#3793](https://github.com/opentripplanner/OpenTripPlanner/pull/3793) - Fix: Account for wait-time in no-wait Raptor strategy [#3798](https://github.com/opentripplanner/OpenTripPlanner/pull/3798) - Read in flex window from Netex feeds [#3800](https://github.com/opentripplanner/OpenTripPlanner/pull/3800) @@ -438,7 +438,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Store stop indices in leg and use them to simplify logic in TripTimeShortHelper [#3820](https://github.com/opentripplanner/OpenTripPlanner/pull/3820) - Include all trips in `stopTimesForStop` [#3817](https://github.com/opentripplanner/OpenTripPlanner/pull/3817) - Store all alerts and add support for route_type and direction_id selectors [#3780](https://github.com/opentripplanner/OpenTripPlanner/pull/3780) -- Remove outdated realtime-update from TimetableSnapshot [#3770](https://github.com/opentripplanner/OpenTripPlanner/pull/3770) +- Remove outdated real-time-update from TimetableSnapshot [#3770](https://github.com/opentripplanner/OpenTripPlanner/pull/3770) - Contributing Guide [#3769](https://github.com/opentripplanner/OpenTripPlanner/pull/3769) - OTP support for NeTEx branding [#3829](https://github.com/opentripplanner/OpenTripPlanner/pull/3829) - Not allowed transfers and support for GTFS transfer points [#3792](https://github.com/opentripplanner/OpenTripPlanner/pull/3792) @@ -465,7 +465,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Optimize RAPTOR trip search by pre-calculating arrival/departure time arrays [#3919](https://github.com/opentripplanner/OpenTripPlanner/pull/3919) - Make turn restrictions faster and thread-safe by moving them into StreetEdge [#3899](https://github.com/opentripplanner/OpenTripPlanner/pull/3899) - Add routing using frequency trips [#3916](https://github.com/opentripplanner/OpenTripPlanner/pull/3916) -- Remove ET realtime override code [#3912](https://github.com/opentripplanner/OpenTripPlanner/pull/3912) +- Remove ET real-time override code [#3912](https://github.com/opentripplanner/OpenTripPlanner/pull/3912) - Allow traversal of pathways without traversal time, distance or steps [#3910](https://github.com/opentripplanner/OpenTripPlanner/pull/3910) @@ -495,8 +495,8 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Support for next/previous paging trip search results [#2941](https://github.com/opentripplanner/OpenTripPlanner/issues/2941) - Fix mismatch in duration for walk legs, resulting in negative wait times [#2955](https://github.com/opentripplanner/OpenTripPlanner/issues/2955) - NeTEx import now supports ServiceLinks [#2951](https://github.com/opentripplanner/OpenTripPlanner/issues/2951) -- Also check TripPatterns added by realtime when showing stoptimes for stop [#2954](https://github.com/opentripplanner/OpenTripPlanner/issues/2954) -- Copy geometries from previous TripPattern when realtime updates result in a TripPattern being replaced [#2987](https://github.com/opentripplanner/OpenTripPlanner/issues/2987) +- Also check TripPatterns added by real-time when showing stoptimes for stop [#2954](https://github.com/opentripplanner/OpenTripPlanner/issues/2954) +- Copy geometries from previous TripPattern when real-time updates result in a TripPattern being replaced [#2987](https://github.com/opentripplanner/OpenTripPlanner/issues/2987) - Support for the Norwegian language. - Update pathways support to official GTFS specification [#2923](https://github.com/opentripplanner/OpenTripPlanner/issues/2923) - Support for XML (de-)serialization is REMOVED from the REST API [#3031](https://github.com/opentripplanner/OpenTripPlanner/issues/3031) @@ -532,7 +532,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Remove the coupling to OneBusAway GTFS within OTP's internal model by creating new classes replacing the external classes [#2494](https://github.com/opentripplanner/OpenTripPlanner/issues/2494) - Allow itineraries in response to be sorted by duration [#2593](https://github.com/opentripplanner/OpenTripPlanner/issues/2593) - Fix reverse optimization bug #2653, #2411 -- increase GTFS-realtime feeds size limit from 64MB to 2G [#2738](https://github.com/opentripplanner/OpenTripPlanner/issues/2738) +- increase GTFS-real-time feeds size limit from 64MB to 2G [#2738](https://github.com/opentripplanner/OpenTripPlanner/issues/2738) - Fix XML response serialization [#2685](https://github.com/opentripplanner/OpenTripPlanner/issues/2685) - Refactor InterleavedBidirectionalHeuristic [#2671](https://github.com/opentripplanner/OpenTripPlanner/issues/2671) - Add "Accept" headers to GTFS-RT HTTP requests [#2796](https://github.com/opentripplanner/OpenTripPlanner/issues/2796) @@ -544,7 +544,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Support OSM highway=razed tag [#2660](https://github.com/opentripplanner/OpenTripPlanner/issues/2660) - Add bicimad bike rental updater [#2503](https://github.com/opentripplanner/OpenTripPlanner/issues/2503) - Add Smoove citybikes updater [#2515](https://github.com/opentripplanner/OpenTripPlanner/issues/2515) -- Allow big GTFS-realtime feeds by increasing protobuf size limit to 2G [#2739](https://github.com/opentripplanner/OpenTripPlanner/issues/2739) +- Allow big GTFS-real-time feeds by increasing protobuf size limit to 2G [#2739](https://github.com/opentripplanner/OpenTripPlanner/issues/2739) - Cannot transfer between stops at exactly the same location [#2371](https://github.com/opentripplanner/OpenTripPlanner/issues/2371) - Improve documentation for `mode` routing parameter [#2809](https://github.com/opentripplanner/OpenTripPlanner/issues/2809) - Switched to single license file, removing all OTP and OBA file license headers @@ -781,7 +781,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Disable some time consuming graph building steps by default - Finnish and Swedish translations - Subway-specific JSON configuration options (street to platform time) -- Realtime fetch / streaming configurable via JSON +- Real-time fetch / streaming configurable via JSON - Stairs reluctance is much higher when carrying a bike - Graph visualizer routing progress animates when a search is triggered via the web API - Assume WGS84 (spherical distance calculations) everywhere @@ -849,7 +849,7 @@ represents a much earlier stage in the development of OTP. ## 0.7.0 (2012-04-29) - Bike rental support (thanks Laurent Grégoire) -- Realtime bike rental availability feed support +- Real-time bike rental availability feed support - Updated to new version of One Bus Away GTFS/CSV, fixing timezone and string interning issues (thanks Brian Ferris) - Bugfixes in area routing, OSM loading, nonexistant NED tiles, route short names - Dutch and French language updates @@ -876,7 +876,7 @@ represents a much earlier stage in the development of OTP. - git commit IDs included in MavenVersion, allowing clearer OTP/Graph version mismatch warnings - fix problems with immediate reboarding and unexpected edges in itinerary builder - favicon (thanks Joel Haasnoot) -- Legs in API response have TripId (for realtime information) +- Legs in API response have TripId (for real-time information) - Polish locale (thanks Łukasz Witkowski) - transfers.txt can define station paths, entry costs for stations - allow loading a base graph into graphbuilder instead of starting from scratch diff --git a/docs/Configuration.md b/docs/Configuration.md index bfd168d5955..6da188c7a1d 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -241,7 +241,7 @@ Here is a list of all features which can be toggled on/off and their default val | `FaresV2` | Enable import of GTFS-Fares v2 data. | | ✓️ | | `FlexRouting` | Enable FLEX routing. | | ✓️ | | `GoogleCloudStorage` | Enable Google Cloud Storage integration. | | ✓️ | -| `RealtimeResolver` | When routing with ignoreRealtimeUpdates=true, add an extra step which populates results with realtime data | | ✓️ | +| `RealtimeResolver` | When routing with ignoreRealtimeUpdates=true, add an extra step which populates results with real-time data | | ✓️ | | `ReportApi` | Enable the report API. | | ✓️ | | `RestAPIPassInDefaultConfigAsJson` | Enable a default RouteRequest to be passed in as JSON on the REST API - FOR DEBUGGING ONLY! | | | | `SandboxAPIGeocoder` | Enable the Geocoder API. | | ✓️ | diff --git a/docs/Data-Sources.md b/docs/Data-Sources.md index 317e0a1f2f7..bf426186f57 100644 --- a/docs/Data-Sources.md +++ b/docs/Data-Sources.md @@ -5,7 +5,7 @@ At the core of OpenTripPlanner is a library of Java code that finds efficient paths through multi-modal transportation networks built from [OpenStreetMap](http://wiki.openstreetmap.org/wiki/Main_Page) -and [GTFS](https://developers.google.com/transit/gtfs/) data. It can also receive GTFS-RT (realtime) +and [GTFS](https://developers.google.com/transit/gtfs/) data. It can also receive GTFS-RT (real-time) data. In addition to GTFS, OTP can also load data in the Nordic Profile of Netex, the EU-standard transit diff --git a/docs/Deployments.md b/docs/Deployments.md index f2946a9f0ba..d1df3984b05 100644 --- a/docs/Deployments.md +++ b/docs/Deployments.md @@ -6,7 +6,7 @@ The following are known deployments of OTP in a government- or agency-sponsored * **Norway (nationwide)** Since November 2017, the national integrated ticketing agency Entur has prodvided a [national journey planner](https://en-tur.no/) which consumes schedule data in the EU - standard NeTEx format with SIRI realtime updates. Entur has contributed greatly to the OTP2 effort + standard NeTEx format with SIRI real-time updates. Entur has contributed greatly to the OTP2 effort and primarily uses OTP2 in production, handling peak loads in excess of 20 requests per second. Most regional agencies in Norway, like **Ruter, Oslo area** uses OTP as a service provided by Entur. * **Finland (nationwide)** The [Helsinki Regional Transport Authority](https://www.reittiopas.fi/), @@ -22,7 +22,7 @@ The following are known deployments of OTP in a government- or agency-sponsored service [Matkahuolto](https://en.wikipedia.org/wiki/Matkahuolto) has [developed a trip planner in partnership with Kyyti](https://www.kyyti.com/matkahuoltos-new-app-brings-real-travel-chains-within-the-reach-of-citizens-in-addition-to-coach-travel-hsl-tickets-are-also-available/). * **Skåne, Sweden**, the JourneyPlanner and mobile app for the regional transit agency [Skånetrafiken](https://www.skanetrafiken.se/) - uses OTP2 with the nordic profile of NeTEx and SIRI for realtime updates. + uses OTP2 with the nordic profile of NeTEx and SIRI for real-time updates. * [**Northern Colorado**](https://discover.rideno.co/) * [**Philadelphia and surrounding areas**](https://plan.septa.org) * **Portland, Oregon** TriMet is the agency that originally started the OpenTripPlanner project. diff --git a/docs/Netex-Norway.md b/docs/Netex-Norway.md index 6157849bedc..0a447237592 100644 --- a/docs/Netex-Norway.md +++ b/docs/Netex-Norway.md @@ -85,11 +85,11 @@ using OTP's built in testing web client. Try some long trips like Oslo to Bergen get long distance trains and flights as alternatives. You might need to increase the walking limit above its very low default value. -## Adding SIRI Realtime Data +## Adding SIRI Real-time Data Another important feature in OTP2 is the ability to -use [SIRI realtime data](https://en.wikipedia.org/wiki/Service_Interface_for_Real_Time_Information). -Within the EU data standards, SIRI is analogous to GTFS-RT: a way to apply realtime updates on top +use [SIRI real-time data](https://en.wikipedia.org/wiki/Service_Interface_for_Real_Time_Information). +Within the EU data standards, SIRI is analogous to GTFS-RT: a way to apply real-time updates on top of schedule data. While technically a distinct specification from Netex, both Netex and SIRI use the Transmodel vocabulary, allowing SIRI messages to reference entities in Netex schedule data. Like GTFS-RT, SIRI is consumed by OTP2 using "graph updaters" which are configured in @@ -143,6 +143,6 @@ Note that between these SIRI updaters and the GTFS-RT Websocket updater, we now and streaming examples of GTFS-RT "incrementality" semantics, so should be able to finalize that part of the specification. -The final updater regularly performs a copy of the realtime data into a format suitable for use by -OTP2's new Raptor router. Without this updater the realtime data will be received and cataloged, but +The final updater regularly performs a copy of the real-time data into a format suitable for use by +OTP2's new Raptor router. Without this updater the real-time data will be received and cataloged, but not visible to the router. diff --git a/docs/RouteRequest.md b/docs/RouteRequest.md index 28c49764810..6825ee67183 100644 --- a/docs/RouteRequest.md +++ b/docs/RouteRequest.md @@ -47,7 +47,7 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe | elevatorHopTime | `integer` | How long does it take to advance one floor on an elevator? | *Optional* | `20` | 2.0 | | escalatorReluctance | `double` | A multiplier for how bad being in an escalator is compared to being in transit for equal lengths of time | *Optional* | `1.5` | 2.4 | | geoidElevation | `boolean` | If true, the Graph's ellipsoidToGeoidDifference is applied to all elevations returned by this query. | *Optional* | `false` | 2.0 | -| ignoreRealtimeUpdates | `boolean` | When true, realtime updates are ignored during this search. | *Optional* | `false` | 2.0 | +| ignoreRealtimeUpdates | `boolean` | When true, real-time updates are ignored during this search. | *Optional* | `false` | 2.0 | | [intersectionTraversalModel](#rd_intersectionTraversalModel) | `enum` | The model that computes the costs of turns. | *Optional* | `"simple"` | 2.2 | | locale | `locale` | TODO | *Optional* | `"en_US"` | 2.0 | | [maxDirectStreetDuration](#rd_maxDirectStreetDuration) | `duration` | This is the maximum duration for a direct street search for each mode. | *Optional* | `"PT4H"` | 2.1 | @@ -297,7 +297,7 @@ Raptor iteration. The value is dynamically assigned a suitable value, if not se medium size operation you may use a fixed value, like 60 minutes. If you have a mixture of high frequency cities routes and infrequent long distant journeys, the best option is normally to use the dynamic auto assignment. If not provided the value is resolved depending on the other input -parameters, available transit options and realtime changes. +parameters, available transit options and real-time changes. There is no need to set this when going to the next/previous page. The OTP Server will increase/decrease the search-window when paging to match the requested number of itineraries. diff --git a/docs/RouterConfiguration.md b/docs/RouterConfiguration.md index d0a58384819..34a3db8f6c6 100644 --- a/docs/RouterConfiguration.md +++ b/docs/RouterConfiguration.md @@ -46,7 +46,7 @@ A full list of them can be found in the [RouteRequest](RouteRequest.md). |          [logKey](#server_traceParameters_0_logKey) | `string` | The log event key used. | *Optional* | | 2.4 | | timetableUpdates | `object` | Global configuration for timetable updaters. | *Optional* | | 2.2 | |    [maxSnapshotFrequency](#timetableUpdates_maxSnapshotFrequency) | `duration` | How long a snapshot should be cached. | *Optional* | `"PT1S"` | 2.2 | -|    purgeExpiredData | `boolean` | Should expired realtime data be purged from the graph. Apply to GTFS-RT and Siri updates. | *Optional* | `true` | 2.2 | +|    purgeExpiredData | `boolean` | Should expired real-time data be purged from the graph. Apply to GTFS-RT and Siri updates. | *Optional* | `true` | 2.2 | | [transit](#transit) | `object` | Configuration for transit searches with RAPTOR. | *Optional* | | na | |    [iterationDepartureStepInSeconds](#transit_iterationDepartureStepInSeconds) | `integer` | Step for departure times between each RangeRaptor iterations. | *Optional* | `60` | na | |    [maxNumberOfTransfers](#transit_maxNumberOfTransfers) | `integer` | This parameter is used to allocate enough memory space for Raptor. | *Optional* | `12` | na | diff --git a/docs/SandboxExtension.md b/docs/SandboxExtension.md index 55ee214979a..4a3f500ae80 100644 --- a/docs/SandboxExtension.md +++ b/docs/SandboxExtension.md @@ -15,8 +15,8 @@ provided "as is". - [Transfer analyser](sandbox/transferanalyzer.md) - Module used for analyzing the transfers between nearby stops generated by routing via OSM data. - [Transmodel API](sandbox/TransmodelApi.md) - Enturs GraphQL Transmodel API. -- [SIRI Updater](sandbox/SiriUpdater.md) - Update OTP with realtime information from a Transmodel SIRI data source. -- [SIRI Azure Updater](sandbox/SiriAzureUpdater.md) - fetch SIRI realtime data through *Azure Service Bus* +- [SIRI Updater](sandbox/SiriUpdater.md) - Update OTP with real-time information from a Transmodel SIRI data source. +- [SIRI Azure Updater](sandbox/SiriAzureUpdater.md) - fetch SIRI real-time data through *Azure Service Bus* - [VehicleRentalServiceDirectory](sandbox/VehicleRentalServiceDirectory.md) - GBFS service directory endpoint. - [Smoove Bike Rental Updator Support](sandbox/SmooveBikeRental.md) - Smoove Bike Rental Updator(HSL) - [Mapbox Vector Tiles API](sandbox/MapboxVectorTilesApi.md) - Mapbox Vector Tiles API diff --git a/docs/System-Requirements.md b/docs/System-Requirements.md index 133370b8ede..387bba0cd03 100644 --- a/docs/System-Requirements.md +++ b/docs/System-Requirements.md @@ -10,7 +10,7 @@ OTP is relatively memory-hungry as it includes all the required data in memory. Single thread performance is an important factor for OTP's performance. Additionally, OTP benefits from larger CPU cache as reading from memory can be a bottleneck. -OTP's performance scales with the number of available CPU cores. OTP processes each request in a separate thread and usually one request doesn't utilize more than one thread, but with some requests and configurations, it's possible that multiple threads are used in parallel for a small part of a request or to process multiple queries within one request. How much parallel processing we utilize in requests might change in the future. Realtime updates also run in a separate thread. Therefore, to have a good performance, it makes sense to have multiple cores available. How OTP uses parallel processing also depends on the available cores (<= 2 cores vs >2 cores) in some cases. Therefore, load testing should be done against a machine that doesn't differ too much from production machines. +OTP's performance scales with the number of available CPU cores. OTP processes each request in a separate thread and usually one request doesn't utilize more than one thread, but with some requests and configurations, it's possible that multiple threads are used in parallel for a small part of a request or to process multiple queries within one request. How much parallel processing we utilize in requests might change in the future. Real-time updates also run in a separate thread. Therefore, to have a good performance, it makes sense to have multiple cores available. How OTP uses parallel processing also depends on the available cores (<= 2 cores vs >2 cores) in some cases. Therefore, load testing should be done against a machine that doesn't differ too much from production machines. Entur and the Digitransit project have found that the 3rd generation AMD processors have a slightly better performance for OTP2 than the Intel 3rd generation CPUs (and especially better than the 2nd generation CPUs). diff --git a/docs/examples/skanetrafiken/Readme.md b/docs/examples/skanetrafiken/Readme.md index ab7ef5f33a6..fc342f4192b 100644 --- a/docs/examples/skanetrafiken/Readme.md +++ b/docs/examples/skanetrafiken/Readme.md @@ -35,13 +35,13 @@ To reduced graph size, only data for southern part of Sweden is used. OSM data is downloaded from `http://download.geofabrik.de/europe/denmark-latest.osm.pbf`. To reduce graph size, only data for northern part of Denmark is used. -## Realtime +## Real-time -The **Azure Service Bus** is used to propagate SIRI SX and ET realtime messages to OTP. +The **Azure Service Bus** is used to propagate SIRI SX and ET real-time messages to OTP. This is solved through Siri Azure updaters that Skånetrafiken had implemented in OTP. There are separate updaters for SIRI SX and ET. Those updaters are used to provide data for Swedish traffic (NeTEx). Right now, there is no -connection to any realtime source for danish traffic (GTFS data). +connection to any real-time source for danish traffic (GTFS data). Except for receiving messages from **Service Bus** there are two endpoints through which historical ET and SX messages can be downloaded at OTP startup. @@ -54,8 +54,8 @@ the subscription. Once the updaters are done with processing of history messages they will change their status to primed, and the system will start channeling request to this OTP instance. -This ensures that no realtime message is omitted and all OTP instance that ran in the -cluster does have exact same realtime data. +This ensures that no real-time message is omitted and all OTP instance that ran in the +cluster does have exact same real-time data. Thi means that no matter which instance the client is hitting it will always get the same search results. diff --git a/docs/examples/skanetrafiken/router-config.json b/docs/examples/skanetrafiken/router-config.json index 9cc8b659734..ddec543e516 100644 --- a/docs/examples/skanetrafiken/router-config.json +++ b/docs/examples/skanetrafiken/router-config.json @@ -33,7 +33,7 @@ "fuzzyTripMatching": false, "history": { "url": "", - // Get all realtime history for current operating day date + // Get all real-time history for current operating day date "fromDateTime": "-P0D", "timeout": 300000 } diff --git a/docs/sandbox/MapboxVectorTilesApi.md b/docs/sandbox/MapboxVectorTilesApi.md index a503e6a839d..8ef8ee179e7 100644 --- a/docs/sandbox/MapboxVectorTilesApi.md +++ b/docs/sandbox/MapboxVectorTilesApi.md @@ -80,7 +80,7 @@ The feature must be configured in `router-config.json` as follows "minZoom": 14, "cacheMaxSeconds": 600 }, - // Contains just stations and realtime information for them + // Contains just stations and real-time information for them { "name": "realtimeRentalStations", "type": "VehicleRentalStation", @@ -90,7 +90,7 @@ The feature must be configured in `router-config.json` as follows "cacheMaxSeconds": 60 }, // This exists for backwards compatibility. At some point, we might want - // to add a new realtime parking mapper with better translation support + // to add a new real-time parking mapper with better translation support // and less unnecessary fields. { "name": "stadtnaviVehicleParking", @@ -101,7 +101,7 @@ The feature must be configured in `router-config.json` as follows "cacheMaxSeconds": 60, "expansionFactor": 0.25 }, - // no realtime, translatable fields are translated based on accept-language header + // no real-time, translatable fields are translated based on accept-language header // and contains less fields than the Stadtnavi mapper { "name": "vehicleParking", @@ -190,4 +190,4 @@ key, and a function to create the mapper, with a `Graph` object as a parameter, * Translatable fields are now translated based on accept-language header * Added DigitransitRealtime for vehicle rental stations * Changed old vehicle parking mapper to be Stadtnavi - * Added a new Digitransit vehicle parking mapper with no realtime information and less fields + * Added a new Digitransit vehicle parking mapper with no real-time information and less fields From 053f87572b2c733d9624bc96e19b331ede0a5ff5 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Sun, 26 Nov 2023 19:01:21 +0100 Subject: [PATCH 49/85] Correct cron expression --- .github/workflows/prune-container-images.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/prune-container-images.yml b/.github/workflows/prune-container-images.yml index de5954b9ba9..518e8afb67b 100644 --- a/.github/workflows/prune-container-images.yml +++ b/.github/workflows/prune-container-images.yml @@ -2,7 +2,7 @@ name: 'Delete unused snapshot container images' on: schedule: - - cron: '0 12 1 * *' + - cron: '0 12 * * 1' workflow_dispatch: jobs: From c93f528f7a8cf824e91d1da5e9fa1a34795b4864 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 02:08:16 +0000 Subject: [PATCH 50/85] chore(deps): update dependency org.codehaus.mojo:build-helper-maven-plugin to v3.5.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1632ab7fea7..4b470516a4e 100644 --- a/pom.xml +++ b/pom.xml @@ -209,7 +209,7 @@ org.codehaus.mojo build-helper-maven-plugin - 3.4.0 + 3.5.0 build-helper-generate-sources From 22e3edacab1b47ff5277a979768c10f5232ff343 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 28 Nov 2023 01:52:04 +0000 Subject: [PATCH 51/85] fix(deps): update dependency ch.qos.logback:logback-classic to v1.4.12 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4b470516a4e..8cb3ff458b1 100644 --- a/pom.xml +++ b/pom.xml @@ -65,7 +65,7 @@ 5.10.1 1.11.5 5.5.3 - 1.4.11 + 1.4.12 9.8.0 2.0.9 2.0.15 From 8588bf6e37fb16ceda7126cdca8ad4a3b4e6039c Mon Sep 17 00:00:00 2001 From: sharhio Date: Tue, 28 Nov 2023 16:54:24 +0200 Subject: [PATCH 52/85] Emissions filter returned after erroneous deletion --- .../algorithm/mapping/RouteRequestToFilterChainMapper.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java index 5c105f4804a..e86249f6fc8 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java @@ -4,9 +4,11 @@ import java.time.Instant; import java.util.List; import java.util.function.Consumer; +import org.opentripplanner.ext.emissions.EmissionsFilter; import org.opentripplanner.ext.fares.FaresFilter; import org.opentripplanner.ext.ridehailing.RideHailingFilter; import org.opentripplanner.ext.stopconsolidation.ConsolidatedStopNameFilter; +import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.routing.algorithm.filterchain.GroupBySimilarity; import org.opentripplanner.routing.algorithm.filterchain.ItineraryListFilterChain; import org.opentripplanner.routing.algorithm.filterchain.ItineraryListFilterChainBuilder; @@ -107,6 +109,10 @@ public static ItineraryListFilterChain createFilterChain( ); } + if (OTPFeature.Co2Emissions.isOn() && context.emissionsService() != null) { + builder.withEmissions(new EmissionsFilter(context.emissionsService())); + } + if (context.stopConsolidationService() != null) { builder.withStopConsolidationFilter( new ConsolidatedStopNameFilter(context.stopConsolidationService()) From 0ef56416887f4c14a2d6eb36d324b59abd726c7d Mon Sep 17 00:00:00 2001 From: Henrik Abrahamsson Date: Tue, 28 Nov 2023 13:26:18 +0100 Subject: [PATCH 53/85] Add TripOnServiceDate to TransitModel in siri AddedTripBuilder --- .../ext/siri/AddedTripBuilderTest.java | 38 ++++++++++---- .../ext/siri/AddedTripBuilder.java | 52 ++++++++++++++++++- .../ext/transmodelapi/model/plan/LegType.java | 12 +---- 3 files changed, 81 insertions(+), 21 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/siri/AddedTripBuilderTest.java b/src/ext-test/java/org/opentripplanner/ext/siri/AddedTripBuilderTest.java index 8da3e118d12..b7431c16091 100644 --- a/src/ext-test/java/org/opentripplanner/ext/siri/AddedTripBuilderTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/siri/AddedTripBuilderTest.java @@ -32,6 +32,7 @@ import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.model.timetable.RealTimeState; import org.opentripplanner.transit.model.timetable.Trip; +import org.opentripplanner.transit.model.timetable.TripIdAndServiceDate; import org.opentripplanner.transit.service.DefaultTransitService; import org.opentripplanner.transit.service.StopModel; import org.opentripplanner.transit.service.TransitModel; @@ -124,7 +125,8 @@ void testAddedTrip() { null, false, SHORT_NAME, - HEADSIGN + HEADSIGN, + List.of() ) .build(); @@ -172,6 +174,16 @@ void testAddedTrip() { .contains(TRANSIT_MODEL.getServiceCodes().get(trip.getServiceId())), "serviceId should be running on service date" ); + assertNotNull( + transitModelIndex.getTripOnServiceDateById().get(TRIP_ID), + "TripOnServiceDate should be added to transit index by id" + ); + assertNotNull( + transitModelIndex + .getTripOnServiceDateForTripAndDay() + .get(new TripIdAndServiceDate(TRIP_ID, SERVICE_DATE)), + "TripOnServiceDate should be added to transit index for trip and day" + ); // Assert stop pattern var stopPattern = addedTrip.successValue().stopPattern(); @@ -242,7 +254,8 @@ void testAddedTripOnAddedRoute() { null, false, SHORT_NAME, - HEADSIGN + HEADSIGN, + List.of() ) .build(); @@ -267,7 +280,8 @@ void testAddedTripOnAddedRoute() { null, false, SHORT_NAME, - HEADSIGN + HEADSIGN, + List.of() ) .build(); @@ -317,7 +331,8 @@ void testAddedTripOnExistingRoute() { null, false, SHORT_NAME, - HEADSIGN + HEADSIGN, + List.of() ) .build(); @@ -347,7 +362,8 @@ void testAddedTripWithoutReplacedRoute() { null, false, SHORT_NAME, - HEADSIGN + HEADSIGN, + List.of() ) .build(); @@ -389,7 +405,8 @@ void testAddedTripFailOnMissingServiceId() { null, false, SHORT_NAME, - HEADSIGN + HEADSIGN, + List.of() ) .build(); @@ -443,7 +460,8 @@ void testAddedTripFailOnNonIncreasingDwellTime() { null, false, SHORT_NAME, - HEADSIGN + HEADSIGN, + List.of() ) .build(); @@ -481,7 +499,8 @@ void testAddedTripFailOnTooFewCalls() { null, false, SHORT_NAME, - HEADSIGN + HEADSIGN, + List.of() ) .build(); @@ -527,7 +546,8 @@ void testAddedTripFailOnUnknownStop() { null, false, SHORT_NAME, - HEADSIGN + HEADSIGN, + List.of() ) .build(); diff --git a/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java b/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java index 2d7789422ea..257639d7873 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java +++ b/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java @@ -28,6 +28,8 @@ import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.model.timetable.RealTimeState; import org.opentripplanner.transit.model.timetable.Trip; +import org.opentripplanner.transit.model.timetable.TripIdAndServiceDate; +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; @@ -40,6 +42,7 @@ import uk.org.siri.siri20.EstimatedVehicleJourney; import uk.org.siri.siri20.NaturalLanguageStringStructure; import uk.org.siri.siri20.OccupancyEnumeration; +import uk.org.siri.siri20.VehicleJourneyRef; import uk.org.siri.siri20.VehicleModesEnumeration; class AddedTripBuilder { @@ -62,6 +65,7 @@ class AddedTripBuilder { private final boolean cancellation; private final String shortName; private final String headsign; + private final List replacedTrips; AddedTripBuilder( EstimatedVehicleJourney estimatedVehicleJourney, @@ -108,6 +112,8 @@ class AddedTripBuilder { this.entityResolver = entityResolver; this.getTripPatternId = getTripPatternId; timeZone = transitModel.getTimeZone(); + + replacedTrips = getReplacedVehicleJourneys(estimatedVehicleJourney); } AddedTripBuilder( @@ -126,7 +132,8 @@ class AddedTripBuilder { OccupancyEnumeration occupancy, boolean cancellation, String shortName, - String headsign + String headsign, + List replacedTrips ) { this.transitModel = transitModel; this.entityResolver = entityResolver; @@ -145,6 +152,7 @@ class AddedTripBuilder { this.cancellation = cancellation; this.shortName = shortName; this.headsign = headsign; + this.replacedTrips = replacedTrips; } Result build() { @@ -236,11 +244,26 @@ Result build() { return TripTimesValidationMapper.toResult(tripId, validityResult.get()); } + var tripOnServiceDate = TripOnServiceDate + .of(tripId) + .withTrip(trip) + .withServiceDate(serviceDate) + .withReplacementFor(replacedTrips) + .build(); + // Adding trip to index necessary to include values in graphql-queries // TODO - SIRI: should more data be added to index? transitModel.getTransitModelIndex().getTripForId().put(tripId, trip); transitModel.getTransitModelIndex().getPatternForTrip().put(trip, pattern); transitModel.getTransitModelIndex().getPatternsForRoute().put(route, pattern); + transitModel + .getTransitModelIndex() + .getTripOnServiceDateById() + .put(tripOnServiceDate.getId(), tripOnServiceDate); + transitModel + .getTransitModelIndex() + .getTripOnServiceDateForTripAndDay() + .put(new TripIdAndServiceDate(tripId, serviceDate), tripOnServiceDate); return Result.success(new TripUpdate(stopPattern, updatedTripTimes, serviceDate)); } @@ -394,4 +417,31 @@ static String resolveTransitSubMode(TransitMode transitMode, Route replacedRoute default -> null; }; } + + private List getReplacedVehicleJourneys( + EstimatedVehicleJourney estimatedVehicleJourney + ) { + List listOfReplacedVehicleJourneys = new ArrayList<>(); + + // VehicleJourneyRef is the reference to the serviceJourney being replaced. + VehicleJourneyRef vehicleJourneyRef = estimatedVehicleJourney.getVehicleJourneyRef(); // getVehicleJourneyRef + if (vehicleJourneyRef != null) { + var replacedDatedServiceJourney = entityResolver.resolveTripOnServiceDate( + vehicleJourneyRef.getValue() + ); + if (replacedDatedServiceJourney != null) { + listOfReplacedVehicleJourneys.add(replacedDatedServiceJourney); + } + } + + // Add additional replaced service journeys if present. + estimatedVehicleJourney + .getAdditionalVehicleJourneyReves() + .stream() + .map(entityResolver::resolveTripOnServiceDate) + .filter(Objects::nonNull) + .forEach(listOfReplacedVehicleJourneys::add); + + return listOfReplacedVehicleJourneys; + } } diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/LegType.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/LegType.java index d233aaec3c2..74c84c79a87 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/LegType.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/LegType.java @@ -308,17 +308,7 @@ public static GraphQLObjectType create( .name("datedServiceJourney") .description("The dated service journey used for this leg.") .type(datedServiceJourneyType) - .dataFetcher(env -> { - var trip = leg(env).getTrip(); - if (trip == null) { - return null; - } - return GqlUtil - .getTransitService(env) - .getTripOnServiceDateForTripAndDay( - new TripIdAndServiceDate(leg(env).getTrip().getId(), leg(env).getServiceDate()) - ); - }) + .dataFetcher(env -> leg(env).getTripOnServiceDate()) .build() ) .field( From 04e369a3f4bbb01584e57482b0d83286f486be62 Mon Sep 17 00:00:00 2001 From: Henrik Abrahamsson Date: Tue, 28 Nov 2023 15:23:39 +0100 Subject: [PATCH 54/85] Remove unused code relating to realtime added TripOnServiceDate --- .../ext/siri/EntityResolver.java | 45 ------------------- .../model/TimetableSnapshot.java | 23 ---------- .../service/DefaultTransitService.java | 18 -------- 3 files changed, 86 deletions(-) diff --git a/src/ext/java/org/opentripplanner/ext/siri/EntityResolver.java b/src/ext/java/org/opentripplanner/ext/siri/EntityResolver.java index a711d395f40..5ca467ad1d5 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/EntityResolver.java +++ b/src/ext/java/org/opentripplanner/ext/siri/EntityResolver.java @@ -238,51 +238,6 @@ public LocalDate resolveServiceDate(EstimatedVehicleJourney vehicleJourney) { return date.toLocalDate().minusDays(daysOffset); } - TripOnServiceDateBuilder createTripOnServiceDateBuilder( - EstimatedVehicleJourney estimatedVehicleJourney - ) { - var datedServiceJourneyId = resolveDatedServiceJourneyId(estimatedVehicleJourney); - - if (datedServiceJourneyId == null) { - if (estimatedVehicleJourney.getFramedVehicleJourneyRef() != null) { - var tripOnDate = resolveTripOnServiceDate( - estimatedVehicleJourney.getFramedVehicleJourneyRef() - ); - if (tripOnDate == null) { - return null; - } - datedServiceJourneyId = tripOnDate.getId(); - } - } - - if (datedServiceJourneyId == null) { - return null; - } - - List listOfReplacedVehicleJourneys = new ArrayList<>(); - - // VehicleJourneyRef is the reference to the serviceJourney being replaced. - VehicleJourneyRef vehicleJourneyRef = estimatedVehicleJourney.getVehicleJourneyRef(); - if (vehicleJourneyRef != null) { - var replacedDatedServiceJourney = resolveTripOnServiceDate(vehicleJourneyRef.getValue()); - if (replacedDatedServiceJourney != null) { - listOfReplacedVehicleJourneys.add(replacedDatedServiceJourney); - } - } - - // Add additional replaced service journeys if present. - estimatedVehicleJourney - .getAdditionalVehicleJourneyReves() - .stream() - .map(this::resolveTripOnServiceDate) - .filter(Objects::nonNull) - .forEach(listOfReplacedVehicleJourneys::add); - - return TripOnServiceDate - .of(datedServiceJourneyId) - .withReplacementFor(listOfReplacedVehicleJourneys); - } - /** * Calculate the difference in days between the service date and the departure at the first stop. */ diff --git a/src/main/java/org/opentripplanner/model/TimetableSnapshot.java b/src/main/java/org/opentripplanner/model/TimetableSnapshot.java index e9d61d9021b..066a27ba15a 100644 --- a/src/main/java/org/opentripplanner/model/TimetableSnapshot.java +++ b/src/main/java/org/opentripplanner/model/TimetableSnapshot.java @@ -70,9 +70,6 @@ public class TimetableSnapshot { */ private HashMap realtimeAddedTripPattern = new HashMap<>(); - private HashMap realtimeAddedTripOnServiceDate = new HashMap<>(); - private HashMap realtimeAddedTripOnServiceDateByTripIdAndServiceDate = new HashMap<>(); - /** * This maps contains all of the new or updated TripPatterns added by realtime data indexed on * stop. This has to be kept in order for them to be included in the stop times api call on a @@ -269,10 +266,6 @@ public TimetableSnapshot commit(TransitLayerUpdater transitLayerUpdater, boolean transitLayerUpdater.update(dirtyTimetables, timetables); } - ret.realtimeAddedTripOnServiceDate = - (HashMap) this.realtimeAddedTripOnServiceDate.clone(); - ret.realtimeAddedTripOnServiceDateByTripIdAndServiceDate = - (HashMap) this.realtimeAddedTripOnServiceDateByTripIdAndServiceDate.clone(); this.dirtyTimetables.clear(); this.dirty = false; @@ -374,22 +367,6 @@ public void setPatternsForStop(SetMultimap patternsFo this.patternsForStop = patternsForStop; } - public void addLastAddedTripOnServiceDate(TripOnServiceDate tripOnServiceDate) { - realtimeAddedTripOnServiceDate.put(tripOnServiceDate.getId(), tripOnServiceDate); - realtimeAddedTripOnServiceDateByTripIdAndServiceDate.put( - tripOnServiceDate.getTripIdAndServiceDate(), - tripOnServiceDate - ); - } - - public HashMap getRealtimeAddedTripOnServiceDate() { - return realtimeAddedTripOnServiceDate; - } - - public HashMap getRealtimeAddedTripOnServiceDateByTripIdAndServiceDate() { - return realtimeAddedTripOnServiceDateByTripIdAndServiceDate; - } - /** * Clear timetable for all patterns matching the provided feed id. * diff --git a/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java b/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java index 41e28e616d8..0d08bcf34b2 100644 --- a/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java +++ b/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java @@ -465,15 +465,6 @@ private TimetableSnapshot lazyGetTimeTableSnapShot() { @Override public TripOnServiceDate getTripOnServiceDateById(FeedScopedId datedServiceJourneyId) { - TimetableSnapshot timetableSnapshot = lazyGetTimeTableSnapShot(); - if (timetableSnapshot != null) { - TripOnServiceDate tripOnServiceDate = timetableSnapshot - .getRealtimeAddedTripOnServiceDate() - .get(datedServiceJourneyId); - if (tripOnServiceDate != null) { - return tripOnServiceDate; - } - } return transitModelIndex.getTripOnServiceDateById().get(datedServiceJourneyId); } @@ -486,15 +477,6 @@ public Collection getAllTripOnServiceDates() { public TripOnServiceDate getTripOnServiceDateForTripAndDay( TripIdAndServiceDate tripIdAndServiceDate ) { - TimetableSnapshot timetableSnapshot = lazyGetTimeTableSnapShot(); - if (timetableSnapshot != null) { - TripOnServiceDate tripOnServiceDate = timetableSnapshot - .getRealtimeAddedTripOnServiceDateByTripIdAndServiceDate() - .get(tripIdAndServiceDate); - if (tripOnServiceDate != null) { - return tripOnServiceDate; - } - } return transitModelIndex.getTripOnServiceDateForTripAndDay().get(tripIdAndServiceDate); } From c3cb814d9c231c9fc8806264f4f662953ac95c9b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 29 Nov 2023 02:03:12 +0000 Subject: [PATCH 55/85] fix(deps): update dependency ch.qos.logback:logback-classic to v1.4.13 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a47094978fc..b69f9a93a2b 100644 --- a/pom.xml +++ b/pom.xml @@ -65,7 +65,7 @@ 5.10.1 1.11.5 5.5.3 - 1.4.12 + 1.4.13 9.8.0 2.0.9 2.0.15 From 91fcf0359fed47861ee600644c4c98c1e5a1ee35 Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Thu, 30 Nov 2023 12:07:19 +0000 Subject: [PATCH 56/85] Add changelog entry for #5523 [ci skip] --- docs/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Changelog.md b/docs/Changelog.md index bccd0c504d5..cb3fef8df5a 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -51,6 +51,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Transfer cost limit [#5516](https://github.com/opentripplanner/OpenTripPlanner/pull/5516) - Fix missed trip when arrive-by search-window is off by one minute [#5520](https://github.com/opentripplanner/OpenTripPlanner/pull/5520) - Transit group priority - Part 1 [#4999](https://github.com/opentripplanner/OpenTripPlanner/pull/4999) +- Remove `matchBusRoutesToStreets` [#5523](https://github.com/opentripplanner/OpenTripPlanner/pull/5523) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.4.0 (2023-09-13) From 7d052cbb5cbff1dea9122e8b026b9d37411435f4 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 30 Nov 2023 14:30:15 +0100 Subject: [PATCH 57/85] Added JavaDoc to TripTimes --- .../transit/model/timetable/TripTimes.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java index a7a5b34e7c2..f45d549fe3d 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java @@ -10,6 +10,15 @@ import org.opentripplanner.model.BookingInfo; import org.opentripplanner.transit.model.basic.Accessibility; +/** + * A TripTimes represents the arrival and departure times for a single trip in a timetable. It is + * one of the core class used for transit routing. This interface allow different kind of trip + * to implement their own trip times. Scheduled/planned trips should be immutable, real-time + * trip times should allow updates and more info, frequency-based trips can use a more compact + * implementation, and Flex may expose part of the trip as a "scheduled/regular" stop-to-stop + * trip using this interface. All times are expressed as seconds since midnight (as in + * GTFS). + */ public interface TripTimes extends Serializable, Comparable { /** * Copy scheduled times, but not the actual times. From 15c87b434762e58431ebef3757d48e9cad5452fa Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 30 Nov 2023 14:44:22 +0100 Subject: [PATCH 58/85] review: Apply suggestions from review --- docs/Configuration.md | 46 +++++++++---------- .../ext/siri/ModifiedTripBuilder.java | 3 +- .../api/mapping/TripPlanMapper.java | 2 +- .../model/plan/pagecursor/readme.md | 2 +- .../netex/mapping/TripPatternMapper.java | 2 +- .../config/framework/json/ConfigType.java | 2 +- .../model/timetable/ScheduledTripTimes.java | 8 +++- .../transit/model/timetable/TripTimes.java | 4 +- 8 files changed, 38 insertions(+), 31 deletions(-) diff --git a/docs/Configuration.md b/docs/Configuration.md index bfd168d5955..76eeeeeb578 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -65,29 +65,29 @@ documentation below we will refer to the following types: -| Type | Description | Examples | -|------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------| -| `boolean` | This is the Boolean JSON type | `true`, `false` | -| `string` | This is the String JSON type. | `"This is a string!"` | -| `double` | A decimal floating point _number_. 64 bit. | `3.15` | -| `integer` | A decimal integer _number_. 32 bit. | `1`, `-7`, `2100` | -| `long` | A decimal integer _number_. 64 bit. | `-1234567890` | -| `enum` | A fixed set of string literals. | `"RAIL"`, `"BUS"` | -| `enum-map` | List of key/value pairs, where the key is a enum and the value can be any given type. | `{ "RAIL: 1.2, "BUS": 2.3 }` | -| `enum-set` | List of enum string values | `[ "RAIL", "TRAM" ]` | -| `locale` | _`Language[\_country[\_variant]]`_. A Locale object represents a specific geographical, political, or cultural region. For more information see the [Java Locale](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Locale.html). | `"en_US"`, `"nn_NO"` | -| `date` | Local date. The format is _YYYY-MM-DD_ (ISO-8601). | `"2020-09-21"` | -| `date-or-period` | A _local date_, or a _period_ relative to today. The local date has the format `YYYY-MM-DD` and the period has the format `PnYnMnD` or `-PnYnMnD` where `n` is a integer number. | `"P1Y"`, `"-P3M2D"`, `"P1D"` | -| `duration` | A _duration_ is a amount of time. The format is `PnDTnHnMnS` or `nDnHnMnS` where `n` is a integer number. The `D`(days), `H`(hours), `M`(minutes) and `S`(seconds) are not case sensitive. | `"3h"`, `"2m"`, `"1d5h2m3s"`, `"-P2dT-1s"` | -| `regexp` | A regular expression pattern used to match a sting. | `"$^"`, `"gtfs"`, `"\w{3})-.*\.xml"` | -| `uri` | An URI path to a resource like a file or a URL. Relative URIs are resolved relative to the OTP base path. | `"http://foo.bar/"`, `"file:///Users/jon/local/file"`, `"graph.obj"` | -| `time-zone` | Time-Zone ID | `"UTC"`, `"Europe/Paris"`, `"-05:00"` | -| `feed-scoped-id` | FeedScopedId | `"NO:1001"`, `"1:101"` | -| `cost-linear-function` | A cost-linear-function used to calculate a cost from another cost or time/duration. Given a function of time: ``` f(t) = a + b * t ``` then `a` is the constant time part, `b` is the time-coefficient, and `t` is the variable. If `a=0s` and `b=0.0`, then the cost is always `0`(zero). Examples: `0s + 2.5t`, `10m + 0t` and `1h5m59s + 9.9t` The `constant` must be 0 or a positive number or duration. The unit is seconds unless specified using the duration format. A duration is automatically converted to a cost. The `coefficient` must be in range: [0.0, 100.0] | | -| `time-penalty` | A time-penalty is used to add a penalty to the duration/arrival-time/depature-time for a path. It will be invisible to the end user, but used during the routing when comparing stop-arrival/paths. Given a function of time: ``` f(t) = a + b * t ``` then `a` is the constant time part, `b` is the time-coefficient, and `t` is the variable. If `a=0s` and `b=0.0`, then the cost is always `0`(zero). Examples: `0s + 2.5t`, `10m + 0 x` and `1h5m59s + 9.9t` The `constant` must be 0 or a positive number(seconds) or a duration. The `coefficient` must be in range: [0.0, 100.0] | | -| `map` | List of key/value pairs, where the key is a string and the value can be any given type. | `{ "one": 1.2, "two": 2.3 }` | -| `object` | Config object containing nested elements | `"walk": { "speed": 1.3, "reluctance": 5 }` | -| `array` | Config object containing an array/list of elements | `"array": [ 1, 2, 3 ]` | +| Type | Description | Examples | +|------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------| +| `boolean` | This is the Boolean JSON type | `true`, `false` | +| `string` | This is the String JSON type. | `"This is a string!"` | +| `double` | A decimal floating point _number_. 64 bit. | `3.15` | +| `integer` | A decimal integer _number_. 32 bit. | `1`, `-7`, `2100` | +| `long` | A decimal integer _number_. 64 bit. | `-1234567890` | +| `enum` | A fixed set of string literals. | `"RAIL"`, `"BUS"` | +| `enum-map` | List of key/value pairs, where the key is a enum and the value can be any given type. | `{ "RAIL: 1.2, "BUS": 2.3 }` | +| `enum-set` | List of enum string values | `[ "RAIL", "TRAM" ]` | +| `locale` | _`Language[\_country[\_variant]]`_. A Locale object represents a specific geographical, political, or cultural region. For more information see the [Java Locale](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Locale.html). | `"en_US"`, `"nn_NO"` | +| `date` | Local date. The format is _YYYY-MM-DD_ (ISO-8601). | `"2020-09-21"` | +| `date-or-period` | A _local date_, or a _period_ relative to today. The local date has the format `YYYY-MM-DD` and the period has the format `PnYnMnD` or `-PnYnMnD` where `n` is a integer number. | `"P1Y"`, `"-P3M2D"`, `"P1D"` | +| `duration` | A _duration_ is a amount of time. The format is `PnDTnHnMnS` or `nDnHnMnS` where `n` is a integer number. The `D`(days), `H`(hours), `M`(minutes) and `S`(seconds) are not case sensitive. | `"3h"`, `"2m"`, `"1d5h2m3s"`, `"-P2dT-1s"` | +| `regexp` | A regular expression pattern used to match a sting. | `"$^"`, `"gtfs"`, `"\w{3})-.*\.xml"` | +| `uri` | An URI path to a resource like a file or a URL. Relative URIs are resolved relative to the OTP base path. | `"http://foo.bar/"`, `"file:///Users/jon/local/file"`, `"graph.obj"` | +| `time-zone` | Time-Zone ID | `"UTC"`, `"Europe/Paris"`, `"-05:00"` | +| `feed-scoped-id` | FeedScopedId | `"NO:1001"`, `"1:101"` | +| `cost-linear-function` | A cost-linear-function used to calculate a cost from another cost or time/duration. Given a function of time: ``` f(t) = a + b * t ``` then `a` is the constant time part, `b` is the time-coefficient, and `t` is the variable. If `a=0s` and `b=0.0`, then the cost is always `0`(zero). Examples: `0s + 2.5t`, `10m + 0t` and `1h5m59s + 9.9t` The `constant` must be 0 or a positive number or duration. The unit is seconds unless specified using the duration format. A duration is automatically converted to a cost. The `coefficient` must be in range: [0.0, 100.0] | | +| `time-penalty` | A time-penalty is used to add a penalty to the duration/arrival-time/departure-time for a path. It will be invisible to the end user, but used during the routing when comparing stop-arrival/paths. Given a function of time: ``` f(t) = a + b * t ``` then `a` is the constant time part, `b` is the time-coefficient, and `t` is the variable. If `a=0s` and `b=0.0`, then the cost is always `0`(zero). Examples: `0s + 2.5t`, `10m + 0 x` and `1h5m59s + 9.9t` The `constant` must be 0 or a positive number(seconds) or a duration. The `coefficient` must be in range: [0.0, 100.0] | | +| `map` | List of key/value pairs, where the key is a string and the value can be any given type. | `{ "one": 1.2, "two": 2.3 }` | +| `object` | Config object containing nested elements | `"walk": { "speed": 1.3, "reluctance": 5 }` | +| `array` | Config object containing an array/list of elements | `"array": [ 1, 2, 3 ]` | diff --git a/src/ext/java/org/opentripplanner/ext/siri/ModifiedTripBuilder.java b/src/ext/java/org/opentripplanner/ext/siri/ModifiedTripBuilder.java index 56cc28d82f4..df4509eb2d6 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/ModifiedTripBuilder.java +++ b/src/ext/java/org/opentripplanner/ext/siri/ModifiedTripBuilder.java @@ -34,7 +34,8 @@ */ public class ModifiedTripBuilder { - private static final Logger LOG = LoggerFactory.getLogger(TimetableHelper.class); + private static final Logger LOG = LoggerFactory.getLogger(ModifiedTripBuilder.class); + private final TripTimes existingTripTimes; private final TripPattern pattern; private final LocalDate serviceDate; diff --git a/src/main/java/org/opentripplanner/api/mapping/TripPlanMapper.java b/src/main/java/org/opentripplanner/api/mapping/TripPlanMapper.java index 3247b675b65..8c5459af8ed 100644 --- a/src/main/java/org/opentripplanner/api/mapping/TripPlanMapper.java +++ b/src/main/java/org/opentripplanner/api/mapping/TripPlanMapper.java @@ -21,7 +21,7 @@ public ApiTripPlan mapTripPlan(TripPlan domain) { } ApiTripPlan api = new ApiTripPlan(); api.date = Date.from(domain.date); - // The origin/destination do not have arrival/depature times; Hence {@code null} is used. + // The origin/destination do not have arrival/departure times; Hence {@code null} is used. api.from = placeMapper.mapPlace(domain.from, null, null, null, null); api.to = placeMapper.mapPlace(domain.to, null, null, null, null); api.itineraries = itineraryMapper.mapItineraries(domain.itineraries); diff --git a/src/main/java/org/opentripplanner/model/plan/pagecursor/readme.md b/src/main/java/org/opentripplanner/model/plan/pagecursor/readme.md index d0707a93a0b..e98071cfa62 100644 --- a/src/main/java/org/opentripplanner/model/plan/pagecursor/readme.md +++ b/src/main/java/org/opentripplanner/model/plan/pagecursor/readme.md @@ -18,7 +18,7 @@ moving on to the next. request to the next page. **sw'** is the search window for the new next/previous page. The search window may change between requests, so we need to account for it when computing the next/previous page cursors. -- **earliest-departure-time (edt)** The search-window start with the earliest-depature-time, which +- **earliest-departure-time (edt)** The search-window start with the earliest-departure-time, which is the first possible time any itinerary may start. **edt'** is the calculated value for the new cursor. - **latest-arrival-time (lat)** The latest time an itinerary can arrive to get accepted. The diff --git a/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java b/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java index 4babac12e03..6fa000c1049 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java @@ -363,7 +363,7 @@ private org.opentripplanner.transit.model.network.Route lookupRoute( private void createTripTimes(List trips, TripPattern tripPattern) { for (Trip trip : trips) { List stopTimes = result.tripStopTimes.get(trip); - if (stopTimes.size() == 0) { + if (stopTimes.isEmpty()) { issueStore.add( "TripWithoutTripTimes", "Trip %s does not contain any trip times.", diff --git a/src/main/java/org/opentripplanner/standalone/config/framework/json/ConfigType.java b/src/main/java/org/opentripplanner/standalone/config/framework/json/ConfigType.java index 18de8ab6e6c..f62ec56dbfc 100644 --- a/src/main/java/org/opentripplanner/standalone/config/framework/json/ConfigType.java +++ b/src/main/java/org/opentripplanner/standalone/config/framework/json/ConfigType.java @@ -94,7 +94,7 @@ public enum ConfigType { TIME_PENALTY( JsonType.string, """ - A time-penalty is used to add a penalty to the duration/arrival-time/depature-time for + A time-penalty is used to add a penalty to the duration/arrival-time/departure-time for a path. It will be invisible to the end user, but used during the routing when comparing stop-arrival/paths. diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java index 0d1efeea7c7..b950cdebc5e 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java @@ -20,6 +20,12 @@ import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.framework.DeduplicatorService; +/** + * Regular/planed/scheduled read-only version of {@link TripTimes}. The set of static + * trip-times are build during graph-build and can not be changed using real-time updates. + * + * @see RealTimeTripTimes for real-time version + */ public final class ScheduledTripTimes implements TripTimes { /** @@ -318,7 +324,7 @@ private void validate() { /** * When creating scheduled trip times we could potentially imply negative running or dwell times. * We really don't want those being used in routing. This method checks that all times are - * increasing. The first stop arrival time and the last stops depature time is NOT checked - + * increasing. The first stop arrival time and the last stops departure time is NOT checked - * these should be ignored by raptor. */ private void validateNonIncreasingTimes() { diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java index f45d549fe3d..7bf1a826c1c 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java @@ -23,7 +23,7 @@ public interface TripTimes extends Serializable, Comparable { /** * Copy scheduled times, but not the actual times. */ - public RealTimeTripTimes copyScheduledTimes(); + RealTimeTripTimes copyScheduledTimes(); /** The code for the service on which this trip runs. For departure search optimizations. */ int getServiceCode(); @@ -59,7 +59,7 @@ public interface TripTimes extends Serializable, Comparable { int getDepartureDelay(int stop); /** - * Whether or not stopIndex is considered a GTFS timepoint. + * Whether stopIndex is considered a GTFS timepoint. */ boolean isTimepoint(int stopIndex); From 379a591ba878ddcb59abf3c22fdbf546a9623834 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 30 Nov 2023 14:47:03 +0100 Subject: [PATCH 59/85] spelling: Replace witch with which --- ARCHITECTURE.md | 2 +- CODE_CONVENTIONS.md | 2 +- .../framework/concurrent/OtpRequestThreadFactory.java | 2 +- .../org/opentripplanner/framework/text/MarkdownFormatter.java | 2 +- src/main/java/org/opentripplanner/framework/text/Table.java | 2 +- .../java/org/opentripplanner/framework/text/TableBuilder.java | 2 +- .../org/opentripplanner/framework/time/DurationUtils.java | 2 +- .../java/org/opentripplanner/framework/time/TimeUtils.java | 4 ++-- .../graph_builder/module/configure/GraphBuilderModules.java | 2 +- src/main/java/org/opentripplanner/model/plan/Itinerary.java | 2 +- .../org/opentripplanner/model/transfer/TransferPoint.java | 4 ++-- .../org/opentripplanner/model/transfer/TransferPointMap.java | 2 +- .../org/opentripplanner/openstreetmap/model/OSMWithTags.java | 2 +- .../raptor/rangeraptor/SystemErrDebugLogger.java | 2 +- .../multicriteria/configure/McRangeRaptorConfig.java | 2 +- .../rangeraptor/standard/heuristics/HeuristicsAdapter.java | 2 +- .../rangeraptor/standard/stoparrivals/view/StopsCursor.java | 4 ++-- .../raptor/rangeraptor/transit/EgressPaths.java | 4 ++-- src/main/java/org/opentripplanner/raptor/spi/Flyweight.java | 2 +- .../opentripplanner/raptor/spi/RaptorBoardOrAlightEvent.java | 2 +- .../routing/algorithm/raptoradapter/router/TransitRouter.java | 2 +- .../transit/constrainedtransfer/TransferIndexGenerator.java | 2 +- .../raptoradapter/transit/request/TripSearchTimetable.java | 2 +- .../api/request/preference/TimeSlopeSafetyTriangle.java | 2 +- .../standalone/api/OtpServerRequestContext.java | 4 ++-- .../standalone/config/framework/json/EnumMapper.java | 2 +- .../transit/model/framework/DataValidationException.java | 2 +- .../org/opentripplanner/transit/model/framework/Result.java | 4 ++-- .../transit/model/framework/TransitBuilder.java | 2 +- .../transit/model/network/RoutingTripPattern.java | 2 +- .../java/org/opentripplanner/_support/arch/ArchComponent.java | 2 +- .../moduletests/F05_OnBoardAccessEgressAndTransfersTest.java | 2 +- .../opentripplanner/transit/speed_test/SpeedTestRequest.java | 2 +- .../transit/speed_test/model/testcase/ExpectedResults.java | 2 +- 34 files changed, 40 insertions(+), 40 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index dd0f986e518..acd90b787e6 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -24,7 +24,7 @@ examples. The Transit model is more complex than the VehiclePosition model. a use-case or set of features. It may have an api with request/response classes. These are usually stateless; Hence the `Use Case Service` does normally not have a model. The implementing class has the same name as the interface with prefix `Default`. - - `Domain Model` A model witch encapsulate a business area. In the drawing two examples are shown, + - `Domain Model` A model which encapsulate a business area. In the drawing two examples are shown, the `transit` and `vhicleposition` domain model. The transit model is more complex so the implementation have a separate `Service` and `Repository`. Almost all http endpoints are , read-only so the `Service` can focus on serving the http API endpoints, while the repository diff --git a/CODE_CONVENTIONS.md b/CODE_CONVENTIONS.md index a9fd73a0497..ce816c481cc 100644 --- a/CODE_CONVENTIONS.md +++ b/CODE_CONVENTIONS.md @@ -38,7 +38,7 @@ be `org.opentripplanner...`. | Package | Description | | ------------------------------- |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `o.o.` | At the top level we should divide OTP into "domain"s like `apis`, `framework`, `transit`, `street`, `astar`, `raptor`, `feeds`, `updaters`, and `application`. | -| `component` and `sub-component` | A group of packages/classes witch naturally belong together, think aggregate as in Domain Driven Design. | +| `component` and `sub-component` | A group of packages/classes which naturally belong together, think aggregate as in Domain Driven Design. | | `component.api` | Used for components to define the programing interface for the component. If present, (see Raptor) all outside dependencies to the component should be through the `api`. | | `component.model` | Used to create a model of a Entites, ValueObjects, ++. If exposed outside the component you should include an entry point like `xyz.model.XyzModel` and/or a Service (in api or component root package). | | `component.service` | Implementation of the service like `DefaultTransitService`, may also contain use-case specific code. Note, the Service interface goes into the component root or `api`, not in the service package. | diff --git a/src/main/java/org/opentripplanner/framework/concurrent/OtpRequestThreadFactory.java b/src/main/java/org/opentripplanner/framework/concurrent/OtpRequestThreadFactory.java index 1ad44728c80..e88bbb420cd 100644 --- a/src/main/java/org/opentripplanner/framework/concurrent/OtpRequestThreadFactory.java +++ b/src/main/java/org/opentripplanner/framework/concurrent/OtpRequestThreadFactory.java @@ -7,7 +7,7 @@ /** * This thread pool factory should be used to create all threads handling "user" requests in OTP. - * It is used to instrument new threads witch enable log information propagation and error handling, + * It is used to instrument new threads which enable log information propagation and error handling, * like thread interruption. By "user" we mean users of the query APIs like GTFS GraphQL API, * Transmodel GraphQL API and other http endpoints. *

diff --git a/src/main/java/org/opentripplanner/framework/text/MarkdownFormatter.java b/src/main/java/org/opentripplanner/framework/text/MarkdownFormatter.java index d74a14a3315..6fdb495a0d5 100644 --- a/src/main/java/org/opentripplanner/framework/text/MarkdownFormatter.java +++ b/src/main/java/org/opentripplanner/framework/text/MarkdownFormatter.java @@ -73,7 +73,7 @@ public static String escapeInTable(String text) { return text.replace("|", "¦"); } - /** Return whitespace witch can be used to indent inside a table cell. */ + /** Return whitespace which can be used to indent inside a table cell. */ public static String indentInTable(int level) { return level <= 0 ? "" : NBSP.repeat(3 * level); } diff --git a/src/main/java/org/opentripplanner/framework/text/Table.java b/src/main/java/org/opentripplanner/framework/text/Table.java index 9db832c9410..940868b82e2 100644 --- a/src/main/java/org/opentripplanner/framework/text/Table.java +++ b/src/main/java/org/opentripplanner/framework/text/Table.java @@ -48,7 +48,7 @@ public static TableBuilder of() { } /** - * Static method witch format a given table as valid Markdown table like: + * Static method which format a given table as valid Markdown table like: *

    * | A | B |
    * |---|---|
diff --git a/src/main/java/org/opentripplanner/framework/text/TableBuilder.java b/src/main/java/org/opentripplanner/framework/text/TableBuilder.java
index cf6623c2d99..12e80fd62a8 100644
--- a/src/main/java/org/opentripplanner/framework/text/TableBuilder.java
+++ b/src/main/java/org/opentripplanner/framework/text/TableBuilder.java
@@ -50,7 +50,7 @@ public TableBuilder withAlights(Collection aligns) {
   }
 
   /**
-   * Return the width needed for each column. The witch is calculated by taking
+   * Return the width needed for each column. The which is calculated by taking
    * the maximum of the {@code minWidth}, header width and the maximum width for all
    * cells in the column.
    */
diff --git a/src/main/java/org/opentripplanner/framework/time/DurationUtils.java b/src/main/java/org/opentripplanner/framework/time/DurationUtils.java
index fb30392cf9b..bf59964fbb2 100644
--- a/src/main/java/org/opentripplanner/framework/time/DurationUtils.java
+++ b/src/main/java/org/opentripplanner/framework/time/DurationUtils.java
@@ -124,7 +124,7 @@ public static Duration duration(String duration) {
   }
 
   /**
-   * This is used to parse a string witch may be a number {@code NNNN}(number of seconds) or a
+   * This is used to parse a string which may be a number {@code NNNN}(number of seconds) or a
    * duration with format {@code NhNmNs}, where {@code N} is a decimal number and
    * {@code h} is hours, {@code m} minutes and {@code s} seconds.
    * 

diff --git a/src/main/java/org/opentripplanner/framework/time/TimeUtils.java b/src/main/java/org/opentripplanner/framework/time/TimeUtils.java index afeeb77ff5d..61549eeced3 100644 --- a/src/main/java/org/opentripplanner/framework/time/TimeUtils.java +++ b/src/main/java/org/opentripplanner/framework/time/TimeUtils.java @@ -205,7 +205,7 @@ public static String msToString(long milliseconds) { * busy-wait again. *

* This method does a "busy" wait - it is not affected by a thread interrupt like - * {@link Thread#sleep(long)}; Hence do not interfere with timeout logic witch uses the interrupt + * {@link Thread#sleep(long)}; Hence do not interfere with timeout logic which uses the interrupt * flag. *

* THIS CODE IS NOT MEANT FOR PRODUCTION! @@ -226,7 +226,7 @@ public static long busyWaitOnce(int waitMs) { * number. *

* This method does a "busy" wait - it is not affected by a thread interrupt like - * {@link Thread#sleep(long)}; Hence do not interfere with timeout logic witch uses the interrupt + * {@link Thread#sleep(long)}; Hence do not interfere with timeout logic which uses the interrupt * flag. *

* THIS CODE IS NOT MEANT FOR PRODUCTION! diff --git a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java index 82f183a0bc4..444adb5b727 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java @@ -46,7 +46,7 @@ import org.opentripplanner.transit.service.TransitModel; /** - * Configure all modules witch is not simple enough to be injected. + * Configure all modules which is not simple enough to be injected. */ @Module public class GraphBuilderModules { diff --git a/src/main/java/org/opentripplanner/model/plan/Itinerary.java b/src/main/java/org/opentripplanner/model/plan/Itinerary.java index 0c111999912..58320bf1652 100644 --- a/src/main/java/org/opentripplanner/model/plan/Itinerary.java +++ b/src/main/java/org/opentripplanner/model/plan/Itinerary.java @@ -428,7 +428,7 @@ public void setLegs(List legs) { * accessible the itinerary is as a whole. This is not a very scientific method but just a rough * guidance that expresses certainty or uncertainty about the accessibility. *

- * An alternative to this is to use the `generalized-cost` and use that to indicate witch itineraries is the + * An alternative to this is to use the `generalized-cost` and use that to indicate which itineraries is the * best/most friendly with respect to making the journey in a wheelchair. The `generalized-cost` include, not * only a penalty for unknown and inaccessible boardings, but also a penalty for undesired uphill and downhill * street traversal. diff --git a/src/main/java/org/opentripplanner/model/transfer/TransferPoint.java b/src/main/java/org/opentripplanner/model/transfer/TransferPoint.java index b10e96e6f1c..dcfc7d381ad 100644 --- a/src/main/java/org/opentripplanner/model/transfer/TransferPoint.java +++ b/src/main/java/org/opentripplanner/model/transfer/TransferPoint.java @@ -52,7 +52,7 @@ */ public interface TransferPoint { /** - * Utility method witch can be used in APIs to get the trip, if it exists, from a transfer point. + * Utility method which can be used in APIs to get the trip, if it exists, from a transfer point. */ @Nullable static Trip getTrip(TransferPoint point) { @@ -60,7 +60,7 @@ static Trip getTrip(TransferPoint point) { } /** - * Utility method witch can be used in APIs to get the route, if it exists, from a transfer + * Utility method which can be used in APIs to get the route, if it exists, from a transfer * point. */ @Nullable diff --git a/src/main/java/org/opentripplanner/model/transfer/TransferPointMap.java b/src/main/java/org/opentripplanner/model/transfer/TransferPointMap.java index 3592153f080..9f231d06aab 100644 --- a/src/main/java/org/opentripplanner/model/transfer/TransferPointMap.java +++ b/src/main/java/org/opentripplanner/model/transfer/TransferPointMap.java @@ -73,7 +73,7 @@ E computeIfAbsent(TransferPoint point, Supplier creator) { } /** - * List all elements witch matches any of the transfer points added to the map. + * List all elements which matches any of the transfer points added to the map. */ List get(Trip trip, StopLocation stop, int stopPointInPattern) { return Stream diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java index 6e596e52076..37757823362 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java @@ -201,7 +201,7 @@ public Optional getTagOpt(String network) { /** * Get tag and convert it to an integer. If the tag exist, but can not be parsed into a number, - * then the error handler is called with the value witch failed to parse. + * then the error handler is called with the value which failed to parse. */ public OptionalInt getTagAsInt(String tag, Consumer errorHandler) { String value = getTag(tag); diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/SystemErrDebugLogger.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/SystemErrDebugLogger.java index b10783c830f..8c35f103106 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/SystemErrDebugLogger.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/SystemErrDebugLogger.java @@ -28,7 +28,7 @@ import org.opentripplanner.raptor.rangeraptor.transit.TripTimesSearch; /** - * A debug logger witch can be plugged into Raptor to do debug logging to standard error. This is + * A debug logger which can be plugged into Raptor to do debug logging to standard error. This is * used by the REST API, SpeedTest and in module tests. *

* See the Raptor design doc for a general description of the logging functionality. diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/configure/McRangeRaptorConfig.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/configure/McRangeRaptorConfig.java index b05b0aeb5b0..649737bc42f 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/configure/McRangeRaptorConfig.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/configure/McRangeRaptorConfig.java @@ -57,7 +57,7 @@ public McRangeRaptorConfig( /** * The PassThroughPointsService is injected into the transit-calculator, so it needs to be - * created before the context(witch create the calculator).So, to be able to do this, this + * created before the context(which create the calculator).So, to be able to do this, this * factory is static, and the service is passed back in when this config is instantiated. */ public static PassThroughPointsService passThroughPointsService( diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/heuristics/HeuristicsAdapter.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/heuristics/HeuristicsAdapter.java index 78aab07f21a..ff38fff6d40 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/heuristics/HeuristicsAdapter.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/heuristics/HeuristicsAdapter.java @@ -185,7 +185,7 @@ private static AggregatedResults create( ); for (RaptorAccessEgress it : list) { - // Prevent transfer(walking) and the egress witch start with walking + // Prevent transfer(walking) and the egress which start with walking if (!(it.stopReachedOnBoard() || stopReachedByTransit)) { continue; } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/view/StopsCursor.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/view/StopsCursor.java index d20f4ba5db8..f72047597a7 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/view/StopsCursor.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/view/StopsCursor.java @@ -59,7 +59,7 @@ public Access fictiveAccess(int round, RaptorAccessEgress accessPath, int arr /** * Return a fictive Transfer stop arrival view. The arrival does not exist in the state, but is - * linked with the previous arrival witch is a "real" arrival present in the state. This enables + * linked with the previous arrival which is a "real" arrival present in the state. This enables * path generation. */ public Transfer fictiveTransfer( @@ -76,7 +76,7 @@ public Transfer fictiveTransfer( /** * Return a fictive Transit stop arrival view. The arrival does not exist in the state, but is - * linked with the previous arrival witch is a "real" arrival present in the state. This enables + * linked with the previous arrival which is a "real" arrival present in the state. This enables * path generation. */ public Transit fictiveTransit( diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressPaths.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressPaths.java index fa4fc57dc84..2038ab543df 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressPaths.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressPaths.java @@ -54,7 +54,7 @@ public Collection listAll() { } /** - * List all stops with an egress path witch start by walking. These egress paths can only be used + * List all stops with an egress path which start by walking. These egress paths can only be used * if arriving at the stop by transit. */ public int[] egressesWitchStartByWalking() { @@ -62,7 +62,7 @@ public int[] egressesWitchStartByWalking() { } /** - * List all stops with an egress path witch start on-board a "transit" ride. These + * List all stops with an egress path which start on-board a "transit" ride. These * egress paths can be used when arriving at the stop with both transfer or transit. */ public int[] egressesWitchStartByARide() { diff --git a/src/main/java/org/opentripplanner/raptor/spi/Flyweight.java b/src/main/java/org/opentripplanner/raptor/spi/Flyweight.java index c2b0536c8ab..a3d9ee0db1c 100644 --- a/src/main/java/org/opentripplanner/raptor/spi/Flyweight.java +++ b/src/main/java/org/opentripplanner/raptor/spi/Flyweight.java @@ -6,7 +6,7 @@ import java.lang.annotation.Target; /** - * This interface is used to tag methods witch return flyweight objects. The implementation may + * This interface is used to tag methods which return flyweight objects. The implementation may * choose not to implement the return type as a flyweight object, but the Raptor implementation * is guaranteed to treat them as such - enabling the optimization. *

diff --git a/src/main/java/org/opentripplanner/raptor/spi/RaptorBoardOrAlightEvent.java b/src/main/java/org/opentripplanner/raptor/spi/RaptorBoardOrAlightEvent.java index da01e397c06..5913f950969 100644 --- a/src/main/java/org/opentripplanner/raptor/spi/RaptorBoardOrAlightEvent.java +++ b/src/main/java/org/opentripplanner/raptor/spi/RaptorBoardOrAlightEvent.java @@ -77,7 +77,7 @@ default int boardStopIndex() { /** * This is a helper method for the Raptor implementation to be able to board or execute * a alternativeBoardingFallback method depending on the event. This logic should ideally - * be put inside raptor, but due to performance(creating lambda instances, witch for some + * be put inside raptor, but due to performance(creating lambda instances, which for some * reason is not inlined) this need to be here. *

* @param boardCallback perform boarding if the event in none empty (or some other special diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java index b9f9685341c..f18aa056504 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java @@ -177,7 +177,7 @@ private AccessEgresses fetchAccessEgresses() { if (OTPFeature.ParallelRouting.isOn()) { try { - // TODO: This is not using {@link OtpRequestThreadFactory} witch mean we do not get + // TODO: This is not using {@link OtpRequestThreadFactory} which mean we do not get // log-trace-parameters-propagation and graceful timeout handling here. CompletableFuture .allOf( diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/constrainedtransfer/TransferIndexGenerator.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/constrainedtransfer/TransferIndexGenerator.java index 9fc2dc69194..7acddd9cea5 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/constrainedtransfer/TransferIndexGenerator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/constrainedtransfer/TransferIndexGenerator.java @@ -57,7 +57,7 @@ public ConstrainedTransfersForPatterns generateTransfers() { for (ConstrainedTransfer tx : constrainedTransfers) { var c = tx.getTransferConstraint(); - // Only add transfers witch have an effect on the Raptor routing here. + // Only add transfers which have an effect on the Raptor routing here. // Some transfers only have the priority set, and that is used in optimized- // transfers, but not in Raptor. if (!c.includeInRaptorRouting()) { diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripSearchTimetable.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripSearchTimetable.java index 88e9d52e878..147eddfc605 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripSearchTimetable.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripSearchTimetable.java @@ -9,7 +9,7 @@ * This interface add two methods the the {@link RaptorTimeTable} to optimize the terip search * inside the transit model. They were previously in Raptor, but the trip Search is moded outside * of Raptor; We have keep these methods in an interface to be able to reuse the complex TripSearch - * in tests, witch do not use the transit model {@link TripSchedule}; Hence also the generic type + * in tests, which do not use the transit model {@link TripSchedule}; Hence also the generic type * on this interface. */ public interface TripSearchTimetable extends RaptorTimeTable { diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/TimeSlopeSafetyTriangle.java b/src/main/java/org/opentripplanner/routing/api/request/preference/TimeSlopeSafetyTriangle.java index e93dd5f5a6c..b901d738213 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/TimeSlopeSafetyTriangle.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/TimeSlopeSafetyTriangle.java @@ -52,7 +52,7 @@ public TimeSlopeSafetyTriangle(double time, double slope, double safety) { } /** - * Creates a special builder, witch used together with the + * Creates a special builder, which used together with the * {@link Builder#buildOrDefault(TimeSlopeSafetyTriangle)} can return a new instance or the * default value if no values are set. This is useful in the APIs where we want to fall back to * the default {@link TimeSlopeSafetyTriangle}, if no values are set. If only one or two values diff --git a/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java b/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java index 8fa8069ba70..fa6ead99c5e 100644 --- a/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java +++ b/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java @@ -47,7 +47,7 @@ * *

* This class is not THREAD-SAFE, each HTTP request gets its own copy, but if there are multiple - * threads witch accesses this context within the HTTP Request, then the caller is responsible + * threads which accesses this context within the HTTP Request, then the caller is responsible * for the synchronization. Only request scoped components need to be synchronized - they are * potentially lazy initialized. */ @@ -107,7 +107,7 @@ public interface OtpServerRequestContext { TileRendererManager tileRendererManager(); /** - * Callback witch is injected into the {@code DirectStreetRouter}, used to visualize the + * Callback which is injected into the {@code DirectStreetRouter}, used to visualize the * search. */ @HttpRequestScoped diff --git a/src/main/java/org/opentripplanner/standalone/config/framework/json/EnumMapper.java b/src/main/java/org/opentripplanner/standalone/config/framework/json/EnumMapper.java index 1bad7033104..ce880058005 100644 --- a/src/main/java/org/opentripplanner/standalone/config/framework/json/EnumMapper.java +++ b/src/main/java/org/opentripplanner/standalone/config/framework/json/EnumMapper.java @@ -31,7 +31,7 @@ public static String kebabCase(String input) { } /** - * Used to create a list of all values with description of each value witch can be included + * Used to create a list of all values with description of each value which can be included * in documentation. The list will look like this: * ``` * - `on` Turn on. diff --git a/src/main/java/org/opentripplanner/transit/model/framework/DataValidationException.java b/src/main/java/org/opentripplanner/transit/model/framework/DataValidationException.java index d1c985e6db0..20848ebecc1 100644 --- a/src/main/java/org/opentripplanner/transit/model/framework/DataValidationException.java +++ b/src/main/java/org/opentripplanner/transit/model/framework/DataValidationException.java @@ -3,7 +3,7 @@ import org.opentripplanner.framework.error.OtpError; /** - * This class is used to throw a data validation exception. It holds an error witch can be + * This class is used to throw a data validation exception. It holds an error which can be * inserted into build issue store, into the updater logs or returned to the APIs. The framework * to catch and handle this is NOT IN PLACE, see * Error code design #5070. diff --git a/src/main/java/org/opentripplanner/transit/model/framework/Result.java b/src/main/java/org/opentripplanner/transit/model/framework/Result.java index 621ce7569f2..9df1c41f190 100644 --- a/src/main/java/org/opentripplanner/transit/model/framework/Result.java +++ b/src/main/java/org/opentripplanner/transit/model/framework/Result.java @@ -10,7 +10,7 @@ *

* It's very similar to the Either or Validation type found in functional programming languages. * - * @deprecated This not possible to use inside a constructor - can only return one thing. Witch, + * @deprecated This not possible to use inside a constructor - can only return one thing. Which, * then makes encapsulation harder and leaves the door open to forget. Also, having to create * error codes, mapping and handling code for hundreds of different possible validation error * types make changes harder, and cleanup impossible. This is a nice way to design APIs, but @@ -18,7 +18,7 @@ * amount of code branches this breaks. *

* I will use the {@link DataValidationException} for now, but we need to make an error - * handling strategy witch take all use-cases and goals into account, also pragmatic goals + * handling strategy which take all use-cases and goals into account, also pragmatic goals * like maintainability. The {@link DataValidationException} is not a solution, but it * at least it allows me to omit returning all possible error on every method ... *

diff --git a/src/main/java/org/opentripplanner/transit/model/framework/TransitBuilder.java b/src/main/java/org/opentripplanner/transit/model/framework/TransitBuilder.java index bd18690d9a3..52fd68165bc 100644 --- a/src/main/java/org/opentripplanner/transit/model/framework/TransitBuilder.java +++ b/src/main/java/org/opentripplanner/transit/model/framework/TransitBuilder.java @@ -5,7 +5,7 @@ public interface TransitBuilder, B extends Transit * Build a new object based on the values set in the builder. This method is NOT context aware - * any context is not updated. Use the {@link TransitEntityBuilder#save()} method instead to * build an object and store it in the context. This method is useful if you need to build an - * object witch should be request scoped or used in a test. + * object which should be request scoped or used in a test. *

* For value objects are stored as "part of" an entity, but OTP tries to reuse objects using the * {@code Deduplicator}. This method may or may not be context aware, using a deduplicator to diff --git a/src/main/java/org/opentripplanner/transit/model/network/RoutingTripPattern.java b/src/main/java/org/opentripplanner/transit/model/network/RoutingTripPattern.java index 0d319af0529..00033b5f798 100644 --- a/src/main/java/org/opentripplanner/transit/model/network/RoutingTripPattern.java +++ b/src/main/java/org/opentripplanner/transit/model/network/RoutingTripPattern.java @@ -12,7 +12,7 @@ * - The RTP is accessed frequently during the Raptor search, and we want it to be as small as * possible to load/access it in the cache and CPU for performance reasons. * - Also, we deduplicate these so a RTP can be reused by more than one TP. - * - This also provide explicit documentation on witch fields are used during a search and which + * - This also provide explicit documentation on which fields are used during a search and which * are not. */ public class RoutingTripPattern implements DefaultTripPattern, Serializable { diff --git a/src/test/java/org/opentripplanner/_support/arch/ArchComponent.java b/src/test/java/org/opentripplanner/_support/arch/ArchComponent.java index a1261d09648..7273b3123e3 100644 --- a/src/test/java/org/opentripplanner/_support/arch/ArchComponent.java +++ b/src/test/java/org/opentripplanner/_support/arch/ArchComponent.java @@ -9,7 +9,7 @@ public interface ArchComponent { /** * ArchUnit cached set of classes in OTP. It takes a bit of time to build the set of * classes, so it is nice to avoid this for every test. ArchUnit also support JUnit5 - * with a @ArchTest annotation witch cache and inject the classes, but the test become + * with a @ArchTest annotation which cache and inject the classes, but the test become * slightly more complex by using it and chasing it here works fine. */ JavaClasses OTP_CLASSES = new ClassFileImporter() diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/F05_OnBoardAccessEgressAndTransfersTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/F05_OnBoardAccessEgressAndTransfersTest.java index 59d7dcd7d4e..72da3f71145 100644 --- a/src/test/java/org/opentripplanner/raptor/moduletests/F05_OnBoardAccessEgressAndTransfersTest.java +++ b/src/test/java/org/opentripplanner/raptor/moduletests/F05_OnBoardAccessEgressAndTransfersTest.java @@ -25,7 +25,7 @@ * FEATURE UNDER TEST *

* Raptor should be able to route Access arriving on-board and egress departing on-board connecting - * to transit by transfers. Access and egress witch arrive/depart at/from the same stops by + * to transit by transfers. Access and egress which arrive/depart at/from the same stops by * walking should not be possible. */ public class F05_OnBoardAccessEgressAndTransfersTest implements RaptorTestConstants { diff --git a/src/test/java/org/opentripplanner/transit/speed_test/SpeedTestRequest.java b/src/test/java/org/opentripplanner/transit/speed_test/SpeedTestRequest.java index 1bfe202f92f..2dfaad077c6 100644 --- a/src/test/java/org/opentripplanner/transit/speed_test/SpeedTestRequest.java +++ b/src/test/java/org/opentripplanner/transit/speed_test/SpeedTestRequest.java @@ -63,7 +63,7 @@ RouteRequest toRouteRequest() { request.setTo(input.toPlace()); // Filter the results inside the SpeedTest, not in the itineraries filter, - // when ignoring street results. This will use the default witch is 50. + // when ignoring street results. This will use the default which is 50. if (!config.ignoreStreetResults) { request.setNumItineraries(opts.numOfItineraries()); } diff --git a/src/test/java/org/opentripplanner/transit/speed_test/model/testcase/ExpectedResults.java b/src/test/java/org/opentripplanner/transit/speed_test/model/testcase/ExpectedResults.java index 1b14bc8fc12..eb66b3671e4 100644 --- a/src/test/java/org/opentripplanner/transit/speed_test/model/testcase/ExpectedResults.java +++ b/src/test/java/org/opentripplanner/transit/speed_test/model/testcase/ExpectedResults.java @@ -10,7 +10,7 @@ /** * This class contains the results for a given test case. A set of results * for each {@link SpeedTestProfile} is kept. A default set is also available, - * witch can be used if there is not set for a given profile. + * which can be used if there is not set for a given profile. */ public class ExpectedResults { From 2546c231060f0f34b0b646e85259d6da2ecc4419 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 1 Dec 2023 13:36:56 +0000 Subject: [PATCH 60/85] fix(deps): update jersey monorepo to v3.1.4 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b69f9a93a2b..9241cafc838 100644 --- a/pom.xml +++ b/pom.xml @@ -61,7 +61,7 @@ 30.1 2.48.1 2.16.0 - 3.1.3 + 3.1.4 5.10.1 1.11.5 5.5.3 From d60af303bdf5b0a22ed8a3e1c0e56edf86ee8899 Mon Sep 17 00:00:00 2001 From: eibakke Date: Fri, 1 Dec 2023 23:48:46 +0100 Subject: [PATCH 61/85] Adds sameSubmode to AlternativeLegsFilter. --- .../org/opentripplanner/ext/transmodelapi/model/EnumTypes.java | 1 + .../routing/alternativelegs/AlternativeLegsFilter.java | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java index 2b6942d7b8a..bc4146fa2f5 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java @@ -49,6 +49,7 @@ public class EnumTypes { .value("noFilter", AlternativeLegsFilter.NO_FILTER) .value("sameAuthority", AlternativeLegsFilter.SAME_AGENCY) .value("sameMode", AlternativeLegsFilter.SAME_MODE) + .value("sameSubmode", AlternativeLegsFilter.SAME_SUBMODE) .value("sameLine", AlternativeLegsFilter.SAME_ROUTE) .build(); diff --git a/src/main/java/org/opentripplanner/routing/alternativelegs/AlternativeLegsFilter.java b/src/main/java/org/opentripplanner/routing/alternativelegs/AlternativeLegsFilter.java index 5154f8cd40a..79ea57324ca 100644 --- a/src/main/java/org/opentripplanner/routing/alternativelegs/AlternativeLegsFilter.java +++ b/src/main/java/org/opentripplanner/routing/alternativelegs/AlternativeLegsFilter.java @@ -14,6 +14,9 @@ public enum AlternativeLegsFilter { ), SAME_MODE((Leg leg) -> (TripPattern tripPattern) -> leg.getTrip().getMode().equals(tripPattern.getMode()) + ), + SAME_SUBMODE((Leg leg) -> + (TripPattern tripPattern) -> leg.getTrip().getNetexSubMode().equals(tripPattern.getNetexSubmode()) ); public final Function> predicateGenerator; From 3393edf4191e522b68ce4c89ff9e39c812102299 Mon Sep 17 00:00:00 2001 From: eibakke Date: Fri, 1 Dec 2023 23:48:46 +0100 Subject: [PATCH 62/85] Adds sameSubmode to AlternativeLegsFilter. --- src/ext/graphql/transmodelapi/schema.graphql | 1 + .../opentripplanner/ext/transmodelapi/model/EnumTypes.java | 1 + .../routing/alternativelegs/AlternativeLegsFilter.java | 4 ++++ 3 files changed, 6 insertions(+) diff --git a/src/ext/graphql/transmodelapi/schema.graphql b/src/ext/graphql/transmodelapi/schema.graphql index 069fefdf54d..9a31b1cd5b9 100644 --- a/src/ext/graphql/transmodelapi/schema.graphql +++ b/src/ext/graphql/transmodelapi/schema.graphql @@ -1405,6 +1405,7 @@ enum AlternativeLegsFilter { sameAuthority sameLine sameMode + sameSubmode } enum ArrivalDeparture { diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java index 2b6942d7b8a..bc4146fa2f5 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java @@ -49,6 +49,7 @@ public class EnumTypes { .value("noFilter", AlternativeLegsFilter.NO_FILTER) .value("sameAuthority", AlternativeLegsFilter.SAME_AGENCY) .value("sameMode", AlternativeLegsFilter.SAME_MODE) + .value("sameSubmode", AlternativeLegsFilter.SAME_SUBMODE) .value("sameLine", AlternativeLegsFilter.SAME_ROUTE) .build(); diff --git a/src/main/java/org/opentripplanner/routing/alternativelegs/AlternativeLegsFilter.java b/src/main/java/org/opentripplanner/routing/alternativelegs/AlternativeLegsFilter.java index 5154f8cd40a..9f883603e77 100644 --- a/src/main/java/org/opentripplanner/routing/alternativelegs/AlternativeLegsFilter.java +++ b/src/main/java/org/opentripplanner/routing/alternativelegs/AlternativeLegsFilter.java @@ -14,6 +14,10 @@ public enum AlternativeLegsFilter { ), SAME_MODE((Leg leg) -> (TripPattern tripPattern) -> leg.getTrip().getMode().equals(tripPattern.getMode()) + ), + SAME_SUBMODE((Leg leg) -> + (TripPattern tripPattern) -> + leg.getTrip().getNetexSubMode().equals(tripPattern.getNetexSubmode()) ); public final Function> predicateGenerator; From 7163a768b7e0e8e732eef7600f41e6cf4385256e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 1 Dec 2023 22:34:11 +0000 Subject: [PATCH 63/85] fix(deps): update dependency ch.qos.logback:logback-classic to v1.4.14 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b69f9a93a2b..e0de45f1f7e 100644 --- a/pom.xml +++ b/pom.xml @@ -65,7 +65,7 @@ 5.10.1 1.11.5 5.5.3 - 1.4.13 + 1.4.14 9.8.0 2.0.9 2.0.15 From f06c5d2188ed2ef6b6d393f989c7a3750aa3c700 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 2 Dec 2023 01:01:13 +0000 Subject: [PATCH 64/85] fix(deps): update google.dagger.version to v2.49 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e0de45f1f7e..baff7d78b69 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ 131 30.1 - 2.48.1 + 2.49 2.16.0 3.1.3 5.10.1 From 7cb67055059d4b0de58aef87ce4f7164eab1045e Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Mon, 4 Dec 2023 11:11:58 +0100 Subject: [PATCH 65/85] refactor: Fix real-time spelling in tests and doc Co-authored-by: Assad Riaz --- doc-templates/StopConsolidation.md | 2 +- docs/RouteRequest.md | 2 +- docs/sandbox/StopConsolidation.md | 2 +- src/ext/graphql/transmodelapi/schema.graphql | 24 +++++++++---------- .../ext/fares/impl/OrcaFareService.java | 2 +- .../realtimeresolver/RealtimeResolver.java | 8 +++---- .../ext/siri/AddedTripBuilder.java | 2 +- .../ext/siri/SiriFuzzyTripMatcher.java | 6 ++--- .../ext/siri/SiriTimetableSnapshotSource.java | 18 +++++++------- .../ext/siri/TimetableHelper.java | 12 +++++----- .../updater/SiriETGooglePubsubUpdater.java | 8 +++---- .../updater/SiriETHttpTripUpdateSource.java | 2 +- .../ext/siri/updater/SiriETUpdater.java | 2 +- .../ext/siri/updater/SiriFileLoader.java | 2 +- .../StopConsolidationModule.java | 2 +- .../model/DefaultRouteRequestType.java | 2 +- .../ext/transmodelapi/model/plan/LegType.java | 9 +++---- .../model/plan/TripPatternType.java | 10 ++++---- .../transmodelapi/model/plan/TripQuery.java | 2 +- .../model/siri/et/EstimatedCallType.java | 4 ++-- .../transmodelapi/model/stop/QuayType.java | 2 +- .../model/stop/StopPlaceType.java | 4 +--- .../timetable/DatedServiceJourneyType.java | 2 +- .../model/timetable/ServiceJourneyType.java | 4 ++-- .../hslpark/HslParkUpdater.java | 2 +- .../api/mapping/PlaceMapper.java | 2 +- .../api/mapping/TripTimeMapper.java | 8 +++---- .../api/model/ApiTripSearchMetadata.java | 4 ++-- .../api/model/ApiTripTimeShort.java | 8 +++---- .../model/ApiVehicleParkingWithEntrance.java | 18 +++++++------- .../apis/gtfs/GraphQLRequestContext.java | 2 +- .../apis/gtfs/datafetchers/PatternImpl.java | 2 +- .../apis/gtfs/datafetchers/StopImpl.java | 2 +- .../apis/gtfs/datafetchers/StoptimeImpl.java | 2 +- .../apis/gtfs/datafetchers/TripImpl.java | 2 +- .../framework/application/OTPFeature.java | 2 +- .../opentripplanner/model/TripTimeOnDate.java | 2 +- .../preference/TransitPreferences.java | 2 +- .../config/routerconfig/UpdatersConfig.java | 2 +- .../routerequest/RouteRequestConfig.java | 2 +- .../updater/trip/TimetableSnapshotSource.java | 2 +- .../opentripplanner/apis/gtfs/schema.graphqls | 2 +- .../__snapshots__/CarSnapshotTest.snap | 4 ++-- 43 files changed, 101 insertions(+), 102 deletions(-) diff --git a/doc-templates/StopConsolidation.md b/doc-templates/StopConsolidation.md index 690fe0b98e6..c92cab6afe1 100644 --- a/doc-templates/StopConsolidation.md +++ b/doc-templates/StopConsolidation.md @@ -30,7 +30,7 @@ This has the following consequences However, this feature has also severe downsides: - - It makes realtime trip updates referencing a stop id much more complicated and in many cases + - It makes real-time trip updates referencing a stop id much more complicated and in many cases impossible to resolve. You can only reference a stop by its sequence, which only works in GTFS-RT, not Siri. - Fare calculation and transfers are unlikely to work as expected. diff --git a/docs/RouteRequest.md b/docs/RouteRequest.md index 6825ee67183..af6dcf00142 100644 --- a/docs/RouteRequest.md +++ b/docs/RouteRequest.md @@ -297,7 +297,7 @@ Raptor iteration. The value is dynamically assigned a suitable value, if not se medium size operation you may use a fixed value, like 60 minutes. If you have a mixture of high frequency cities routes and infrequent long distant journeys, the best option is normally to use the dynamic auto assignment. If not provided the value is resolved depending on the other input -parameters, available transit options and real-time changes. +parameters, available transit options and realtime changes. There is no need to set this when going to the next/previous page. The OTP Server will increase/decrease the search-window when paging to match the requested number of itineraries. diff --git a/docs/sandbox/StopConsolidation.md b/docs/sandbox/StopConsolidation.md index fa3ff822e88..750a873b547 100644 --- a/docs/sandbox/StopConsolidation.md +++ b/docs/sandbox/StopConsolidation.md @@ -30,7 +30,7 @@ This has the following consequences However, this feature has also severe downsides: - - It makes realtime trip updates referencing a stop id much more complicated and in many cases + - It makes real-time trip updates referencing a stop id much more complicated and in many cases impossible to resolve. You can only reference a stop by its sequence, which only works in GTFS-RT, not Siri. - Fare calculation and transfers are unlikely to work as expected. diff --git a/src/ext/graphql/transmodelapi/schema.graphql b/src/ext/graphql/transmodelapi/schema.graphql index 069fefdf54d..f90c186ce02 100644 --- a/src/ext/graphql/transmodelapi/schema.graphql +++ b/src/ext/graphql/transmodelapi/schema.graphql @@ -158,7 +158,7 @@ type Contact { "A planned journey on a specific day" type DatedServiceJourney { - "Returns scheduled passingTimes for this dated service journey, updated with realtime-updates (if available). " + "Returns scheduled passingTimes for this dated service journey, updated with real-time-updates (if available). " estimatedCalls: [EstimatedCall] @timingData id: ID! "JourneyPattern for the dated service journey." @@ -211,7 +211,7 @@ type EstimatedCall { aimedDepartureTime: DateTime! "Booking arrangements for this EstimatedCall." bookingArrangements: BookingArrangement - "Whether stop is cancelled. This means that either the ServiceJourney has a planned cancellation, the ServiceJourney has been cancelled by realtime data, or this particular StopPoint has been cancelled. This also means that both boarding and alighting has been cancelled." + "Whether stop is cancelled. This means that either the ServiceJourney has a planned cancellation, the ServiceJourney has been cancelled by real-time data, or this particular StopPoint has been cancelled. This also means that both boarding and alighting has been cancelled." cancellation: Boolean! "The date the estimated call is valid for." date: Date! @@ -315,9 +315,9 @@ type Leg { includes both the start and end coordinate. """ elevationProfile: [ElevationProfileStep]! - "The expected, realtime adjusted date and time this leg ends." + "The expected, real-time adjusted date and time this leg ends." expectedEndTime: DateTime! - "The expected, realtime adjusted date and time this leg starts." + "The expected, real-time adjusted date and time this leg starts." expectedStartTime: DateTime! "EstimatedCall for the quay where the leg originates." fromEstimatedCall: EstimatedCall @timingData @@ -560,7 +560,7 @@ type Quay implements PlaceInterface { estimatedCalls( "Filters results by either departures, arrivals or both. For departures forBoarding has to be true and the departure time has to be within the specified time range. For arrivals, forAlight has to be true and the arrival time has to be within the specified time range. If both are asked for, either the conditions for arrivals or the conditions for departures will have to be true for an EstimatedCall to show." arrivalDeparture: ArrivalDeparture = departures, - "Indicates that realtime-cancelled trips should also be included." + "Indicates that real-time-cancelled trips should also be included." includeCancelledTrips: Boolean = false, "Limit the total number of departures returned." numberOfDepartures: Int = 5, @@ -797,7 +797,7 @@ type QueryType { filters: [TripFilterInput!], "The start location" from: Location!, - "When true, realtime updates are ignored during this search." + "When true, real-time updates are ignored during this search." ignoreRealtimeUpdates: Boolean = false, "When true, service journeys cancelled in scheduled route data will be included during this search." includePlannedCancellations: Boolean = false, @@ -1012,7 +1012,7 @@ type RoutingParameters { elevatorHopTime: Int "Whether to apply the ellipsoid->geoid offset to all elevations in the response." geoIdElevation: Boolean - "When true, realtime updates are ignored during this search." + "When true, real-time updates are ignored during this search." ignoreRealTimeUpdates: Boolean "When true, service journeys cancelled in scheduled route data will be included during this search." includedPlannedCancellations: Boolean @@ -1081,7 +1081,7 @@ type ServiceJourney { "Booking arrangements for flexible services." bookingArrangements: BookingArrangement @deprecated(reason : "BookingArrangements are defined per stop, and can be found under `passingTimes` or `estimatedCalls`") directionType: DirectionType - "Returns scheduled passingTimes for this ServiceJourney for a given date, updated with realtime-updates (if available). NB! This takes a date as argument (default=today) and returns estimatedCalls for that date and should only be used if the date is known when creating the request. For fetching estimatedCalls for a given trip.leg, use leg.serviceJourneyEstimatedCalls instead." + "Returns scheduled passingTimes for this ServiceJourney for a given date, updated with real-time-updates (if available). NB! This takes a date as argument (default=today) and returns estimatedCalls for that date and should only be used if the date is known when creating the request. For fetching estimatedCalls for a given trip.leg, use leg.serviceJourneyEstimatedCalls instead." estimatedCalls( "Date to get estimated calls for. Defaults to today." date: Date @@ -1092,7 +1092,7 @@ type ServiceJourney { line: Line! notices: [Notice!]! operator: Operator - "Returns scheduled passing times only - without realtime-updates, for realtime-data use 'estimatedCalls'" + "Returns scheduled passing times only - without real-time-updates, for realtime-data use 'estimatedCalls'" passingTimes: [TimetabledPassingTime]! @timingData "Detailed path travelled by service journey. Not available for flexible trips." pointsOnLink: PointsOnLink @@ -1122,7 +1122,7 @@ type StopPlace implements PlaceInterface { "List of visits to this stop place as part of vehicle journeys." estimatedCalls( arrivalDeparture: ArrivalDeparture = departures, - "Indicates that realtime-cancelled trips should also be included." + "Indicates that real-time-cancelled trips should also be included." includeCancelledTrips: Boolean = false, "Limit the total number of departures returned." numberOfDepartures: Int = 5, @@ -1280,9 +1280,9 @@ type TripPattern { duration: Long "Time that the trip arrives." endTime: DateTime @deprecated(reason : "Replaced with expectedEndTime") - "The expected, realtime adjusted date and time the trip ends." + "The expected, real-time adjusted date and time the trip ends." expectedEndTime: DateTime! - "The expected, realtime adjusted date and time the trip starts." + "The expected, real-time adjusted date and time the trip starts." expectedStartTime: DateTime! "Generalized cost or weight of the itinerary. Used for debugging." generalizedCost: Int diff --git a/src/ext/java/org/opentripplanner/ext/fares/impl/OrcaFareService.java b/src/ext/java/org/opentripplanner/ext/fares/impl/OrcaFareService.java index 486362697b3..4f1aca5bf63 100644 --- a/src/ext/java/org/opentripplanner/ext/fares/impl/OrcaFareService.java +++ b/src/ext/java/org/opentripplanner/ext/fares/impl/OrcaFareService.java @@ -395,7 +395,7 @@ protected Optional getRidePrice( * If free transfers are applicable, the most expensive discount fare across all legs is added to * the final cumulative price. *

- * The computed fare for Orca card users takes into account realtime trip updates where available, + * The computed fare for Orca card users takes into account real-time trip updates where available, * so that, for instance, when a leg on a long itinerary is delayed to begin after the initial two * hour window has expired, the calculated fare for that trip will be two one-way fares instead of * one. diff --git a/src/ext/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolver.java b/src/ext/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolver.java index a59d9fc589a..d03ba1a2fc5 100644 --- a/src/ext/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolver.java +++ b/src/ext/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolver.java @@ -11,7 +11,7 @@ public class RealtimeResolver { /** - * Loop through all itineraries and populate legs with realtime data using legReference from the original leg + * Loop through all itineraries and populate legs with real-time data using legReference from the original leg */ public static void populateLegsWithRealtime( List itineraries, @@ -35,10 +35,10 @@ public static void populateLegsWithRealtime( return leg; } - var realtimeLeg = ref.getLeg(transitService); - if (realtimeLeg != null) { + var realTimeLeg = ref.getLeg(transitService); + if (realTimeLeg != null) { return combineReferenceWithOriginal( - realtimeLeg.asScheduledTransitLeg(), + realTimeLeg.asScheduledTransitLeg(), leg.asScheduledTransitLeg() ); } diff --git a/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java b/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java index 2d7789422ea..7ef843575e5 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java +++ b/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java @@ -246,7 +246,7 @@ Result build() { } /** - * Method to create a Route. Commonly used to create a route if a realtime message + * Method to create a Route. Commonly used to create a route if a real-time message * refers to a route that is not in the transit model. * * We will find the first Route with same Operator, and use the same Authority diff --git a/src/ext/java/org/opentripplanner/ext/siri/SiriFuzzyTripMatcher.java b/src/ext/java/org/opentripplanner/ext/siri/SiriFuzzyTripMatcher.java index 006329a404b..2f2f113fc79 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/SiriFuzzyTripMatcher.java +++ b/src/ext/java/org/opentripplanner/ext/siri/SiriFuzzyTripMatcher.java @@ -297,9 +297,9 @@ private TripAndPattern getTripAndPatternForJourney( continue; } - var realtimeAddedTripPattern = getRealtimeAddedTripPattern.apply(trip.getId(), serviceDate); - TripPattern tripPattern = realtimeAddedTripPattern != null - ? realtimeAddedTripPattern + var realTimeAddedTripPattern = getRealtimeAddedTripPattern.apply(trip.getId(), serviceDate); + TripPattern tripPattern = realTimeAddedTripPattern != null + ? realTimeAddedTripPattern : transitService.getPatternForTrip(trip); var firstStop = tripPattern.firstStop(); diff --git a/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java b/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java index 0cedc491f79..7eb69546aad 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java +++ b/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java @@ -35,8 +35,8 @@ import uk.org.siri.siri20.EstimatedVehicleJourney; /** - * This class should be used to create snapshots of lookup tables of realtime data. This is - * necessary to provide planning threads a consistent constant view of a graph with realtime data at + * This class should be used to create snapshots of lookup tables of real-time data. This is + * necessary to provide planning threads a consistent constant view of a graph with real-time data at * a specific point in time. */ public class SiriTimetableSnapshotSource implements TimetableSnapshotProvider { @@ -59,7 +59,7 @@ public class SiriTimetableSnapshotSource implements TimetableSnapshotProvider { */ private final SiriTripPatternIdGenerator tripPatternIdGenerator = new SiriTripPatternIdGenerator(); /** - * A synchronized cache of trip patterns that are added to the graph due to GTFS-realtime + * A synchronized cache of trip patterns that are added to the graph due to GTFS-real-time * messages. */ private final SiriTripPatternCache tripPatternCache; @@ -81,7 +81,7 @@ public class SiriTimetableSnapshotSource implements TimetableSnapshotProvider { */ private volatile TimetableSnapshot snapshot = null; - /** Should expired realtime data be purged from the graph. */ + /** Should expired real-time data be purged from the graph. */ private final boolean purgeExpiredData; protected LocalDate lastPurgeDate = null; @@ -371,7 +371,7 @@ private Result addTripToGraphAndBuffer(TripUpdate tr // Add new trip times to the buffer and return success var result = buffer.update(pattern, tripUpdate.tripTimes(), serviceDate); - LOG.debug("Applied realtime data for trip {} on {}", trip, serviceDate); + LOG.debug("Applied real-time data for trip {} on {}", trip, serviceDate); return result; } @@ -416,10 +416,8 @@ private boolean removePreviousRealtimeUpdate(final Trip trip, final LocalDate se final TripPattern pattern = buffer.getRealtimeAddedTripPattern(trip.getId(), serviceDate); if (pattern != null) { - /* - Remove the previous realtime-added TripPattern from buffer. - Only one version of the realtime-update should exist - */ + // Remove the previous real-time-added TripPattern from buffer. + // Only one version of the real-time-update should exist buffer.removeLastAddedTripPattern(trip.getId(), serviceDate); buffer.removeRealtimeUpdatedTripTimes(pattern, trip.getId(), serviceDate); success = true; @@ -436,7 +434,7 @@ private boolean purgeExpiredData() { return false; } - LOG.debug("purging expired realtime data"); + LOG.debug("purging expired real-time data"); lastPurgeDate = previously; diff --git a/src/ext/java/org/opentripplanner/ext/siri/TimetableHelper.java b/src/ext/java/org/opentripplanner/ext/siri/TimetableHelper.java index ade320db298..5f44b3486a9 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/TimetableHelper.java +++ b/src/ext/java/org/opentripplanner/ext/siri/TimetableHelper.java @@ -75,29 +75,29 @@ public static void applyUpdates( } int scheduledArrivalTime = tripTimes.getArrivalTime(index); - int realtimeArrivalTime = getAvailableTime( + int realTimeArrivalTime = getAvailableTime( departureDate, call::getActualArrivalTime, call::getExpectedArrivalTime ); int scheduledDepartureTime = tripTimes.getDepartureTime(index); - int realtimeDepartureTime = getAvailableTime( + int realTimeDepartureTime = getAvailableTime( departureDate, call::getActualDepartureTime, call::getExpectedDepartureTime ); int[] possibleArrivalTimes = index == 0 - ? new int[] { realtimeArrivalTime, realtimeDepartureTime, scheduledArrivalTime } - : new int[] { realtimeArrivalTime, scheduledArrivalTime }; + ? new int[] { realTimeArrivalTime, realTimeDepartureTime, scheduledArrivalTime } + : new int[] { realTimeArrivalTime, scheduledArrivalTime }; var arrivalTime = handleMissingRealtime(possibleArrivalTimes); int arrivalDelay = arrivalTime - scheduledArrivalTime; tripTimes.updateArrivalDelay(index, arrivalDelay); int[] possibleDepartureTimes = isLastStop - ? new int[] { realtimeDepartureTime, realtimeArrivalTime, scheduledDepartureTime } - : new int[] { realtimeDepartureTime, scheduledDepartureTime }; + ? new int[] { realTimeDepartureTime, realTimeArrivalTime, scheduledDepartureTime } + : new int[] { realTimeDepartureTime, scheduledDepartureTime }; var departureTime = handleMissingRealtime(possibleDepartureTimes); int departureDelay = departureTime - scheduledDepartureTime; tripTimes.updateDepartureDelay(index, departureDelay); diff --git a/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETGooglePubsubUpdater.java b/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETGooglePubsubUpdater.java index cb664632443..29c0fa0c7a2 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETGooglePubsubUpdater.java +++ b/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETGooglePubsubUpdater.java @@ -126,10 +126,10 @@ public SiriETGooglePubsubUpdater( SiriTimetableSnapshotSource timetableSnapshot ) { this.configRef = config.configRef(); - /* - URL that responds to HTTP GET which returns all initial data in protobuf-format. - Will be called once to initialize realtime-data. All updates will be received from Google Cloud Pubsub - */ + + // URL that responds to HTTP GET which returns all initial data in protobuf-format. Will be + // called once to initialize real-time-data. All updates will be received from Google Cloud + // Pubsub this.dataInitializationUrl = URI.create(config.dataInitializationUrl()); this.feedId = config.feedId(); this.reconnectPeriod = config.reconnectPeriod(); diff --git a/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETHttpTripUpdateSource.java b/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETHttpTripUpdateSource.java index 3f1de760701..9dab72446ea 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETHttpTripUpdateSource.java +++ b/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETHttpTripUpdateSource.java @@ -88,7 +88,7 @@ public String toString() { } private static SiriLoader createLoader(String url, Parameters parameters) { - // Load realtime updates from a file. + // Load real-time updates from a file. if (SiriFileLoader.matchesUrl(url)) { return new SiriFileLoader(url); } diff --git a/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETUpdater.java b/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETUpdater.java index b4cf5f143e3..c8ccd2c533b 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETUpdater.java +++ b/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETUpdater.java @@ -39,7 +39,7 @@ public class SiriETUpdater extends PollingGraphUpdater { protected WriteToGraphCallback saveResultOnGraph; /** - * The place where we'll record the incoming realtime timetables to make them available to the + * The place where we'll record the incoming real-time timetables to make them available to the * router in a thread safe way. */ private final SiriTimetableSnapshotSource snapshotSource; diff --git a/src/ext/java/org/opentripplanner/ext/siri/updater/SiriFileLoader.java b/src/ext/java/org/opentripplanner/ext/siri/updater/SiriFileLoader.java index 306deeca484..84da542ef47 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/updater/SiriFileLoader.java +++ b/src/ext/java/org/opentripplanner/ext/siri/updater/SiriFileLoader.java @@ -72,7 +72,7 @@ private Optional fetchFeed() { if (!matchFilename(file)) { continue; } - LOG.info("Process realtime input file: " + file.getAbsolutePath()); + LOG.info("Process real-time input file: " + file.getAbsolutePath()); var inProgressFile = newFile(file, SUFFIX_IN_PROGRESS); try { file.renameTo(inProgressFile); diff --git a/src/ext/java/org/opentripplanner/ext/stopconsolidation/StopConsolidationModule.java b/src/ext/java/org/opentripplanner/ext/stopconsolidation/StopConsolidationModule.java index c70e91fca52..91cbe5e1856 100644 --- a/src/ext/java/org/opentripplanner/ext/stopconsolidation/StopConsolidationModule.java +++ b/src/ext/java/org/opentripplanner/ext/stopconsolidation/StopConsolidationModule.java @@ -20,7 +20,7 @@ * that represent the same stop place) and swaps the "secondary" stops in patterns with their * "primary" equivalent. *

- * NOTE: This will make realtime trip updates for a modified pattern a lot harder. For Arcadis' + * NOTE: This will make real-time trip updates for a modified pattern a lot harder. For Arcadis' * initial implementation this is acceptable and will serve as encouragement for the data producers to * produce a consolidated transit feed rather than relying on this feature. */ diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/DefaultRouteRequestType.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/DefaultRouteRequestType.java index 0d52fb280d9..fd9af09822e 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/DefaultRouteRequestType.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/DefaultRouteRequestType.java @@ -394,7 +394,7 @@ private GraphQLObjectType createGraphQLType() { GraphQLFieldDefinition .newFieldDefinition() .name("ignoreRealTimeUpdates") - .description("When true, realtime updates are ignored during this search.") + .description("When true, real-time updates are ignored during this search.") .type(Scalars.GraphQLBoolean) .dataFetcher(env -> preferences.transit().ignoreRealtimeUpdates()) .build() diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/LegType.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/LegType.java index d233aaec3c2..5125efca902 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/LegType.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/LegType.java @@ -75,7 +75,8 @@ public static GraphQLObjectType create( .name("aimedStartTime") .description("The aimed date and time this leg starts.") .type(new GraphQLNonNull(gqlUtil.dateTimeScalar)) - .dataFetcher(env -> // startTime is already adjusted for realtime - need to subtract delay to get aimed time + .dataFetcher(env -> + // startTime is already adjusted for real-time - need to subtract delay to get aimed time leg(env) .getStartTime() .minusSeconds(leg(env).getDepartureDelay()) @@ -88,7 +89,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("expectedStartTime") - .description("The expected, realtime adjusted date and time this leg starts.") + .description("The expected, real-time adjusted date and time this leg starts.") .type(new GraphQLNonNull(gqlUtil.dateTimeScalar)) .dataFetcher(env -> leg(env).getStartTime().toInstant().toEpochMilli()) .build() @@ -99,7 +100,7 @@ public static GraphQLObjectType create( .name("aimedEndTime") .description("The aimed date and time this leg ends.") .type(new GraphQLNonNull(gqlUtil.dateTimeScalar)) - .dataFetcher(env -> // endTime is already adjusted for realtime - need to subtract delay to get aimed time + .dataFetcher(env -> // endTime is already adjusted for real-time - need to subtract delay to get aimed time leg(env) .getEndTime() .minusSeconds(leg(env).getArrivalDelay()) @@ -112,7 +113,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("expectedEndTime") - .description("The expected, realtime adjusted date and time this leg ends.") + .description("The expected, real-time adjusted date and time this leg ends.") .type(new GraphQLNonNull(gqlUtil.dateTimeScalar)) .dataFetcher(env -> leg(env).getEndTime().toInstant().toEpochMilli()) .build() diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripPatternType.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripPatternType.java index 4381557d472..de90b89b38f 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripPatternType.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripPatternType.java @@ -50,7 +50,8 @@ public static GraphQLObjectType create( .name("aimedStartTime") .description("The aimed date and time the trip starts.") .type(new GraphQLNonNull(gqlUtil.dateTimeScalar)) - .dataFetcher(env -> // startTime is already adjusted for realtime - need to subtract delay to get aimed time + .dataFetcher(env -> + // startTime is already adjusted for real-time - need to subtract delay to get aimed time itinerary(env) .startTime() .minusSeconds(itinerary(env).departureDelay()) @@ -63,7 +64,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("expectedStartTime") - .description("The expected, realtime adjusted date and time the trip starts.") + .description("The expected, real-time adjusted date and time the trip starts.") .type(new GraphQLNonNull(gqlUtil.dateTimeScalar)) .dataFetcher(env -> itinerary(env).startTime().toInstant().toEpochMilli()) .build() @@ -74,7 +75,8 @@ public static GraphQLObjectType create( .name("aimedEndTime") .description("The aimed date and time the trip ends.") .type(new GraphQLNonNull(gqlUtil.dateTimeScalar)) - .dataFetcher(env -> // endTime is already adjusted for realtime - need to subtract delay to get aimed time + .dataFetcher(env -> + // endTime is already adjusted for real-time - need to subtract delay to get aimed time itinerary(env) .endTime() .minusSeconds(itinerary(env).arrivalDelay()) @@ -87,7 +89,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("expectedEndTime") - .description("The expected, realtime adjusted date and time the trip ends.") + .description("The expected, real-time adjusted date and time the trip ends.") .type(new GraphQLNonNull(gqlUtil.dateTimeScalar)) .dataFetcher(env -> itinerary(env).endTime().toInstant().toEpochMilli()) .build() diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java index cf56e2ef34d..4ac182f514f 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java @@ -188,7 +188,7 @@ public static GraphQLFieldDefinition create( GraphQLArgument .newArgument() .name("ignoreRealtimeUpdates") - .description("When true, realtime updates are ignored during this search.") + .description("When true, real-time updates are ignored during this search.") .type(Scalars.GraphQLBoolean) .defaultValue(preferences.transit().ignoreRealtimeUpdates()) .build() diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/et/EstimatedCallType.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/et/EstimatedCallType.java index 7a39aee6cca..3111f17b698 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/et/EstimatedCallType.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/et/EstimatedCallType.java @@ -194,7 +194,7 @@ public static GraphQLObjectType create( .newFieldDefinition() .name("realtimeState") .type(new GraphQLNonNull(EnumTypes.REALTIME_STATE)) - .dataFetcher(environment -> ((TripTimeOnDate) environment.getSource()).getRealtimeState()) + .dataFetcher(environment -> ((TripTimeOnDate) environment.getSource()).getRealTimeState()) .build() ) .field( @@ -258,7 +258,7 @@ public static GraphQLObjectType create( .description( "Whether stop is cancelled. This means that either the " + "ServiceJourney has a planned cancellation, the ServiceJourney has been " + - "cancelled by realtime data, or this particular StopPoint has been " + + "cancelled by real-time data, or this particular StopPoint has been " + "cancelled. This also means that both boarding and alighting has been " + "cancelled." ) diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/QuayType.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/QuayType.java index 203efb10452..8b9296b9137 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/QuayType.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/QuayType.java @@ -299,7 +299,7 @@ public static GraphQLObjectType create( GraphQLArgument .newArgument() .name("includeCancelledTrips") - .description("Indicates that realtime-cancelled trips should also be included.") + .description("Indicates that real-time-cancelled trips should also be included.") .type(Scalars.GraphQLBoolean) .defaultValue(false) .build() diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/StopPlaceType.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/StopPlaceType.java index 5a803138eb1..a60ee5df20f 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/StopPlaceType.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/StopPlaceType.java @@ -20,7 +20,6 @@ import java.util.Collection; import java.util.HashSet; import java.util.List; -import java.util.Locale; import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -34,7 +33,6 @@ import org.opentripplanner.ext.transmodelapi.model.plan.JourneyWhiteListed; import org.opentripplanner.ext.transmodelapi.support.GqlUtil; import org.opentripplanner.framework.graphql.GraphQLUtils; -import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.model.StopTimesInPattern; import org.opentripplanner.model.TripTimeOnDate; import org.opentripplanner.routing.stoptimes.ArrivalDeparture; @@ -348,7 +346,7 @@ public static GraphQLObjectType create( GraphQLArgument .newArgument() .name("includeCancelledTrips") - .description("Indicates that realtime-cancelled trips should also be included.") + .description("Indicates that real-time-cancelled trips should also be included.") .type(Scalars.GraphQLBoolean) .defaultValue(false) .build() diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/DatedServiceJourneyType.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/DatedServiceJourneyType.java index 6634ee4e4d3..e9cb689036b 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/DatedServiceJourneyType.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/DatedServiceJourneyType.java @@ -146,7 +146,7 @@ public static GraphQLObjectType create( .withDirective(gqlUtil.timingData) .description( "Returns scheduled passingTimes for this dated service journey, " + - "updated with realtime-updates (if available). " + "updated with real-time-updates (if available). " ) .dataFetcher(environment -> { TripOnServiceDate tripOnServiceDate = tripOnServiceDate(environment); diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/ServiceJourneyType.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/ServiceJourneyType.java index 3603db05120..f03dad44fc1 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/ServiceJourneyType.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/ServiceJourneyType.java @@ -240,7 +240,7 @@ public static GraphQLObjectType create( .type(new GraphQLNonNull(new GraphQLList(timetabledPassingTimeType))) .withDirective(gqlUtil.timingData) .description( - "Returns scheduled passing times only - without realtime-updates, for realtime-data use 'estimatedCalls'" + "Returns scheduled passing times only - without real-time-updates, for realtime-data use 'estimatedCalls'" ) .dataFetcher(env -> { Trip trip = trip(env); @@ -259,7 +259,7 @@ public static GraphQLObjectType create( .type(new GraphQLList(estimatedCallType)) .withDirective(gqlUtil.timingData) .description( - "Returns scheduled passingTimes for this ServiceJourney for a given date, updated with realtime-updates (if available). " + + "Returns scheduled passingTimes for this ServiceJourney for a given date, updated with real-time-updates (if available). " + "NB! This takes a date as argument (default=today) and returns estimatedCalls for that date and should only be used if the date is " + "known when creating the request. For fetching estimatedCalls for a given trip.leg, use leg.serviceJourneyEstimatedCalls instead." ) diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/hslpark/HslParkUpdater.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/hslpark/HslParkUpdater.java index a921836d907..e5086630941 100644 --- a/src/ext/java/org/opentripplanner/ext/vehicleparking/hslpark/HslParkUpdater.java +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/hslpark/HslParkUpdater.java @@ -67,7 +67,7 @@ public HslParkUpdater( /** * Update the data from the sources. It first fetches parks from the facilities URL and park - * groups from hubs URL and then realtime updates from utilizations URL. If facilitiesFrequencySec + * groups from hubs URL and then real-time updates from utilizations URL. If facilitiesFrequencySec * is configured to be over 0, it also occasionally retches the parks as new parks might have been * added or the state of the old parks might have changed. * diff --git a/src/main/java/org/opentripplanner/api/mapping/PlaceMapper.java b/src/main/java/org/opentripplanner/api/mapping/PlaceMapper.java index f6b0defd709..e806c5615d2 100644 --- a/src/main/java/org/opentripplanner/api/mapping/PlaceMapper.java +++ b/src/main/java/org/opentripplanner/api/mapping/PlaceMapper.java @@ -130,7 +130,7 @@ private ApiVehicleParkingWithEntrance mapVehicleParking( .hasWheelchairAccessibleCarPlaces(vp.hasWheelchairAccessibleCarPlaces()) .availability(mapVehicleParkingSpaces(vp.getAvailability())) .capacity(mapVehicleParkingSpaces(vp.getCapacity())) - .realtime(vehicleParkingWithEntrance.isRealtime()) + .realTime(vehicleParkingWithEntrance.isRealtime()) .build(); } } diff --git a/src/main/java/org/opentripplanner/api/mapping/TripTimeMapper.java b/src/main/java/org/opentripplanner/api/mapping/TripTimeMapper.java index c5303c744cf..2f5bfe91026 100644 --- a/src/main/java/org/opentripplanner/api/mapping/TripTimeMapper.java +++ b/src/main/java/org/opentripplanner/api/mapping/TripTimeMapper.java @@ -28,13 +28,13 @@ public static ApiTripTimeShort mapToApi(TripTimeOnDate domain) { api.stopCount = domain.getStopCount(); api.scheduledArrival = domain.getScheduledArrival(); api.scheduledDeparture = domain.getScheduledDeparture(); - api.realtimeArrival = domain.getRealtimeArrival(); - api.realtimeDeparture = domain.getRealtimeDeparture(); + api.realTimeArrival = domain.getRealtimeArrival(); + api.realTimeDeparture = domain.getRealtimeDeparture(); api.arrivalDelay = domain.getArrivalDelay(); api.departureDelay = domain.getDepartureDelay(); api.timepoint = domain.isTimepoint(); - api.realtime = domain.isRealtime(); - api.realtimeState = ApiRealTimeState.RealTimeState(domain.getRealtimeState()); + api.realTime = domain.isRealtime(); + api.realTimeState = ApiRealTimeState.RealTimeState(domain.getRealTimeState()); api.blockId = domain.getBlockId(); api.headsign = I18NStringMapper.mapToApi(domain.getHeadsign(), null); api.tripId = FeedScopedIdMapper.mapToApi(domain.getTrip().getId()); diff --git a/src/main/java/org/opentripplanner/api/model/ApiTripSearchMetadata.java b/src/main/java/org/opentripplanner/api/model/ApiTripSearchMetadata.java index 49c98d6e5b6..486118883f9 100644 --- a/src/main/java/org/opentripplanner/api/model/ApiTripSearchMetadata.java +++ b/src/main/java/org/opentripplanner/api/model/ApiTripSearchMetadata.java @@ -22,7 +22,7 @@ public class ApiTripSearchMetadata { * This is the suggested search time for the "next page" or time-window. Insert it together with * the {@link #searchWindowUsed} in the request to get a new set of trips following in the * time-window AFTER the current search. No duplicate trips should be returned, unless a trip is - * delayed and new realtime-data is available. + * delayed and new real-time-data is available. *

* Be careful to use paging/scrolling with the {@code numOfItineraries} parameter set. It is safe * to scroll forward when the {@code arriveBy=false}, but not if {@code arriveBy=true}. If you @@ -42,7 +42,7 @@ public class ApiTripSearchMetadata { * This is the suggested search time for the "previous page" or time window. Insert it together * with the {@link #searchWindowUsed} in the request to get a new set of trips preceding in the * time-window BEFORE the current search. No duplicate trips should be returned, unless a trip is - * delayed and new realtime-data is available. + * delayed and new real-time-data is available. *

* Be careful to use paging/scrolling with the {@code numOfItineraries} parameter set. It is safe * to scroll backward when the {@code arriveBy=true}, but not if {@code arriveBy=false}. If you diff --git a/src/main/java/org/opentripplanner/api/model/ApiTripTimeShort.java b/src/main/java/org/opentripplanner/api/model/ApiTripTimeShort.java index 92a78f4aade..0a340d4a812 100644 --- a/src/main/java/org/opentripplanner/api/model/ApiTripTimeShort.java +++ b/src/main/java/org/opentripplanner/api/model/ApiTripTimeShort.java @@ -11,13 +11,13 @@ public class ApiTripTimeShort implements Serializable { public int stopCount; public int scheduledArrival = UNDEFINED; public int scheduledDeparture = UNDEFINED; - public int realtimeArrival = UNDEFINED; - public int realtimeDeparture = UNDEFINED; + public int realTimeArrival = UNDEFINED; + public int realTimeDeparture = UNDEFINED; public int arrivalDelay = UNDEFINED; public int departureDelay = UNDEFINED; public boolean timepoint = false; - public boolean realtime = false; - public ApiRealTimeState realtimeState = ApiRealTimeState.SCHEDULED; + public boolean realTime = false; + public ApiRealTimeState realTimeState = ApiRealTimeState.SCHEDULED; public long serviceDay; public String tripId; public String blockId; diff --git a/src/main/java/org/opentripplanner/api/model/ApiVehicleParkingWithEntrance.java b/src/main/java/org/opentripplanner/api/model/ApiVehicleParkingWithEntrance.java index 56e3e0c3710..8cac78f6c9e 100644 --- a/src/main/java/org/opentripplanner/api/model/ApiVehicleParkingWithEntrance.java +++ b/src/main/java/org/opentripplanner/api/model/ApiVehicleParkingWithEntrance.java @@ -73,15 +73,15 @@ public class ApiVehicleParkingWithEntrance { public final ApiVehicleParkingSpaces capacity; /** - * The number of available spaces. Only present if there is a realtime updater present. Maybe + * The number of available spaces. Only present if there is a real-time updater present. Maybe * {@code null} if unknown. */ public final ApiVehicleParkingSpaces availability; /** - * True if realtime information is used for checking availability. + * True if real-time information is used for checking availability. */ - public final boolean realtime; + public final boolean realTime; ApiVehicleParkingWithEntrance( String id, @@ -98,7 +98,7 @@ public class ApiVehicleParkingWithEntrance { boolean hasWheelchairAccessibleCarPlaces, ApiVehicleParkingSpaces capacity, ApiVehicleParkingSpaces availability, - boolean realtime + boolean realTime ) { this.id = id; this.name = name; @@ -114,7 +114,7 @@ public class ApiVehicleParkingWithEntrance { this.hasWheelchairAccessibleCarPlaces = hasWheelchairAccessibleCarPlaces; this.capacity = capacity; this.availability = availability; - this.realtime = realtime; + this.realTime = realTime; } public static ApiVehicleParkingWithEntranceBuilder builder() { @@ -137,7 +137,7 @@ public static class ApiVehicleParkingWithEntranceBuilder { private boolean hasWheelchairAccessibleCarPlaces; private ApiVehicleParkingSpaces capacity; private ApiVehicleParkingSpaces availability; - private boolean realtime; + private boolean realTime; ApiVehicleParkingWithEntranceBuilder() {} @@ -213,8 +213,8 @@ public ApiVehicleParkingWithEntranceBuilder availability(ApiVehicleParkingSpaces return this; } - public ApiVehicleParkingWithEntranceBuilder realtime(boolean realtime) { - this.realtime = realtime; + public ApiVehicleParkingWithEntranceBuilder realTime(boolean realTime) { + this.realTime = realTime; return this; } @@ -234,7 +234,7 @@ public ApiVehicleParkingWithEntrance build() { hasWheelchairAccessibleCarPlaces, capacity, availability, - realtime + realTime ); } } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLRequestContext.java b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLRequestContext.java index d7229b84b1f..dfc3e2d75f8 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLRequestContext.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLRequestContext.java @@ -17,7 +17,7 @@ public record GraphQLRequestContext( FareService fareService, VehicleParkingService vehicleParkingService, VehicleRentalService vehicleRentalService, - RealtimeVehicleService realtimeVehicleService, + RealtimeVehicleService realTimeVehicleService, GraphFinder graphFinder, RouteRequest defaultRouteRequest ) { diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/PatternImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/PatternImpl.java index 18ab5c5d5e9..241434c664f 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/PatternImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/PatternImpl.java @@ -249,7 +249,7 @@ private List getTrips(DataFetchingEnvironment environment) { } private RealtimeVehicleService getRealtimeVehiclesService(DataFetchingEnvironment environment) { - return environment.getContext().realtimeVehicleService(); + return environment.getContext().realTimeVehicleService(); } private TransitService getTransitService(DataFetchingEnvironment environment) { diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StopImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StopImpl.java index 9353027439b..0730d2fbc91 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StopImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StopImpl.java @@ -538,7 +538,7 @@ private List getTripTimeOnDatesForPatternAtStopIncludingTripsWit } /** - * Get a stream of {@link TripPattern} that were created realtime based of the provided pattern. + * Get a stream of {@link TripPattern} that were created real-time based of the provided pattern. * Only patterns that don't have removed (stops can still be skipped) or added stops are included. */ private Stream getRealtimeAddedPatternsAsStream( diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StoptimeImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StoptimeImpl.java index a74d4f9461d..faf59ef9d6e 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StoptimeImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StoptimeImpl.java @@ -71,7 +71,7 @@ public DataFetcher realtimeState() { return environment -> getSource(environment).isCanceledEffectively() ? RealTimeState.CANCELED.name() - : getSource(environment).getRealtimeState().name(); + : getSource(environment).getRealTimeState().name(); } @Override diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/TripImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/TripImpl.java index 78255ce7787..2502d2b9539 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/TripImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/TripImpl.java @@ -405,7 +405,7 @@ private TransitService getTransitService(DataFetchingEnvironment environment) { } private RealtimeVehicleService getRealtimeVehiclesService(DataFetchingEnvironment environment) { - return environment.getContext().realtimeVehicleService(); + return environment.getContext().realTimeVehicleService(); } private Trip getSource(DataFetchingEnvironment environment) { diff --git a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java index 9c730b94216..5f39872eed5 100644 --- a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java +++ b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java @@ -84,7 +84,7 @@ public enum OTPFeature { RealtimeResolver( false, true, - "When routing with ignoreRealtimeUpdates=true, add an extra step which populates results with realtime data" + "When routing with ignoreRealtimeUpdates=true, add an extra step which populates results with real-time data" ), ReportApi(false, true, "Enable the report API."), RestAPIPassInDefaultConfigAsJson( diff --git a/src/main/java/org/opentripplanner/model/TripTimeOnDate.java b/src/main/java/org/opentripplanner/model/TripTimeOnDate.java index 5cbdc682a1e..9ba85d6b532 100644 --- a/src/main/java/org/opentripplanner/model/TripTimeOnDate.java +++ b/src/main/java/org/opentripplanner/model/TripTimeOnDate.java @@ -189,7 +189,7 @@ public boolean isNoDataStop() { return tripTimes.isNoDataStop(stopIndex); } - public RealTimeState getRealtimeState() { + public RealTimeState getRealTimeState() { return tripTimes.isNoDataStop(stopIndex) ? RealTimeState.SCHEDULED : tripTimes.getRealTimeState(); diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java index ff72bf3efb5..da5c4aad60d 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java @@ -138,7 +138,7 @@ public CostLinearFunction relaxTransitPriorityGroup() { } /** - * When true, realtime updates are ignored during this search. + * When true, real-time updates are ignored during this search. */ public boolean ignoreRealtimeUpdates() { return ignoreRealtimeUpdates; diff --git a/src/main/java/org/opentripplanner/standalone/config/routerconfig/UpdatersConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerconfig/UpdatersConfig.java index 7d8b0f7b33f..0f61fa4f7a2 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerconfig/UpdatersConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerconfig/UpdatersConfig.java @@ -125,7 +125,7 @@ private TimetableSnapshotSourceParameters timetableUpdates(NodeAdapter c) { .of("purgeExpiredData") .since(V2_2) .summary( - "Should expired realtime data be purged from the graph. Apply to GTFS-RT and Siri updates." + "Should expired real-time data be purged from the graph. Apply to GTFS-RT and Siri updates." ) .asBoolean(dflt.purgeExpiredData()) ); diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java index 66f92da04eb..118dca43318 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java @@ -302,7 +302,7 @@ The board time is added to the time when going from the stop (offboard) to onboa c .of("ignoreRealtimeUpdates") .since(V2_0) - .summary("When true, realtime updates are ignored during this search.") + .summary("When true, real-time updates are ignored during this search.") .asBoolean(dft.ignoreRealtimeUpdates()) ) .setOtherThanPreferredRoutesPenalty( diff --git a/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java b/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java index 6cda95eb983..e53939354b7 100644 --- a/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java +++ b/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java @@ -119,7 +119,7 @@ public class TimetableSnapshotSource implements TimetableSnapshotProvider { */ private volatile TimetableSnapshot snapshot = null; - /** Should expired realtime data be purged from the graph. */ + /** Should expired real-time data be purged from the graph. */ private final boolean purgeExpiredData; protected LocalDate lastPurgeDate = null; diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 9dba2c90a5e..d6cc164e3c1 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -3098,7 +3098,7 @@ type QueryType { omitCanceled: Boolean = true """ - When true, realtime updates are ignored during this search. Default value: false + When true, real-time updates are ignored during this search. Default value: false """ ignoreRealtimeUpdates: Boolean diff --git a/src/test/java/org/opentripplanner/routing/algorithm/mapping/__snapshots__/CarSnapshotTest.snap b/src/test/java/org/opentripplanner/routing/algorithm/mapping/__snapshots__/CarSnapshotTest.snap index 8f527b15bf7..31d59483807 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/mapping/__snapshots__/CarSnapshotTest.snap +++ b/src/test/java/org/opentripplanner/routing/algorithm/mapping/__snapshots__/CarSnapshotTest.snap @@ -106,7 +106,7 @@ org.opentripplanner.routing.algorithm.mapping.CarSnapshotTest.directCarPark=[ "hasWheelchairAccessibleCarPlaces" : false, "id" : "OSM:OSMWay/-102488", "name" : "P+R", - "realtime" : false, + "realTime" : false, "tags" : [ "osm:amenity=parking" ] @@ -137,7 +137,7 @@ org.opentripplanner.routing.algorithm.mapping.CarSnapshotTest.directCarPark=[ "hasWheelchairAccessibleCarPlaces" : false, "id" : "OSM:OSMWay/-102488", "name" : "P+R", - "realtime" : false, + "realTime" : false, "tags" : [ "osm:amenity=parking" ] From bdae8134146bb06e9e6514d960ae5e4a56ed4f3c Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 4 Dec 2023 17:35:39 +0100 Subject: [PATCH 66/85] Fix syntax [ci skip] --- .github/workflows/prune-container-images.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/prune-container-images.yml b/.github/workflows/prune-container-images.yml index 518e8afb67b..17da231ff61 100644 --- a/.github/workflows/prune-container-images.yml +++ b/.github/workflows/prune-container-images.yml @@ -1,4 +1,4 @@ -name: 'Delete unused snapshot container images' +name: 'Prune container images' on: schedule: @@ -19,4 +19,4 @@ jobs: # remove all snapshot container images that have not been pulled for over a year # --keep-semver makes sure that any image with a x.y.z version scheme is unaffected by this pip install prune-container-repo==0.0.3 - prune-container-repo -u ${CONTAINER_REGISTRY_USER} -r ${CONTAINER_REPO} --days=365 --keep-semver --activate + prune-container-repo -u ${CONTAINER_REGISTRY_USER} -r ${CONTAINER_REPO} --days=365 --keep-semver=true --activate From 449c297fab00afd20d78551c959207ba418cb460 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 4 Dec 2023 17:54:48 +0100 Subject: [PATCH 67/85] Upgrade prune version [ci skip] --- .github/workflows/prune-container-images.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/prune-container-images.yml b/.github/workflows/prune-container-images.yml index 17da231ff61..c1653701c3b 100644 --- a/.github/workflows/prune-container-images.yml +++ b/.github/workflows/prune-container-images.yml @@ -18,5 +18,5 @@ jobs: run: | # remove all snapshot container images that have not been pulled for over a year # --keep-semver makes sure that any image with a x.y.z version scheme is unaffected by this - pip install prune-container-repo==0.0.3 - prune-container-repo -u ${CONTAINER_REGISTRY_USER} -r ${CONTAINER_REPO} --days=365 --keep-semver=true --activate + pip install prune-container-repo==0.0.4 + prune-container-repo -u ${CONTAINER_REGISTRY_USER} -r ${CONTAINER_REPO} --days=365 --keep-semver --activate From 15d0a81c958efdb7dfc1876223a041b15c8f8086 Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Tue, 5 Dec 2023 10:51:56 +0000 Subject: [PATCH 68/85] Add changelog entry for #5535 [ci skip] --- docs/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Changelog.md b/docs/Changelog.md index 36cdc98f5a2..0fa8e2363cf 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -52,6 +52,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Fix missed trip when arrive-by search-window is off by one minute [#5520](https://github.com/opentripplanner/OpenTripPlanner/pull/5520) - Transit group priority - Part 1 [#4999](https://github.com/opentripplanner/OpenTripPlanner/pull/4999) - Remove `matchBusRoutesToStreets` [#5523](https://github.com/opentripplanner/OpenTripPlanner/pull/5523) +- Rename realtime to real-time in docs [#5535](https://github.com/opentripplanner/OpenTripPlanner/pull/5535) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.4.0 (2023-09-13) From 5759a42fbf4953d5bf18f35c0faddc4af99ba54d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 6 Dec 2023 10:56:29 +0000 Subject: [PATCH 69/85] fix(deps): update dependency org.apache.httpcomponents.client5:httpclient5 to v5.3 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e0de45f1f7e..c43b576b446 100644 --- a/pom.xml +++ b/pom.xml @@ -898,7 +898,7 @@ org.apache.httpcomponents.client5 httpclient5 - 5.2.1 + 5.3 commons-cli From a66b53411d0287e9ba3a9f3f5b81c1944c8aa22f Mon Sep 17 00:00:00 2001 From: Henrik Abrahamsson Date: Wed, 6 Dec 2023 14:38:55 +0100 Subject: [PATCH 70/85] Fix issue where stop points are sometimes added twice to index This only happens when there are duplicate JourneyPattern in the netex files. --- .../netex/mapping/GroupNetexMapper.java | 4 +- .../netex/mapping/NetexMapper.java | 3 +- .../netex/mapping/TransferMapper.java | 39 ++++++++++--------- 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/opentripplanner/netex/mapping/GroupNetexMapper.java b/src/main/java/org/opentripplanner/netex/mapping/GroupNetexMapper.java index 3428f0f8abd..7abb05854f8 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/GroupNetexMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/GroupNetexMapper.java @@ -3,7 +3,9 @@ import com.google.common.collect.ArrayListMultimap; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.model.impl.OtpTransitServiceBuilder; import org.opentripplanner.model.transfer.ConstrainedTransfer; @@ -25,7 +27,7 @@ class GroupNetexMapper { /** * A map from trip/serviceJourney id to an ordered list of scheduled stop point ids. */ - final ArrayListMultimap scheduledStopPointsIndex = ArrayListMultimap.create(); + final Map> scheduledStopPointsIndex = new HashMap<>(); GroupNetexMapper( FeedScopedIdFactory idFactory, diff --git a/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java b/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java index 4c20eee2ce6..26016947c09 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java @@ -1,6 +1,7 @@ package org.opentripplanner.netex.mapping; import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; import jakarta.xml.bind.JAXBElement; import java.time.LocalDateTime; import java.time.ZoneId; @@ -469,7 +470,7 @@ private void mapTripPatterns(Map serviceIds) { transitBuilder.getTripPatterns().put(it.getKey(), it.getValue()); } currentMapperIndexes.addStopTimesByNetexId(result.stopTimeByNetexId); - groupMapper.scheduledStopPointsIndex.putAll(result.scheduledStopPointsIndex); + groupMapper.scheduledStopPointsIndex.putAll(Multimaps.asMap(result.scheduledStopPointsIndex)); transitBuilder.getTripOnServiceDates().addAll(result.tripOnServiceDates); } } diff --git a/src/main/java/org/opentripplanner/netex/mapping/TransferMapper.java b/src/main/java/org/opentripplanner/netex/mapping/TransferMapper.java index b5b8f42ba36..cb908922160 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/TransferMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/TransferMapper.java @@ -1,6 +1,7 @@ package org.opentripplanner.netex.mapping; -import com.google.common.collect.ArrayListMultimap; +import java.util.List; +import java.util.Map; import javax.annotation.Nullable; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.model.transfer.ConstrainedTransfer; @@ -23,13 +24,13 @@ public class TransferMapper { private final FeedScopedIdFactory idFactory; private final DataImportIssueStore issueStore; - private final ArrayListMultimap scheduledStopPointsIndex; + private final Map> scheduledStopPointsIndex; private final EntityById trips; public TransferMapper( FeedScopedIdFactory idFactory, DataImportIssueStore issueStore, - ArrayListMultimap scheduledStopPointsIndex, + Map> scheduledStopPointsIndex, EntityById trips ) { this.idFactory = idFactory; @@ -139,32 +140,34 @@ private int findStopPosition( ScheduledStopPointRefStructure scheduledStopPointRef ) { String sspId = scheduledStopPointRef.getRef(); + var scheduledStopPoints = scheduledStopPointsIndex.get(sjId); + String errorMessage; + + if (scheduledStopPoints != null) { + var index = + switch (label) { + case Label.TO -> scheduledStopPoints.indexOf(sspId); + case Label.FROM -> scheduledStopPoints.lastIndexOf(sspId); + }; + if (index >= 0) { + return index; + } - int index = -1; - if (label == Label.TO) { - index = scheduledStopPointsIndex.get(sjId).indexOf(sspId); - } else if (label == Label.FROM) { - index = scheduledStopPointsIndex.get(sjId).lastIndexOf(sspId); - } - - if (index >= 0) { - return index; + errorMessage = "Scheduled-stop-point-ref not found"; + } else { + errorMessage = "Service-journey not found"; } - String detailedMsg = scheduledStopPointsIndex.containsKey(sjId) - ? "Scheduled-stop-point-ref not found" - : "Service-journey not found"; - issueStore.add( new InterchangePointMappingFailed( - detailedMsg, + errorMessage, interchangeId, label.label(fieldName), sjId, sspId ) ); - return index; + return -1; } private FeedScopedId createId(String id) { From 13cd9fca521b489010ef2ac1e664e380996993e4 Mon Sep 17 00:00:00 2001 From: Eivind Morris Bakke Date: Wed, 6 Dec 2023 21:45:58 +0100 Subject: [PATCH 71/85] Adds doc to src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java Co-authored-by: Thomas Gran --- .../org/opentripplanner/ext/transmodelapi/model/EnumTypes.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java index bc4146fa2f5..e585ad55c57 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java @@ -49,7 +49,7 @@ public class EnumTypes { .value("noFilter", AlternativeLegsFilter.NO_FILTER) .value("sameAuthority", AlternativeLegsFilter.SAME_AGENCY) .value("sameMode", AlternativeLegsFilter.SAME_MODE) - .value("sameSubmode", AlternativeLegsFilter.SAME_SUBMODE) + .value("sameSubmode", AlternativeLegsFilter.SAME_SUBMODE, "Must match both subMode and mode") .value("sameLine", AlternativeLegsFilter.SAME_ROUTE) .build(); From 7f075c1b5e788659bc069e76d04904a1c6fcdfe7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 7 Dec 2023 09:32:04 +0000 Subject: [PATCH 72/85] fix(deps): update dependency org.entur.gbfs:gbfs-java-model to v3.0.16 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c43b576b446..c133a8e9310 100644 --- a/pom.xml +++ b/pom.xml @@ -703,7 +703,7 @@ org.entur.gbfs gbfs-java-model - 3.0.13 + 3.0.16 From 9e9e0786ce6fa6016d0bffdd8747d03652322fe3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 7 Dec 2023 12:07:04 +0000 Subject: [PATCH 73/85] fix(deps): update dependency org.onebusaway:onebusaway-gtfs to v1.4.9 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index baf3cc48fc0..195d2580fda 100644 --- a/pom.xml +++ b/pom.xml @@ -853,7 +853,7 @@ org.onebusaway onebusaway-gtfs - 1.4.5 + 1.4.9 org.slf4j From 28c892d7587cc1f357fbcbbc3e0913c070e51110 Mon Sep 17 00:00:00 2001 From: OTP Serialization Version Bot Date: Thu, 7 Dec 2023 14:56:11 +0000 Subject: [PATCH 74/85] Bump serialization version id for #5525 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index baf3cc48fc0..d48aa7056a6 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ - 131 + 132 30.1 2.49 From c894b80a7290c3ecf5aa1c54a9fdce61152aa045 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 8 Dec 2023 09:41:52 +0100 Subject: [PATCH 75/85] refactor: Add "-Dps" as alias for -P prettierSkip --- pom.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pom.xml b/pom.xml index d48aa7056a6..f9a7037b8bc 100644 --- a/pom.xml +++ b/pom.xml @@ -966,6 +966,17 @@ prettierSkip + + + + ps + + + From 3e011bdb46b5a3d730713087e322605f5ce75e2f Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 8 Dec 2023 10:29:56 +0100 Subject: [PATCH 76/85] Update pom.xml Co-authored-by: Leonard Ehrenfried --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f9a7037b8bc..f670d56cfc3 100644 --- a/pom.xml +++ b/pom.xml @@ -969,7 +969,7 @@ From 978603516eae95fbc72af49943f6152a8c0d7a47 Mon Sep 17 00:00:00 2001 From: eibakke Date: Fri, 8 Dec 2023 22:28:23 +0100 Subject: [PATCH 77/85] Updates schema. --- src/ext/graphql/transmodelapi/schema.graphql | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ext/graphql/transmodelapi/schema.graphql b/src/ext/graphql/transmodelapi/schema.graphql index 9a31b1cd5b9..f6eed80d2b3 100644 --- a/src/ext/graphql/transmodelapi/schema.graphql +++ b/src/ext/graphql/transmodelapi/schema.graphql @@ -1405,6 +1405,7 @@ enum AlternativeLegsFilter { sameAuthority sameLine sameMode + "Must match both subMode and mode" sameSubmode } From 421e58b39afed7c15b1c1c838b8c63fe1e1df856 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 00:28:55 +0000 Subject: [PATCH 78/85] chore(deps): update dependency com.tngtech.archunit:archunit to v1.2.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ddd90349001..523aeb20d03 100644 --- a/pom.xml +++ b/pom.xml @@ -722,7 +722,7 @@ com.tngtech.archunit archunit - 1.2.0 + 1.2.1 test From 48fb2533f66b1af42a5574c66f00cc43e512f7b7 Mon Sep 17 00:00:00 2001 From: OTP Serialization Version Bot Date: Thu, 7 Dec 2023 15:56:11 +0100 Subject: [PATCH 79/85] fix: Relax TripTimes validation to [-12h, 20d] Some trips last for more than 10 days, so we relax this sanity check to 20 days. --- .../model/timetable/ScheduledTripTimes.java | 2 +- .../timetable/ScheduledTripTimesTest.java | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java index b950cdebc5e..90ed7ac31cc 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java @@ -35,7 +35,7 @@ public final class ScheduledTripTimes implements TripTimes { /** * We allow a trip to last for maximum 10 days. In Norway the longest trip is 6 days. */ - private static final int MAX_TIME = DurationUtils.durationInSeconds("10d"); + private static final int MAX_TIME = DurationUtils.durationInSeconds("20d"); /** * Implementation notes: This allows re-using the same scheduled arrival and departure time diff --git a/src/test/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesTest.java b/src/test/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesTest.java index d548be2309b..ed6c5c70f92 100644 --- a/src/test/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesTest.java +++ b/src/test/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opentripplanner.transit.model._data.TransitModelForTest.id; @@ -36,7 +37,7 @@ class ScheduledTripTimesTest { } private final ScheduledTripTimes subject = ScheduledTripTimes - .of(null) + .of() .withArrivalTimes("10:00 11:00 12:00") .withDepartureTimes("10:01 11:02 12:03") .withServiceCode(SERVICE_CODE) @@ -98,6 +99,21 @@ void isTimepoint() { assertFalse(subject.isTimepoint(STOP_POS_2)); } + @Test + void validateLastArrivalTimeIsNotMoreThan20DaysAfterFirstDepartureTime() { + var ex = assertThrows( + IllegalArgumentException.class, + () -> + ScheduledTripTimes + .of() + .withDepartureTimes("10:00 12:00 10:00:01+20d") + .withServiceCode(SERVICE_CODE) + .withTrip(TRIP) + .build() + ); + assertEquals("The value is not in range[-43200, 1728000]: 1728001", ex.getMessage()); + } + @Test void getTrip() { assertEquals(TRIP, subject.getTrip()); From 168c0951e2b54b0ef927b401a25eb346ee208a76 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Mon, 11 Dec 2023 14:56:18 +0100 Subject: [PATCH 80/85] Update src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java Co-authored-by: Leonard Ehrenfried --- .../transit/model/timetable/ScheduledTripTimes.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java index 90ed7ac31cc..f502a220073 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java @@ -33,7 +33,7 @@ public final class ScheduledTripTimes implements TripTimes { */ private static final int MIN_TIME = DurationUtils.durationInSeconds("-12h"); /** - * We allow a trip to last for maximum 10 days. In Norway the longest trip is 6 days. + * We allow a trip to last for maximum 20 days. In Norway the longest trip is 6 days. */ private static final int MAX_TIME = DurationUtils.durationInSeconds("20d"); From 6265fc8d1866f3d5d2bc9e606a913fa013651031 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 12 Dec 2023 01:13:32 +0000 Subject: [PATCH 81/85] chore(deps): update dependency org.mockito:mockito-core to v5.8.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 523aeb20d03..6110c4034f2 100644 --- a/pom.xml +++ b/pom.xml @@ -728,7 +728,7 @@ org.mockito mockito-core - 5.7.0 + 5.8.0 test From ff814e36f8d1ada1a6d77250a631a3eb9fcf9ae6 Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Tue, 12 Dec 2023 09:48:58 +0000 Subject: [PATCH 82/85] Add changelog entry for #5548 [ci skip] --- docs/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Changelog.md b/docs/Changelog.md index 0fa8e2363cf..9f15a929505 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -53,6 +53,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Transit group priority - Part 1 [#4999](https://github.com/opentripplanner/OpenTripPlanner/pull/4999) - Remove `matchBusRoutesToStreets` [#5523](https://github.com/opentripplanner/OpenTripPlanner/pull/5523) - Rename realtime to real-time in docs [#5535](https://github.com/opentripplanner/OpenTripPlanner/pull/5535) +- Add same submode in alternative legs filter [#5548](https://github.com/opentripplanner/OpenTripPlanner/pull/5548) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.4.0 (2023-09-13) From 0af8a79ab2a308ae04d1e5bb0cb7f142c5685fc4 Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Tue, 12 Dec 2023 09:52:36 +0000 Subject: [PATCH 83/85] Add changelog entry for #5552 [ci skip] --- docs/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Changelog.md b/docs/Changelog.md index 9f15a929505..06beb1d120c 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -54,6 +54,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Remove `matchBusRoutesToStreets` [#5523](https://github.com/opentripplanner/OpenTripPlanner/pull/5523) - Rename realtime to real-time in docs [#5535](https://github.com/opentripplanner/OpenTripPlanner/pull/5535) - Add same submode in alternative legs filter [#5548](https://github.com/opentripplanner/OpenTripPlanner/pull/5548) +- Fix issue where stop points are sometimes added twice to index [#5552](https://github.com/opentripplanner/OpenTripPlanner/pull/5552) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.4.0 (2023-09-13) From 7f71178cf63e918537d0353d8e0f7dcbbf9602d8 Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Tue, 12 Dec 2023 11:13:49 +0000 Subject: [PATCH 84/85] Add changelog entry for #5514 [ci skip] --- docs/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Changelog.md b/docs/Changelog.md index 06beb1d120c..98f716c15a0 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -55,6 +55,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Rename realtime to real-time in docs [#5535](https://github.com/opentripplanner/OpenTripPlanner/pull/5535) - Add same submode in alternative legs filter [#5548](https://github.com/opentripplanner/OpenTripPlanner/pull/5548) - Fix issue where stop points are sometimes added twice to index [#5552](https://github.com/opentripplanner/OpenTripPlanner/pull/5552) +- Improve shutdown logic [#5514](https://github.com/opentripplanner/OpenTripPlanner/pull/5514) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.4.0 (2023-09-13) From 21fa2733439a77b54bae2fd19875dde11d2b0ed6 Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Wed, 13 Dec 2023 10:29:40 +0000 Subject: [PATCH 85/85] Add changelog entry for #5542 [ci skip] --- docs/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Changelog.md b/docs/Changelog.md index 98f716c15a0..08a323fe6f6 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -56,6 +56,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Add same submode in alternative legs filter [#5548](https://github.com/opentripplanner/OpenTripPlanner/pull/5548) - Fix issue where stop points are sometimes added twice to index [#5552](https://github.com/opentripplanner/OpenTripPlanner/pull/5552) - Improve shutdown logic [#5514](https://github.com/opentripplanner/OpenTripPlanner/pull/5514) +- Create TripOnServiceDate for new siri realtime servicejourneys [#5542](https://github.com/opentripplanner/OpenTripPlanner/pull/5542) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.4.0 (2023-09-13)