result = new ArrayList<>();
-
- var in = new ObjectInputStream(input);
-
- readAndMatchVersion(in, definition, matchNewVersionPlusOne);
+ matchVersion(definition, matchNewVersionPlusOne);
+ int index = 1;
for (FieldDefinition field : definition.listFields()) {
if (matchNewVersionPlusOne && field.deprecated()) {
continue;
}
- var v = read(in, field);
+ var v = read(field, index);
if (!field.deprecated()) {
result.add(v);
}
+ ++index;
}
return result;
}
- private void readAndMatchVersion(
- ObjectInputStream in,
- TokenDefinition definition,
- boolean matchVersionPlusOne
- ) throws IOException {
+ private void matchVersion(TokenDefinition definition, boolean matchVersionPlusOne) {
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()
+ int version = readVersion();
+ if (version != matchVersion) {
+ throw new IllegalStateException(
+ "Version does not match. Token version: " +
+ version +
+ ", 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());
+ private Object read(FieldDefinition field, int index) {
+ return field.type().stringToValue(values.get(index));
}
- private static Instant readTimeInstant(ObjectInputStream in) throws IOException {
- return Instant.parse(in.readUTF());
+ private int readVersion() {
+ return Integer.parseInt(values.get(0));
}
}
diff --git a/src/main/java/org/opentripplanner/framework/token/Serializer.java b/src/main/java/org/opentripplanner/framework/token/Serializer.java
index d65f0b8983d..2d01a0ebf5e 100644
--- a/src/main/java/org/opentripplanner/framework/token/Serializer.java
+++ b/src/main/java/org/opentripplanner/framework/token/Serializer.java
@@ -1,81 +1,50 @@
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;
-import java.time.Instant;
import java.util.Base64;
-import org.opentripplanner.framework.time.DurationUtils;
+import org.opentripplanner.framework.text.CharacterEscapeFormatter;
-class Serializer implements Closeable {
+class Serializer {
private final TokenDefinition definition;
private final Object[] values;
- private final ObjectOutputStream out;
- private final ByteArrayOutputStream buf = new ByteArrayOutputStream();
+ private final StringBuilder buf = new StringBuilder();
+ private final CharacterEscapeFormatter tokenFormatter = TokenFormatterConfiguration.tokenFormatter();
- private Serializer(TokenDefinition definition, Object[] values) throws IOException {
+ private Serializer(TokenDefinition definition, Object[] values) {
this.definition = definition;
this.values = values;
- this.out = new ObjectOutputStream(buf);
}
- @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()) {
- s.write(fieldName);
- }
- return s.serialize();
+ static String serialize(TokenDefinition definition, Object[] values) {
+ var s = new Serializer(definition, values);
+ s.writeVersion(definition.version());
+ for (var fieldName : definition.fieldNames()) {
+ s.write(fieldName);
}
+ return s.serialize();
}
- private String serialize() throws IOException {
- out.close();
- return Base64.getUrlEncoder().encodeToString(buf.toByteArray());
+ private String serialize() {
+ return Base64.getUrlEncoder().encodeToString(buf.toString().getBytes());
}
- private void write(String fieldName) throws IOException {
+ private void write(String fieldName) {
write(fieldName, values[definition.index(fieldName)]);
}
- private void write(String fieldName, Object value) throws IOException {
+ private void write(String fieldName, Object value) {
var type = definition.type(fieldName);
- switch (type) {
- 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 void writeByte(byte value) throws IOException {
- out.writeByte(value);
+ writeString(type.valueToString(value));
}
- private void writeInt(int value) throws IOException {
- out.writeUTF(Integer.toString(value));
+ private void writeVersion(int value) {
+ writeString(TokenType.INT.valueToString(value));
}
- private void writeString(String value) throws IOException {
- out.writeUTF(value);
- }
-
- private void writeDuration(Duration duration) throws IOException {
- out.writeUTF(DurationUtils.durationToStr(duration));
- }
-
- private void writeTimeInstant(Instant time) throws IOException {
- out.writeUTF(time.toString());
+ private void writeString(String value) {
+ if (value != null) {
+ buf.append(tokenFormatter.encode(value));
+ }
+ buf.append(TokenFormatterConfiguration.fieldSeparator());
}
}
diff --git a/src/main/java/org/opentripplanner/framework/token/Token.java b/src/main/java/org/opentripplanner/framework/token/Token.java
index 0d527a16cbe..2dc02169271 100644
--- a/src/main/java/org/opentripplanner/framework/token/Token.java
+++ b/src/main/java/org/opentripplanner/framework/token/Token.java
@@ -4,6 +4,7 @@
import java.time.Instant;
import java.util.List;
import java.util.Objects;
+import java.util.Optional;
import java.util.stream.Collectors;
/**
@@ -24,6 +25,10 @@ public int version() {
return definition.version();
}
+ public boolean getBoolean(String fieldName) {
+ return (boolean) get(fieldName, TokenType.BOOLEAN);
+ }
+
public byte getByte(String fieldName) {
return (byte) get(fieldName, TokenType.BYTE);
}
@@ -40,6 +45,26 @@ public String getString(String fieldName) {
return (String) get(fieldName, TokenType.STRING);
}
+ /**
+ * Be careful with enums. If values are added or deleted the backward/forward compatibility
+ * is compromised. This method return an empty value if the enum does not exist.
+ *
+ * To ensure that enum values are forward compatible the value must first be added, and then it
+ * can not be used in a token before OTP is released and deployed. Then when the enum value
+ * exist in the deployed server, then a new version of OTP can be rolled out which now can use
+ * the new value.
+ *
+ * To ensure backwards compatibility, enum values should be **deprecated**, not removed. The enum
+ * value can only be deleted, when all tokens with the value has expired (depends on use-case).
+ */
+ public > Optional getEnum(String fieldName, Class enumClass) {
+ try {
+ return Optional.of(Enum.valueOf(enumClass, (String) get(fieldName, TokenType.ENUM)));
+ } catch (IllegalArgumentException ignore) {
+ return Optional.empty();
+ }
+ }
+
public Instant getTimeInstant(String fieldName) {
return (Instant) get(fieldName, TokenType.TIME_INSTANT);
}
diff --git a/src/main/java/org/opentripplanner/framework/token/TokenBuilder.java b/src/main/java/org/opentripplanner/framework/token/TokenBuilder.java
index d5f1b043818..4061d073a2d 100644
--- a/src/main/java/org/opentripplanner/framework/token/TokenBuilder.java
+++ b/src/main/java/org/opentripplanner/framework/token/TokenBuilder.java
@@ -1,6 +1,5 @@
package org.opentripplanner.framework.token;
-import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import org.opentripplanner.framework.lang.ObjectUtils;
@@ -18,10 +17,18 @@ public TokenBuilder(TokenDefinition definition) {
this.values = new Object[definition.size()];
}
+ public TokenBuilder withBoolean(String fieldName, boolean v) {
+ return with(fieldName, TokenType.BOOLEAN, v);
+ }
+
public TokenBuilder withByte(String fieldName, byte v) {
return with(fieldName, TokenType.BYTE, v);
}
+ public TokenBuilder withEnum(String fieldName, Enum> v) {
+ return with(fieldName, TokenType.ENUM, v);
+ }
+
public TokenBuilder withDuration(String fieldName, Duration v) {
return with(fieldName, TokenType.DURATION, v);
}
@@ -39,11 +46,7 @@ public TokenBuilder withTimeInstant(String fieldName, Instant v) {
}
public String build() {
- try {
- return Serializer.serialize(definition, values);
- } catch (IOException e) {
- throw new RuntimeException(e.getMessage(), e);
- }
+ return Serializer.serialize(definition, values);
}
private TokenBuilder with(String fieldName, TokenType type, Object value) {
diff --git a/src/main/java/org/opentripplanner/framework/token/TokenDefinitionBuilder.java b/src/main/java/org/opentripplanner/framework/token/TokenDefinitionBuilder.java
index f4d3014128e..06a935c7b47 100644
--- a/src/main/java/org/opentripplanner/framework/token/TokenDefinitionBuilder.java
+++ b/src/main/java/org/opentripplanner/framework/token/TokenDefinitionBuilder.java
@@ -26,10 +26,18 @@ public TokenDefinitionBuilder addByte(String fieldName) {
return add(fieldName, TokenType.BYTE);
}
+ public TokenDefinitionBuilder addBoolean(String fieldName) {
+ return add(fieldName, TokenType.BOOLEAN);
+ }
+
public TokenDefinitionBuilder addDuration(String fieldName) {
return add(fieldName, TokenType.DURATION);
}
+ public TokenDefinitionBuilder addEnum(String fieldName) {
+ return add(fieldName, TokenType.ENUM);
+ }
+
public TokenDefinitionBuilder addInt(String fieldName) {
return add(fieldName, TokenType.INT);
}
diff --git a/src/main/java/org/opentripplanner/framework/token/TokenFormatterConfiguration.java b/src/main/java/org/opentripplanner/framework/token/TokenFormatterConfiguration.java
new file mode 100644
index 00000000000..3945014dad5
--- /dev/null
+++ b/src/main/java/org/opentripplanner/framework/token/TokenFormatterConfiguration.java
@@ -0,0 +1,25 @@
+package org.opentripplanner.framework.token;
+
+import org.opentripplanner.framework.text.CharacterEscapeFormatter;
+
+class TokenFormatterConfiguration {
+
+ private static final char TOKEN_ESCAPE = '\\';
+ private static final char TOKEN_SUBSTITUTION = '+';
+ private static final char FIELD_SEPARATOR = '|';
+
+ /** Prevent instantiation - this is a utility class. */
+ private TokenFormatterConfiguration() {}
+
+ /**
+ * We use the pipe '|' for field separations. The IDs included in the token frequently use
+ * ':' so the visual difference is better than the alternatives like ',' ';' and TAB.
+ */
+ static char fieldSeparator() {
+ return FIELD_SEPARATOR;
+ }
+
+ static CharacterEscapeFormatter tokenFormatter() {
+ return new CharacterEscapeFormatter(TOKEN_ESCAPE, FIELD_SEPARATOR, TOKEN_SUBSTITUTION);
+ }
+}
diff --git a/src/main/java/org/opentripplanner/framework/token/TokenType.java b/src/main/java/org/opentripplanner/framework/token/TokenType.java
index 0a890a44005..aea39949b0e 100644
--- a/src/main/java/org/opentripplanner/framework/token/TokenType.java
+++ b/src/main/java/org/opentripplanner/framework/token/TokenType.java
@@ -1,5 +1,10 @@
package org.opentripplanner.framework.token;
+import java.time.Duration;
+import java.time.Instant;
+import javax.annotation.Nullable;
+import org.opentripplanner.framework.time.DurationUtils;
+
/**
* List of types we can store in a token.
*
@@ -8,13 +13,47 @@
* compatible with the new value of the enum.
*/
public enum TokenType {
+ BOOLEAN,
BYTE,
DURATION,
+ ENUM,
INT,
STRING,
TIME_INSTANT;
+ private static final String EMPTY = "";
+
boolean isNot(TokenType other) {
return this != other;
}
+
+ public String valueToString(@Nullable Object value) {
+ if (value == null) {
+ return EMPTY;
+ }
+ return switch (this) {
+ case BOOLEAN -> Boolean.toString((boolean) value);
+ case BYTE -> Byte.toString((byte) value);
+ case DURATION -> DurationUtils.durationToStr((Duration) value);
+ case ENUM -> ((Enum>) value).name();
+ case INT -> Integer.toString((int) value);
+ case STRING -> (String) value;
+ case TIME_INSTANT -> value.toString();
+ };
+ }
+
+ public Object stringToValue(String value) {
+ if (EMPTY.equals(value)) {
+ return null;
+ }
+ return switch (this) {
+ case BOOLEAN -> Boolean.valueOf(value);
+ case BYTE -> Byte.valueOf(value);
+ case DURATION -> DurationUtils.duration(value);
+ case ENUM -> value;
+ case INT -> Integer.valueOf(value);
+ case STRING -> value;
+ case TIME_INSTANT -> Instant.parse(value);
+ };
+ }
}
diff --git a/src/main/java/org/opentripplanner/framework/tostring/ToStringBuilder.java b/src/main/java/org/opentripplanner/framework/tostring/ToStringBuilder.java
index d2cec512420..cb5d26294db 100644
--- a/src/main/java/org/opentripplanner/framework/tostring/ToStringBuilder.java
+++ b/src/main/java/org/opentripplanner/framework/tostring/ToStringBuilder.java
@@ -60,6 +60,14 @@ public static ToStringBuilder of(Class> clazz) {
return new ToStringBuilder(clazz.getSimpleName());
}
+ /**
+ * Create a ToStringBuilder for a "named" type. The preferred method is {@link #of(Class)},
+ * but this can be used if the type is unknown or irrelevant.
+ */
+ public static ToStringBuilder of(String name) {
+ return new ToStringBuilder(name);
+ }
+
/**
* Create a ToStringBuilder for a regular POJO type without including the type in the name. Some
* classes are always embedded in other classes and the type is given, for these cases this
diff --git a/src/main/java/org/opentripplanner/framework/tostring/ValueObjectToStringBuilder.java b/src/main/java/org/opentripplanner/framework/tostring/ValueObjectToStringBuilder.java
index 7882bd347da..14584a8dd4a 100644
--- a/src/main/java/org/opentripplanner/framework/tostring/ValueObjectToStringBuilder.java
+++ b/src/main/java/org/opentripplanner/framework/tostring/ValueObjectToStringBuilder.java
@@ -1,6 +1,7 @@
package org.opentripplanner.framework.tostring;
import java.time.Duration;
+import java.time.Instant;
import java.util.function.Function;
import org.opentripplanner.framework.lang.OtpNumberFormat;
import org.opentripplanner.framework.time.DurationUtils;
@@ -153,6 +154,10 @@ public ValueObjectToStringBuilder addDurationSec(Integer durationSeconds) {
return addIt(durationSeconds, DurationUtils::durationToStr);
}
+ public ValueObjectToStringBuilder addTime(Instant time) {
+ return addIt(time, Object::toString);
+ }
+
/**
* Add a cost in the format $N, as in "transit seconds", not centi-seconds as used by Raptor.
*/
diff --git a/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java b/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java
index 3a2bb777f04..8b2e55ffc2f 100644
--- a/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java
+++ b/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java
@@ -160,6 +160,10 @@ public static GraphBuilder create(
graphBuilder.addModule(factory.graphCoherencyCheckerModule());
}
+ if (OTPFeature.Co2Emissions.isOn()) {
+ graphBuilder.addModule(factory.emissionsModule());
+ }
+
if (config.dataImportReport) {
graphBuilder.addModule(factory.dataImportIssueReporter());
}
@@ -168,10 +172,6 @@ public static GraphBuilder create(
graphBuilder.addModuleOptional(factory.dataOverlayFactory());
}
- if (OTPFeature.Co2Emissions.isOn()) {
- graphBuilder.addModule(factory.emissionsModule());
- }
-
graphBuilder.addModule(factory.calculateWorldEnvelopeModule());
return graphBuilder;
diff --git a/src/main/java/org/opentripplanner/gtfs/mapping/LocationGroupMapper.java b/src/main/java/org/opentripplanner/gtfs/mapping/LocationGroupMapper.java
index 4dec2b779ca..591a59c492e 100644
--- a/src/main/java/org/opentripplanner/gtfs/mapping/LocationGroupMapper.java
+++ b/src/main/java/org/opentripplanner/gtfs/mapping/LocationGroupMapper.java
@@ -5,7 +5,9 @@
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
+import java.util.Objects;
import org.onebusaway.gtfs.model.Location;
+import org.onebusaway.gtfs.model.LocationGroup;
import org.onebusaway.gtfs.model.Stop;
import org.opentripplanner.framework.collection.MapUtils;
import org.opentripplanner.framework.i18n.NonLocalizedString;
@@ -19,7 +21,7 @@ public class LocationGroupMapper {
private final LocationMapper locationMapper;
private final StopModelBuilder stopModelBuilder;
- private final Map mappedLocationGroups = new HashMap<>();
+ private final Map mappedLocationGroups = new HashMap<>();
public LocationGroupMapper(
StopMapper stopMapper,
@@ -31,29 +33,34 @@ public LocationGroupMapper(
this.stopModelBuilder = stopModelBuilder;
}
- Collection map(Collection allLocationGroups) {
+ Collection map(Collection allLocationGroups) {
return MapUtils.mapToList(allLocationGroups, this::map);
}
/** Map from GTFS to OTP model, {@code null} safe. */
- GroupStop map(org.onebusaway.gtfs.model.LocationGroup original) {
+ GroupStop map(LocationGroup original) {
return original == null ? null : mappedLocationGroups.computeIfAbsent(original, this::doMap);
}
- private GroupStop doMap(org.onebusaway.gtfs.model.LocationGroup element) {
+ private GroupStop doMap(LocationGroup element) {
GroupStopBuilder groupStopBuilder = stopModelBuilder
.groupStop(mapAgencyAndId(element.getId()))
.withName(new NonLocalizedString(element.getName()));
- for (org.onebusaway.gtfs.model.StopLocation location : element.getLocations()) {
- if (location instanceof Stop) {
- groupStopBuilder.addLocation(stopMapper.map((Stop) location));
- } else if (location instanceof Location) {
- groupStopBuilder.addLocation(locationMapper.map((Location) location));
- } else if (location instanceof org.onebusaway.gtfs.model.LocationGroup) {
- throw new RuntimeException("Nested GroupStops are not allowed");
- } else {
- throw new RuntimeException("Unknown location type: " + location.getClass().getSimpleName());
+ for (var location : element.getLocations()) {
+ Objects.requireNonNull(
+ location,
+ "Location group '%s' contains a null element.".formatted(element.getId())
+ );
+ switch (location) {
+ case Stop stop -> groupStopBuilder.addLocation(stopMapper.map(stop));
+ case Location loc -> groupStopBuilder.addLocation(locationMapper.map(loc));
+ case LocationGroup ignored -> throw new RuntimeException(
+ "Nested GroupStops are not allowed"
+ );
+ default -> throw new RuntimeException(
+ "Unknown location type: " + location.getClass().getSimpleName()
+ );
}
}
diff --git a/src/main/java/org/opentripplanner/gtfs/mapping/StopTimeMapper.java b/src/main/java/org/opentripplanner/gtfs/mapping/StopTimeMapper.java
index 148a5160d24..47f49a58fc1 100644
--- a/src/main/java/org/opentripplanner/gtfs/mapping/StopTimeMapper.java
+++ b/src/main/java/org/opentripplanner/gtfs/mapping/StopTimeMapper.java
@@ -3,6 +3,7 @@
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
+import java.util.Objects;
import org.onebusaway.gtfs.model.Location;
import org.onebusaway.gtfs.model.LocationGroup;
import org.onebusaway.gtfs.model.Stop;
@@ -57,12 +58,18 @@ private StopTime doMap(org.onebusaway.gtfs.model.StopTime rhs) {
StopTime lhs = new StopTime();
lhs.setTrip(tripMapper.map(rhs.getTrip()));
- if (rhs.getStop() instanceof Stop) {
- lhs.setStop(stopMapper.map((Stop) rhs.getStop()));
- } else if (rhs.getStop() instanceof Location) {
- lhs.setStop(locationMapper.map((Location) rhs.getStop()));
- } else if (rhs.getStop() instanceof LocationGroup) {
- lhs.setStop(locationGroupMapper.map((LocationGroup) rhs.getStop()));
+ var stopLocation = rhs.getStopLocation();
+ Objects.requireNonNull(
+ stopLocation,
+ "Trip %s contains stop_time with no stop, location or group.".formatted(rhs.getTrip())
+ );
+ switch (stopLocation) {
+ case Stop stop -> lhs.setStop(stopMapper.map(stop));
+ case Location location -> lhs.setStop(locationMapper.map(location));
+ case LocationGroup locGroup -> lhs.setStop(locationGroupMapper.map(locGroup));
+ default -> throw new IllegalArgumentException(
+ "Unknown location type: %s".formatted(stopLocation)
+ );
}
I18NString stopHeadsign = null;
diff --git a/src/main/java/org/opentripplanner/model/plan/ItinerarySortKey.java b/src/main/java/org/opentripplanner/model/plan/ItinerarySortKey.java
index 0918f398325..b9165270444 100644
--- a/src/main/java/org/opentripplanner/model/plan/ItinerarySortKey.java
+++ b/src/main/java/org/opentripplanner/model/plan/ItinerarySortKey.java
@@ -1,6 +1,7 @@
package org.opentripplanner.model.plan;
import java.time.Instant;
+import org.opentripplanner.framework.tostring.ValueObjectToStringBuilder;
import org.opentripplanner.routing.algorithm.filterchain.comparator.SortOrderComparator;
import org.opentripplanner.routing.algorithm.filterchain.filter.SortingFilter;
@@ -18,4 +19,21 @@ public interface ItinerarySortKey {
int getGeneralizedCost();
int getNumberOfTransfers();
boolean isOnStreetAllTheWay();
+
+ default String keyAsString() {
+ return ValueObjectToStringBuilder
+ .of()
+ .addText("[")
+ .addTime(startTimeAsInstant())
+ .addText(", ")
+ .addTime(endTimeAsInstant())
+ .addText(", ")
+ .addCost(getGeneralizedCost())
+ .addText(", Tx")
+ .addNum(getNumberOfTransfers())
+ .addText(", ")
+ .addBool(isOnStreetAllTheWay(), "onStreet", "transit")
+ .addText("]")
+ .toString();
+ }
}
diff --git a/src/main/java/org/opentripplanner/model/plan/Place.java b/src/main/java/org/opentripplanner/model/plan/Place.java
index 6f39dcad3d1..71c6d9bc188 100644
--- a/src/main/java/org/opentripplanner/model/plan/Place.java
+++ b/src/main/java/org/opentripplanner/model/plan/Place.java
@@ -139,9 +139,9 @@ public static Place forVehicleParkingEntrance(VehicleParkingEntranceVertex verte
traverseMode = TraverseMode.BICYCLE;
}
- boolean realTime =
- request.parking().useAvailabilityInformation() &&
- vertex.getVehicleParking().hasRealTimeDataForMode(traverseMode, request.wheelchair());
+ boolean realTime = vertex
+ .getVehicleParking()
+ .hasRealTimeDataForMode(traverseMode, request.wheelchair());
return new Place(
vertex.getName(),
WgsCoordinate.creatOptionalCoordinate(vertex.getLat(), vertex.getLon()),
diff --git a/src/main/java/org/opentripplanner/model/plan/SortOrder.java b/src/main/java/org/opentripplanner/model/plan/SortOrder.java
index 812973ebb4d..342f963ba00 100644
--- a/src/main/java/org/opentripplanner/model/plan/SortOrder.java
+++ b/src/main/java/org/opentripplanner/model/plan/SortOrder.java
@@ -39,7 +39,7 @@ public enum SortOrder {
* This returns {@code true} for the default depart-after search, and {@code false} for an
* arrive-by search.
*/
- public boolean isSortedByArrivalTimeAscending() {
+ public boolean isSortedByAscendingArrivalTime() {
return this == STREET_AND_ARRIVAL_TIME;
}
}
diff --git a/src/main/java/org/opentripplanner/model/plan/pagecursor/ItineraryPageCut.java b/src/main/java/org/opentripplanner/model/plan/pagecursor/ItineraryPageCut.java
deleted file mode 100644
index 53958837dd8..00000000000
--- a/src/main/java/org/opentripplanner/model/plan/pagecursor/ItineraryPageCut.java
+++ /dev/null
@@ -1,108 +0,0 @@
-package org.opentripplanner.model.plan.pagecursor;
-
-import java.time.Instant;
-import org.opentripplanner.framework.tostring.ToStringBuilder;
-import org.opentripplanner.model.plan.ItinerarySortKey;
-import org.opentripplanner.model.plan.SortOrder;
-import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.DefaultCostCalculator;
-
-/**
- * This class contains all the information needed to dedupe itineraries when
- * paging.
- *
- * It implements the ItinerarySortKey interface so that it can be sorted with itineraries which
- * potentially contain duplicates.
- */
-public record ItineraryPageCut(
- Instant windowStart,
- Instant windowEnd,
- SortOrder sortOrder,
- PagingDeduplicationSection deduplicationSection,
- Instant arrivalTimeThreshold,
- Instant departureTimeThreshold,
- int generalizedCostThreshold,
- int numOfTransfersThreshold,
- boolean onStreetAllTheWayThreshold
-)
- implements ItinerarySortKey {
- @Override
- public String toString() {
- return ToStringBuilder
- .of(ItineraryPageCut.class)
- .addDateTime("windowStart", windowStart)
- .addDateTime("windowEnd", windowEnd)
- .addEnum("sortOrder", sortOrder)
- .addEnum("deduplicationSection", deduplicationSection)
- .addBool("isOnStreetAllTheWayThreshold", onStreetAllTheWayThreshold)
- .addDateTime("arrivalTimeThreshold", arrivalTimeThreshold)
- .addCost(
- "generalizedCostThreshold",
- generalizedCostThreshold,
- DefaultCostCalculator.ZERO_COST
- )
- .addNum("numOfTransfersThreshold", numOfTransfersThreshold)
- .addDateTime("departureTimeThreshold", departureTimeThreshold)
- .toString();
- }
-
- @Override
- public Instant startTimeAsInstant() {
- return departureTimeThreshold();
- }
-
- @Override
- public Instant endTimeAsInstant() {
- return arrivalTimeThreshold();
- }
-
- @Override
- public int getGeneralizedCost() {
- return generalizedCostThreshold();
- }
-
- @Override
- public int getNumberOfTransfers() {
- return numOfTransfersThreshold();
- }
-
- @Override
- public boolean isOnStreetAllTheWay() {
- return isOnStreetAllTheWayThreshold();
- }
-
- public Instant windowStart() {
- return windowStart;
- }
-
- public Instant windowEnd() {
- return windowEnd;
- }
-
- public PagingDeduplicationSection deduplicationSection() {
- return deduplicationSection;
- }
-
- public SortOrder sortOrder() {
- return sortOrder;
- }
-
- public boolean isOnStreetAllTheWayThreshold() {
- return onStreetAllTheWayThreshold;
- }
-
- public Instant arrivalTimeThreshold() {
- return arrivalTimeThreshold;
- }
-
- public int generalizedCostThreshold() {
- return generalizedCostThreshold;
- }
-
- public int numOfTransfersThreshold() {
- return numOfTransfersThreshold;
- }
-
- public Instant departureTimeThreshold() {
- return departureTimeThreshold;
- }
-}
diff --git a/src/main/java/org/opentripplanner/model/plan/pagecursor/PageCursorInput.java b/src/main/java/org/opentripplanner/model/plan/pagecursor/PageCursorInput.java
deleted file mode 100644
index 58cd6c7e24b..00000000000
--- a/src/main/java/org/opentripplanner/model/plan/pagecursor/PageCursorInput.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package org.opentripplanner.model.plan.pagecursor;
-
-import java.time.Instant;
-
-/**
- * This class holds information needed to create the next/previous page cursors when there were
- * itineraries removed due to cropping the list of itineraries using the numItineraries parameter.
- *
- * The Instant fields come from the sets of itineraries that were removed and the ones that were
- * kept as a result of using the numItineraries parameter.
- */
-public interface PageCursorInput {
- Instant earliestRemovedDeparture();
- Instant earliestKeptArrival();
- Instant latestRemovedDeparture();
- Instant latestRemovedArrival();
- Instant firstRemovedArrivalTime();
- boolean firstRemovedIsOnStreetAllTheWay();
- int firstRemovedGeneralizedCost();
- int firstRemovedNumOfTransfers();
- Instant firstRemovedDepartureTime();
- PagingDeduplicationSection deduplicationSection();
-}
diff --git a/src/main/java/org/opentripplanner/model/plan/pagecursor/PageCursorSerializer.java b/src/main/java/org/opentripplanner/model/plan/pagecursor/PageCursorSerializer.java
deleted file mode 100644
index b01f07892ac..00000000000
--- a/src/main/java/org/opentripplanner/model/plan/pagecursor/PageCursorSerializer.java
+++ /dev/null
@@ -1,174 +0,0 @@
-package org.opentripplanner.model.plan.pagecursor;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.time.Duration;
-import java.time.Instant;
-import java.time.ZoneId;
-import java.time.ZonedDateTime;
-import java.util.Base64;
-import javax.annotation.Nullable;
-import org.opentripplanner.framework.lang.StringUtils;
-import org.opentripplanner.model.plan.SortOrder;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-final class PageCursorSerializer {
-
- private static final int NOT_SET = Integer.MIN_VALUE;
- private static final byte VERSION = 1;
- private static final long TIME_ZERO = ZonedDateTime
- .of(2020, 1, 1, 0, 0, 0, 0, ZoneId.of("UTC"))
- .toEpochSecond();
- private static final Logger LOG = LoggerFactory.getLogger(PageCursor.class);
-
- /** private constructor to prevent instantiating this utility class */
- private PageCursorSerializer() {
- /* empty */
- }
-
- @Nullable
- public static String encode(PageCursor cursor) {
- var buf = new ByteArrayOutputStream();
- try (var out = new ObjectOutputStream(buf)) {
- // The order must be the same in the encode and decode function
- writeByte(VERSION, out);
- writeEnum(cursor.type, out);
- writeTime(cursor.earliestDepartureTime, out);
- writeTime(cursor.latestArrivalTime, out);
- writeDuration(cursor.searchWindow, out);
- writeEnum(cursor.originalSortOrder, out);
-
- if (cursor.containsItineraryPageCut()) {
- writeTime(cursor.itineraryPageCut.windowStart(), out);
- writeTime(cursor.itineraryPageCut.windowEnd(), out);
- writeEnum(cursor.itineraryPageCut.deduplicationSection(), out);
- writeBoolean(cursor.itineraryPageCut.isOnStreetAllTheWayThreshold(), out);
- writeTime(cursor.itineraryPageCut.arrivalTimeThreshold(), out);
- writeInt(cursor.itineraryPageCut.generalizedCostThreshold(), out);
- writeInt(cursor.itineraryPageCut.numOfTransfersThreshold(), out);
- writeTime(cursor.itineraryPageCut.departureTimeThreshold(), out);
- }
- out.flush();
- return Base64.getUrlEncoder().encodeToString(buf.toByteArray());
- } catch (IOException e) {
- LOG.error("Failed to encode page cursor", e);
- return null;
- }
- }
-
- @Nullable
- public static PageCursor decode(String cursor) {
- if (StringUtils.hasNoValueOrNullAsString(cursor)) {
- return null;
- }
- try {
- var buf = Base64.getUrlDecoder().decode(cursor);
- var input = new ByteArrayInputStream(buf);
-
- var in = new ObjectInputStream(input);
- // The order must be the same in the encode and decode function
-
- // The version should be used to make serialization read/write forward and backward
- // compatible in the future.
- var version = readByte(in);
- var type = readEnum(in, PageType.class);
- var edt = readTime(in);
- var lat = readTime(in);
- var searchWindow = readDuration(in);
- var originalSortOrder = readEnum(in, SortOrder.class);
-
- if (in.available() > 0) {
- var dedupeWindowStart = readTime(in);
- var dedupeWindowEnd = readTime(in);
- var cropSection = readEnum(in, PagingDeduplicationSection.class);
- var isOnStreetAllTheWayThreshold = readBoolean(in);
- var arrivalTimeDeletionThreshold = readTime(in);
- var generalizedCostDeletionThreshold = readInt(in);
- var numOfTransfersDeletionThreshold = readInt(in);
- var departureTimeDeletionThreshold = readTime(in);
-
- ItineraryPageCut itineraryPageCut = new ItineraryPageCut(
- dedupeWindowStart,
- dedupeWindowEnd,
- originalSortOrder,
- cropSection,
- arrivalTimeDeletionThreshold,
- departureTimeDeletionThreshold,
- generalizedCostDeletionThreshold,
- numOfTransfersDeletionThreshold,
- isOnStreetAllTheWayThreshold
- );
- return new PageCursor(type, originalSortOrder, edt, lat, searchWindow)
- .withItineraryPageCut(itineraryPageCut);
- }
-
- return new PageCursor(type, originalSortOrder, edt, lat, searchWindow);
- } catch (Exception e) {
- String details = e.getMessage();
- if (details != null && !details.isBlank()) {
- LOG.warn("Unable to decode page cursor: '{}'. Details: {}", cursor, details);
- } else {
- LOG.warn("Unable to decode page cursor: '{}'.", cursor);
- }
- return null;
- }
- }
-
- private static void writeByte(byte value, ObjectOutputStream out) throws IOException {
- out.writeByte(value);
- }
-
- private static byte readByte(ObjectInputStream in) throws IOException {
- return in.readByte();
- }
-
- private static void writeInt(int value, ObjectOutputStream out) throws IOException {
- out.writeInt(value);
- }
-
- private static int readInt(ObjectInputStream in) throws IOException {
- return in.readInt();
- }
-
- private static void writeBoolean(boolean value, ObjectOutputStream out) throws IOException {
- out.writeBoolean(value);
- }
-
- private static boolean readBoolean(ObjectInputStream in) throws IOException {
- return in.readBoolean();
- }
-
- private static void writeTime(Instant time, ObjectOutputStream out) throws IOException {
- out.writeInt(time == null ? NOT_SET : (int) (time.getEpochSecond() - TIME_ZERO));
- }
-
- @Nullable
- private static Instant readTime(ObjectInputStream in) throws IOException {
- var value = in.readInt();
- return value == NOT_SET ? null : Instant.ofEpochSecond(TIME_ZERO + value);
- }
-
- private static void writeDuration(Duration duration, ObjectOutputStream out) throws IOException {
- out.writeInt((int) duration.toSeconds());
- }
-
- private static Duration readDuration(ObjectInputStream in) throws IOException {
- return Duration.ofSeconds(in.readInt());
- }
-
- private static > void writeEnum(T value, ObjectOutputStream out)
- throws IOException {
- out.writeUTF(value.name());
- }
-
- @SuppressWarnings("SameParameterValue")
- private static > T readEnum(ObjectInputStream in, Class enumType)
- throws IOException {
- String value = in.readUTF();
- return Enum.valueOf(enumType, value);
- }
-}
diff --git a/src/main/java/org/opentripplanner/model/plan/pagecursor/PagingDeduplicationSection.java b/src/main/java/org/opentripplanner/model/plan/pagecursor/PagingDeduplicationSection.java
deleted file mode 100644
index 0a469fdc6be..00000000000
--- a/src/main/java/org/opentripplanner/model/plan/pagecursor/PagingDeduplicationSection.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package org.opentripplanner.model.plan.pagecursor;
-
-/**
- * The PagingDeduplicationSection enum is used to signal which part of an itinerary list may contain
- * duplicates. When paging it is the opposite of the CropSection defined in ListSection. That is, if
- * the list of itineraries was cropped at the bottom, then any duplicates will appear at the top of
- * the list and vice versa.
- */
-public enum PagingDeduplicationSection {
- HEAD,
- TAIL,
-}
diff --git a/src/main/java/org/opentripplanner/model/plan/PagingSearchWindowAdjuster.java b/src/main/java/org/opentripplanner/model/plan/paging/PagingSearchWindowAdjuster.java
similarity index 99%
rename from src/main/java/org/opentripplanner/model/plan/PagingSearchWindowAdjuster.java
rename to src/main/java/org/opentripplanner/model/plan/paging/PagingSearchWindowAdjuster.java
index 703499a5d26..f9e939532bb 100644
--- a/src/main/java/org/opentripplanner/model/plan/PagingSearchWindowAdjuster.java
+++ b/src/main/java/org/opentripplanner/model/plan/paging/PagingSearchWindowAdjuster.java
@@ -1,4 +1,4 @@
-package org.opentripplanner.model.plan;
+package org.opentripplanner.model.plan.paging;
import java.time.Duration;
import java.time.Instant;
diff --git a/src/main/java/org/opentripplanner/model/plan/paging/cursor/DeduplicationPageCut.java b/src/main/java/org/opentripplanner/model/plan/paging/cursor/DeduplicationPageCut.java
new file mode 100644
index 00000000000..611b8116b8b
--- /dev/null
+++ b/src/main/java/org/opentripplanner/model/plan/paging/cursor/DeduplicationPageCut.java
@@ -0,0 +1,50 @@
+package org.opentripplanner.model.plan.paging.cursor;
+
+import java.time.Instant;
+import org.opentripplanner.model.plan.ItinerarySortKey;
+
+/**
+ * This class contains all the information needed to dedupe itineraries when
+ * paging - the exact same information as the {@link ItinerarySortKey}.
+ *
+ * It implements the ItinerarySortKey interface so that it can be sorted with itineraries which
+ * potentially contain duplicates.
+ */
+record DeduplicationPageCut(
+ Instant departureTime,
+ Instant arrivalTime,
+ int generalizedCost,
+ int numOfTransfers,
+ boolean onStreet
+)
+ implements ItinerarySortKey {
+ @Override
+ public Instant startTimeAsInstant() {
+ return departureTime;
+ }
+
+ @Override
+ public Instant endTimeAsInstant() {
+ return arrivalTime;
+ }
+
+ @Override
+ public int getGeneralizedCost() {
+ return generalizedCost;
+ }
+
+ @Override
+ public int getNumberOfTransfers() {
+ return numOfTransfers;
+ }
+
+ @Override
+ public boolean isOnStreetAllTheWay() {
+ return onStreet;
+ }
+
+ @Override
+ public String toString() {
+ return keyAsString();
+ }
+}
diff --git a/src/main/java/org/opentripplanner/model/plan/pagecursor/PageCursor.java b/src/main/java/org/opentripplanner/model/plan/paging/cursor/PageCursor.java
similarity index 51%
rename from src/main/java/org/opentripplanner/model/plan/pagecursor/PageCursor.java
rename to src/main/java/org/opentripplanner/model/plan/paging/cursor/PageCursor.java
index e0c1e30c64d..a52dc0429c1 100644
--- a/src/main/java/org/opentripplanner/model/plan/pagecursor/PageCursor.java
+++ b/src/main/java/org/opentripplanner/model/plan/paging/cursor/PageCursor.java
@@ -1,9 +1,11 @@
-package org.opentripplanner.model.plan.pagecursor;
+package org.opentripplanner.model.plan.paging.cursor;
import java.time.Duration;
import java.time.Instant;
import javax.annotation.Nullable;
+import org.opentripplanner.framework.collection.ListSection;
import org.opentripplanner.framework.tostring.ToStringBuilder;
+import org.opentripplanner.model.plan.ItinerarySortKey;
import org.opentripplanner.model.plan.SortOrder;
/**
@@ -16,39 +18,17 @@
*
* THIS CLASS IS IMMUTABLE AND THREAD-SAFE
*/
-public class PageCursor {
-
- public final PageType type;
- public final SortOrder originalSortOrder;
- public final Instant earliestDepartureTime;
- public final Instant latestArrivalTime;
- public final Duration searchWindow;
-
- public ItineraryPageCut itineraryPageCut;
-
- PageCursor(
- PageType type,
- SortOrder originalSortOrder,
- Instant earliestDepartureTime,
- Instant latestArrivalTime,
- Duration searchWindow
- ) {
- this.type = type;
- this.searchWindow = searchWindow;
- this.earliestDepartureTime = earliestDepartureTime;
- this.latestArrivalTime = latestArrivalTime;
- this.originalSortOrder = originalSortOrder;
- }
-
- public PageCursor withItineraryPageCut(ItineraryPageCut itineraryPageCut) {
- this.itineraryPageCut = itineraryPageCut;
- return this;
- }
-
+public record PageCursor(
+ PageType type,
+ SortOrder originalSortOrder,
+ Instant earliestDepartureTime,
+ Instant latestArrivalTime,
+ Duration searchWindow,
+ @Nullable ItinerarySortKey itineraryPageCut
+) {
public boolean containsItineraryPageCut() {
return itineraryPageCut != null;
}
-
@Nullable
public String encode() {
return PageCursorSerializer.encode(this);
@@ -59,6 +39,27 @@ public static PageCursor decode(String cursor) {
return PageCursorSerializer.decode(cursor);
}
+ /**
+ * When paging we must crop the list of itineraries in the right end according to the sorting of
+ * the original search and according to the paging direction(next or previous).
+ */
+ public ListSection cropItinerariesAt() {
+ // Depart after search
+ if (originalSortOrder().isSortedByAscendingArrivalTime()) {
+ return switch (type) {
+ case NEXT_PAGE -> ListSection.TAIL;
+ case PREVIOUS_PAGE -> ListSection.HEAD;
+ };
+ }
+ // Arrive by search
+ else {
+ return switch (type) {
+ case NEXT_PAGE -> ListSection.HEAD;
+ case PREVIOUS_PAGE -> ListSection.TAIL;
+ };
+ }
+ }
+
@Override
public String toString() {
return ToStringBuilder
@@ -68,7 +69,8 @@ public String toString() {
.addDateTime("edt", earliestDepartureTime)
.addDateTime("lat", latestArrivalTime)
.addDuration("searchWindow", searchWindow)
- .addObj("itineraryPageCut", itineraryPageCut)
+ // This will only include the sort vector, not everything else in the itinerary
+ .addObjOp("itineraryPageCut", itineraryPageCut, ItinerarySortKey::keyAsString)
.toString();
}
}
diff --git a/src/main/java/org/opentripplanner/model/plan/pagecursor/PageCursorFactory.java b/src/main/java/org/opentripplanner/model/plan/paging/cursor/PageCursorFactory.java
similarity index 55%
rename from src/main/java/org/opentripplanner/model/plan/pagecursor/PageCursorFactory.java
rename to src/main/java/org/opentripplanner/model/plan/paging/cursor/PageCursorFactory.java
index a6073a540b5..92f59319cc1 100644
--- a/src/main/java/org/opentripplanner/model/plan/pagecursor/PageCursorFactory.java
+++ b/src/main/java/org/opentripplanner/model/plan/paging/cursor/PageCursorFactory.java
@@ -1,13 +1,13 @@
-package org.opentripplanner.model.plan.pagecursor;
+package org.opentripplanner.model.plan.paging.cursor;
-import static org.opentripplanner.model.plan.pagecursor.PageType.NEXT_PAGE;
-import static org.opentripplanner.model.plan.pagecursor.PageType.PREVIOUS_PAGE;
+import static org.opentripplanner.model.plan.paging.cursor.PageType.NEXT_PAGE;
+import static org.opentripplanner.model.plan.paging.cursor.PageType.PREVIOUS_PAGE;
import java.time.Duration;
import java.time.Instant;
-import java.time.temporal.ChronoUnit;
import javax.annotation.Nullable;
import org.opentripplanner.framework.tostring.ToStringBuilder;
+import org.opentripplanner.model.plan.ItinerarySortKey;
import org.opentripplanner.model.plan.SortOrder;
public class PageCursorFactory {
@@ -15,10 +15,11 @@ public class PageCursorFactory {
private final SortOrder sortOrder;
private final Duration newSearchWindow;
private PageType currentPageType;
- private SearchTime current = null;
+ private Instant currentEdt = null;
+ private Instant currentLat = null;
private Duration currentSearchWindow = null;
private boolean wholeSwUsed = true;
- private ItineraryPageCut itineraryPageCut = null;
+ private ItinerarySortKey itineraryPageCut = null;
private PageCursorInput pageCursorInput = null;
private PageCursor nextCursor = null;
@@ -42,7 +43,8 @@ public PageCursorFactory withOriginalSearch(
this.currentPageType =
pageType == null ? resolvePageTypeForTheFirstSearch(sortOrder) : pageType;
- this.current = new SearchTime(edt, lat);
+ this.currentEdt = edt;
+ this.currentLat = lat;
this.currentSearchWindow = searchWindow;
return this;
}
@@ -59,18 +61,7 @@ public PageCursorFactory withOriginalSearch(
public PageCursorFactory withRemovedItineraries(PageCursorInput pageCursorFactoryParams) {
this.wholeSwUsed = false;
this.pageCursorInput = pageCursorFactoryParams;
- this.itineraryPageCut =
- new ItineraryPageCut(
- pageCursorFactoryParams.earliestRemovedDeparture().truncatedTo(ChronoUnit.SECONDS),
- current.edt.plus(currentSearchWindow),
- sortOrder,
- pageCursorFactoryParams.deduplicationSection(),
- pageCursorFactoryParams.firstRemovedArrivalTime(),
- pageCursorFactoryParams.firstRemovedDepartureTime(),
- pageCursorFactoryParams.firstRemovedGeneralizedCost(),
- pageCursorFactoryParams.firstRemovedNumOfTransfers(),
- pageCursorFactoryParams.firstRemovedIsOnStreetAllTheWay()
- );
+ this.itineraryPageCut = pageCursorFactoryParams.pageCut();
return this;
}
@@ -92,7 +83,8 @@ public String toString() {
.of(PageCursorFactory.class)
.addEnum("sortOrder", sortOrder)
.addEnum("currentPageType", currentPageType)
- .addObj("current", current)
+ .addDateTime("currentEdt", currentEdt)
+ .addDateTime("currentLat", currentLat)
.addDuration("currentSearchWindow", currentSearchWindow)
.addDuration("newSearchWindow", newSearchWindow)
.addBoolIfTrue("searchWindowCropped", !wholeSwUsed)
@@ -108,77 +100,53 @@ public String toString() {
* equivalent when creating new cursors.
*/
private static PageType resolvePageTypeForTheFirstSearch(SortOrder sortOrder) {
- return sortOrder.isSortedByArrivalTimeAscending() ? NEXT_PAGE : PREVIOUS_PAGE;
+ return sortOrder.isSortedByAscendingArrivalTime() ? NEXT_PAGE : PREVIOUS_PAGE;
}
/** Create page cursor pair (next and previous) */
private void createPageCursors() {
- if (current == null || nextCursor != null || prevCursor != null) {
+ if (currentEdt == null || nextCursor != null || prevCursor != null) {
return;
}
- SearchTime prev = new SearchTime(null, null);
- SearchTime next = new SearchTime(null, null);
+ Instant prevEdt;
+ Instant nextEdt;
if (wholeSwUsed) {
- prev.edt = edtBeforeNewSw();
- next.edt = edtAfterUsedSw();
- if (!sortOrder.isSortedByArrivalTimeAscending()) {
- prev.lat = current.lat;
- }
- } else { // If the whole search window was not used (i.e. if there were removed itineraries)
+ prevEdt = edtBeforeNewSw();
+ nextEdt = edtAfterUsedSw();
+ }
+ // If the whole search window was not used (i.e. if there were removed itineraries)
+ else {
if (currentPageType == NEXT_PAGE) {
- prev.edt = edtBeforeNewSw();
- next.edt = pageCursorInput.earliestRemovedDeparture();
- if (sortOrder.isSortedByArrivalTimeAscending()) {
- prev.lat = pageCursorInput.earliestKeptArrival().truncatedTo(ChronoUnit.MINUTES);
- } else {
- prev.lat = current.lat;
- }
+ prevEdt = edtBeforeNewSw();
+ nextEdt = pageCursorInput.earliestRemovedDeparture();
} else {
// The search-window start and end is [inclusive, exclusive], so to calculate the start of the
// search-window from the last time included in the search window we need to include one extra
// minute at the end.
- prev.edt = pageCursorInput.latestRemovedDeparture().minus(newSearchWindow).plusSeconds(60);
- next.edt = edtAfterUsedSw();
- prev.lat = pageCursorInput.latestRemovedArrival();
+ prevEdt = pageCursorInput.latestRemovedDeparture().minus(newSearchWindow).plusSeconds(60);
+ nextEdt = edtAfterUsedSw();
}
}
- prevCursor = new PageCursor(PREVIOUS_PAGE, sortOrder, prev.edt, prev.lat, newSearchWindow);
- nextCursor = new PageCursor(NEXT_PAGE, sortOrder, next.edt, next.lat, newSearchWindow);
-
- if (itineraryPageCut != null) {
- nextCursor = nextCursor.withItineraryPageCut(itineraryPageCut);
- prevCursor = prevCursor.withItineraryPageCut(itineraryPageCut);
- }
+ prevCursor =
+ new PageCursor(
+ PREVIOUS_PAGE,
+ sortOrder,
+ prevEdt,
+ currentLat,
+ newSearchWindow,
+ itineraryPageCut
+ );
+ nextCursor =
+ new PageCursor(NEXT_PAGE, sortOrder, nextEdt, null, newSearchWindow, itineraryPageCut);
}
private Instant edtBeforeNewSw() {
- return current.edt.minus(newSearchWindow);
+ return currentEdt.minus(newSearchWindow);
}
private Instant edtAfterUsedSw() {
- return current.edt.plus(currentSearchWindow);
- }
-
- /** Temporary data class used to hold a pair of edt and lat */
- private static class SearchTime {
-
- Instant edt;
- Instant lat;
-
- private SearchTime(Instant edt, Instant lat) {
- this.edt = edt;
- this.lat = lat;
- }
-
- @Override
- public String toString() {
- return ToStringBuilder
- .of(SearchTime.class)
- .addDateTime("edt", edt)
- .addDateTime("lat", lat)
- .toString();
- }
+ return currentEdt.plus(currentSearchWindow);
}
}
diff --git a/src/main/java/org/opentripplanner/model/plan/paging/cursor/PageCursorInput.java b/src/main/java/org/opentripplanner/model/plan/paging/cursor/PageCursorInput.java
new file mode 100644
index 00000000000..a9bef266739
--- /dev/null
+++ b/src/main/java/org/opentripplanner/model/plan/paging/cursor/PageCursorInput.java
@@ -0,0 +1,29 @@
+package org.opentripplanner.model.plan.paging.cursor;
+
+import java.time.Instant;
+import org.opentripplanner.model.plan.ItinerarySortKey;
+
+/**
+ * This class holds information needed to create the next/previous page cursors when there were
+ * itineraries removed due to cropping the list of itineraries using the numItineraries parameter.
+ *
+ * The Instant fields come from the sets of itineraries that were removed and the ones that were
+ * kept as a result of using the numItineraries parameter.
+ */
+public interface PageCursorInput {
+ /**
+ * The earliest-removed-departure defines the start of the search-window following the
+ * current window. To include this removed itinerary (and all other removed itineraries)
+ * in the next-page search the search windows must overlap.
+ */
+ Instant earliestRemovedDeparture();
+ Instant latestRemovedDeparture();
+
+ /**
+ * In case the result has too many results: The {@code numberOfItineraries} request parameter
+ * is less than the number of itineraries found, then we keep the last itinerary kept and
+ * returned as part of the result. The sort vector will be included in the page-cursor and
+ * used in the next/previous page to filter away duplicates.
+ */
+ ItinerarySortKey pageCut();
+}
diff --git a/src/main/java/org/opentripplanner/model/plan/paging/cursor/PageCursorSerializer.java b/src/main/java/org/opentripplanner/model/plan/paging/cursor/PageCursorSerializer.java
new file mode 100644
index 00000000000..98f64c68062
--- /dev/null
+++ b/src/main/java/org/opentripplanner/model/plan/paging/cursor/PageCursorSerializer.java
@@ -0,0 +1,113 @@
+package org.opentripplanner.model.plan.paging.cursor;
+
+import javax.annotation.Nullable;
+import org.opentripplanner.framework.lang.StringUtils;
+import org.opentripplanner.framework.token.TokenSchema;
+import org.opentripplanner.model.plan.ItinerarySortKey;
+import org.opentripplanner.model.plan.SortOrder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+final class PageCursorSerializer {
+
+ private static final byte VERSION = 1;
+ private static final Logger LOG = LoggerFactory.getLogger(PageCursor.class);
+
+ private static final String TYPE_FIELD = "Type";
+ private static final String EDT_FIELD = "EDT";
+ private static final String LAT_FIELD = "LAT";
+ private static final String SEARCH_WINDOW_FIELD = "SW";
+ private static final String SORT_ORDER_FIELD = "SortOrder";
+ private static final String CUT_ON_STREET_FIELD = "cutOnStreet";
+ private static final String CUT_DEPARTURE_TIME_FIELD = "cutDepartureTime";
+ private static final String CUT_ARRIVAL_TIME_FIELD = "cutArrivalTime";
+ private static final String CUT_N_TRANSFERS_FIELD = "cutTx";
+ private static final String CUT_COST_FIELD = "cutCost";
+
+ private static final TokenSchema SCHEMA_TOKEN = TokenSchema
+ .ofVersion(VERSION)
+ .addEnum(TYPE_FIELD)
+ .addTimeInstant(EDT_FIELD)
+ .addTimeInstant(LAT_FIELD)
+ .addDuration(SEARCH_WINDOW_FIELD)
+ .addEnum(SORT_ORDER_FIELD)
+ .addBoolean(CUT_ON_STREET_FIELD)
+ .addTimeInstant(CUT_DEPARTURE_TIME_FIELD)
+ .addTimeInstant(CUT_ARRIVAL_TIME_FIELD)
+ .addInt(CUT_N_TRANSFERS_FIELD)
+ .addInt(CUT_COST_FIELD)
+ .build();
+
+ /** private constructor to prevent instantiating this utility class */
+ private PageCursorSerializer() {}
+
+ @Nullable
+ public static String encode(PageCursor cursor) {
+ var tokenBuilder = SCHEMA_TOKEN
+ .encode()
+ .withEnum(TYPE_FIELD, cursor.type())
+ .withTimeInstant(EDT_FIELD, cursor.earliestDepartureTime())
+ .withTimeInstant(LAT_FIELD, cursor.latestArrivalTime())
+ .withDuration(SEARCH_WINDOW_FIELD, cursor.searchWindow())
+ .withEnum(SORT_ORDER_FIELD, cursor.originalSortOrder());
+
+ var cut = cursor.itineraryPageCut();
+ if (cut != null) {
+ tokenBuilder
+ .withBoolean(CUT_ON_STREET_FIELD, cut.isOnStreetAllTheWay())
+ .withTimeInstant(CUT_DEPARTURE_TIME_FIELD, cut.startTimeAsInstant())
+ .withTimeInstant(CUT_ARRIVAL_TIME_FIELD, cut.endTimeAsInstant())
+ .withInt(CUT_N_TRANSFERS_FIELD, cut.getNumberOfTransfers())
+ .withInt(CUT_COST_FIELD, cut.getGeneralizedCost());
+ }
+
+ return tokenBuilder.build();
+ }
+
+ @Nullable
+ public static PageCursor decode(String cursor) {
+ if (StringUtils.hasNoValueOrNullAsString(cursor)) {
+ return null;
+ }
+ try {
+ ItinerarySortKey itineraryPageCut = null;
+ var token = SCHEMA_TOKEN.decode(cursor);
+
+ // This throws an exception if an enum is serialized which is not in the code.
+ // This is a forward compatibility issue. To avoid this, add the value enum, role out.
+ // Start using the enum, roll out again.
+ PageType type = token.getEnum(TYPE_FIELD, PageType.class).orElseThrow();
+ var edt = token.getTimeInstant(EDT_FIELD);
+ var lat = token.getTimeInstant(LAT_FIELD);
+ var searchWindow = token.getDuration(SEARCH_WINDOW_FIELD);
+ var originalSortOrder = token.getEnum(SORT_ORDER_FIELD, SortOrder.class).orElseThrow();
+
+ // We use the departure time to determine if the cut is present or not
+ var cutDepartureTime = token.getTimeInstant(CUT_DEPARTURE_TIME_FIELD);
+
+ if (cutDepartureTime != null) {
+ itineraryPageCut =
+ new DeduplicationPageCut(
+ cutDepartureTime,
+ token.getTimeInstant(CUT_ARRIVAL_TIME_FIELD),
+ token.getInt(CUT_COST_FIELD),
+ token.getInt(CUT_N_TRANSFERS_FIELD),
+ token.getBoolean(CUT_ON_STREET_FIELD)
+ );
+ }
+
+ // Add logic to read in data from next version here.
+ // if(token.version() > 1) { /* get v2 here */}
+
+ return new PageCursor(type, originalSortOrder, edt, lat, searchWindow, itineraryPageCut);
+ } catch (Exception e) {
+ String details = e.getMessage();
+ if (StringUtils.hasValue(details)) {
+ LOG.warn("Unable to decode page cursor: '{}'. Details: {}", cursor, details);
+ } else {
+ LOG.warn("Unable to decode page cursor: '{}'.", cursor);
+ }
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/org/opentripplanner/model/plan/pagecursor/PageType.java b/src/main/java/org/opentripplanner/model/plan/paging/cursor/PageType.java
similarity index 82%
rename from src/main/java/org/opentripplanner/model/plan/pagecursor/PageType.java
rename to src/main/java/org/opentripplanner/model/plan/paging/cursor/PageType.java
index 94b2ce47c93..ad85e169448 100644
--- a/src/main/java/org/opentripplanner/model/plan/pagecursor/PageType.java
+++ b/src/main/java/org/opentripplanner/model/plan/paging/cursor/PageType.java
@@ -1,4 +1,4 @@
-package org.opentripplanner.model.plan.pagecursor;
+package org.opentripplanner.model.plan.paging.cursor;
/**
* Used to tell which way the paging is going, to the {@link #NEXT_PAGE} or to the {@link
@@ -17,5 +17,9 @@ public enum PageType {
* the sort order, the next page may hold itineraries which depart/arrive after or before the
* current result.
*/
- NEXT_PAGE,
+ NEXT_PAGE;
+
+ public boolean isNext() {
+ return this == NEXT_PAGE;
+ }
}
diff --git a/src/main/java/org/opentripplanner/model/plan/pagecursor/images/pagecursor.excalidraw b/src/main/java/org/opentripplanner/model/plan/paging/cursor/images/pagecursor.excalidraw
similarity index 100%
rename from src/main/java/org/opentripplanner/model/plan/pagecursor/images/pagecursor.excalidraw
rename to src/main/java/org/opentripplanner/model/plan/paging/cursor/images/pagecursor.excalidraw
diff --git a/src/main/java/org/opentripplanner/model/plan/pagecursor/images/sort-by-arrival-crop-sw-prev-page.svg b/src/main/java/org/opentripplanner/model/plan/paging/cursor/images/sort-by-arrival-crop-sw-prev-page.svg
similarity index 100%
rename from src/main/java/org/opentripplanner/model/plan/pagecursor/images/sort-by-arrival-crop-sw-prev-page.svg
rename to src/main/java/org/opentripplanner/model/plan/paging/cursor/images/sort-by-arrival-crop-sw-prev-page.svg
diff --git a/src/main/java/org/opentripplanner/model/plan/pagecursor/images/sort-by-arrival-crop-sw.svg b/src/main/java/org/opentripplanner/model/plan/paging/cursor/images/sort-by-arrival-crop-sw.svg
similarity index 100%
rename from src/main/java/org/opentripplanner/model/plan/pagecursor/images/sort-by-arrival-crop-sw.svg
rename to src/main/java/org/opentripplanner/model/plan/paging/cursor/images/sort-by-arrival-crop-sw.svg
diff --git a/src/main/java/org/opentripplanner/model/plan/pagecursor/images/sort-by-arrival.svg b/src/main/java/org/opentripplanner/model/plan/paging/cursor/images/sort-by-arrival.svg
similarity index 100%
rename from src/main/java/org/opentripplanner/model/plan/pagecursor/images/sort-by-arrival.svg
rename to src/main/java/org/opentripplanner/model/plan/paging/cursor/images/sort-by-arrival.svg
diff --git a/src/main/java/org/opentripplanner/model/plan/pagecursor/images/sort-by-departure-crop-sw-next-page.svg b/src/main/java/org/opentripplanner/model/plan/paging/cursor/images/sort-by-departure-crop-sw-next-page.svg
similarity index 100%
rename from src/main/java/org/opentripplanner/model/plan/pagecursor/images/sort-by-departure-crop-sw-next-page.svg
rename to src/main/java/org/opentripplanner/model/plan/paging/cursor/images/sort-by-departure-crop-sw-next-page.svg
diff --git a/src/main/java/org/opentripplanner/model/plan/pagecursor/images/sort-by-departure-crop-sw.svg b/src/main/java/org/opentripplanner/model/plan/paging/cursor/images/sort-by-departure-crop-sw.svg
similarity index 100%
rename from src/main/java/org/opentripplanner/model/plan/pagecursor/images/sort-by-departure-crop-sw.svg
rename to src/main/java/org/opentripplanner/model/plan/paging/cursor/images/sort-by-departure-crop-sw.svg
diff --git a/src/main/java/org/opentripplanner/model/plan/pagecursor/images/sort-by-departure.svg b/src/main/java/org/opentripplanner/model/plan/paging/cursor/images/sort-by-departure.svg
similarity index 100%
rename from src/main/java/org/opentripplanner/model/plan/pagecursor/images/sort-by-departure.svg
rename to src/main/java/org/opentripplanner/model/plan/paging/cursor/images/sort-by-departure.svg
diff --git a/src/main/java/org/opentripplanner/model/plan/pagecursor/readme.md b/src/main/java/org/opentripplanner/model/plan/paging/cursor/readme.md
similarity index 100%
rename from src/main/java/org/opentripplanner/model/plan/pagecursor/readme.md
rename to src/main/java/org/opentripplanner/model/plan/paging/cursor/readme.md
diff --git a/src/main/java/org/opentripplanner/raptor/api/model/RaptorAccessEgress.java b/src/main/java/org/opentripplanner/raptor/api/model/RaptorAccessEgress.java
index 1953c60d5d6..16dff3b2e99 100644
--- a/src/main/java/org/opentripplanner/raptor/api/model/RaptorAccessEgress.java
+++ b/src/main/java/org/opentripplanner/raptor/api/model/RaptorAccessEgress.java
@@ -4,7 +4,6 @@
import static org.opentripplanner.raptor.api.model.RaptorConstants.TIME_NOT_SET;
import javax.annotation.Nullable;
-import org.opentripplanner.framework.lang.OtpNumberFormat;
import org.opentripplanner.framework.time.DurationUtils;
import org.opentripplanner.framework.time.TimeUtils;
@@ -34,7 +33,7 @@ public interface RaptorAccessEgress {
*
* If this is {@link #isFree()}, then this method must return 0(zero).
*/
- int generalizedCost();
+ int c1();
/**
* The time duration to walk or travel the path in seconds. This is not the entire duration from
@@ -203,8 +202,8 @@ default String asString(boolean includeStop, boolean includeCost, @Nullable Stri
buf.append("Walk");
}
buf.append(' ').append(DurationUtils.durationToStr(durationInSeconds()));
- if (includeCost && generalizedCost() > 0) {
- buf.append(' ').append(OtpNumberFormat.formatCostCenti(generalizedCost()));
+ if (includeCost && c1() > 0) {
+ buf.append(' ').append(RaptorValueFormatter.formatC1(c1()));
}
if (hasRides()) {
buf.append(' ').append(numberOfRides()).append('x');
diff --git a/src/main/java/org/opentripplanner/raptor/api/model/RaptorTransfer.java b/src/main/java/org/opentripplanner/raptor/api/model/RaptorTransfer.java
index 5245c410266..bab4b9ab166 100644
--- a/src/main/java/org/opentripplanner/raptor/api/model/RaptorTransfer.java
+++ b/src/main/java/org/opentripplanner/raptor/api/model/RaptorTransfer.java
@@ -20,7 +20,7 @@ public interface RaptorTransfer {
* This method is called many times, so care needs to be taken that the value is stored, not
* calculated for each invocation.
*/
- int generalizedCost();
+ int c1();
/**
* The time duration to walk or travel the path in seconds. This is not the entire duration from
diff --git a/src/main/java/org/opentripplanner/raptor/api/model/RaptorValueFormatter.java b/src/main/java/org/opentripplanner/raptor/api/model/RaptorValueFormatter.java
new file mode 100644
index 00000000000..dd40c1175ee
--- /dev/null
+++ b/src/main/java/org/opentripplanner/raptor/api/model/RaptorValueFormatter.java
@@ -0,0 +1,58 @@
+package org.opentripplanner.raptor.api.model;
+
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+
+public class RaptorValueFormatter {
+
+ private static final String UNIT_C1 = "C₁";
+ private static final String UNIT_C2 = "C₂";
+ private static final String UNIT_WAIT_TIME_COST = "wtC₁";
+ private static final String UNIT_TRANSFER_PRIORITY = "Tₚ";
+ private static final String UNIT_TRANSFERS = "Tₓ";
+
+ private static final DecimalFormatSymbols DECIMAL_SYMBOLS = new DecimalFormatSymbols();
+
+ static {
+ DECIMAL_SYMBOLS.setDecimalSeparator('.');
+ DECIMAL_SYMBOLS.setGroupingSeparator('_');
+ DECIMAL_SYMBOLS.setMinusSign('-');
+ }
+
+ // In general DecimalFormat is not thread-safe, but we are not changing the state here,
+ // so this is ok. The 'format' is not changing the state.
+ private static final DecimalFormat FORMAT_CENTI = new DecimalFormat("#,##0.0#", DECIMAL_SYMBOLS);
+ private static final DecimalFormat FORMAT_INT = new DecimalFormat("#,##0", DECIMAL_SYMBOLS);
+
+ public static String formatC1(int c1) {
+ return UNIT_C1 + formatCenti(c1);
+ }
+
+ public static String formatC2(int c2) {
+ return UNIT_C2 + c2;
+ }
+
+ public static String formatWaitTimeCost(int value) {
+ return UNIT_WAIT_TIME_COST + formatCenti(value);
+ }
+
+ public static String formatNumOfTransfers(int value) {
+ return UNIT_TRANSFERS + FORMAT_INT.format(value);
+ }
+
+ public static String formatTransferPriority(int value) {
+ return UNIT_TRANSFER_PRIORITY + FORMAT_INT.format(value);
+ }
+
+ /** Format integers in centi units like 1234 => 12.34. */
+ private static String formatCenti(int value) {
+ if (value % 100 == 0) {
+ value /= 100;
+ return FORMAT_INT.format(value);
+ }
+ if (Math.abs(value) >= 1_000_000) {
+ return FORMAT_INT.format(value / 100);
+ }
+ return FORMAT_CENTI.format(value / 100.0);
+ }
+}
diff --git a/src/main/java/org/opentripplanner/raptor/api/path/AccessPathLeg.java b/src/main/java/org/opentripplanner/raptor/api/path/AccessPathLeg.java
index 1b9673bd66e..b76e0a1df4d 100644
--- a/src/main/java/org/opentripplanner/raptor/api/path/AccessPathLeg.java
+++ b/src/main/java/org/opentripplanner/raptor/api/path/AccessPathLeg.java
@@ -16,20 +16,20 @@ public final class AccessPathLeg implements PathLe
private final RaptorAccessEgress access;
private final int fromTime;
private final int toTime;
- private final int generalizedCost;
+ private final int c1;
private final PathLeg next;
public AccessPathLeg(
@Nonnull RaptorAccessEgress access,
int fromTime,
int toTime,
- int generalizedCost,
+ int c1,
@Nonnull PathLeg next
) {
this.access = access;
this.fromTime = fromTime;
this.toTime = toTime;
- this.generalizedCost = generalizedCost;
+ this.c1 = c1;
this.next = next;
}
@@ -52,8 +52,8 @@ public int toStop() {
}
@Override
- public int generalizedCost() {
- return generalizedCost;
+ public int c1() {
+ return c1;
}
@Override
diff --git a/src/main/java/org/opentripplanner/raptor/api/path/EgressPathLeg.java b/src/main/java/org/opentripplanner/raptor/api/path/EgressPathLeg.java
index fd137516cf4..2bbf06f1cda 100644
--- a/src/main/java/org/opentripplanner/raptor/api/path/EgressPathLeg.java
+++ b/src/main/java/org/opentripplanner/raptor/api/path/EgressPathLeg.java
@@ -15,13 +15,13 @@ public final class EgressPathLeg implements PathLe
private final RaptorAccessEgress egress;
private final int fromTime;
private final int toTime;
- private final int generalizedCost;
+ private final int c1;
- public EgressPathLeg(RaptorAccessEgress egress, int fromTime, int toTime, int generalizedCost) {
+ public EgressPathLeg(RaptorAccessEgress egress, int fromTime, int toTime, int c1) {
this.egress = egress;
this.fromTime = fromTime;
this.toTime = toTime;
- this.generalizedCost = generalizedCost;
+ this.c1 = c1;
}
@Override
@@ -43,8 +43,8 @@ public int toTime() {
}
@Override
- public int generalizedCost() {
- return generalizedCost;
+ public int c1() {
+ return c1;
}
@Override
diff --git a/src/main/java/org/opentripplanner/raptor/api/path/PathLeg.java b/src/main/java/org/opentripplanner/raptor/api/path/PathLeg.java
index a0cabc7e1e9..35a351a4d77 100644
--- a/src/main/java/org/opentripplanner/raptor/api/path/PathLeg.java
+++ b/src/main/java/org/opentripplanner/raptor/api/path/PathLeg.java
@@ -69,20 +69,20 @@ default int duration() {
*
* The unit is centi-seconds (Raptor cost unit)
*/
- int generalizedCost();
+ int c1();
/**
- * The computed generalized-cost for this leg plus all legs following it.
+ * The c1 for this leg plus all legs following it.
*
* {@code -1} is returned if no cost is computed by raptor.
*
* The unit is centi-seconds (Raptor cost unit)
*/
- default int generalizedCostTotal() {
- if (generalizedCost() < 0) {
- return generalizedCost();
+ default int c1Total() {
+ if (c1() < 0) {
+ return c1();
}
- return stream().mapToInt(PathLeg::generalizedCost).sum();
+ return stream().mapToInt(PathLeg::c1).sum();
}
/**
diff --git a/src/main/java/org/opentripplanner/raptor/api/path/PathStringBuilder.java b/src/main/java/org/opentripplanner/raptor/api/path/PathStringBuilder.java
index 34a9be91889..4cf13e362fd 100644
--- a/src/main/java/org/opentripplanner/raptor/api/path/PathStringBuilder.java
+++ b/src/main/java/org/opentripplanner/raptor/api/path/PathStringBuilder.java
@@ -3,11 +3,11 @@
import java.time.ZonedDateTime;
import java.util.function.Consumer;
import javax.annotation.Nullable;
-import org.opentripplanner.framework.lang.OtpNumberFormat;
import org.opentripplanner.framework.time.DurationUtils;
import org.opentripplanner.framework.time.TimeUtils;
import org.opentripplanner.raptor.api.model.RaptorAccessEgress;
import org.opentripplanner.raptor.api.model.RaptorConstants;
+import org.opentripplanner.raptor.api.model.RaptorValueFormatter;
import org.opentripplanner.raptor.spi.RaptorCostCalculator;
/**
@@ -73,30 +73,32 @@ public PathStringBuilder street(String modeName, ZonedDateTime fromTime, ZonedDa
return legSep().text(modeName).time(fromTime, toTime);
}
- public PathStringBuilder timeAndCostCentiSec(int fromTime, int toTime, int generalizedCost) {
- return time(fromTime, toTime).generalizedCostSentiSec(generalizedCost);
+ public PathStringBuilder c1(int c1) {
+ if (c1 == RaptorCostCalculator.ZERO_COST) {
+ return this;
+ }
+ return text(RaptorValueFormatter.formatC1(c1));
}
- /** Add generalizedCostCentiSec {@link #costCentiSec(int, int, String)} */
- public PathStringBuilder generalizedCostSentiSec(int cost) {
- return costCentiSec(cost, RaptorCostCalculator.ZERO_COST, null);
+ public PathStringBuilder c2(int c2) {
+ if (c2 == RaptorConstants.NOT_SET) {
+ return this;
+ }
+ return text(RaptorValueFormatter.formatC2(c2));
}
- /**
- * Add a cost to the string with an optional unit. Try to be consistent with unit naming, use
- * lower-case:
- *
- * {@code null} - Generalized-cost (no unit used)
- * {@code "wtc"} - Wait-time cost
- * {@code "pri"} - Transfer priority cost
- *
- */
- public PathStringBuilder costCentiSec(int generalizedCostCents, int defaultValue, String unit) {
- if (generalizedCostCents == defaultValue) {
+ public PathStringBuilder waitTimeCost(int wtc, int defaultValue) {
+ if (wtc == defaultValue) {
+ return this;
+ }
+ return text(RaptorValueFormatter.formatWaitTimeCost(wtc));
+ }
+
+ public PathStringBuilder transferPriority(int transferPriorityCost, int defaultValue) {
+ if (transferPriorityCost == defaultValue) {
return this;
}
- var costText = OtpNumberFormat.formatCostCenti(generalizedCostCents);
- return (unit != null) ? text(costText + unit) : text(costText);
+ return text(RaptorValueFormatter.formatTransferPriority(transferPriorityCost));
}
public PathStringBuilder duration(int duration) {
@@ -112,34 +114,34 @@ public PathStringBuilder time(int time) {
}
public PathStringBuilder numberOfTransfers(int nTransfers) {
- return nTransfers != RaptorConstants.NOT_SET ? text(nTransfers + "tx") : this;
+ return nTransfers != RaptorConstants.NOT_SET
+ ? text(RaptorValueFormatter.formatNumOfTransfers(nTransfers))
+ : this;
}
- public PathStringBuilder summary(int generalizedCostCents) {
- return summaryStart().generalizedCostSentiSec(generalizedCostCents).summaryEnd();
+ public PathStringBuilder summary(int c1) {
+ return summaryStart().c1(c1).summaryEnd();
}
- public PathStringBuilder summary(
- int startTime,
- int endTime,
- int nTransfers,
- int generalizedCostCents
- ) {
- return summary(startTime, endTime, nTransfers, generalizedCostCents, null);
+ public PathStringBuilder summary(int startTime, int endTime, int nTransfers, int c1, int c2) {
+ return summary(startTime, endTime, nTransfers, c1, c2, null);
}
public PathStringBuilder summary(
int startTime,
int endTime,
int nTransfers,
- int generalizedCostCents,
+ int c1,
+ int c2,
@Nullable Consumer appendToSummary
) {
summaryStart()
.time(startTime, endTime)
.duration(Math.abs(endTime - startTime))
.numberOfTransfers(nTransfers)
- .generalizedCostSentiSec(generalizedCostCents);
+ .c1(c1)
+ .c2(c2);
+
if (appendToSummary != null) {
appendToSummary.accept(this);
}
diff --git a/src/main/java/org/opentripplanner/raptor/api/path/TransferPathLeg.java b/src/main/java/org/opentripplanner/raptor/api/path/TransferPathLeg.java
index 4aba197f1ce..8e098add45b 100644
--- a/src/main/java/org/opentripplanner/raptor/api/path/TransferPathLeg.java
+++ b/src/main/java/org/opentripplanner/raptor/api/path/TransferPathLeg.java
@@ -15,7 +15,7 @@ public final class TransferPathLeg implements Path
private final int fromTime;
private final int toStop;
private final int toTime;
- private final int generalizedCost;
+ private final int c1;
private final RaptorTransfer transfer;
private final PathLeg next;
@@ -23,7 +23,7 @@ public TransferPathLeg(
int fromStop,
int fromTime,
int toTime,
- int generalizedCost,
+ int c1,
RaptorTransfer transfer,
PathLeg next
) {
@@ -31,7 +31,7 @@ public TransferPathLeg(
this.fromTime = fromTime;
this.toStop = transfer.stop();
this.toTime = toTime;
- this.generalizedCost = generalizedCost;
+ this.c1 = c1;
this.transfer = transfer;
this.next = next;
}
@@ -61,8 +61,8 @@ public int toStop() {
}
@Override
- public int generalizedCost() {
- return generalizedCost;
+ public int c1() {
+ return c1;
}
@Override
diff --git a/src/main/java/org/opentripplanner/raptor/api/path/TransitPathLeg.java b/src/main/java/org/opentripplanner/raptor/api/path/TransitPathLeg.java
index e752920b6da..a01fb32a28d 100644
--- a/src/main/java/org/opentripplanner/raptor/api/path/TransitPathLeg.java
+++ b/src/main/java/org/opentripplanner/raptor/api/path/TransitPathLeg.java
@@ -17,7 +17,7 @@ public final class TransitPathLeg implements PathL
private final int boardStopPos;
private final int alightStopPos;
private final RaptorConstrainedTransfer constrainedTransferAfterLeg;
- private final int generalizedCost;
+ private final int c1;
private final PathLeg next;
private final int boardStop;
private final int alightStop;
@@ -29,7 +29,7 @@ public TransitPathLeg(
int boardStopPos,
int alightStopPos,
RaptorConstrainedTransfer constrainedTransferAfterLeg,
- int generalizedCost,
+ int c1,
PathLeg next
) {
this.trip = trip;
@@ -38,7 +38,7 @@ public TransitPathLeg(
this.boardStopPos = boardStopPos;
this.alightStopPos = alightStopPos;
this.constrainedTransferAfterLeg = constrainedTransferAfterLeg;
- this.generalizedCost = generalizedCost;
+ this.c1 = c1;
this.next = next;
this.boardStop = trip.pattern().stopIndex(boardStopPos);
this.alightStop = trip.pattern().stopIndex(alightStopPos);
@@ -90,8 +90,8 @@ public int toStop() {
}
@Override
- public int generalizedCost() {
- return generalizedCost;
+ public int c1() {
+ return c1;
}
@Override
diff --git a/src/main/java/org/opentripplanner/raptor/api/view/ArrivalView.java b/src/main/java/org/opentripplanner/raptor/api/view/ArrivalView.java
index cfd47614bfc..862f7f17fb6 100644
--- a/src/main/java/org/opentripplanner/raptor/api/view/ArrivalView.java
+++ b/src/main/java/org/opentripplanner/raptor/api/view/ArrivalView.java
@@ -1,13 +1,16 @@
package org.opentripplanner.raptor.api.view;
+import java.util.function.IntFunction;
import javax.annotation.Nullable;
-import org.opentripplanner.framework.lang.OtpNumberFormat;
import org.opentripplanner.framework.time.TimeUtils;
import org.opentripplanner.raptor.api.model.PathLegType;
+import org.opentripplanner.raptor.api.model.RaptorConstants;
import org.opentripplanner.raptor.api.model.RaptorTransfer;
import org.opentripplanner.raptor.api.model.RaptorTripSchedule;
+import org.opentripplanner.raptor.api.model.RaptorValueFormatter;
import org.opentripplanner.raptor.api.model.TransitArrival;
import org.opentripplanner.raptor.spi.RaptorCostCalculator;
+import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.DefaultCostCalculator;
/**
* The purpose of the stop-arrival-view is to provide a common interface for stop-arrivals for
@@ -80,20 +83,12 @@ default boolean isFirstRound() {
*/
int c1();
- /**
- * Whether the model supports accumulated criteria TWO. If C2 is not supported then calling
- * {@link #c2()} will result in an exception.
- */
- default boolean supportsC2() {
- return false;
- }
-
/**
* The accumulated criteria TWO. Can be used for any int criteria used during routing. A
* state with c1 and c2 is created dynamically if c2 is in use, if not this method will
* throw an exception.
*
- * {@link RaptorCostCalculator#ZERO_COST} is returned if no criteria exist, but the model
+ * {@link RaptorConstants#NOT_SET} is returned if no criteria exist, but the model
* support it.
*/
int c2();
@@ -157,13 +152,13 @@ default EgressPathView egressPath() {
boolean arrivedOnBoard();
- /** Use this to easy create a to String implementation. */
+ /** Use this to create a {@code toString()} implementation. */
default String asString() {
String arrival =
"[" +
TimeUtils.timeToStrCompact(arrivalTime()) +
- " " +
- OtpNumberFormat.formatCostCenti(c1()) +
+ cost(c1(), DefaultCostCalculator.ZERO_COST, RaptorValueFormatter::formatC1) +
+ cost(c2(), RaptorConstants.NOT_SET, RaptorValueFormatter::formatC2) +
"]";
return switch (arrivedBy()) {
case ACCESS -> String.format(
@@ -195,4 +190,8 @@ default String asString() {
);
};
}
+
+ private static String cost(int cost, int defaultValue, IntFunction toString) {
+ return cost == defaultValue ? "" : " " + toString.apply(cost);
+ }
}
diff --git a/src/main/java/org/opentripplanner/raptor/path/Path.java b/src/main/java/org/opentripplanner/raptor/path/Path.java
index eb123131089..a4d7770dd64 100644
--- a/src/main/java/org/opentripplanner/raptor/path/Path.java
+++ b/src/main/java/org/opentripplanner/raptor/path/Path.java
@@ -7,6 +7,7 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Nullable;
+import org.opentripplanner.raptor.api.model.RaptorConstants;
import org.opentripplanner.raptor.api.model.RaptorTransferConstraint;
import org.opentripplanner.raptor.api.model.RaptorTripSchedule;
import org.opentripplanner.raptor.api.path.AccessPathLeg;
@@ -28,7 +29,7 @@ public class Path implements RaptorPath {
private final int startTime;
private final int endTime;
private final int numberOfTransfers;
- private final int generalizedCost;
+ private final int c1;
private final int c2;
private final AccessPathLeg accessLeg;
private final EgressPathLeg egressLeg;
@@ -39,22 +40,22 @@ private Path(
int startTime,
int endTime,
int numberOfTransfers,
- int generalizedCost
+ int c1
) {
this.iterationDepartureTime = iterationDepartureTime;
this.startTime = startTime;
this.endTime = endTime;
this.numberOfTransfers = numberOfTransfers;
- this.generalizedCost = generalizedCost;
+ this.c1 = c1;
this.accessLeg = null;
this.egressLeg = null;
- this.c2 = 0;
+ this.c2 = RaptorConstants.NOT_SET;
}
- public Path(int iterationDepartureTime, AccessPathLeg accessLeg, int generalizedCost, int c2) {
+ public Path(int iterationDepartureTime, AccessPathLeg accessLeg, int c1, int c2) {
this.iterationDepartureTime = iterationDepartureTime;
this.startTime = accessLeg.fromTime();
- this.generalizedCost = generalizedCost;
+ this.c1 = c1;
this.accessLeg = accessLeg;
this.egressLeg = findEgressLeg(accessLeg);
this.numberOfTransfers = countNumberOfTransfers(accessLeg, egressLeg);
@@ -62,8 +63,8 @@ public Path(int iterationDepartureTime, AccessPathLeg accessLeg, int generali
this.c2 = c2;
}
- public Path(int iterationDepartureTime, AccessPathLeg accessLeg, int generalizedCost) {
- this(iterationDepartureTime, accessLeg, generalizedCost, 0);
+ public Path(int iterationDepartureTime, AccessPathLeg accessLeg, int c1) {
+ this(iterationDepartureTime, accessLeg, c1, RaptorConstants.NOT_SET);
}
/** Copy constructor */
@@ -122,7 +123,7 @@ public final int numberOfTransfersExAccessEgress() {
@Override
public final int c1() {
- return generalizedCost;
+ return c1;
}
@Override
@@ -240,7 +241,7 @@ protected String buildString(
);
if (detailed) {
buf.duration(leg.duration());
- buf.generalizedCostSentiSec(leg.generalizedCost());
+ buf.c1(leg.c1());
}
if (transitLeg.getConstrainedTransferAfterLeg() != null) {
constraintPrevLeg =
@@ -263,7 +264,7 @@ else if (leg.isEgressLeg()) {
}
}
// Add summary info
- buf.summary(startTime, endTime, numberOfTransfers, generalizedCost, appendToSummary);
+ buf.summary(startTime, endTime, numberOfTransfers, c1, c2, appendToSummary);
return buf.toString();
}
@@ -293,7 +294,10 @@ private static int countNumberOfTransfers(
private void addWalkDetails(boolean detailed, PathStringBuilder buf, PathLeg leg) {
if (detailed) {
- buf.timeAndCostCentiSec(leg.fromTime(), leg.toTime(), leg.generalizedCost());
+ int fromTime = leg.fromTime();
+ int toTime = leg.toTime();
+ int cost = leg.c1();
+ buf.time(fromTime, toTime).c1(cost);
}
}
}
diff --git a/src/main/java/org/opentripplanner/raptor/path/PathBuilder.java b/src/main/java/org/opentripplanner/raptor/path/PathBuilder.java
index 6dae7ee2c5a..c046a8f240d 100644
--- a/src/main/java/org/opentripplanner/raptor/path/PathBuilder.java
+++ b/src/main/java/org/opentripplanner/raptor/path/PathBuilder.java
@@ -185,7 +185,7 @@ public int c2() {
public RaptorPath build() {
updateAggregatedFields();
var pathLegs = createPathLegs(costCalculator, slackProvider);
- return new Path<>(iterationDepartureTime, pathLegs, pathLegs.generalizedCostTotal(), c2());
+ return new Path<>(iterationDepartureTime, pathLegs, pathLegs.c1Total(), c2());
}
@Override
diff --git a/src/main/java/org/opentripplanner/raptor/path/PathBuilderLeg.java b/src/main/java/org/opentripplanner/raptor/path/PathBuilderLeg.java
index eebdf69bfd9..76d49ef4a0a 100644
--- a/src/main/java/org/opentripplanner/raptor/path/PathBuilderLeg.java
+++ b/src/main/java/org/opentripplanner/raptor/path/PathBuilderLeg.java
@@ -214,18 +214,15 @@ public void timeShiftThisAndNextLeg(
*
* This method is safe to use event as long as the next leg is set.
*/
- public int generalizedCost(
- RaptorCostCalculator costCalculator,
- RaptorSlackProvider slackProvider
- ) {
+ public int c1(RaptorCostCalculator costCalculator, RaptorSlackProvider slackProvider) {
if (costCalculator == null) {
return RaptorCostCalculator.ZERO_COST;
}
if (isAccess()) {
- return asAccessLeg().streetPath.generalizedCost();
+ return asAccessLeg().streetPath.c1();
}
if (isTransfer()) {
- return asTransferLeg().transfer.generalizedCost();
+ return asTransferLeg().transfer.c1();
}
if (isTransit()) {
return transitCost(costCalculator, slackProvider);
@@ -360,11 +357,11 @@ AccessPathLeg createAccessPathLeg(
/* Build helper methods, package local */
private static int cost(RaptorCostCalculator> costCalculator, RaptorAccessEgress streetPath) {
- return costCalculator != null ? streetPath.generalizedCost() : RaptorCostCalculator.ZERO_COST;
+ return costCalculator != null ? streetPath.c1() : RaptorCostCalculator.ZERO_COST;
}
private static int cost(RaptorCostCalculator> costCalculator, RaptorTransfer transfer) {
- return costCalculator != null ? transfer.generalizedCost() : RaptorCostCalculator.ZERO_COST;
+ return costCalculator != null ? transfer.c1() : RaptorCostCalculator.ZERO_COST;
}
private void setTime(int fromTime, int toTime) {
diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/MultiCriteriaRoutingStrategy.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/MultiCriteriaRoutingStrategy.java
index 446bca40fc1..b17b3078fc9 100644
--- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/MultiCriteriaRoutingStrategy.java
+++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/MultiCriteriaRoutingStrategy.java
@@ -32,7 +32,7 @@ public class MultiCriteriaRoutingStrategy patternRideFactory;
private final ParetoSet patternRides;
private final PassThroughPointsService passThroughPointsService;
- private final RaptorCostCalculator generalizedCostCalculator;
+ private final RaptorCostCalculator c1Calculator;
private final SlackProvider slackProvider;
public MultiCriteriaRoutingStrategy(
@@ -40,7 +40,7 @@ public MultiCriteriaRoutingStrategy(
TimeBasedBoardingSupport boardingSupport,
PatternRideFactory patternRideFactory,
PassThroughPointsService passThroughPointsService,
- RaptorCostCalculator generalizedCostCalculator,
+ RaptorCostCalculator c1Calculator,
SlackProvider slackProvider,
ParetoSet patternRides
) {
@@ -48,7 +48,7 @@ public MultiCriteriaRoutingStrategy(
this.boardingSupport = Objects.requireNonNull(boardingSupport);
this.patternRideFactory = Objects.requireNonNull(patternRideFactory);
this.passThroughPointsService = Objects.requireNonNull(passThroughPointsService);
- this.generalizedCostCalculator = Objects.requireNonNull(generalizedCostCalculator);
+ this.c1Calculator = Objects.requireNonNull(c1Calculator);
this.slackProvider = Objects.requireNonNull(slackProvider);
this.patternRides = Objects.requireNonNull(patternRides);
}
@@ -195,7 +195,7 @@ private int calculateCostAtBoardTime(
) {
return (
prevArrival.c1() +
- generalizedCostCalculator.boardingCost(
+ c1Calculator.boardingCost(
prevArrival.isFirstRound(),
prevArrival.arrivalTime(),
boardEvent.boardStopIndex(),
@@ -214,6 +214,6 @@ private int calculateCostAtBoardTime(
* origin in the same iteration, having used the same number-of-rounds to board the same trip.
*/
private int calculateOnTripRelativeCost(int boardTime, T tripSchedule) {
- return generalizedCostCalculator.onTripRelativeRidingCost(boardTime, tripSchedule);
+ return c1Calculator.onTripRelativeRidingCost(boardTime, tripSchedule);
}
}
diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/ArrivalParetoSetComparatorFactory.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/ArrivalParetoSetComparatorFactory.java
index f7907fa932a..68e103b18db 100644
--- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/ArrivalParetoSetComparatorFactory.java
+++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/ArrivalParetoSetComparatorFactory.java
@@ -8,7 +8,7 @@
public interface ArrivalParetoSetComparatorFactory> {
/**
* This comparator is used to compare regular stop arrivals. It uses {@code arrivalTime},
- * {@code paretoRound} and {@code generalizedCost} to compare arrivals. It does NOT include
+ * {@code paretoRound} and {@code c1} to compare arrivals. It does NOT include
* {@code arrivedOnBoard}. Normally arriving on-board should give the arrival an advantage
* - you can continue on foot, walking to the next stop. But, we only do this if it happens
* in the same Raptor iteration and round - if it does it is taken care of by the order
diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/AccessStopArrival.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/AccessStopArrival.java
index be318be0645..e3787d41f78 100644
--- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/AccessStopArrival.java
+++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/AccessStopArrival.java
@@ -23,7 +23,7 @@ final class AccessStopArrival extends McStopArriva
access.stop(),
departureTime,
access.durationInSeconds(),
- access.generalizedCost(),
+ access.c1(),
access.numberOfRides()
);
this.access = access;
@@ -31,7 +31,7 @@ final class AccessStopArrival extends McStopArriva
@Override
public int c2() {
- throw new UnsupportedOperationException("C2 is not available for the C1 implementation");
+ return RaptorConstants.NOT_SET;
}
@Override
diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/TransferStopArrival.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/TransferStopArrival.java
index cbe553f8f8f..05c165a158b 100644
--- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/TransferStopArrival.java
+++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/TransferStopArrival.java
@@ -3,6 +3,7 @@
import static org.opentripplanner.raptor.api.model.PathLegType.TRANSFER;
import org.opentripplanner.raptor.api.model.PathLegType;
+import org.opentripplanner.raptor.api.model.RaptorConstants;
import org.opentripplanner.raptor.api.model.RaptorTransfer;
import org.opentripplanner.raptor.api.model.RaptorTripSchedule;
import org.opentripplanner.raptor.api.model.TransitArrival;
@@ -25,14 +26,14 @@ final class TransferStopArrival extends McStopArri
1,
transferPath.stop(),
arrivalTime,
- previousState.c1() + transferPath.generalizedCost()
+ previousState.c1() + transferPath.c1()
);
this.transfer = transferPath;
}
@Override
public int c2() {
- throw new UnsupportedOperationException("C2 is not available for the C1 implementation");
+ return RaptorConstants.NOT_SET;
}
@Override
diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/TransitStopArrival.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/TransitStopArrival.java
index 60252a679b1..a30581512a7 100644
--- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/TransitStopArrival.java
+++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/TransitStopArrival.java
@@ -3,6 +3,7 @@
import static org.opentripplanner.raptor.api.model.PathLegType.TRANSIT;
import org.opentripplanner.raptor.api.model.PathLegType;
+import org.opentripplanner.raptor.api.model.RaptorConstants;
import org.opentripplanner.raptor.api.model.RaptorTripSchedule;
import org.opentripplanner.raptor.api.model.TransitArrival;
import org.opentripplanner.raptor.api.view.TransitPathView;
@@ -36,7 +37,7 @@ final class TransitStopArrival
@Override
public int c2() {
- throw new UnsupportedOperationException("C2 is not available for the C1 implementation");
+ return RaptorConstants.NOT_SET;
}
@Override
diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/AbstractStopArrivalC2.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/AbstractStopArrivalC2.java
index 33942592466..067772cb014 100644
--- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/AbstractStopArrivalC2.java
+++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/AbstractStopArrivalC2.java
@@ -49,11 +49,6 @@ abstract class AbstractStopArrivalC2 extends McSto
this.c2 = c2;
}
- @Override
- public boolean supportsC2() {
- return true;
- }
-
public final int c2() {
return c2;
}
diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/AccessStopArrivalC2.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/AccessStopArrivalC2.java
index 674cb7c6522..ed4d9df1415 100644
--- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/AccessStopArrivalC2.java
+++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/AccessStopArrivalC2.java
@@ -24,7 +24,7 @@ final class AccessStopArrivalC2 extends AbstractSt
departureTime,
access.durationInSeconds(),
access.numberOfRides(),
- access.generalizedCost(),
+ access.c1(),
RaptorCostCalculator.ZERO_COST
);
this.access = access;
diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/TransferStopArrivalC2.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/TransferStopArrivalC2.java
index eb245d1fee9..42ee55ff784 100644
--- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/TransferStopArrivalC2.java
+++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/TransferStopArrivalC2.java
@@ -21,7 +21,7 @@ final class TransferStopArrivalC2 extends Abstract
1,
transferPath.stop(),
arrivalTime,
- previous.c1() + transferPath.generalizedCost(),
+ previous.c1() + transferPath.c1(),
previous.c2()
);
this.transfer = transferPath;
diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrival.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrival.java
index 6133bd03952..351a432cc20 100644
--- a/src/main/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrival.java
+++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrival.java
@@ -35,18 +35,21 @@ public class DestinationArrival implements Arrival
private final int arrivalTime;
private final int numberOfTransfers;
private final int c1;
+ private final int c2;
public DestinationArrival(
RaptorAccessEgress egress,
ArrivalView previous,
int arrivalTime,
- int additionalCost
+ int additionalC1,
+ int c2
) {
this.previous = previous;
this.egress = egress;
this.arrivalTime = arrivalTime;
this.numberOfTransfers = previous.round() - 1;
- this.c1 = previous.c1() + additionalCost;
+ this.c1 = previous.c1() + additionalC1;
+ this.c2 = c2;
}
@Override
@@ -69,14 +72,9 @@ public int c1() {
return c1;
}
- @Override
- public boolean supportsC2() {
- return false;
- }
-
@Override
public int c2() {
- return previous.c2();
+ return c2;
}
@Override
diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrivalPaths.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrivalPaths.java
index 5f0fa376f74..6ed88da4c89 100644
--- a/src/main/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrivalPaths.java
+++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrivalPaths.java
@@ -188,7 +188,13 @@ private DestinationArrival createDestinationArrivalView(
additionalCost += costCalculator.costEgress(egressPath);
}
- return new DestinationArrival<>(egressPath, stopArrival, arrivalTime, additionalCost);
+ return new DestinationArrival<>(
+ egressPath,
+ stopArrival,
+ arrivalTime,
+ additionalCost,
+ stopArrival.c2()
+ );
}
/**
diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/path/ForwardPathMapper.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/path/ForwardPathMapper.java
index 9f7d392c2f0..8e9b77f9cb8 100644
--- a/src/main/java/org/opentripplanner/raptor/rangeraptor/path/ForwardPathMapper.java
+++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/path/ForwardPathMapper.java
@@ -4,7 +4,6 @@
import org.opentripplanner.raptor.api.path.RaptorPath;
import org.opentripplanner.raptor.api.path.RaptorStopNameResolver;
import org.opentripplanner.raptor.api.view.ArrivalView;
-import org.opentripplanner.raptor.path.Path;
import org.opentripplanner.raptor.path.PathBuilder;
import org.opentripplanner.raptor.rangeraptor.internalapi.WorkerLifeCycle;
import org.opentripplanner.raptor.rangeraptor.transit.TripTimesSearch;
@@ -70,9 +69,7 @@ public RaptorPath mapToPath(final DestinationArrival destinationArrival) {
arrival = arrival.previous();
}
- if (destinationArrival.supportsC2()) {
- pathBuilder.c2(destinationArrival.c2());
- }
+ pathBuilder.c2(destinationArrival.c2());
return pathBuilder.build();
}
diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/path/ReversePathMapper.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/path/ReversePathMapper.java
index 7dd09c2ac92..fde483b4cd8 100644
--- a/src/main/java/org/opentripplanner/raptor/rangeraptor/path/ReversePathMapper.java
+++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/path/ReversePathMapper.java
@@ -65,6 +65,9 @@ public RaptorPath mapToPath(final DestinationArrival destinationArrival) {
switch (arrival.arrivedBy()) {
case ACCESS:
pathBuilder.egress(arrival.accessPath().access());
+
+ pathBuilder.c2(arrival.c2());
+
return pathBuilder.build();
case TRANSIT:
var times = tripSearch.find(arrival);
diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/view/Access.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/view/Access.java
index 6344d6d5c8e..e78b9d0a381 100644
--- a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/view/Access.java
+++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/view/Access.java
@@ -4,6 +4,7 @@
import org.opentripplanner.raptor.api.model.PathLegType;
import org.opentripplanner.raptor.api.model.RaptorAccessEgress;
+import org.opentripplanner.raptor.api.model.RaptorConstants;
import org.opentripplanner.raptor.api.model.RaptorTripSchedule;
import org.opentripplanner.raptor.api.view.AccessPathView;
import org.opentripplanner.raptor.api.view.ArrivalView;
@@ -29,7 +30,7 @@ public int c1() {
@Override
public int c2() {
- throw new UnsupportedOperationException("C2 is not available for the C1 implementation");
+ return RaptorConstants.NOT_SET;
}
@Override
diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/view/Transfer.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/view/Transfer.java
index a6c1fc7c003..a19a9f1c7e6 100644
--- a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/view/Transfer.java
+++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/view/Transfer.java
@@ -3,6 +3,7 @@
import static org.opentripplanner.raptor.api.model.PathLegType.TRANSFER;
import org.opentripplanner.raptor.api.model.PathLegType;
+import org.opentripplanner.raptor.api.model.RaptorConstants;
import org.opentripplanner.raptor.api.model.RaptorTransfer;
import org.opentripplanner.raptor.api.model.RaptorTripSchedule;
import org.opentripplanner.raptor.api.view.ArrivalView;
@@ -27,7 +28,7 @@ public int c1() {
@Override
public int c2() {
- throw new UnsupportedOperationException("C2 is not available for the C1 implementation");
+ return RaptorConstants.NOT_SET;
}
@Override
diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/view/Transit.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/view/Transit.java
index 4a967642bf4..e38849f6da7 100644
--- a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/view/Transit.java
+++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/view/Transit.java
@@ -3,6 +3,7 @@
import static org.opentripplanner.raptor.api.model.PathLegType.TRANSIT;
import org.opentripplanner.raptor.api.model.PathLegType;
+import org.opentripplanner.raptor.api.model.RaptorConstants;
import org.opentripplanner.raptor.api.model.RaptorTripSchedule;
import org.opentripplanner.raptor.api.view.ArrivalView;
import org.opentripplanner.raptor.api.view.TransitPathView;
@@ -29,7 +30,7 @@ public int c1() {
@Override
public int c2() {
- throw new UnsupportedOperationException("C2 is not available for the C1 implementation");
+ return RaptorConstants.NOT_SET;
}
@Override
diff --git a/src/main/java/org/opentripplanner/raptor/spi/RaptorCostCalculator.java b/src/main/java/org/opentripplanner/raptor/spi/RaptorCostCalculator.java
index bd1b91d46d7..d575a0e4d8c 100644
--- a/src/main/java/org/opentripplanner/raptor/spi/RaptorCostCalculator.java
+++ b/src/main/java/org/opentripplanner/raptor/spi/RaptorCostCalculator.java
@@ -12,7 +12,7 @@
*/
public interface RaptorCostCalculator {
/**
- * The cost is zero(0) it it is not calculated or if the cost "element" have no cost associated
+ * The cost is zero (0) if it's not calculated or if the cost "element" have no cost associated.
* with it.
*/
int ZERO_COST = 0;
@@ -63,7 +63,7 @@ int boardingCost(
* This method allows the cost calculator to add cost in addition to the generalized-cost of the
* given egress itself. For example you might want to add a transfer cost to FLEX egress.
*
- * @return the {@link RaptorTransfer#generalizedCost()} plus any additional board or transfer
+ * @return the {@link RaptorTransfer#c1()} plus any additional board or transfer
* cost.
*/
int costEgress(RaptorAccessEgress egress);
diff --git a/src/main/java/org/opentripplanner/raptor/spi/UnknownPath.java b/src/main/java/org/opentripplanner/raptor/spi/UnknownPath.java
index f1b0a5e0c25..b95417e86b6 100644
--- a/src/main/java/org/opentripplanner/raptor/spi/UnknownPath.java
+++ b/src/main/java/org/opentripplanner/raptor/spi/UnknownPath.java
@@ -2,6 +2,7 @@
import java.util.List;
import java.util.stream.Stream;
+import org.opentripplanner.raptor.api.model.RaptorConstants;
import org.opentripplanner.raptor.api.model.RaptorTripSchedule;
import org.opentripplanner.raptor.api.path.AccessPathLeg;
import org.opentripplanner.raptor.api.path.EgressPathLeg;
@@ -72,7 +73,7 @@ public int c1() {
@Override
public int c2() {
- return RaptorCostCalculator.ZERO_COST;
+ return RaptorConstants.NOT_SET;
}
@Override
@@ -126,7 +127,7 @@ public String toString() {
if (departureTime == 0 && arrivalTime == 0) {
pathBuilder.summary(c1());
} else {
- pathBuilder.summary(startTime(), endTime(), numberOfTransfers, c1());
+ pathBuilder.summary(startTime(), endTime(), numberOfTransfers, c1(), c2());
}
return pathBuilder.toString();
}
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java b/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java
index 24442311617..ad2c4c92639 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java
@@ -16,11 +16,11 @@
import org.opentripplanner.framework.application.OTPRequestTimeoutException;
import org.opentripplanner.framework.time.ServiceDateUtils;
import org.opentripplanner.model.plan.Itinerary;
-import org.opentripplanner.model.plan.PagingSearchWindowAdjuster;
import org.opentripplanner.raptor.api.request.RaptorTuningParameters;
import org.opentripplanner.raptor.api.request.SearchParams;
import org.opentripplanner.routing.algorithm.filterchain.ItineraryListFilterChain;
import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.NumItinerariesFilterResults;
+import org.opentripplanner.routing.algorithm.mapping.PagingServiceFactory;
import org.opentripplanner.routing.algorithm.mapping.RouteRequestToFilterChainMapper;
import org.opentripplanner.routing.algorithm.mapping.RoutingResponseMapper;
import org.opentripplanner.routing.algorithm.raptoradapter.router.AdditionalSearchDays;
@@ -28,13 +28,13 @@
import org.opentripplanner.routing.algorithm.raptoradapter.router.TransitRouter;
import org.opentripplanner.routing.algorithm.raptoradapter.router.street.DirectFlexRouter;
import org.opentripplanner.routing.algorithm.raptoradapter.router.street.DirectStreetRouter;
-import org.opentripplanner.routing.algorithm.raptoradapter.transit.TransitTuningParameters;
import org.opentripplanner.routing.api.request.RouteRequest;
import org.opentripplanner.routing.api.request.StreetMode;
import org.opentripplanner.routing.api.response.RoutingError;
import org.opentripplanner.routing.api.response.RoutingResponse;
import org.opentripplanner.routing.error.RoutingValidationException;
import org.opentripplanner.routing.framework.DebugTimingAggregator;
+import org.opentripplanner.service.paging.PagingService;
import org.opentripplanner.standalone.api.OtpServerRequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -49,8 +49,7 @@ public class RoutingWorker {
private static final Logger LOG = LoggerFactory.getLogger(RoutingWorker.class);
/** An object that accumulates profiling and debugging info for inclusion in the response. */
- public final DebugTimingAggregator debugTimingAggregator;
- public final PagingSearchWindowAdjuster pagingSearchWindowAdjuster;
+ private final DebugTimingAggregator debugTimingAggregator;
private final RouteRequest request;
private final OtpServerRequestContext serverContext;
@@ -78,11 +77,6 @@ public RoutingWorker(OtpServerRequestContext serverContext, RouteRequest request
request.preferences().system().tags()
);
this.transitSearchTimeZero = ServiceDateUtils.asStartOfService(request.dateTime(), zoneId);
- this.pagingSearchWindowAdjuster =
- createPagingSearchWindowAdjuster(
- serverContext.transitTuningParameters(),
- serverContext.raptorTuningParameters()
- );
this.additionalSearchDays =
createAdditionalSearchDays(serverContext.raptorTuningParameters(), zoneId, request);
}
@@ -165,18 +159,17 @@ public RoutingResponse route() {
// Adjust the search-window for the next search if the current search-window
// is off (too few or too many results found).
- var searchWindowNextSearch = calculateSearchWindowNextSearch(filteredItineraries);
+
+ var pagingService = createPagingService(itineraries);
return RoutingResponseMapper.map(
request,
- transitSearchTimeZero,
raptorSearchParamsUsed,
- searchWindowNextSearch,
- numItinerariesFilterResults,
filteredItineraries,
routingErrors,
debugTimingAggregator,
- serverContext.transitService()
+ serverContext.transitService(),
+ pagingService
);
}
@@ -197,27 +190,6 @@ private static AdditionalSearchDays createAdditionalSearchDays(
);
}
- /**
- * Filter itineraries away that depart after the latest-departure-time for depart after search.
- * These itineraries are a result of time-shifting the access leg and is needed for the raptor to
- * prune the results. These itineraries are often not ideal, but if they pareto optimal for the
- * "next" window, they will appear when a "next" search is performed.
- */
- private Instant filterOnLatestDepartureTime() {
- if (
- !request.arriveBy() &&
- raptorSearchParamsUsed != null &&
- raptorSearchParamsUsed.isSearchWindowSet() &&
- raptorSearchParamsUsed.isEarliestDepartureTimeSet()
- ) {
- int ldt =
- raptorSearchParamsUsed.earliestDepartureTime() +
- raptorSearchParamsUsed.searchWindowInSeconds();
- return transitSearchTimeZero.plusSeconds(ldt).toInstant();
- }
- return null;
- }
-
/**
* Calculate the earliest-departure-time used in the transit search.
* This method returns {@code null} if no transit search is performed.
@@ -300,53 +272,19 @@ private Void routeTransit(List itineraries, Collection
return null;
}
- private Duration calculateSearchWindowNextSearch(List itineraries) {
- // No transit search performed
- if (raptorSearchParamsUsed == null) {
- return null;
- }
-
- var sw = Duration.ofSeconds(raptorSearchParamsUsed.searchWindowInSeconds());
-
- // SearchWindow cropped -> decrease search-window
- if (numItinerariesFilterResults != null) {
- Instant swStartTime = searchStartTime()
- .plusSeconds(raptorSearchParamsUsed.earliestDepartureTime());
- boolean cropSWHead = request.doCropSearchWindowAtTail();
- Instant rmItineraryStartTime = numItinerariesFilterResults.firstRemovedDepartureTime;
-
- return pagingSearchWindowAdjuster.decreaseSearchWindow(
- sw,
- swStartTime,
- rmItineraryStartTime,
- cropSWHead
- );
- }
- // (num-of-itineraries found <= numItineraries) -> increase or keep search-window
- else {
- int nRequested = request.numItineraries();
- int nFound = (int) itineraries
- .stream()
- .filter(it -> !it.isFlaggedForDeletion() && it.hasTransit())
- .count();
-
- return pagingSearchWindowAdjuster.increaseOrKeepSearchWindow(sw, nRequested, nFound);
- }
- }
-
private Instant searchStartTime() {
return transitSearchTimeZero.toInstant();
}
- private PagingSearchWindowAdjuster createPagingSearchWindowAdjuster(
- TransitTuningParameters transitTuningParameters,
- RaptorTuningParameters raptorTuningParameters
- ) {
- var c = raptorTuningParameters.dynamicSearchWindowCoefficients();
- return new PagingSearchWindowAdjuster(
- c.minWindow(),
- c.maxWindow(),
- transitTuningParameters.pagingSearchWindowAdjustments()
+ private PagingService createPagingService(List itineraries) {
+ return PagingServiceFactory.createPagingService(
+ searchStartTime(),
+ serverContext.transitTuningParameters(),
+ serverContext.raptorTuningParameters(),
+ request,
+ raptorSearchParamsUsed,
+ numItinerariesFilterResults,
+ itineraries
);
}
}
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java
index 1c5583ce0bd..872bad6b4ae 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java
@@ -12,12 +12,14 @@
import java.util.function.Function;
import javax.annotation.Nullable;
import org.opentripplanner.ext.accessibilityscore.AccessibilityScoreFilter;
+import org.opentripplanner.framework.collection.ListSection;
import org.opentripplanner.framework.lang.Sandbox;
import org.opentripplanner.model.plan.Itinerary;
+import org.opentripplanner.model.plan.ItinerarySortKey;
import org.opentripplanner.model.plan.SortOrder;
-import org.opentripplanner.model.plan.pagecursor.ItineraryPageCut;
import org.opentripplanner.routing.algorithm.filterchain.api.TransitGeneralizedCostFilterParams;
import org.opentripplanner.routing.algorithm.filterchain.comparator.SortOrderComparator;
+import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.ItineraryDeletionFlagger;
import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.MaxLimitFilter;
import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.NonTransitGeneralizedCostFilter;
import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.NumItinerariesFilter;
@@ -59,7 +61,7 @@ public class ItineraryListFilterChainBuilder {
private ItineraryFilterDebugProfile debug = ItineraryFilterDebugProfile.OFF;
private int maxNumberOfItineraries = NOT_SET;
- private ListSection maxNumberOfItinerariesCrop = ListSection.TAIL;
+ private ListSection maxNumberOfItinerariesCropSection = ListSection.TAIL;
private CostLinearFunction removeTransitWithHigherCostThanBestOnStreetOnly;
private boolean removeWalkAllTheWayResults;
private boolean sameFirstOrLastTripFilter;
@@ -77,7 +79,7 @@ public class ItineraryListFilterChainBuilder {
private boolean removeItinerariesWithSameRoutesAndStops;
private double minBikeParkingDistance;
private boolean removeTransitIfWalkingIsBetter = true;
- private ItineraryPageCut itineraryPageCut;
+ private ItinerarySortKey itineraryPageCut;
/**
* Sandbox filters which decorate the itineraries with extra information.
@@ -103,7 +105,7 @@ public ItineraryListFilterChainBuilder(SortOrder sortOrder) {
* The maximum number of itineraries returned. This will remove all itineraries at the end of the
* list AFTER the final sort of the itineraries.
*
- * Se also the {@link #withMaxNumberOfItinerariesCrop(ListSection)} to change which end of the
+ * Se also the {@link #withMaxNumberOfItinerariesCropSection(ListSection)} to change which end of the
* list is cropped.
*
* Use {@code -1} to disable.
@@ -121,8 +123,10 @@ public ItineraryListFilterChainBuilder withMaxNumberOfItineraries(int value) {
* The default is to crop the tail. But, we need to crop the head to be able to paginate in the
* opposite direction of the main sort-order of the original search.
*/
- public ItineraryListFilterChainBuilder withMaxNumberOfItinerariesCrop(ListSection section) {
- this.maxNumberOfItinerariesCrop = section;
+ public ItineraryListFilterChainBuilder withMaxNumberOfItinerariesCropSection(
+ ListSection section
+ ) {
+ this.maxNumberOfItinerariesCropSection = section;
return this;
}
@@ -279,7 +283,7 @@ public ItineraryListFilterChainBuilder withNumItinerariesFilterResultsConsumer(
* @param itineraryPageCut contains the parameters to use for deduplication.
*/
public ItineraryListFilterChainBuilder withPagingDeduplicationFilter(
- ItineraryPageCut itineraryPageCut
+ ItinerarySortKey itineraryPageCut
) {
this.itineraryPageCut = itineraryPageCut;
return this;
@@ -350,10 +354,6 @@ public ItineraryListFilterChainBuilder withStopConsolidationFilter(
public ItineraryListFilterChain build() {
List filters = new ArrayList<>();
- if (itineraryPageCut != null) {
- filters.add(new DeletionFlaggingFilter(new PagingFilter(itineraryPageCut)));
- }
-
filters.addAll(buildGroupByTripIdAndDistanceFilters());
if (removeItinerariesWithSameRoutesAndStops) {
@@ -362,7 +362,7 @@ public ItineraryListFilterChain build() {
if (sameFirstOrLastTripFilter) {
filters.add(new SortingFilter(generalizedCostComparator()));
- filters.add(new DeletionFlaggingFilter(new SameFirstOrLastTripFilter()));
+ addRmFilter(filters, new SameFirstOrLastTripFilter());
}
if (minBikeParkingDistance > 0) {
@@ -389,23 +389,18 @@ public ItineraryListFilterChain build() {
// Filter transit itineraries on generalized-cost
if (transitGeneralizedCostFilterParams != null) {
- filters.add(
- new DeletionFlaggingFilter(
- new TransitGeneralizedCostFilter(
- transitGeneralizedCostFilterParams.costLimitFunction(),
- transitGeneralizedCostFilterParams.intervalRelaxFactor()
- )
+ addRmFilter(
+ filters,
+ new TransitGeneralizedCostFilter(
+ transitGeneralizedCostFilterParams.costLimitFunction(),
+ transitGeneralizedCostFilterParams.intervalRelaxFactor()
)
);
}
// Filter non-transit itineraries on generalized-cost
if (nonTransitGeneralizedCostLimit != null) {
- filters.add(
- new DeletionFlaggingFilter(
- new NonTransitGeneralizedCostFilter(nonTransitGeneralizedCostLimit)
- )
- );
+ addRmFilter(filters, new NonTransitGeneralizedCostFilter(nonTransitGeneralizedCostLimit));
}
// Apply all absolute filters AFTER the groupBy filters. Absolute filters are filters that
@@ -420,60 +415,61 @@ public ItineraryListFilterChain build() {
{
// Filter transit itineraries by comparing against non-transit using generalized-cost
if (removeTransitWithHigherCostThanBestOnStreetOnly != null) {
- filters.add(
- new DeletionFlaggingFilter(
- new RemoveTransitIfStreetOnlyIsBetterFilter(
- removeTransitWithHigherCostThanBestOnStreetOnly
- )
+ addRmFilter(
+ filters,
+ new RemoveTransitIfStreetOnlyIsBetterFilter(
+ removeTransitWithHigherCostThanBestOnStreetOnly
)
);
}
if (removeTransitIfWalkingIsBetter) {
- filters.add(new DeletionFlaggingFilter(new RemoveTransitIfWalkingIsBetterFilter()));
+ addRmFilter(filters, new RemoveTransitIfWalkingIsBetterFilter());
}
if (removeWalkAllTheWayResults) {
- filters.add(new DeletionFlaggingFilter(new RemoveWalkOnlyFilter()));
- }
-
- if (earliestDepartureTime != null) {
- filters.add(
- new DeletionFlaggingFilter(
- new OutsideSearchWindowFilter(earliestDepartureTime, searchWindow)
- )
- );
+ addRmFilter(filters, new RemoveWalkOnlyFilter());
}
if (bikeRentalDistanceRatio > 0) {
- filters.add(
- new DeletionFlaggingFilter(
- new RemoveBikerentalWithMostlyWalkingFilter(bikeRentalDistanceRatio)
- )
- );
+ addRmFilter(filters, new RemoveBikerentalWithMostlyWalkingFilter(bikeRentalDistanceRatio));
}
if (parkAndRideDurationRatio > 0) {
- filters.add(
- new DeletionFlaggingFilter(
- new RemoveParkAndRideWithMostlyWalkingFilter(parkAndRideDurationRatio)
- )
+ addRmFilter(
+ filters,
+ new RemoveParkAndRideWithMostlyWalkingFilter(parkAndRideDurationRatio)
);
}
}
- // Remove itineraries if max limit is set
- if (maxNumberOfItineraries > 0) {
- filters.add(new SortingFilter(SortOrderComparator.comparator(sortOrder)));
- filters.add(
- new DeletionFlaggingFilter(
+ // Paging related filters - these filters are run after group-by filters to allow a result
+ // outside the page to also take effect inside the window. This is debatable but lead to less
+ // noise, however it is not deterministic because the result depends on the size of the search-window and
+ // where the "cut" between each page is located.
+ {
+ // Limit to search-window
+ if (earliestDepartureTime != null) {
+ addRmFilter(filters, new OutsideSearchWindowFilter(earliestDepartureTime, searchWindow));
+ }
+
+ // Remove itineraries present in the page retrieved before this page/search.
+ if (itineraryPageCut != null) {
+ addRmFilter(filters, new PagingFilter(sortOrder, deduplicateSection(), itineraryPageCut));
+ }
+
+ // Remove itineraries if max limit is set
+ if (maxNumberOfItineraries > 0) {
+ filters.add(new SortingFilter(SortOrderComparator.comparator(sortOrder)));
+ addRmFilter(
+ filters,
new NumItinerariesFilter(
maxNumberOfItineraries,
- maxNumberOfItinerariesCrop,
+ maxNumberOfItinerariesCropSection,
numItinerariesFilterResultsConsumer
)
- )
- );
+ );
+ }
}
// Do the final itineraries sort
@@ -572,13 +568,12 @@ private List buildGroupByTripIdAndDistanceFilters() {
if (group.maxCostOtherLegsFactor > 1.0) {
var flagger = new OtherThanSameLegsMaxGeneralizedCostFilter(group.maxCostOtherLegsFactor);
sysTags.add(flagger.name());
- nested.add(new DeletionFlaggingFilter(flagger));
+ addRmFilter(nested, flagger);
}
nested.add(new SortingFilter(generalizedCostComparator()));
- nested.add(
- new DeletionFlaggingFilter(new MaxLimitFilter(tag, group.maxNumOfItinerariesPerGroup))
- );
+
+ addRmFilter(nested, new MaxLimitFilter(tag, group.maxNumOfItinerariesPerGroup));
nested.add(new RemoveDeletionFlagForLeastTransfersItinerary(sysTags));
@@ -589,4 +584,15 @@ private List buildGroupByTripIdAndDistanceFilters() {
return groupByFilters;
}
+
+ private ListSection deduplicateSection() {
+ return maxNumberOfItinerariesCropSection.invert();
+ }
+
+ private static void addRmFilter(
+ List filters,
+ ItineraryDeletionFlagger removeFilter
+ ) {
+ filters.add(new DeletionFlaggingFilter(removeFilter));
+ }
}
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/comparator/SortOrderComparator.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/comparator/SortOrderComparator.java
index c48b6f0bd5d..e52e53ab1c7 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/comparator/SortOrderComparator.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/comparator/SortOrderComparator.java
@@ -101,12 +101,9 @@ public static SortOrderComparator numberOfTransfersComparator() {
}
public static SortOrderComparator comparator(SortOrder sortOrder) {
- switch (sortOrder) {
- case STREET_AND_ARRIVAL_TIME:
- return STREET_AND_ARRIVAL_TIME;
- case STREET_AND_DEPARTURE_TIME:
- return STREET_AND_DEPARTURE_TIME;
- }
- throw new IllegalArgumentException();
+ return switch (sortOrder) {
+ case STREET_AND_ARRIVAL_TIME -> STREET_AND_ARRIVAL_TIME;
+ case STREET_AND_DEPARTURE_TIME -> STREET_AND_DEPARTURE_TIME;
+ };
}
}
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/ItineraryDeletionFlagger.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/ItineraryDeletionFlagger.java
index 3f88179d65c..c6db9c14cf6 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/ItineraryDeletionFlagger.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/ItineraryDeletionFlagger.java
@@ -1,5 +1,6 @@
package org.opentripplanner.routing.algorithm.filterchain.deletionflagger;
+import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -53,4 +54,16 @@ default List flagForRemoval(List itineraries) {
default boolean skipAlreadyFlaggedItineraries() {
return true;
}
+
+ /**
+ * Filter given {@code input} and remove itineraries which should be flagged for removal. This
+ * can be used in unit-tests - either testing the filter or using the filter in a test.
+ *
+ * This method should be used in unit-tests only.
+ */
+ default List removeMatchesForTest(List input) {
+ var res = new ArrayList<>(input);
+ res.removeAll(flagForRemoval(input));
+ return res;
+ }
}
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/NumItinerariesFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/NumItinerariesFilter.java
index eb9874ad8ce..b939dd8ca68 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/NumItinerariesFilter.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/NumItinerariesFilter.java
@@ -2,8 +2,8 @@
import java.util.List;
import java.util.function.Consumer;
+import org.opentripplanner.framework.collection.ListSection;
import org.opentripplanner.model.plan.Itinerary;
-import org.opentripplanner.routing.algorithm.filterchain.ListSection;
/**
* Flag all itineraries after the provided limit. This flags the itineraries at the end of the list
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/NumItinerariesFilterResults.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/NumItinerariesFilterResults.java
index e41ca954fe3..7226c19535b 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/NumItinerariesFilterResults.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/NumItinerariesFilterResults.java
@@ -2,25 +2,19 @@
import java.time.Instant;
import java.util.List;
+import org.opentripplanner.framework.collection.ListSection;
+import org.opentripplanner.framework.collection.ListUtils;
import org.opentripplanner.framework.tostring.ToStringBuilder;
import org.opentripplanner.model.plan.Itinerary;
-import org.opentripplanner.model.plan.pagecursor.PageCursorInput;
-import org.opentripplanner.model.plan.pagecursor.PagingDeduplicationSection;
-import org.opentripplanner.routing.algorithm.filterchain.ListSection;
+import org.opentripplanner.model.plan.ItinerarySortKey;
+import org.opentripplanner.model.plan.paging.cursor.PageCursorInput;
public class NumItinerariesFilterResults implements PageCursorInput {
- public final Instant earliestRemovedDeparture;
- public final Instant latestRemovedDeparture;
- public final Instant earliestRemovedArrival;
- public final Instant latestRemovedArrival;
- public final Instant earliestKeptArrival;
- public final Instant firstRemovedArrivalTime;
- public final boolean firstRemovedIsOnStreetAllTheWay;
- public final int firstRemovedGeneralizedCost;
- public final int firstRemovedNumOfTransfers;
- public final Instant firstRemovedDepartureTime;
- public final ListSection cropSection;
+ private final Instant earliestRemovedDeparture;
+ private final Instant latestRemovedDeparture;
+ private final ItinerarySortKey pageCut;
+ private final ListSection cropSection;
/**
* The NumItinerariesFilter removes itineraries from a list of itineraries based on the number to
@@ -38,105 +32,40 @@ public NumItinerariesFilterResults(
.stream()
.map(it -> it.startTime().toInstant())
.toList();
- List removedArrivals = removedItineraries
- .stream()
- .map(it -> it.endTime().toInstant())
- .toList();
this.earliestRemovedDeparture = removedDepartures.stream().min(Instant::compareTo).orElse(null);
this.latestRemovedDeparture = removedDepartures.stream().max(Instant::compareTo).orElse(null);
- this.earliestRemovedArrival = removedArrivals.stream().min(Instant::compareTo).orElse(null);
- this.latestRemovedArrival = removedArrivals.stream().max(Instant::compareTo).orElse(null);
-
- this.earliestKeptArrival =
- keptItineraries
- .stream()
- .map(it -> it.endTime().toInstant())
- .min(Instant::compareTo)
- .orElseThrow();
- Itinerary firstRemovedItinerary;
if (cropSection == ListSection.HEAD) {
- firstRemovedItinerary = removedItineraries.get(removedItineraries.size() - 1);
+ pageCut = ListUtils.first(keptItineraries);
} else {
- firstRemovedItinerary = removedItineraries.get(0);
+ pageCut = ListUtils.last(keptItineraries);
}
-
- this.firstRemovedIsOnStreetAllTheWay = firstRemovedItinerary.isOnStreetAllTheWay();
- this.firstRemovedArrivalTime = firstRemovedItinerary.endTime().toInstant();
- this.firstRemovedGeneralizedCost = firstRemovedItinerary.getGeneralizedCost();
- this.firstRemovedNumOfTransfers = firstRemovedItinerary.getNumberOfTransfers();
- this.firstRemovedDepartureTime = firstRemovedItinerary.startTime().toInstant();
-
this.cropSection = cropSection;
}
- @Override
- public String toString() {
- return ToStringBuilder
- .of(NumItinerariesFilterResults.class)
- .addDateTime("earliestRemovedDeparture", earliestRemovedDeparture)
- .addDateTime("latestRemovedDeparture", latestRemovedDeparture)
- .addDateTime("earliestRemovedArrival", earliestRemovedArrival)
- .addDateTime("latestRemovedArrival", latestRemovedArrival)
- .addDateTime("earliestKeptArrival", earliestKeptArrival)
- .addDateTime("firstRemovedArrivalTime", firstRemovedArrivalTime)
- .addNum("firstRemovedGeneralizedCost", firstRemovedGeneralizedCost)
- .addNum("firstRemovedNumOfTransfers", firstRemovedNumOfTransfers)
- .addDateTime("firstRemovedDepartureTime", firstRemovedDepartureTime)
- .addEnum("cropSection", cropSection)
- .toString();
- }
-
@Override
public Instant earliestRemovedDeparture() {
return earliestRemovedDeparture;
}
- @Override
- public Instant earliestKeptArrival() {
- return earliestKeptArrival;
- }
-
@Override
public Instant latestRemovedDeparture() {
return latestRemovedDeparture;
}
@Override
- public Instant latestRemovedArrival() {
- return latestRemovedArrival;
- }
-
- @Override
- public Instant firstRemovedArrivalTime() {
- return firstRemovedArrivalTime;
- }
-
- @Override
- public boolean firstRemovedIsOnStreetAllTheWay() {
- return firstRemovedIsOnStreetAllTheWay;
- }
-
- @Override
- public int firstRemovedGeneralizedCost() {
- return firstRemovedGeneralizedCost;
- }
-
- @Override
- public int firstRemovedNumOfTransfers() {
- return firstRemovedNumOfTransfers;
+ public ItinerarySortKey pageCut() {
+ return pageCut;
}
@Override
- public Instant firstRemovedDepartureTime() {
- return firstRemovedDepartureTime;
- }
-
- @Override
- public PagingDeduplicationSection deduplicationSection() {
- return switch (cropSection) {
- case HEAD -> PagingDeduplicationSection.TAIL;
- case TAIL -> PagingDeduplicationSection.HEAD;
- };
+ public String toString() {
+ return ToStringBuilder
+ .of(NumItinerariesFilterResults.class)
+ .addDateTime("earliestRemovedDeparture", earliestRemovedDeparture)
+ .addDateTime("latestRemovedDeparture", latestRemovedDeparture)
+ .addObjOp("pageCut", pageCut, ItinerarySortKey::keyAsString)
+ .addEnum("cropSection", cropSection)
+ .toString();
}
}
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/OutsideSearchWindowFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/OutsideSearchWindowFilter.java
index abbcb7a28ee..dc189efbc33 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/OutsideSearchWindowFilter.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/OutsideSearchWindowFilter.java
@@ -11,8 +11,8 @@
* should appear again when paging to the next page. Hence, this filter will remove
* such itineraries. The same is true for when paging to the previous page for arriveBy=true.
*
- * Itineraries matching the start(earliest-departure-time) and end(latest-departure-time)
- * of the search-window are included [inclusive, inclusive].
+ * Itineraries matching the start(earliest-departure-time) are included and itineraries matching
+ * the end(latest-departure-time) are not. The filter is {@code [inclusive, exclusive]}.
*/
public class OutsideSearchWindowFilter implements ItineraryDeletionFlagger {
@@ -35,7 +35,7 @@ public String name() {
public Predicate shouldBeFlaggedForRemoval() {
return it -> {
var time = it.startTime().toInstant();
- return time.isBefore(earliestDepartureTime) || time.isAfter(latestDepartureTime);
+ return time.isBefore(earliestDepartureTime) || !time.isBefore(latestDepartureTime);
};
}
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/PagingFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/PagingFilter.java
index f15e683d4f7..893a239b46b 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/PagingFilter.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/PagingFilter.java
@@ -3,9 +3,10 @@
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
+import org.opentripplanner.framework.collection.ListSection;
import org.opentripplanner.model.plan.Itinerary;
import org.opentripplanner.model.plan.ItinerarySortKey;
-import org.opentripplanner.model.plan.pagecursor.ItineraryPageCut;
+import org.opentripplanner.model.plan.SortOrder;
import org.opentripplanner.routing.algorithm.filterchain.comparator.SortOrderComparator;
/**
@@ -22,12 +23,18 @@ public class PagingFilter implements ItineraryDeletionFlagger {
public static final String TAG = "paging-filter";
- private final ItineraryPageCut itineraryPageCut;
+ private final ListSection deduplicateSection;
+ private final ItinerarySortKey itineraryPageCut;
private final Comparator sortOrderComparator;
- public PagingFilter(ItineraryPageCut itineraryPageCut) {
+ public PagingFilter(
+ SortOrder sortOrder,
+ ListSection deduplicateSection,
+ ItinerarySortKey itineraryPageCut
+ ) {
+ this.deduplicateSection = deduplicateSection;
this.itineraryPageCut = itineraryPageCut;
- this.sortOrderComparator = SortOrderComparator.comparator(itineraryPageCut.sortOrder());
+ this.sortOrderComparator = SortOrderComparator.comparator(sortOrder);
}
@Override
@@ -36,9 +43,9 @@ public String name() {
}
private boolean sortsIntoDeduplicationAreaRelativeToRemovedItinerary(Itinerary itinerary) {
- return switch (itineraryPageCut.deduplicationSection()) {
- case HEAD -> sortOrderComparator.compare(itinerary, itineraryPageCut) < 0;
- case TAIL -> sortOrderComparator.compare(itinerary, itineraryPageCut) > 0;
+ return switch (deduplicateSection) {
+ case HEAD -> sortOrderComparator.compare(itinerary, itineraryPageCut) <= 0;
+ case TAIL -> sortOrderComparator.compare(itinerary, itineraryPageCut) >= 0;
};
}
@@ -46,13 +53,7 @@ private boolean sortsIntoDeduplicationAreaRelativeToRemovedItinerary(Itinerary i
public List flagForRemoval(List itineraries) {
return itineraries
.stream()
- .filter(it ->
- (
- it.startTime().toInstant().isAfter(itineraryPageCut.windowStart()) &&
- it.startTime().toInstant().isBefore(itineraryPageCut.windowEnd()) &&
- sortsIntoDeduplicationAreaRelativeToRemovedItinerary(it)
- )
- )
+ .filter(this::sortsIntoDeduplicationAreaRelativeToRemovedItinerary)
.collect(Collectors.toList());
}
}
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/mapping/PagingServiceFactory.java b/src/main/java/org/opentripplanner/routing/algorithm/mapping/PagingServiceFactory.java
new file mode 100644
index 00000000000..8b122e6cf24
--- /dev/null
+++ b/src/main/java/org/opentripplanner/routing/algorithm/mapping/PagingServiceFactory.java
@@ -0,0 +1,61 @@
+package org.opentripplanner.routing.algorithm.mapping;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.List;
+import org.opentripplanner.model.plan.Itinerary;
+import org.opentripplanner.raptor.api.request.RaptorTuningParameters;
+import org.opentripplanner.raptor.api.request.SearchParams;
+import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.NumItinerariesFilterResults;
+import org.opentripplanner.routing.algorithm.raptoradapter.transit.TransitTuningParameters;
+import org.opentripplanner.routing.api.request.RouteRequest;
+import org.opentripplanner.service.paging.PagingService;
+
+public class PagingServiceFactory {
+
+ public static PagingService createPagingService(
+ Instant searchStartTime,
+ TransitTuningParameters transitTuningParameters,
+ RaptorTuningParameters raptorTuningParameters,
+ RouteRequest request,
+ SearchParams raptorSearchParamsUsed,
+ NumItinerariesFilterResults numItinerariesFilterResults,
+ List itineraries
+ ) {
+ return new PagingService(
+ transitTuningParameters.pagingSearchWindowAdjustments(),
+ raptorTuningParameters.dynamicSearchWindowCoefficients().minWindow(),
+ raptorTuningParameters.dynamicSearchWindowCoefficients().maxWindow(),
+ searchWindowOf(raptorSearchParamsUsed),
+ edt(searchStartTime, raptorSearchParamsUsed),
+ lat(searchStartTime, raptorSearchParamsUsed),
+ request.itinerariesSortOrder(),
+ request.arriveBy(),
+ request.numItineraries(),
+ request.pageCursor(),
+ numItinerariesFilterResults,
+ itineraries
+ );
+ }
+
+ static Duration searchWindowOf(SearchParams searchParamsUsed) {
+ if (searchParamsUsed == null || !searchParamsUsed.isSearchWindowSet()) {
+ return null;
+ }
+ return Duration.ofSeconds(searchParamsUsed.searchWindowInSeconds());
+ }
+
+ static Instant edt(Instant transitSearchStartTime, SearchParams searchParamsUsed) {
+ if (searchParamsUsed == null || !searchParamsUsed.isEarliestDepartureTimeSet()) {
+ return null;
+ }
+ return transitSearchStartTime.plusSeconds(searchParamsUsed.earliestDepartureTime());
+ }
+
+ static Instant lat(Instant transitSearchStartTime, SearchParams searchParamsUsed) {
+ if (searchParamsUsed == null || !searchParamsUsed.isLatestArrivalTimeSet()) {
+ return null;
+ }
+ return transitSearchStartTime.plusSeconds(searchParamsUsed.latestArrivalTime());
+ }
+}
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java
index 61ac1dd5cc1..a9b042083bf 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java
@@ -191,7 +191,7 @@ private Leg mapTransitLeg(Leg prevTransitLeg, TransitPathLeg pathLeg) {
int lastLegCost = 0;
PathLeg nextLeg = pathLeg.nextLeg();
if (nextLeg.isEgressLeg() && isFree(nextLeg.asEgressLeg())) {
- lastLegCost = pathLeg.nextLeg().generalizedCost();
+ lastLegCost = pathLeg.nextLeg().c1();
}
// Find stop positions in pattern where this leg boards and alights.
@@ -221,7 +221,7 @@ private Leg mapTransitLeg(Leg prevTransitLeg, TransitPathLeg pathLeg) {
(prevTransitLeg == null ? null : prevTransitLeg.getTransferToNextLeg())
)
.withTransferToNextLeg((ConstrainedTransfer) pathLeg.getConstrainedTransferAfterLeg())
- .withGeneralizedCost(toOtpDomainCost(pathLeg.generalizedCost() + lastLegCost))
+ .withGeneralizedCost(toOtpDomainCost(pathLeg.c1() + lastLegCost))
.withFrequencyHeadwayInSeconds(frequencyHeadwayInSeconds)
.build();
}
@@ -242,7 +242,7 @@ private Leg mapTransitLeg(Leg prevTransitLeg, TransitPathLeg pathLeg) {
(prevTransitLeg == null ? null : prevTransitLeg.getTransferToNextLeg())
)
.withTransferToNextLeg((ConstrainedTransfer) pathLeg.getConstrainedTransferAfterLeg())
- .withGeneralizedCost(toOtpDomainCost(pathLeg.generalizedCost() + lastLegCost))
+ .withGeneralizedCost(toOtpDomainCost(pathLeg.c1() + lastLegCost))
.build();
}
@@ -308,7 +308,7 @@ private List mapNonTransitLeg(
.withFrom(from)
.withTo(to)
.withDistanceMeters(transfer.getDistanceMeters())
- .withGeneralizedCost(toOtpDomainCost(pathLeg.generalizedCost()))
+ .withGeneralizedCost(toOtpDomainCost(pathLeg.c1()))
.withGeometry(GeometryUtils.makeLineString(transfer.getCoordinates()))
.withWalkSteps(List.of())
.build()
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 e86249f6fc8..b379480bc33 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java
@@ -12,7 +12,6 @@
import org.opentripplanner.routing.algorithm.filterchain.GroupBySimilarity;
import org.opentripplanner.routing.algorithm.filterchain.ItineraryListFilterChain;
import org.opentripplanner.routing.algorithm.filterchain.ItineraryListFilterChainBuilder;
-import org.opentripplanner.routing.algorithm.filterchain.ListSection;
import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.NumItinerariesFilterResults;
import org.opentripplanner.routing.api.request.RouteRequest;
import org.opentripplanner.routing.api.request.StreetMode;
@@ -44,7 +43,7 @@ public static ItineraryListFilterChain createFilterChain(
// The page cursor has deduplication information only in certain cases.
if (request.pageCursor() != null && request.pageCursor().containsItineraryPageCut()) {
- builder = builder.withPagingDeduplicationFilter(request.pageCursor().itineraryPageCut);
+ builder = builder.withPagingDeduplicationFilter(request.pageCursor().itineraryPageCut());
}
ItineraryFilterPreferences params = request.preferences().itineraryFilter();
@@ -66,12 +65,9 @@ public static ItineraryListFilterChain createFilterChain(
);
}
- if (request.maxNumberOfItinerariesCropHead()) {
- builder.withMaxNumberOfItinerariesCrop(ListSection.HEAD);
- }
-
builder
.withMaxNumberOfItineraries(Math.min(request.numItineraries(), MAX_NUMBER_OF_ITINERARIES))
+ .withMaxNumberOfItinerariesCropSection(request.cropItinerariesAt())
.withTransitGeneralizedCostLimit(params.transitGeneralizedCostLimit())
.withBikeRentalDistanceRatio(params.bikeRentalDistanceRatio())
.withParkAndRideDurationRatio(params.parkAndRideDurationRatio())
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RoutingResponseMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RoutingResponseMapper.java
index 89263b1b7a4..94b8c792d95 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RoutingResponseMapper.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RoutingResponseMapper.java
@@ -2,26 +2,17 @@
import static org.opentripplanner.ext.realtimeresolver.RealtimeResolver.populateLegsWithRealtime;
-import java.time.Duration;
-import java.time.Instant;
-import java.time.ZonedDateTime;
import java.util.List;
-import java.util.Objects;
import java.util.Set;
-import javax.annotation.Nullable;
import org.opentripplanner.framework.application.OTPFeature;
import org.opentripplanner.model.plan.Itinerary;
-import org.opentripplanner.model.plan.SortOrder;
-import org.opentripplanner.model.plan.pagecursor.PageCursor;
-import org.opentripplanner.model.plan.pagecursor.PageCursorFactory;
-import org.opentripplanner.model.plan.pagecursor.PageType;
+import org.opentripplanner.model.plan.paging.cursor.PageCursor;
import org.opentripplanner.raptor.api.request.SearchParams;
-import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.NumItinerariesFilterResults;
import org.opentripplanner.routing.api.request.RouteRequest;
import org.opentripplanner.routing.api.response.RoutingError;
import org.opentripplanner.routing.api.response.RoutingResponse;
-import org.opentripplanner.routing.api.response.TripSearchMetadata;
import org.opentripplanner.routing.framework.DebugTimingAggregator;
+import org.opentripplanner.service.paging.PagingService;
import org.opentripplanner.transit.service.TransitService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -32,14 +23,12 @@ public class RoutingResponseMapper {
public static RoutingResponse map(
RouteRequest request,
- ZonedDateTime transitSearchTimeZero,
SearchParams raptorSearchParamsUsed,
- Duration searchWindowForNextSearch,
- NumItinerariesFilterResults numItinerariesFilterResults,
List itineraries,
Set routingErrors,
DebugTimingAggregator debugTimingAggregator,
- TransitService transitService
+ TransitService transitService,
+ PagingService pagingService
) {
// Search is performed without realtime, but we still want to
// include realtime information in the result
@@ -52,32 +41,15 @@ public static RoutingResponse map(
// Create response
var tripPlan = TripPlanMapper.mapTripPlan(request, itineraries);
- var factory = mapIntoPageCursorFactory(
- request.itinerariesSortOrder(),
- transitSearchTimeZero,
- raptorSearchParamsUsed,
- searchWindowForNextSearch,
- numItinerariesFilterResults,
- request.pageCursor() == null ? null : request.pageCursor().type
- );
-
- PageCursor nextPageCursor = factory.nextPageCursor();
- PageCursor prevPageCursor = factory.previousPageCursor();
+ // Paging
+ PageCursor nextPageCursor = pagingService.nextPageCursor();
+ PageCursor prevPageCursor = pagingService.previousPageCursor();
if (LOG.isDebugEnabled()) {
logPagingInformation(request.pageCursor(), prevPageCursor, nextPageCursor, routingErrors);
}
- var metadata = createTripSearchMetadata(
- request,
- raptorSearchParamsUsed,
- numItinerariesFilterResults == null
- ? null
- : numItinerariesFilterResults.firstRemovedDepartureTime,
- numItinerariesFilterResults == null
- ? null
- : numItinerariesFilterResults.firstRemovedArrivalTime
- );
+ var metadata = pagingService.createTripSearchMetadata();
return new RoutingResponse(
tripPlan,
@@ -89,96 +61,6 @@ public static RoutingResponse map(
);
}
- public static PageCursorFactory mapIntoPageCursorFactory(
- SortOrder sortOrder,
- ZonedDateTime transitSearchTimeZero,
- SearchParams raptorSearchParamsUsed,
- Duration searchWindowNextSearch,
- NumItinerariesFilterResults numItinerariesFilterResults,
- @Nullable PageType currentPageType
- ) {
- Objects.requireNonNull(sortOrder);
- Objects.requireNonNull(transitSearchTimeZero);
-
- var factory = new PageCursorFactory(sortOrder, searchWindowNextSearch);
-
- // No transit search performed
- if (raptorSearchParamsUsed == null) {
- return factory;
- }
-
- assertRequestPrerequisites(raptorSearchParamsUsed);
-
- factory =
- mapSearchParametersIntoFactory(
- factory,
- transitSearchTimeZero,
- raptorSearchParamsUsed,
- currentPageType
- );
-
- if (numItinerariesFilterResults != null) {
- factory = factory.withRemovedItineraries(numItinerariesFilterResults);
- }
- return factory;
- }
-
- private static PageCursorFactory mapSearchParametersIntoFactory(
- PageCursorFactory factory,
- ZonedDateTime transitSearchTimeZero,
- SearchParams raptorSearchParamsUsed,
- PageType currentPageType
- ) {
- Instant edt = transitSearchTimeZero
- .plusSeconds(raptorSearchParamsUsed.earliestDepartureTime())
- .toInstant();
-
- Instant lat = raptorSearchParamsUsed.isLatestArrivalTimeSet()
- ? transitSearchTimeZero.plusSeconds(raptorSearchParamsUsed.latestArrivalTime()).toInstant()
- : null;
-
- var searchWindowUsed = Duration.ofSeconds(raptorSearchParamsUsed.routerSearchWindowInSeconds());
-
- return factory.withOriginalSearch(currentPageType, edt, lat, searchWindowUsed);
- }
-
- @Nullable
- private static TripSearchMetadata createTripSearchMetadata(
- RouteRequest request,
- SearchParams searchParams,
- Instant firstRemovedDepartureTime,
- Instant firstRemovedArrivalTime
- ) {
- if (searchParams == null) {
- return null;
- }
-
- Instant reqTime = request.dateTime();
-
- if (request.arriveBy()) {
- return TripSearchMetadata.createForArriveBy(
- reqTime,
- searchParams.searchWindowInSeconds(),
- firstRemovedArrivalTime
- );
- } else {
- return TripSearchMetadata.createForDepartAfter(
- reqTime,
- searchParams.searchWindowInSeconds(),
- firstRemovedDepartureTime
- );
- }
- }
-
- private static void assertRequestPrerequisites(SearchParams raptorSearchParamsUsed) {
- if (!raptorSearchParamsUsed.isSearchWindowSet()) {
- throw new IllegalStateException("SearchWindow not set");
- }
- if (!raptorSearchParamsUsed.isEarliestDepartureTimeSet()) {
- throw new IllegalStateException("Earliest departure time not set");
- }
- }
-
private static void logPagingInformation(
PageCursor currentPageCursor,
PageCursor prevPageCursor,
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java
index 491f68800b8..e0ca070d76f 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java
@@ -37,7 +37,7 @@ protected DefaultAccessEgress(DefaultAccessEgress other, TimeAndCost penalty) {
}
this.stop = other.stop();
this.durationInSeconds = other.durationInSeconds() + (int) penalty.time().toSeconds();
- this.generalizedCost = other.generalizedCost() + penalty.cost().toCentiSeconds();
+ this.generalizedCost = other.c1() + penalty.cost().toCentiSeconds();
this.penalty = penalty;
this.lastState = other.getLastState();
}
@@ -62,7 +62,7 @@ public int stop() {
}
@Override
- public int generalizedCost() {
+ public int c1() {
return generalizedCost;
}
@@ -119,7 +119,7 @@ public final boolean equals(Object o) {
return (
stop() == that.stop() &&
durationInSeconds() == that.durationInSeconds() &&
- generalizedCost() == that.generalizedCost() &&
+ c1() == that.c1() &&
penalty().equals(that.penalty())
);
}
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultRaptorTransfer.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultRaptorTransfer.java
index 4914970811a..3547a7ba05b 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultRaptorTransfer.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultRaptorTransfer.java
@@ -2,18 +2,13 @@
import org.opentripplanner.raptor.api.model.RaptorTransfer;
-public record DefaultRaptorTransfer(
- int stop,
- int durationInSeconds,
- int generalizedCost,
- Transfer transfer
-)
+public record DefaultRaptorTransfer(int stop, int durationInSeconds, int c1, Transfer transfer)
implements RaptorTransfer {
public static DefaultRaptorTransfer reverseOf(int fromStopIndex, RaptorTransfer transfer) {
return new DefaultRaptorTransfer(
fromStopIndex,
transfer.durationInSeconds(),
- transfer.generalizedCost(),
+ transfer.c1(),
transfer instanceof DefaultRaptorTransfer drt ? drt.transfer : null
);
}
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RaptorTransferIndex.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RaptorTransferIndex.java
index 975526b946e..1d9b804067c 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RaptorTransferIndex.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RaptorTransferIndex.java
@@ -43,11 +43,7 @@ public static RaptorTransferIndex create(
.stream()
.flatMap(s -> s.asRaptorTransfer(request).stream())
.collect(
- toMap(
- RaptorTransfer::stop,
- Function.identity(),
- (a, b) -> a.generalizedCost() < b.generalizedCost() ? a : b
- )
+ toMap(RaptorTransfer::stop, Function.identity(), (a, b) -> a.c1() < b.c1() ? a : b)
)
.values();
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java
index b738d63054e..26a1286d10d 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java
@@ -134,15 +134,15 @@ public int calculateMinCost(int minTravelTime, int minNumTransfers) {
@Override
public int costEgress(RaptorAccessEgress egress) {
if (egress.hasRides()) {
- return egress.generalizedCost() + transferCostOnly;
+ return egress.c1() + transferCostOnly;
} else if (stopTransferCost != null) {
// Remove cost that was added during alighting.
// We do not want to add this cost on last alighting since it should only be applied on transfers
// It has to be done here because during alighting we do not know yet if it will be
// a transfer or not.
- return egress.generalizedCost() - stopTransferCost[egress.stop()];
+ return egress.c1() - stopTransferCost[egress.stop()];
} else {
- return egress.generalizedCost();
+ return egress.c1();
}
}
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/RaptorCostConverter.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/RaptorCostConverter.java
index 0ee465c2e53..210197f107b 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/RaptorCostConverter.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/RaptorCostConverter.java
@@ -1,7 +1,7 @@
package org.opentripplanner.routing.algorithm.raptoradapter.transit.cost;
import java.time.Duration;
-import org.opentripplanner.framework.lang.OtpNumberFormat;
+import org.opentripplanner.raptor.api.model.RaptorValueFormatter;
/**
* Convert Raptor internal cost to OTP domain model cost, and back.
@@ -47,7 +47,7 @@ public static double toOtpDomainFactor(int raptorFactor) {
* Convert Raptor internal cost to a string with format $###.## (in seconds)
*/
public static String toString(int raptorCost) {
- return OtpNumberFormat.formatCostCenti(raptorCost);
+ return RaptorValueFormatter.formatC1(raptorCost);
}
/**
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 147601cbbae..75c1bcf7214 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
@@ -97,13 +97,13 @@ private RaptorRequest doMap() {
} else {
var c = request.pageCursor();
- if (c.earliestDepartureTime != null) {
- searchParams.earliestDepartureTime(relativeTime(c.earliestDepartureTime));
+ if (c.earliestDepartureTime() != null) {
+ searchParams.earliestDepartureTime(relativeTime(c.earliestDepartureTime()));
}
- if (c.latestArrivalTime != null) {
- searchParams.latestArrivalTime(relativeTime(c.latestArrivalTime));
+ if (c.latestArrivalTime() != null) {
+ searchParams.latestArrivalTime(relativeTime(c.latestArrivalTime()));
}
- searchParams.searchWindow(c.searchWindow);
+ searchParams.searchWindow(c.searchWindow());
}
if (preferences.transfer().maxTransfers() != null) {
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 9564bfa0fd3..ba9af45adba 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
@@ -26,7 +26,6 @@ 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;
@@ -36,7 +35,6 @@ public class PriorityGroupConfigurator {
private PriorityGroupConfigurator() {
this.enabled = false;
- this.baseMatchers = null;
this.agencyMatchers = null;
this.globalMatchers = null;
this.agencyMatchersIds = List.of();
@@ -44,15 +42,12 @@ private PriorityGroupConfigurator() {
}
private PriorityGroupConfigurator(
- Collection base,
Collection byAgency,
Collection global
) {
- this.baseMatchers = PriorityGroupMatcher.of(base);
this.agencyMatchers = PriorityGroupMatcher.of(byAgency);
this.globalMatchers = PriorityGroupMatcher.of(global);
- this.enabled =
- Stream.of(baseMatchers, agencyMatchers, globalMatchers).anyMatch(ArrayUtils::hasContent);
+ this.enabled = Stream.of(agencyMatchers, globalMatchers).anyMatch(ArrayUtils::hasContent);
this.globalMatchersIds =
Arrays.stream(globalMatchers).map(m -> new MatcherAndId(m, nextGroupId())).toList();
// We need to populate this dynamically
@@ -64,14 +59,13 @@ public static PriorityGroupConfigurator empty() {
}
public static PriorityGroupConfigurator of(
- Collection base,
Collection byAgency,
Collection global
) {
- if (Stream.of(base, byAgency, global).allMatch(Collection::isEmpty)) {
+ if (Stream.of(byAgency, global).allMatch(Collection::isEmpty)) {
return empty();
}
- return new PriorityGroupConfigurator(base, byAgency, global);
+ return new PriorityGroupConfigurator(byAgency, global);
}
/**
@@ -86,11 +80,6 @@ public int lookupTransitPriorityGroupId(RoutingTripPattern tripPattern) {
var p = tripPattern.getPattern();
- for (PriorityGroupMatcher m : baseMatchers) {
- if (m.match(p)) {
- return BASE_GROUP_ID;
- }
- }
for (var it : agencyMatchersIds) {
if (it.matcher().match(p)) {
var agencyId = p.getRoute().getAgency().getId();
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 b362587c773..f1212b2f545 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,12 +244,11 @@ public RaptorConstrainedBoardingSearch transferConstraintsReverseS
}
private PriorityGroupConfigurator createTransitPriorityGroupConfigurator(RouteRequest request) {
- if (!request.preferences().transit().relaxTransitPriorityGroup().isNormal()) {
+ if (request.preferences().transit().relaxTransitPriorityGroup().isNormal()) {
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/transferoptimization/api/OptimizedPath.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/api/OptimizedPath.java
index d18da1f0e08..ad4df23a42c 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/api/OptimizedPath.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/api/OptimizedPath.java
@@ -30,6 +30,7 @@ public OptimizedPath(RaptorPath originalPath) {
originalPath.accessLeg(),
originalPath.rangeRaptorIterationDepartureTime(),
originalPath.c1(),
+ originalPath.c2(),
priorityCost(originalPath),
NEUTRAL_COST,
NEUTRAL_COST
@@ -40,11 +41,12 @@ public OptimizedPath(
AccessPathLeg accessPathLeg,
int iterationStartTime,
int generalizedCost,
+ int c2,
int transferPriorityCost,
int waitTimeOptimizedCost,
int breakTieCost
) {
- super(iterationStartTime, accessPathLeg, generalizedCost);
+ super(iterationStartTime, accessPathLeg, generalizedCost, c2);
this.transferPriorityCost = transferPriorityCost;
this.waitTimeOptimizedCost = waitTimeOptimizedCost;
this.breakTieCost = breakTieCost;
@@ -61,7 +63,7 @@ public static int priorityCost(boolean transferExist, Supplier leg) {
}
private void appendSummary(PathStringBuilder buf) {
- buf.costCentiSec(transferPriorityCost, TransferConstraint.ZERO_COST, "pri");
- buf.costCentiSec(generalizedCostWaitTimeOptimized(), c1(), "wtc");
+ buf.transferPriority(transferPriorityCost, TransferConstraint.ZERO_COST);
+ buf.waitTimeCost(generalizedCostWaitTimeOptimized(), c1());
}
}
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizedPathTail.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizedPathTail.java
index 365ff1ef860..fa74648e8eb 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizedPathTail.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizedPathTail.java
@@ -3,8 +3,10 @@
import javax.annotation.Nullable;
import org.opentripplanner.framework.tostring.ValueObjectToStringBuilder;
import org.opentripplanner.model.transfer.TransferConstraint;
+import org.opentripplanner.raptor.api.model.RaptorConstants;
import org.opentripplanner.raptor.api.model.RaptorTransfer;
import org.opentripplanner.raptor.api.model.RaptorTripSchedule;
+import org.opentripplanner.raptor.api.model.RaptorValueFormatter;
import org.opentripplanner.raptor.api.path.RaptorStopNameResolver;
import org.opentripplanner.raptor.api.path.TransitPathLeg;
import org.opentripplanner.raptor.path.PathBuilder;
@@ -130,6 +132,7 @@ public OptimizedPath build() {
createPathLegs(costCalculator(), slackProvider()),
iterationDepartureTime,
generalizedCost,
+ c2(),
transferPriorityCost,
waitTimeOptimizedCost,
breakTieCost()
@@ -138,15 +141,21 @@ public OptimizedPath build() {
@Override
public String toString() {
- return ValueObjectToStringBuilder
- .of()
- .addObj(super.toString())
- .addText(" [")
- .addCostCenti(generalizedCost())
- .addCostCenti(transferPriorityCost, "pri")
- .addCostCenti(generalizedCostWaitTimeOptimized(), "wtc")
- .addText("]")
- .toString();
+ var builder = ValueObjectToStringBuilder.of().addObj(super.toString()).addText(" [");
+
+ if (generalizedCost != RaptorCostCalculator.ZERO_COST) {
+ builder.addObj(RaptorValueFormatter.formatC1(generalizedCost()));
+ }
+ if (c2() != RaptorConstants.NOT_SET) {
+ builder.addObj(RaptorValueFormatter.formatC2(c2()));
+ }
+ if (transferPriorityCost != TransferConstraint.ZERO_COST) {
+ builder.addObj(RaptorValueFormatter.formatTransferPriority(transferPriorityCost));
+ }
+ if (waitTimeOptimizedCost != TransferWaitTimeCostCalculator.ZERO_COST) {
+ builder.addObj(RaptorValueFormatter.formatWaitTimeCost(generalizedCostWaitTimeOptimized()));
+ }
+ return builder.addText("]").toString();
}
@Override
@@ -203,7 +212,7 @@ private void updateGeneralizedCost() {
return;
}
this.generalizedCost =
- legsAsStream().mapToInt(it -> it.generalizedCost(costCalculator(), slackProvider())).sum();
+ legsAsStream().mapToInt(it -> it.c1(costCalculator(), slackProvider())).sum();
}
/*private methods */
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/TripToTripTransfer.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/TripToTripTransfer.java
index 5014ae95bcd..0dce8933e3f 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/TripToTripTransfer.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/TripToTripTransfer.java
@@ -45,7 +45,7 @@ public int transferDuration() {
}
public int generalizedCost() {
- return sameStop() ? 0 : pathTransfer.generalizedCost();
+ return sameStop() ? 0 : pathTransfer.c1();
}
public boolean sameStop() {
diff --git a/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java b/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java
index 586b106bc66..ce2fdb31c44 100644
--- a/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java
+++ b/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java
@@ -13,12 +13,12 @@
import java.util.Locale;
import java.util.function.Consumer;
import javax.annotation.Nullable;
+import org.opentripplanner.framework.collection.ListSection;
import org.opentripplanner.framework.time.DateUtils;
import org.opentripplanner.framework.tostring.ToStringBuilder;
import org.opentripplanner.model.GenericLocation;
import org.opentripplanner.model.plan.SortOrder;
-import org.opentripplanner.model.plan.pagecursor.PageCursor;
-import org.opentripplanner.model.plan.pagecursor.PageType;
+import org.opentripplanner.model.plan.paging.cursor.PageCursor;
import org.opentripplanner.routing.api.request.preference.RoutingPreferences;
import org.opentripplanner.routing.api.request.request.JourneyRequest;
import org.opentripplanner.routing.api.response.InputField;
@@ -157,7 +157,7 @@ public boolean isTripPlannedForNow() {
public SortOrder itinerariesSortOrder() {
if (pageCursor != null) {
- return pageCursor.originalSortOrder;
+ return pageCursor.originalSortOrder();
}
return arriveBy ? SortOrder.STREET_AND_DEPARTURE_TIME : SortOrder.STREET_AND_ARRIVAL_TIME;
}
@@ -171,10 +171,11 @@ public void applyPageCursor() {
if (pageCursor != null) {
// We switch to "depart-after" search when paging next(lat==null). It does not make
// sense anymore to keep the latest-arrival-time when going to the "next page".
- if (pageCursor.latestArrivalTime == null) {
+ if (pageCursor.latestArrivalTime() == null) {
arriveBy = false;
}
- this.dateTime = arriveBy ? pageCursor.latestArrivalTime : pageCursor.earliestDepartureTime;
+ this.dateTime =
+ arriveBy ? pageCursor.latestArrivalTime() : pageCursor.earliestDepartureTime();
journey.setModes(journey.modes().copyOf().withDirectMode(StreetMode.NOT_SET).build());
LOG.debug("Request dateTime={} set from pageCursor.", dateTime);
}
@@ -182,35 +183,12 @@ public void applyPageCursor() {
/**
* When paging we must crop the list of itineraries in the right end according to the sorting of
- * the original search and according to the page cursor type (next or previous).
- *
- * We need to flip the cropping and crop the head/start of the itineraries when:
- *
- * Paging to the previous page for a {@code depart-after/sort-on-arrival-time} search.
- * Paging to the next page for a {@code arrive-by/sort-on-departure-time} search.
- *
+ * the original search and according to the paging direction (next or previous). We always
+ * crop at the end of the initial search. This is a utility function delegating to the
+ * pageCursor, if available.
*/
- public boolean maxNumberOfItinerariesCropHead() {
- if (pageCursor == null) {
- return false;
- }
-
- var previousPage = pageCursor.type == PageType.PREVIOUS_PAGE;
- return pageCursor.originalSortOrder.isSortedByArrivalTimeAscending() == previousPage;
- }
-
- /**
- * Related to {@link #maxNumberOfItinerariesCropHead()}, but is {@code true} if we should crop the
- * search-window head(in the beginning) or tail(in the end).
- *
- * For the first search we look if the sort is ascending(crop tail) or descending(crop head), and
- * for paged results we look at the paging type: next(tail) and previous(head).
- */
- public boolean doCropSearchWindowAtTail() {
- if (pageCursor == null) {
- return itinerariesSortOrder().isSortedByArrivalTimeAscending();
- }
- return pageCursor.type == PageType.NEXT_PAGE;
+ public ListSection cropItinerariesAt() {
+ return pageCursor == null ? ListSection.TAIL : pageCursor.cropItinerariesAt();
}
/**
diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/BikePreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/BikePreferences.java
index 0985a132dd0..115e1264036 100644
--- a/src/main/java/org/opentripplanner/routing/api/request/preference/BikePreferences.java
+++ b/src/main/java/org/opentripplanner/routing/api/request/preference/BikePreferences.java
@@ -1,6 +1,7 @@
package org.opentripplanner.routing.api.request.preference;
import static org.opentripplanner.framework.lang.DoubleUtils.doubleEquals;
+import static org.opentripplanner.framework.lang.ObjectUtils.ifNotNull;
import java.io.Serializable;
import java.util.Objects;
@@ -28,8 +29,7 @@ public final class BikePreferences implements Serializable {
private final double walkingReluctance;
private final int switchTime;
private final Cost switchCost;
- private final int parkTime;
- private final Cost parkCost;
+ private final VehicleParkingPreferences parking;
private final double stairsReluctance;
private final BicycleOptimizeType optimizeType;
private final TimeSlopeSafetyTriangle optimizeTriangle;
@@ -42,9 +42,7 @@ private BikePreferences() {
this.walkingReluctance = 5.0;
this.switchTime = 0;
this.switchCost = Cost.ZERO;
- this.parkTime = 60;
- /** Cost of parking a bike. */
- this.parkCost = Cost.costOfSeconds(120);
+ this.parking = VehicleParkingPreferences.DEFAULT;
this.optimizeType = BicycleOptimizeType.SAFE;
this.optimizeTriangle = TimeSlopeSafetyTriangle.DEFAULT;
// very high reluctance to carry the bike up/down a flight of stairs
@@ -59,8 +57,7 @@ private BikePreferences(Builder builder) {
this.walkingReluctance = Units.reluctance(builder.walkingReluctance);
this.switchTime = Units.duration(builder.switchTime);
this.switchCost = builder.switchCost;
- this.parkTime = Units.duration(builder.parkTime);
- this.parkCost = builder.parkCost;
+ this.parking = builder.parking;
this.optimizeType = Objects.requireNonNull(builder.optimizeType);
this.optimizeTriangle = Objects.requireNonNull(builder.optimizeTriangle);
this.stairsReluctance = Units.reluctance(builder.stairsReluctance);
@@ -124,14 +121,9 @@ public int switchCost() {
return switchCost.toSeconds();
}
- /** Time to park a bike */
- public int parkTime() {
- return parkTime;
- }
-
- /** Cost of parking a bike. */
- public int parkCost() {
- return parkCost.toSeconds();
+ /** Parking preferences that can be different per request */
+ public VehicleParkingPreferences parking() {
+ return parking;
}
/**
@@ -162,8 +154,7 @@ public boolean equals(Object o) {
doubleEquals(that.walkingReluctance, walkingReluctance) &&
switchTime == that.switchTime &&
switchCost.equals(that.switchCost) &&
- parkTime == that.parkTime &&
- parkCost.equals(that.parkCost) &&
+ parking.equals(that.parking) &&
optimizeType == that.optimizeType &&
optimizeTriangle.equals(that.optimizeTriangle) &&
doubleEquals(stairsReluctance, that.stairsReluctance)
@@ -180,8 +171,7 @@ public int hashCode() {
walkingReluctance,
switchTime,
switchCost,
- parkTime,
- parkCost,
+ parking,
optimizeType,
optimizeTriangle,
stairsReluctance
@@ -199,8 +189,7 @@ public String toString() {
.addNum("walkingReluctance", walkingReluctance, DEFAULT.walkingReluctance)
.addDurationSec("switchTime", switchTime, DEFAULT.switchTime)
.addObj("switchCost", switchCost, DEFAULT.switchCost)
- .addDurationSec("parkTime", parkTime, DEFAULT.parkTime)
- .addObj("parkCost", parkCost, DEFAULT.parkCost)
+ .addObj("parking", parking, DEFAULT.parking)
.addEnum("optimizeType", optimizeType, DEFAULT.optimizeType)
.addObj("optimizeTriangle", optimizeTriangle, DEFAULT.optimizeTriangle)
.toString();
@@ -217,8 +206,7 @@ public static class Builder {
private double walkingReluctance;
private int switchTime;
private Cost switchCost;
- private int parkTime;
- private Cost parkCost;
+ private VehicleParkingPreferences parking;
private BicycleOptimizeType optimizeType;
private TimeSlopeSafetyTriangle optimizeTriangle;
@@ -233,8 +221,7 @@ public Builder(BikePreferences original) {
this.walkingReluctance = original.walkingReluctance;
this.switchTime = original.switchTime;
this.switchCost = original.switchCost;
- this.parkTime = original.parkTime;
- this.parkCost = original.parkCost;
+ this.parking = original.parking;
this.optimizeType = original.optimizeType;
this.optimizeTriangle = original.optimizeTriangle;
this.stairsReluctance = original.stairsReluctance;
@@ -307,21 +294,8 @@ public Builder withSwitchCost(int switchCost) {
return this;
}
- public int parkTime() {
- return parkTime;
- }
-
- public Builder withParkTime(int parkTime) {
- this.parkTime = parkTime;
- return this;
- }
-
- public Cost parkCost() {
- return parkCost;
- }
-
- public Builder withParkCost(int parkCost) {
- this.parkCost = Cost.costOfSeconds(parkCost);
+ public Builder withParking(Consumer body) {
+ this.parking = ifNotNull(this.parking, original.parking).copyOf().apply(body).build();
return this;
}
diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/CarPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/CarPreferences.java
index 014b2b0cdec..523e19afb70 100644
--- a/src/main/java/org/opentripplanner/routing/api/request/preference/CarPreferences.java
+++ b/src/main/java/org/opentripplanner/routing/api/request/preference/CarPreferences.java
@@ -1,5 +1,7 @@
package org.opentripplanner.routing.api.request.preference;
+import static org.opentripplanner.framework.lang.ObjectUtils.ifNotNull;
+
import java.io.Serializable;
import java.util.Objects;
import java.util.function.Consumer;
@@ -21,8 +23,7 @@ public final class CarPreferences implements Serializable {
private final double speed;
private final double reluctance;
- private final int parkTime;
- private final Cost parkCost;
+ private final VehicleParkingPreferences parking;
private final int pickupTime;
private final Cost pickupCost;
private final int dropoffTime;
@@ -33,8 +34,7 @@ public final class CarPreferences implements Serializable {
private CarPreferences() {
this.speed = 40.0;
this.reluctance = 2.0;
- this.parkTime = 60;
- this.parkCost = Cost.costOfMinutes(2);
+ this.parking = VehicleParkingPreferences.DEFAULT;
this.pickupTime = 60;
this.pickupCost = Cost.costOfMinutes(2);
this.dropoffTime = 120;
@@ -45,8 +45,7 @@ private CarPreferences() {
private CarPreferences(Builder builder) {
this.speed = Units.speed(builder.speed);
this.reluctance = Units.reluctance(builder.reluctance);
- this.parkTime = Units.duration(builder.parkTime);
- this.parkCost = builder.parkCost;
+ this.parking = builder.parking;
this.pickupTime = Units.duration(builder.pickupTime);
this.pickupCost = builder.pickupCost;
this.dropoffTime = Units.duration(builder.dropoffTime);
@@ -75,14 +74,9 @@ public double reluctance() {
return reluctance;
}
- /** Time to park a car. */
- public int parkTime() {
- return parkTime;
- }
-
- /** Cost of parking a car. */
- public int parkCost() {
- return parkCost.toSeconds();
+ /** Parking preferences that can be different per request */
+ public VehicleParkingPreferences parking() {
+ return parking;
}
/** Time of getting in/out of a carPickup (taxi) */
@@ -127,8 +121,7 @@ public boolean equals(Object o) {
return (
DoubleUtils.doubleEquals(that.speed, speed) &&
DoubleUtils.doubleEquals(that.reluctance, reluctance) &&
- parkTime == that.parkTime &&
- parkCost.equals(that.parkCost) &&
+ parking.equals(that.parking) &&
pickupTime == that.pickupTime &&
pickupCost.equals(that.pickupCost) &&
dropoffTime == that.dropoffTime &&
@@ -142,8 +135,7 @@ public int hashCode() {
return Objects.hash(
speed,
reluctance,
- parkTime,
- parkCost,
+ parking,
pickupTime,
pickupCost,
dropoffTime,
@@ -158,8 +150,7 @@ public String toString() {
.of(CarPreferences.class)
.addNum("speed", speed, DEFAULT.speed)
.addNum("reluctance", reluctance, DEFAULT.reluctance)
- .addNum("parkTime", parkTime, DEFAULT.parkTime)
- .addObj("parkCost", parkCost, DEFAULT.parkCost)
+ .addObj("parking", parking, DEFAULT.parking)
.addNum("pickupTime", pickupTime, DEFAULT.pickupTime)
.addObj("pickupCost", pickupCost, DEFAULT.pickupCost)
.addNum("dropoffTime", dropoffTime, DEFAULT.dropoffTime)
@@ -174,8 +165,7 @@ public static class Builder {
private final CarPreferences original;
private double speed;
private double reluctance;
- private int parkTime;
- private Cost parkCost;
+ private VehicleParkingPreferences parking;
private int pickupTime;
private Cost pickupCost;
private int dropoffTime;
@@ -186,8 +176,7 @@ public Builder(CarPreferences original) {
this.original = original;
this.speed = original.speed;
this.reluctance = original.reluctance;
- this.parkTime = original.parkTime;
- this.parkCost = original.parkCost;
+ this.parking = original.parking;
this.pickupTime = original.pickupTime;
this.pickupCost = original.pickupCost;
this.dropoffTime = original.dropoffTime;
@@ -209,13 +198,8 @@ public Builder withReluctance(double reluctance) {
return this;
}
- public Builder withParkTime(int parkTime) {
- this.parkTime = parkTime;
- return this;
- }
-
- public Builder withParkCost(int parkCost) {
- this.parkCost = Cost.costOfSeconds(parkCost);
+ public Builder withParking(Consumer body) {
+ this.parking = ifNotNull(this.parking, original.parking).copyOf().apply(body).build();
return this;
}
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 a4dfcedd0c7..b728b99f8e3 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
@@ -96,6 +96,13 @@ public VehicleRentalPreferences rental() {
return rental;
}
+ /**
+ * Get parking preferences for the traverse mode. Note, only car and bike are supported.
+ */
+ public VehicleParkingPreferences parking(TraverseMode mode) {
+ return mode == TraverseMode.CAR ? car.parking() : bike.parking();
+ }
+
@Nonnull
public ItineraryFilterPreferences itineraryFilter() {
return itineraryFilter;
diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/VehicleParkingPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/VehicleParkingPreferences.java
new file mode 100644
index 00000000000..c02862c4d79
--- /dev/null
+++ b/src/main/java/org/opentripplanner/routing/api/request/preference/VehicleParkingPreferences.java
@@ -0,0 +1,208 @@
+package org.opentripplanner.routing.api.request.preference;
+
+import java.io.Serializable;
+import java.time.Duration;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Consumer;
+import org.opentripplanner.framework.model.Cost;
+import org.opentripplanner.framework.tostring.ToStringBuilder;
+import org.opentripplanner.routing.api.request.preference.filter.VehicleParkingFilter;
+import org.opentripplanner.routing.api.request.preference.filter.VehicleParkingSelect;
+
+/**
+ * The parking preferences contain preferences for car and bicycle parking. These preferences
+ * include filtering, preference and realtime usage.
+ *
+ * THIS CLASS IS IMMUTABLE AND THREAD-SAFE.
+ */
+public final class VehicleParkingPreferences implements Serializable {
+
+ public static final VehicleParkingPreferences DEFAULT = new VehicleParkingPreferences();
+ private final Cost unpreferredVehicleParkingTagCost;
+ private final VehicleParkingFilter filter;
+ private final VehicleParkingFilter preferred;
+ private final Duration parkTime;
+ private final Cost parkCost;
+
+ /** Create a new instance with default values. */
+ private VehicleParkingPreferences() {
+ this.unpreferredVehicleParkingTagCost = Cost.costOfMinutes(5);
+ this.filter = VehicleParkingFilter.empty();
+ this.preferred = VehicleParkingFilter.empty();
+ this.parkTime = Duration.ofMinutes(1);
+ this.parkCost = Cost.costOfMinutes(2);
+ }
+
+ private VehicleParkingPreferences(Builder builder) {
+ this.unpreferredVehicleParkingTagCost = builder.unpreferredVehicleParkingTagCost;
+ this.filter =
+ new VehicleParkingFilter(
+ builder.bannedVehicleParkingTags,
+ builder.requiredVehicleParkingTags
+ );
+ this.preferred =
+ new VehicleParkingFilter(
+ builder.notPreferredVehicleParkingTags,
+ builder.preferredVehicleParkingTags
+ );
+ this.parkTime = builder.parkTime;
+ this.parkCost = builder.parkCost;
+ }
+
+ public static VehicleParkingPreferences.Builder of() {
+ return new Builder(DEFAULT);
+ }
+
+ public VehicleParkingPreferences.Builder copyOf() {
+ return new Builder(this);
+ }
+
+ /**
+ * What cost is applied to using parking that is not preferred.
+ */
+ public Cost unpreferredVehicleParkingTagCost() {
+ return unpreferredVehicleParkingTagCost;
+ }
+
+ /**
+ * Parking containing select filters must only be usable and parking containing with not filters
+ * cannot be used.
+ */
+ public VehicleParkingFilter filter() {
+ return filter;
+ }
+
+ /**
+ * Which vehicle parking tags are preferred. Vehicle parking facilities that don't have one of these
+ * tags receive an extra cost.
+ *
+ * This is useful if you want to use certain kind of facilities, like lockers for expensive e-bikes.
+ */
+ public VehicleParkingFilter preferred() {
+ return preferred;
+ }
+
+ /** Time to park a vehicle */
+ public Duration parkTime() {
+ return parkTime;
+ }
+
+ /** Cost of parking a bike. */
+ public Cost parkCost() {
+ return parkCost;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ VehicleParkingPreferences that = (VehicleParkingPreferences) o;
+ return (
+ Objects.equals(unpreferredVehicleParkingTagCost, that.unpreferredVehicleParkingTagCost) &&
+ Objects.equals(filter, that.filter) &&
+ Objects.equals(preferred, that.preferred) &&
+ Objects.equals(parkCost, that.parkCost) &&
+ Objects.equals(parkTime, that.parkTime)
+ );
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(unpreferredVehicleParkingTagCost, filter, preferred, parkCost, parkTime);
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder
+ .of(VehicleParkingPreferences.class)
+ .addObj(
+ "unpreferredVehicleParkingTagCost",
+ unpreferredVehicleParkingTagCost,
+ DEFAULT.unpreferredVehicleParkingTagCost
+ )
+ .addObj("filter", filter, DEFAULT.filter)
+ .addObj("preferred", preferred, DEFAULT.preferred)
+ .addObj("parkCost", parkCost, DEFAULT.parkCost)
+ .addObj("parkTime", parkTime, DEFAULT.parkTime)
+ .toString();
+ }
+
+ public static class Builder {
+
+ private final VehicleParkingPreferences original;
+ private Cost unpreferredVehicleParkingTagCost;
+ private List bannedVehicleParkingTags;
+ private List requiredVehicleParkingTags;
+ private List preferredVehicleParkingTags;
+ private List notPreferredVehicleParkingTags;
+ private Cost parkCost;
+ private Duration parkTime;
+
+ private Builder(VehicleParkingPreferences original) {
+ this.original = original;
+ this.unpreferredVehicleParkingTagCost = original.unpreferredVehicleParkingTagCost;
+ this.bannedVehicleParkingTags = original.filter.not();
+ this.requiredVehicleParkingTags = original.filter.select();
+ this.preferredVehicleParkingTags = original.preferred.select();
+ this.notPreferredVehicleParkingTags = original.preferred.not();
+ this.parkCost = original.parkCost;
+ this.parkTime = original.parkTime;
+ }
+
+ public Builder withUnpreferredVehicleParkingTagCost(int cost) {
+ this.unpreferredVehicleParkingTagCost = Cost.costOfSeconds(cost);
+ return this;
+ }
+
+ public Builder withBannedVehicleParkingTags(Set bannedVehicleParkingTags) {
+ this.bannedVehicleParkingTags =
+ List.of(new VehicleParkingSelect.TagsSelect(bannedVehicleParkingTags));
+ return this;
+ }
+
+ public Builder withRequiredVehicleParkingTags(Set requiredVehicleParkingTags) {
+ this.requiredVehicleParkingTags =
+ List.of(new VehicleParkingSelect.TagsSelect(requiredVehicleParkingTags));
+ return this;
+ }
+
+ public Builder withPreferredVehicleParkingTags(Set preferredVehicleParkingTags) {
+ this.preferredVehicleParkingTags =
+ List.of(new VehicleParkingSelect.TagsSelect(preferredVehicleParkingTags));
+ return this;
+ }
+
+ public Builder withNotPreferredVehicleParkingTags(Set notPreferredVehicleParkingTags) {
+ this.notPreferredVehicleParkingTags =
+ List.of(new VehicleParkingSelect.TagsSelect(notPreferredVehicleParkingTags));
+ return this;
+ }
+
+ public Builder withParkCost(int cost) {
+ this.parkCost = Cost.costOfSeconds(cost);
+ return this;
+ }
+
+ public Builder withParkTime(int seconds) {
+ this.parkTime = Duration.ofSeconds(seconds);
+ return this;
+ }
+
+ public Builder withParkTime(Duration duration) {
+ this.parkTime = duration;
+ return this;
+ }
+
+ public Builder apply(Consumer body) {
+ body.accept(this);
+ return this;
+ }
+
+ public VehicleParkingPreferences build() {
+ var newObj = new VehicleParkingPreferences(this);
+ return original.equals(newObj) ? original : newObj;
+ }
+ }
+}
diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/filter/VehicleParkingFilter.java b/src/main/java/org/opentripplanner/routing/api/request/preference/filter/VehicleParkingFilter.java
new file mode 100644
index 00000000000..c4f5cee422f
--- /dev/null
+++ b/src/main/java/org/opentripplanner/routing/api/request/preference/filter/VehicleParkingFilter.java
@@ -0,0 +1,94 @@
+package org.opentripplanner.routing.api.request.preference.filter;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import javax.annotation.Nonnull;
+import org.opentripplanner.framework.tostring.ToStringBuilder;
+import org.opentripplanner.routing.vehicle_parking.VehicleParking;
+
+/**
+ * A filter class that checks if parking faclities match certain conditions for
+ * inclusion/exclusion or preference/unpreference.
+ */
+public class VehicleParkingFilter implements Serializable {
+
+ private final VehicleParkingSelect[] not;
+ private final VehicleParkingSelect[] select;
+
+ public VehicleParkingFilter(
+ Collection not,
+ Collection select
+ ) {
+ this.not = makeFilter(not);
+ this.select = makeFilter(select);
+ }
+
+ public VehicleParkingFilter(VehicleParkingSelect not, VehicleParkingSelect select) {
+ this(List.of(not), List.of(select));
+ }
+
+ public List not() {
+ return Arrays.asList(not);
+ }
+
+ public List select() {
+ return Arrays.asList(select);
+ }
+
+ /**
+ * Create a request with no conditions.
+ */
+ public static VehicleParkingFilter empty() {
+ return new VehicleParkingFilter(List.of(), List.of());
+ }
+
+ /**
+ * Checks if a parking facility matches the conditions defined in this filter.
+ */
+ public boolean matches(VehicleParking p) {
+ for (var n : not) {
+ if (n.matches(p)) {
+ return false;
+ }
+ }
+ // not doesn't match and no selects means it matches
+ if (select.length == 0) {
+ return true;
+ }
+ for (var s : select) {
+ if (s.matches(p)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder
+ .of(this.getClass())
+ .addCol("not", Arrays.asList(not))
+ .addCol("select", Arrays.asList(select))
+ .toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ VehicleParkingFilter that = (VehicleParkingFilter) o;
+ return (Arrays.equals(not, that.not) && Arrays.equals(select, that.select));
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(not) + Arrays.hashCode(select);
+ }
+
+ @Nonnull
+ private static VehicleParkingSelect[] makeFilter(Collection select) {
+ return select.stream().filter(f -> !f.isEmpty()).toArray(VehicleParkingSelect[]::new);
+ }
+}
diff --git a/src/main/java/org/opentripplanner/routing/api/request/request/filter/VehicleParkingFilter.java b/src/main/java/org/opentripplanner/routing/api/request/preference/filter/VehicleParkingSelect.java
similarity index 74%
rename from src/main/java/org/opentripplanner/routing/api/request/request/filter/VehicleParkingFilter.java
rename to src/main/java/org/opentripplanner/routing/api/request/preference/filter/VehicleParkingSelect.java
index a4c240e7160..2d3935461d4 100644
--- a/src/main/java/org/opentripplanner/routing/api/request/request/filter/VehicleParkingFilter.java
+++ b/src/main/java/org/opentripplanner/routing/api/request/preference/filter/VehicleParkingSelect.java
@@ -1,4 +1,4 @@
-package org.opentripplanner.routing.api.request.request.filter;
+package org.opentripplanner.routing.api.request.preference.filter;
import java.util.Collections;
import java.util.Set;
@@ -8,18 +8,18 @@
* A set of conditions that can be used to check if a parking facility should be included/excluded
* or preferred/unpreferred.
*/
-public sealed interface VehicleParkingFilter {
+public sealed interface VehicleParkingSelect {
/**
- * Checks if the parking facilities matches the conditions of the filter.
+ * Checks if the parking facilities matches the conditions of the select.
*/
boolean matches(VehicleParking p);
/**
- * Whether this filter defines any condition.
+ * Whether this select defines any condition.
*/
boolean isEmpty();
- record TagsFilter(Set tags) implements VehicleParkingFilter {
+ record TagsSelect(Set tags) implements VehicleParkingSelect {
@Override
public boolean matches(VehicleParking p) {
return !Collections.disjoint(tags, p.getTags());
diff --git a/src/main/java/org/opentripplanner/routing/api/request/request/JourneyRequest.java b/src/main/java/org/opentripplanner/routing/api/request/request/JourneyRequest.java
index b500bdd2398..39a775bf7f5 100644
--- a/src/main/java/org/opentripplanner/routing/api/request/request/JourneyRequest.java
+++ b/src/main/java/org/opentripplanner/routing/api/request/request/JourneyRequest.java
@@ -8,7 +8,6 @@ public class JourneyRequest implements Cloneable, Serializable {
// TODO VIA (Hannes): Move the fields below into StreetRequest
private VehicleRentalRequest rental = new VehicleRentalRequest();
- private VehicleParkingRequest parking = new VehicleParkingRequest();
private TransitRequest transit = new TransitRequest();
private StreetRequest access = new StreetRequest();
private StreetRequest egress = new StreetRequest();
@@ -19,10 +18,6 @@ public VehicleRentalRequest rental() {
return rental;
}
- public VehicleParkingRequest parking() {
- return parking;
- }
-
public TransitRequest transit() {
return transit;
}
@@ -64,7 +59,6 @@ public JourneyRequest clone() {
try {
var clone = (JourneyRequest) super.clone();
clone.rental = this.rental.clone();
- clone.parking = this.parking.clone();
clone.transit = this.transit.clone();
clone.access = this.access.clone();
clone.egress = this.egress.clone();
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 fcb9b4bdabf..67a56249328 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
@@ -31,7 +31,6 @@ public class TransitRequest implements Cloneable, Serializable {
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();
@@ -58,27 +57,12 @@ 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 treated 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.
+ *
+ * Note! Entities that are not matched are put in the BASE-GROUP with id 0.
*/
public List priorityGroupsByAgency() {
return priorityGroupsByAgency;
@@ -93,6 +77,11 @@ public void addPriorityGroupsByAgency(
this.priorityGroupsByAgency.addAll(priorityGroupsByAgency);
}
+ /**
+ * A unique group-id is assigned all patterns grouped by matching selects.
+ *