vehicleParkingToRemove = new ArrayList<>();
for (VehicleParkingEntranceVertex vehicleParkingEntranceVertex : graph.getVerticesOfType(
VehicleParkingEntranceVertex.class
)) {
- if (vehicleParkingEntranceHasLinks(vehicleParkingEntranceVertex)) {
+ if (vehicleParkingEntranceVertex.isLinkedToGraph()) {
continue;
}
@@ -296,22 +292,6 @@ private void linkVehicleParks(Graph graph, DataImportIssueStore issueStore) {
var vehicleParkingService = graph.getVehicleParkingService();
vehicleParkingService.updateVehicleParking(List.of(), vehicleParkingToRemove);
}
- graph.hasLinkedBikeParks = true;
- }
-
- private boolean vehicleParkingEntranceHasLinks(
- VehicleParkingEntranceVertex vehicleParkingEntranceVertex
- ) {
- return !(
- vehicleParkingEntranceVertex
- .getIncoming()
- .stream()
- .allMatch(VehicleParkingEdge.class::isInstance) &&
- vehicleParkingEntranceVertex
- .getOutgoing()
- .stream()
- .allMatch(VehicleParkingEdge.class::isInstance)
- );
}
/**
diff --git a/src/main/java/org/opentripplanner/inspector/vector/KeyValue.java b/src/main/java/org/opentripplanner/inspector/vector/KeyValue.java
index 6c8b0f3aa4e..edad5e1b295 100644
--- a/src/main/java/org/opentripplanner/inspector/vector/KeyValue.java
+++ b/src/main/java/org/opentripplanner/inspector/vector/KeyValue.java
@@ -1,7 +1,43 @@
package org.opentripplanner.inspector.vector;
+import jakarta.annotation.Nullable;
+import java.util.Collection;
+import java.util.stream.Collectors;
+import org.opentripplanner.transit.model.framework.FeedScopedId;
+
+/**
+ * A key value pair that represents data being sent to the vector tile library for visualisation
+ * in a map (including popups).
+ *
+ * The underlying format (and library) supports only a limited number of Java types and silently
+ * drops those that aren't supported: https://github.com/CI-CMG/mapbox-vector-tile/blob/master/src/main/java/edu/colorado/cires/cmg/mvt/encoding/MvtValue.java#L18-L40
+ *
+ * For this reason this class also has static initializer that automatically converts common
+ * OTP classes into vector tile-compatible strings.
+ */
public record KeyValue(String key, Object value) {
public static KeyValue kv(String key, Object value) {
return new KeyValue(key, value);
}
+
+ /**
+ * A {@link FeedScopedId} is not a type that can be converted to a vector tile feature property
+ * value. Therefore, we convert it to a string after performing a null check.
+ */
+ public static KeyValue kv(String key, @Nullable FeedScopedId value) {
+ if (value != null) {
+ return new KeyValue(key, value.toString());
+ } else {
+ return new KeyValue(key, null);
+ }
+ }
+
+ /**
+ * Takes a key and a collection of values, calls toString on the values and joins them using
+ * comma as the separator.
+ */
+ public static KeyValue kColl(String key, Collection> value) {
+ var values = value.stream().map(Object::toString).collect(Collectors.joining(","));
+ return new KeyValue(key, values);
+ }
}
diff --git a/src/main/java/org/opentripplanner/inspector/vector/vertex/VertexPropertyMapper.java b/src/main/java/org/opentripplanner/inspector/vector/vertex/VertexPropertyMapper.java
index a493269cc3b..01f5263b11a 100644
--- a/src/main/java/org/opentripplanner/inspector/vector/vertex/VertexPropertyMapper.java
+++ b/src/main/java/org/opentripplanner/inspector/vector/vertex/VertexPropertyMapper.java
@@ -1,15 +1,22 @@
package org.opentripplanner.inspector.vector.vertex;
+import static org.opentripplanner.inspector.vector.KeyValue.kColl;
import static org.opentripplanner.inspector.vector.KeyValue.kv;
import java.util.Collection;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import org.opentripplanner.apis.support.mapping.PropertyMapper;
import org.opentripplanner.framework.collection.ListUtils;
import org.opentripplanner.inspector.vector.KeyValue;
+import org.opentripplanner.routing.vehicle_parking.VehicleParking;
+import org.opentripplanner.routing.vehicle_parking.VehicleParkingEntrance;
import org.opentripplanner.service.vehiclerental.street.VehicleRentalPlaceVertex;
import org.opentripplanner.street.model.vertex.BarrierVertex;
+import org.opentripplanner.street.model.vertex.VehicleParkingEntranceVertex;
import org.opentripplanner.street.model.vertex.Vertex;
+import org.opentripplanner.street.search.TraverseMode;
public class VertexPropertyMapper extends PropertyMapper {
@@ -22,9 +29,36 @@ protected Collection map(Vertex input) {
List properties =
switch (input) {
case BarrierVertex v -> List.of(kv("permission", v.getBarrierPermissions().toString()));
- case VehicleRentalPlaceVertex v -> List.of(kv("rentalId", v.getStation().getId()));
+ case VehicleRentalPlaceVertex v -> List.of(kv("rentalId", v.getStation()));
+ case VehicleParkingEntranceVertex v -> List.of(
+ kv("parkingId", v.getVehicleParking().getId()),
+ kColl("spacesFor", spacesFor(v.getVehicleParking())),
+ kColl("traversalPermission", traversalPermissions(v.getParkingEntrance()))
+ );
default -> List.of();
};
return ListUtils.combine(baseProps, properties);
}
+
+ private Set spacesFor(VehicleParking vehicleParking) {
+ var ret = new HashSet();
+ if (vehicleParking.hasAnyCarPlaces()) {
+ ret.add(TraverseMode.CAR);
+ }
+ if (vehicleParking.hasBicyclePlaces()) {
+ ret.add(TraverseMode.BICYCLE);
+ }
+ return ret;
+ }
+
+ private Set traversalPermissions(VehicleParkingEntrance entrance) {
+ var ret = new HashSet();
+ if (entrance.isCarAccessible()) {
+ ret.add(TraverseMode.CAR);
+ }
+ if (entrance.isWalkAccessible()) {
+ ret.add(TraverseMode.WALK);
+ }
+ return ret;
+ }
}
diff --git a/src/main/java/org/opentripplanner/model/TimetableSnapshot.java b/src/main/java/org/opentripplanner/model/TimetableSnapshot.java
index d7d9385d9b8..c0a7737abce 100644
--- a/src/main/java/org/opentripplanner/model/TimetableSnapshot.java
+++ b/src/main/java/org/opentripplanner/model/TimetableSnapshot.java
@@ -65,6 +65,11 @@
* up timetables on this class could conceivably be replaced with snapshotting entire views of the
* transit network. It would also be possible to make the realtime version of Timetables or
* TripTimes the primary view, and include references back to their scheduled versions.
+ *
+ * Implementation note: when a snapshot is committed, the mutable state of this class is stored
+ * in final fields and completely initialized in the constructor. This provides an additional
+ * guarantee of safe-publication without synchronization.
+ * (see final Field Semantics)
*/
public class TimetableSnapshot {
@@ -93,7 +98,7 @@ public class TimetableSnapshot {
* The compound key approach better reflects the fact that there should be only one Timetable per
* TripPattern and date.
*/
- private Map> timetables = new HashMap<>();
+ private final Map> timetables;
/**
* For cases where the trip pattern (sequence of stops visited) has been changed by a realtime
@@ -101,7 +106,7 @@ public class TimetableSnapshot {
* trip ID and the service date.
* TODO RT_AB: clarify if this is an index or the original source of truth.
*/
- private Map realtimeAddedTripPattern = new HashMap<>();
+ private final Map realtimeAddedTripPattern;
/**
* This is an index of TripPatterns, not the primary collection. It tracks which TripPatterns
@@ -111,13 +116,13 @@ public class TimetableSnapshot {
* more than once.
* TODO RT_AB: More general handling of all realtime indexes outside primary data structures.
*/
- private SetMultimap patternsForStop = HashMultimap.create();
+ private final SetMultimap patternsForStop;
/**
* Boolean value indicating that timetable snapshot is read only if true. Once it is true, it
* shouldn't be possible to change it to false anymore.
*/
- private boolean readOnly = false;
+ private final boolean readOnly;
/**
* Boolean value indicating that this timetable snapshot contains changes compared to the state of
@@ -125,6 +130,22 @@ public class TimetableSnapshot {
*/
private boolean dirty = false;
+ public TimetableSnapshot() {
+ this(new HashMap<>(), new HashMap<>(), HashMultimap.create(), false);
+ }
+
+ private TimetableSnapshot(
+ Map> timetables,
+ Map realtimeAddedTripPattern,
+ SetMultimap patternsForStop,
+ boolean readOnly
+ ) {
+ this.timetables = timetables;
+ this.realtimeAddedTripPattern = realtimeAddedTripPattern;
+ this.patternsForStop = patternsForStop;
+ this.readOnly = readOnly;
+ }
+
/**
* Returns an updated timetable for the specified pattern if one is available in this snapshot, or
* the originally scheduled timetable if there are no updates in this snapshot.
@@ -235,12 +256,15 @@ public TimetableSnapshot commit(TransitLayerUpdater transitLayerUpdater, boolean
throw new ConcurrentModificationException("This TimetableSnapshot is read-only.");
}
- TimetableSnapshot ret = new TimetableSnapshot();
if (!force && !this.isDirty()) {
return null;
}
- ret.timetables = Map.copyOf(timetables);
- ret.realtimeAddedTripPattern = Map.copyOf(realtimeAddedTripPattern);
+ TimetableSnapshot ret = new TimetableSnapshot(
+ Map.copyOf(timetables),
+ Map.copyOf(realtimeAddedTripPattern),
+ ImmutableSetMultimap.copyOf(patternsForStop),
+ true
+ );
if (transitLayerUpdater != null) {
transitLayerUpdater.update(dirtyTimetables, timetables);
@@ -249,9 +273,6 @@ public TimetableSnapshot commit(TransitLayerUpdater transitLayerUpdater, boolean
this.dirtyTimetables.clear();
this.dirty = false;
- ret.patternsForStop = ImmutableSetMultimap.copyOf(patternsForStop);
-
- ret.readOnly = true; // mark the snapshot as henceforth immutable
return ret;
}
diff --git a/src/main/java/org/opentripplanner/model/impl/OtpTransitServiceBuilder.java b/src/main/java/org/opentripplanner/model/impl/OtpTransitServiceBuilder.java
index 43c18cec59d..373b99f0bc6 100644
--- a/src/main/java/org/opentripplanner/model/impl/OtpTransitServiceBuilder.java
+++ b/src/main/java/org/opentripplanner/model/impl/OtpTransitServiceBuilder.java
@@ -24,6 +24,7 @@
import org.opentripplanner.model.transfer.ConstrainedTransfer;
import org.opentripplanner.model.transfer.TransferPoint;
import org.opentripplanner.routing.api.request.framework.TimePenalty;
+import org.opentripplanner.routing.vehicle_parking.VehicleParking;
import org.opentripplanner.transit.model.basic.Notice;
import org.opentripplanner.transit.model.framework.AbstractTransitEntity;
import org.opentripplanner.transit.model.framework.DefaultEntityById;
@@ -116,6 +117,8 @@ public class OtpTransitServiceBuilder {
private final EntityById groupOfRouteById = new DefaultEntityById<>();
+ private final List vehicleParkings = new ArrayList<>();
+
private final DataImportIssueStore issueStore;
public OtpTransitServiceBuilder(StopModel stopModel, DataImportIssueStore issueStore) {
@@ -264,6 +267,14 @@ public CalendarServiceData buildCalendarServiceData() {
);
}
+ /**
+ * The list of parking lots contained in the transit data (so far only NeTEx).
+ * Note that parking lots can also be sourced from OSM data as well as realtime updaters.
+ */
+ public List vehicleParkings() {
+ return vehicleParkings;
+ }
+
public OtpTransitService build() {
return new OtpTransitServiceImpl(this);
}
diff --git a/src/main/java/org/opentripplanner/model/plan/Itinerary.java b/src/main/java/org/opentripplanner/model/plan/Itinerary.java
index cb14227e83d..dee80addd91 100644
--- a/src/main/java/org/opentripplanner/model/plan/Itinerary.java
+++ b/src/main/java/org/opentripplanner/model/plan/Itinerary.java
@@ -177,11 +177,6 @@ public Leg lastLeg() {
return getLegs().get(getLegs().size() - 1);
}
- /** Get the first transit leg if one exist */
- public Optional firstTransitLeg() {
- return getLegs().stream().filter(TransitLeg.class::isInstance).findFirst();
- }
-
/**
* An itinerary can be flagged for removal with a system notice.
*
@@ -225,105 +220,6 @@ public Itinerary withTimeShiftToStartAt(ZonedDateTime afterTime) {
return newItin;
}
- /** @see #equals(Object) */
- @Override
- public final int hashCode() {
- return super.hashCode();
- }
-
- /**
- * Return {@code true} it the other object is the same object using the {@link
- * Object#equals(Object)}. An itinerary is a temporary object and the equals method should not be
- * used for comparision of 2 instances, only to check that to objects are the same instance.
- */
- @Override
- public final boolean equals(Object o) {
- return super.equals(o);
- }
-
- /**
- * Used to convert a list of itineraries to a SHORT human-readable string.
- *
- * @see #toStr()
- *
- * It is great for comparing lists of itineraries in a test: {@code
- * assertEquals(toStr(List.of(it1)), toStr(result))}.
- */
- public static String toStr(List list) {
- return list.stream().map(Itinerary::toStr).collect(Collectors.joining(", "));
- }
-
- @Override
- public String toString() {
- return ToStringBuilder
- .of(Itinerary.class)
- .addStr("from", firstLeg().getFrom().toStringShort())
- .addStr("to", lastLeg().getTo().toStringShort())
- .addTime("start", firstLeg().getStartTime())
- .addTime("end", lastLeg().getEndTime())
- .addNum("nTransfers", numberOfTransfers)
- .addDuration("duration", duration)
- .addDuration("nonTransitTime", nonTransitDuration)
- .addDuration("transitTime", transitDuration)
- .addDuration("waitingTime", waitingDuration)
- .addNum("generalizedCost", generalizedCost, UNKNOWN)
- .addNum("generalizedCost2", generalizedCost2)
- .addNum("waitTimeOptimizedCost", waitTimeOptimizedCost, UNKNOWN)
- .addNum("transferPriorityCost", transferPriorityCost, UNKNOWN)
- .addNum("nonTransitDistance", nonTransitDistanceMeters, "m")
- .addBool("tooSloped", tooSloped)
- .addNum("elevationLost", elevationLost, 0.0)
- .addNum("elevationGained", elevationGained, 0.0)
- .addCol("legs", legs)
- .addObj("fare", fare)
- .addObj("emissionsPerPerson", emissionsPerPerson)
- .toString();
- }
-
- /**
- * Used to convert an itinerary to a SHORT human readable string - including just a few of the
- * most important fields. It is much shorter and easier to read then the {@link
- * Itinerary#toString()}.
- *
- * It is great for comparing to itineraries in a test: {@code assertEquals(toStr(it1),
- * toStr(it2))}.
- *
- * Example: {@code A ~ Walk 2m ~ B ~ BUS 55 12:04 12:14 ~ C [cost: 1066]}
- *
- * Reads: Start at A, walk 2 minutes to stop B, take bus 55, board at 12:04 and alight at 12:14
- * ...
- */
- public String toStr() {
- // No translater needed, stop indexes are never passed to the builder
- PathStringBuilder buf = new PathStringBuilder(null);
- buf.stop(firstLeg().getFrom().name.toString());
-
- for (Leg leg : legs) {
- if (leg.isWalkingLeg()) {
- buf.walk((int) leg.getDuration().toSeconds());
- } else if (leg instanceof TransitLeg transitLeg) {
- buf.transit(
- transitLeg.getMode().name(),
- transitLeg.getTrip().logName(),
- transitLeg.getStartTime(),
- transitLeg.getEndTime()
- );
- } else if (leg instanceof StreetLeg streetLeg) {
- buf.street(streetLeg.getMode().name(), leg.getStartTime(), leg.getEndTime());
- }
- buf.stop(leg.getTo().name.toString());
- }
-
- // The generalizedCost2 is printed as is, it is a special cost and the scale depends on the
- // use-case.
- buf.summary(
- RaptorCostConverter.toRaptorCost(generalizedCost),
- getGeneralizedCost2().orElse(RaptorConstants.NOT_SET)
- );
-
- return buf.toString();
- }
-
/** Total duration of the itinerary in seconds */
public Duration getDuration() {
return duration;
@@ -698,6 +594,105 @@ public Duration walkDuration() {
return walkDuration;
}
+ /** @see #equals(Object) */
+ @Override
+ public final int hashCode() {
+ return super.hashCode();
+ }
+
+ /**
+ * Return {@code true} it the other object is the same object using the {@link
+ * Object#equals(Object)}. An itinerary is a temporary object and the equals method should not be
+ * used for comparision of 2 instances, only to check that to objects are the same instance.
+ */
+ @Override
+ public final boolean equals(Object o) {
+ return super.equals(o);
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder
+ .of(Itinerary.class)
+ .addStr("from", firstLeg().getFrom().toStringShort())
+ .addStr("to", lastLeg().getTo().toStringShort())
+ .addTime("start", firstLeg().getStartTime())
+ .addTime("end", lastLeg().getEndTime())
+ .addNum("nTransfers", numberOfTransfers)
+ .addDuration("duration", duration)
+ .addDuration("nonTransitTime", nonTransitDuration)
+ .addDuration("transitTime", transitDuration)
+ .addDuration("waitingTime", waitingDuration)
+ .addNum("generalizedCost", generalizedCost, UNKNOWN)
+ .addNum("generalizedCost2", generalizedCost2)
+ .addNum("waitTimeOptimizedCost", waitTimeOptimizedCost, UNKNOWN)
+ .addNum("transferPriorityCost", transferPriorityCost, UNKNOWN)
+ .addNum("nonTransitDistance", nonTransitDistanceMeters, "m")
+ .addBool("tooSloped", tooSloped)
+ .addNum("elevationLost", elevationLost, 0.0)
+ .addNum("elevationGained", elevationGained, 0.0)
+ .addCol("legs", legs)
+ .addObj("fare", fare)
+ .addObj("emissionsPerPerson", emissionsPerPerson)
+ .toString();
+ }
+
+ /**
+ * Used to convert a list of itineraries to a SHORT human-readable string.
+ *
+ * @see #toStr()
+ *
+ * It is great for comparing lists of itineraries in a test: {@code
+ * assertEquals(toStr(List.of(it1)), toStr(result))}.
+ */
+ public static String toStr(List list) {
+ return list.stream().map(Itinerary::toStr).collect(Collectors.joining(", "));
+ }
+
+ /**
+ * Used to convert an itinerary to a SHORT human readable string - including just a few of the
+ * most important fields. It is much shorter and easier to read then the {@link
+ * Itinerary#toString()}.
+ *
+ * It is great for comparing to itineraries in a test: {@code assertEquals(toStr(it1),
+ * toStr(it2))}.
+ *
+ * Example: {@code A ~ Walk 2m ~ B ~ BUS 55 12:04 12:14 ~ C [cost: 1066]}
+ *
+ * Reads: Start at A, walk 2 minutes to stop B, take bus 55, board at 12:04 and alight at 12:14
+ * ...
+ */
+ public String toStr() {
+ // No translater needed, stop indexes are never passed to the builder
+ PathStringBuilder buf = new PathStringBuilder(null);
+ buf.stop(firstLeg().getFrom().name.toString());
+
+ for (Leg leg : legs) {
+ if (leg.isWalkingLeg()) {
+ buf.walk((int) leg.getDuration().toSeconds());
+ } else if (leg instanceof TransitLeg transitLeg) {
+ buf.transit(
+ transitLeg.getMode().name(),
+ transitLeg.getTrip().logName(),
+ transitLeg.getStartTime(),
+ transitLeg.getEndTime()
+ );
+ } else if (leg instanceof StreetLeg streetLeg) {
+ buf.street(streetLeg.getMode().name(), leg.getStartTime(), leg.getEndTime());
+ }
+ buf.stop(leg.getTo().name.toString());
+ }
+
+ // The generalizedCost2 is printed as is, it is a special cost and the scale depends on the
+ // use-case.
+ buf.summary(
+ RaptorCostConverter.toRaptorCost(generalizedCost),
+ getGeneralizedCost2().orElse(RaptorConstants.NOT_SET)
+ );
+
+ return buf.toString();
+ }
+
private static int penaltyCost(TimeAndCost penalty) {
return penalty.cost().toSeconds();
}
diff --git a/src/main/java/org/opentripplanner/model/plan/Leg.java b/src/main/java/org/opentripplanner/model/plan/Leg.java
index 1ee72761d66..2a0b6726560 100644
--- a/src/main/java/org/opentripplanner/model/plan/Leg.java
+++ b/src/main/java/org/opentripplanner/model/plan/Leg.java
@@ -184,6 +184,7 @@ default Route getRoute() {
/**
* For transit legs, the trip. For non-transit legs, null.
*/
+ @Nullable
default Trip getTrip() {
return null;
}
diff --git a/src/main/java/org/opentripplanner/model/plan/grouppriority/TransitGroupPriorityItineraryDecorator.java b/src/main/java/org/opentripplanner/model/plan/grouppriority/TransitGroupPriorityItineraryDecorator.java
new file mode 100644
index 00000000000..9c89e9d4ca8
--- /dev/null
+++ b/src/main/java/org/opentripplanner/model/plan/grouppriority/TransitGroupPriorityItineraryDecorator.java
@@ -0,0 +1,49 @@
+package org.opentripplanner.model.plan.grouppriority;
+
+import java.util.Collection;
+import org.opentripplanner.model.plan.Itinerary;
+import org.opentripplanner.model.plan.Leg;
+import org.opentripplanner.raptor.api.request.RaptorTransitGroupPriorityCalculator;
+import org.opentripplanner.transit.model.network.grouppriority.DefaultTransitGroupPriorityCalculator;
+import org.opentripplanner.transit.model.network.grouppriority.TransitGroupPriorityService;
+
+/**
+ * This class will set the {@link Itinerary#getGeneralizedCost2()} value if the feature is
+ * enabled and no such value is set. The AStar router does not produce itineraries with this,
+ * so we decorate itineraries with this here to make sure the `c2` is set correct and can be
+ * used in the itinerary-filter-chain.
+ */
+public class TransitGroupPriorityItineraryDecorator {
+
+ private final TransitGroupPriorityService priorityGroupConfigurator;
+ private final RaptorTransitGroupPriorityCalculator transitGroupCalculator;
+
+ public TransitGroupPriorityItineraryDecorator(
+ TransitGroupPriorityService priorityGroupConfigurator
+ ) {
+ this.priorityGroupConfigurator = priorityGroupConfigurator;
+ this.transitGroupCalculator = new DefaultTransitGroupPriorityCalculator();
+ }
+
+ public void decorate(Collection itineraries) {
+ if (!priorityGroupConfigurator.isEnabled()) {
+ return;
+ }
+ for (Itinerary it : itineraries) {
+ decorate(it);
+ }
+ }
+
+ public void decorate(Itinerary itinerary) {
+ if (itinerary.getGeneralizedCost2().isEmpty() && priorityGroupConfigurator.isEnabled()) {
+ int c2 = priorityGroupConfigurator.baseGroupId();
+ for (Leg leg : itinerary.getLegs()) {
+ if (leg.getTrip() != null) {
+ int newGroupId = priorityGroupConfigurator.lookupTransitGroupPriorityId(leg.getTrip());
+ c2 = transitGroupCalculator.mergeInGroupId(c2, newGroupId);
+ }
+ }
+ itinerary.setGeneralizedCost2(c2);
+ }
+ }
+}
diff --git a/src/main/java/org/opentripplanner/netex/NetexBundle.java b/src/main/java/org/opentripplanner/netex/NetexBundle.java
index 8d6f098de89..3cd52cd246e 100644
--- a/src/main/java/org/opentripplanner/netex/NetexBundle.java
+++ b/src/main/java/org/opentripplanner/netex/NetexBundle.java
@@ -9,6 +9,7 @@
import org.opentripplanner.datastore.api.DataSource;
import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore;
import org.opentripplanner.model.impl.OtpTransitServiceBuilder;
+import org.opentripplanner.netex.config.IgnorableFeature;
import org.opentripplanner.netex.config.NetexFeedParameters;
import org.opentripplanner.netex.index.NetexEntityIndex;
import org.opentripplanner.netex.loader.GroupEntries;
@@ -45,7 +46,7 @@ public class NetexBundle implements Closeable {
private final Set ferryIdsNotAllowedForBicycle;
private final double maxStopToShapeSnapDistance;
private final boolean noTransfersOnIsolatedStops;
- private final boolean ignoreFareFrame;
+ private final Set ignoredFeatures;
/** The NeTEx entities loaded from the input files and passed on to the mapper. */
private NetexEntityIndex index = new NetexEntityIndex();
/** Report errors to issue store */
@@ -62,7 +63,7 @@ public NetexBundle(
Set ferryIdsNotAllowedForBicycle,
double maxStopToShapeSnapDistance,
boolean noTransfersOnIsolatedStops,
- boolean ignoreFareFrame
+ Set ignorableFeatures
) {
this.feedId = feedId;
this.source = source;
@@ -71,7 +72,7 @@ public NetexBundle(
this.ferryIdsNotAllowedForBicycle = ferryIdsNotAllowedForBicycle;
this.maxStopToShapeSnapDistance = maxStopToShapeSnapDistance;
this.noTransfersOnIsolatedStops = noTransfersOnIsolatedStops;
- this.ignoreFareFrame = ignoreFareFrame;
+ this.ignoredFeatures = Set.copyOf(ignorableFeatures);
}
/** load the bundle, map it to the OTP transit model and return */
@@ -136,7 +137,7 @@ private void loadFileEntries() {
});
}
mapper.finishUp();
- NetexDocumentParser.finnishUp();
+ NetexDocumentParser.finishUp();
}
/**
@@ -179,7 +180,7 @@ private void loadSingeFileEntry(String fileDescription, DataSource entry) {
LOG.info("reading entity {}: {}", fileDescription, entry.name());
issueStore.startProcessingSource(entry.name());
PublicationDeliveryStructure doc = xmlParser.parseXmlDoc(entry.asInputStream());
- NetexDocumentParser.parseAndPopulateIndex(index, doc, ignoreFareFrame);
+ NetexDocumentParser.parseAndPopulateIndex(index, doc, ignoredFeatures);
} catch (JAXBException e) {
throw new RuntimeException(e.getMessage(), e);
} finally {
diff --git a/src/main/java/org/opentripplanner/netex/NetexModule.java b/src/main/java/org/opentripplanner/netex/NetexModule.java
index b9a05d25b10..2bf3403395c 100644
--- a/src/main/java/org/opentripplanner/netex/NetexModule.java
+++ b/src/main/java/org/opentripplanner/netex/NetexModule.java
@@ -13,6 +13,7 @@
import org.opentripplanner.model.calendar.ServiceDateInterval;
import org.opentripplanner.model.impl.OtpTransitServiceBuilder;
import org.opentripplanner.routing.graph.Graph;
+import org.opentripplanner.routing.vehicle_parking.VehicleParkingHelper;
import org.opentripplanner.standalone.config.BuildConfig;
import org.opentripplanner.transit.service.TransitModel;
@@ -100,6 +101,11 @@ public void buildGraph() {
);
transitModel.validateTimeZones();
+
+ var lots = transitBuilder.vehicleParkings();
+ graph.getVehicleParkingService().updateVehicleParking(lots, List.of());
+ var linker = new VehicleParkingHelper(graph);
+ lots.forEach(linker::linkVehicleParkingToGraph);
}
transitModel.updateCalendarServiceData(hasActiveTransit, calendarServiceData, issueStore);
diff --git a/src/main/java/org/opentripplanner/netex/config/IgnorableFeature.java b/src/main/java/org/opentripplanner/netex/config/IgnorableFeature.java
new file mode 100644
index 00000000000..53fe7f87f48
--- /dev/null
+++ b/src/main/java/org/opentripplanner/netex/config/IgnorableFeature.java
@@ -0,0 +1,9 @@
+package org.opentripplanner.netex.config;
+
+/**
+ * Optional data that can be ignored during the NeTEx parsing process.
+ */
+public enum IgnorableFeature {
+ FARE_FRAME,
+ PARKING,
+}
diff --git a/src/main/java/org/opentripplanner/netex/config/NetexFeedParameters.java b/src/main/java/org/opentripplanner/netex/config/NetexFeedParameters.java
index cffecea0d48..0c6c75c4db3 100644
--- a/src/main/java/org/opentripplanner/netex/config/NetexFeedParameters.java
+++ b/src/main/java/org/opentripplanner/netex/config/NetexFeedParameters.java
@@ -1,6 +1,8 @@
package org.opentripplanner.netex.config;
import static java.util.Objects.requireNonNull;
+import static org.opentripplanner.netex.config.IgnorableFeature.FARE_FRAME;
+import static org.opentripplanner.netex.config.IgnorableFeature.PARKING;
import java.net.URI;
import java.util.Collection;
@@ -29,7 +31,7 @@ public class NetexFeedParameters implements DataSourceConfig {
private static final String SHARED_GROUP_FILE_PATTERN = "(\\w{3})-.*-shared\\.xml";
private static final String GROUP_FILE_PATTERN = "(\\w{3})-.*\\.xml";
private static final boolean NO_TRANSFERS_ON_ISOLATED_STOPS = false;
- private static final boolean IGNORE_FARE_FRAME = false;
+ private static final Set IGNORED_FEATURES = Set.of(PARKING);
private static final Set FERRY_IDS_NOT_ALLOWED_FOR_BICYCLE = Collections.emptySet();
@@ -48,7 +50,7 @@ public class NetexFeedParameters implements DataSourceConfig {
private final String ignoreFilePattern;
private final Set ferryIdsNotAllowedForBicycle;
private final boolean noTransfersOnIsolatedStops;
- private final boolean ignoreFareFrame;
+ private final Set ignoredFeatures;
private NetexFeedParameters() {
this.source = null;
@@ -63,7 +65,7 @@ private NetexFeedParameters() {
}
this.ferryIdsNotAllowedForBicycle = FERRY_IDS_NOT_ALLOWED_FOR_BICYCLE;
this.noTransfersOnIsolatedStops = NO_TRANSFERS_ON_ISOLATED_STOPS;
- this.ignoreFareFrame = IGNORE_FARE_FRAME;
+ this.ignoredFeatures = IGNORED_FEATURES;
}
private NetexFeedParameters(Builder builder) {
@@ -75,7 +77,7 @@ private NetexFeedParameters(Builder builder) {
this.ignoreFilePattern = requireNonNull(builder.ignoreFilePattern);
this.ferryIdsNotAllowedForBicycle = Set.copyOf(builder.ferryIdsNotAllowedForBicycle);
this.noTransfersOnIsolatedStops = builder.noTransfersOnIsolatedStops;
- this.ignoreFareFrame = builder.ignoreFareFrame;
+ this.ignoredFeatures = Set.copyOf(builder.ignoredFeatures);
}
public static Builder of() {
@@ -127,7 +129,11 @@ public boolean noTransfersOnIsolatedStops() {
/** See {@link org.opentripplanner.standalone.config.buildconfig.NetexConfig}. */
public boolean ignoreFareFrame() {
- return ignoreFareFrame;
+ return ignoredFeatures.contains(FARE_FRAME);
+ }
+
+ public boolean ignoreParking() {
+ return ignoredFeatures.contains(PARKING);
}
@Override
@@ -142,7 +148,7 @@ public boolean equals(Object o) {
sharedFilePattern.equals(that.sharedFilePattern) &&
sharedGroupFilePattern.equals(that.sharedGroupFilePattern) &&
groupFilePattern.equals(that.groupFilePattern) &&
- ignoreFareFrame == that.ignoreFareFrame &&
+ ignoredFeatures.equals(that.ignoredFeatures) &&
ferryIdsNotAllowedForBicycle.equals(that.ferryIdsNotAllowedForBicycle)
);
}
@@ -156,7 +162,7 @@ public int hashCode() {
sharedFilePattern,
sharedGroupFilePattern,
groupFilePattern,
- ignoreFareFrame,
+ ignoredFeatures,
ferryIdsNotAllowedForBicycle
);
}
@@ -171,11 +177,15 @@ public String toString() {
.addStr("sharedGroupFilePattern", sharedGroupFilePattern, DEFAULT.sharedGroupFilePattern)
.addStr("groupFilePattern", groupFilePattern, DEFAULT.groupFilePattern)
.addStr("ignoreFilePattern", ignoreFilePattern, DEFAULT.ignoreFilePattern)
- .addBoolIfTrue("ignoreFareFrame", ignoreFareFrame)
+ .addCol("ignoredFeatures", ignoredFeatures)
.addCol("ferryIdsNotAllowedForBicycle", ferryIdsNotAllowedForBicycle, Set.of())
.toString();
}
+ public Set ignoredFeatures() {
+ return ignoredFeatures;
+ }
+
public static class Builder {
private final NetexFeedParameters original;
@@ -187,7 +197,7 @@ public static class Builder {
private String ignoreFilePattern;
private final Set ferryIdsNotAllowedForBicycle = new HashSet<>();
private boolean noTransfersOnIsolatedStops;
- private boolean ignoreFareFrame;
+ private final Set ignoredFeatures;
private Builder(NetexFeedParameters original) {
this.original = original;
@@ -199,7 +209,7 @@ private Builder(NetexFeedParameters original) {
this.ignoreFilePattern = original.ignoreFilePattern;
this.ferryIdsNotAllowedForBicycle.addAll(original.ferryIdsNotAllowedForBicycle);
this.noTransfersOnIsolatedStops = original.noTransfersOnIsolatedStops;
- this.ignoreFareFrame = original.ignoreFareFrame;
+ this.ignoredFeatures = new HashSet<>(original.ignoredFeatures);
}
public URI source() {
@@ -247,7 +257,19 @@ public Builder withNoTransfersOnIsolatedStops(boolean noTransfersOnIsolatedStops
}
public Builder withIgnoreFareFrame(boolean ignoreFareFrame) {
- this.ignoreFareFrame = ignoreFareFrame;
+ return applyIgnore(ignoreFareFrame, FARE_FRAME);
+ }
+
+ public Builder withIgnoreParking(boolean ignoreParking) {
+ return applyIgnore(ignoreParking, PARKING);
+ }
+
+ private Builder applyIgnore(boolean ignore, IgnorableFeature feature) {
+ if (ignore) {
+ ignoredFeatures.add(feature);
+ } else {
+ ignoredFeatures.remove(feature);
+ }
return this;
}
diff --git a/src/main/java/org/opentripplanner/netex/configure/NetexConfigure.java b/src/main/java/org/opentripplanner/netex/configure/NetexConfigure.java
index 464c03f28e1..50c49836246 100644
--- a/src/main/java/org/opentripplanner/netex/configure/NetexConfigure.java
+++ b/src/main/java/org/opentripplanner/netex/configure/NetexConfigure.java
@@ -75,7 +75,7 @@ public NetexBundle netexBundle(
config.ferryIdsNotAllowedForBicycle(),
buildParams.maxStopToShapeSnapDistance,
config.noTransfersOnIsolatedStops(),
- config.ignoreFareFrame()
+ config.ignoredFeatures()
);
}
diff --git a/src/main/java/org/opentripplanner/netex/index/NetexEntityIndex.java b/src/main/java/org/opentripplanner/netex/index/NetexEntityIndex.java
index 0ca734d5b6a..0cb94f66a77 100644
--- a/src/main/java/org/opentripplanner/netex/index/NetexEntityIndex.java
+++ b/src/main/java/org/opentripplanner/netex/index/NetexEntityIndex.java
@@ -28,6 +28,7 @@
import org.rutebanken.netex.model.OperatingDay;
import org.rutebanken.netex.model.OperatingPeriod_VersionStructure;
import org.rutebanken.netex.model.Operator;
+import org.rutebanken.netex.model.Parking;
import org.rutebanken.netex.model.Quay;
import org.rutebanken.netex.model.Route;
import org.rutebanken.netex.model.ServiceJourney;
@@ -97,8 +98,9 @@ public class NetexEntityIndex {
public final HierarchicalVersionMapById stopPlaceById;
public final HierarchicalVersionMapById tariffZonesById;
public final HierarchicalMapById brandingById;
+ public final HierarchicalMapById parkings;
- // Relations between entities - The Netex XML sometimes rely on the the
+ // Relations between entities - The Netex XML sometimes relies on the
// nested structure of the XML document, rater than explicit references.
// Since we throw away the document we need to keep track of these.
@@ -142,6 +144,7 @@ public NetexEntityIndex() {
this.tariffZonesById = new HierarchicalVersionMapById<>();
this.brandingById = new HierarchicalMapById<>();
this.timeZone = new HierarchicalElement<>();
+ this.parkings = new HierarchicalMapById<>();
}
/**
@@ -184,6 +187,7 @@ public NetexEntityIndex(NetexEntityIndex parent) {
this.tariffZonesById = new HierarchicalVersionMapById<>(parent.tariffZonesById);
this.brandingById = new HierarchicalMapById<>(parent.brandingById);
this.timeZone = new HierarchicalElement<>(parent.timeZone);
+ this.parkings = new HierarchicalMapById<>(parent.parkings);
}
/**
@@ -353,6 +357,11 @@ public ReadOnlyHierarchicalVersionMapById getStopPlaceById() {
return stopPlaceById;
}
+ @Override
+ public ReadOnlyHierarchicalMapById getParkingsById() {
+ return parkings;
+ }
+
@Override
public ReadOnlyHierarchicalVersionMapById getTariffZonesById() {
return tariffZonesById;
diff --git a/src/main/java/org/opentripplanner/netex/index/api/NetexEntityIndexReadOnlyView.java b/src/main/java/org/opentripplanner/netex/index/api/NetexEntityIndexReadOnlyView.java
index 3c7bc98b36a..37b8e9790b9 100644
--- a/src/main/java/org/opentripplanner/netex/index/api/NetexEntityIndexReadOnlyView.java
+++ b/src/main/java/org/opentripplanner/netex/index/api/NetexEntityIndexReadOnlyView.java
@@ -19,6 +19,7 @@
import org.rutebanken.netex.model.OperatingDay;
import org.rutebanken.netex.model.OperatingPeriod_VersionStructure;
import org.rutebanken.netex.model.Operator;
+import org.rutebanken.netex.model.Parking;
import org.rutebanken.netex.model.Quay;
import org.rutebanken.netex.model.Route;
import org.rutebanken.netex.model.ServiceJourney;
@@ -80,6 +81,8 @@ public interface NetexEntityIndexReadOnlyView {
ReadOnlyHierarchicalVersionMapById getStopPlaceById();
+ ReadOnlyHierarchicalMapById getParkingsById();
+
ReadOnlyHierarchicalVersionMapById getTariffZonesById();
ReadOnlyHierarchicalMapById getBrandingById();
diff --git a/src/main/java/org/opentripplanner/netex/loader/parser/NetexDocumentParser.java b/src/main/java/org/opentripplanner/netex/loader/parser/NetexDocumentParser.java
index 925b6dfd019..f8058f2df8e 100644
--- a/src/main/java/org/opentripplanner/netex/loader/parser/NetexDocumentParser.java
+++ b/src/main/java/org/opentripplanner/netex/loader/parser/NetexDocumentParser.java
@@ -1,8 +1,12 @@
package org.opentripplanner.netex.loader.parser;
+import static org.opentripplanner.netex.config.IgnorableFeature.FARE_FRAME;
+
import jakarta.xml.bind.JAXBElement;
import java.util.Collection;
import java.util.List;
+import java.util.Set;
+import org.opentripplanner.netex.config.IgnorableFeature;
import org.opentripplanner.netex.index.NetexEntityIndex;
import org.rutebanken.netex.model.Common_VersionFrameStructure;
import org.rutebanken.netex.model.CompositeFrame;
@@ -30,11 +34,11 @@ public class NetexDocumentParser {
private static final Logger LOG = LoggerFactory.getLogger(NetexDocumentParser.class);
private final NetexEntityIndex netexIndex;
- private final boolean ignoreFareFrame;
+ private final Set ignoredFeatures;
- private NetexDocumentParser(NetexEntityIndex netexIndex, boolean ignoreFareFrame) {
+ private NetexDocumentParser(NetexEntityIndex netexIndex, Set ignoredFeatures) {
this.netexIndex = netexIndex;
- this.ignoreFareFrame = ignoreFareFrame;
+ this.ignoredFeatures = ignoredFeatures;
}
/**
@@ -44,12 +48,12 @@ private NetexDocumentParser(NetexEntityIndex netexIndex, boolean ignoreFareFrame
public static void parseAndPopulateIndex(
NetexEntityIndex index,
PublicationDeliveryStructure doc,
- boolean ignoreFareFrame
+ Set ignoredFeatures
) {
- new NetexDocumentParser(index, ignoreFareFrame).parse(doc);
+ new NetexDocumentParser(index, ignoredFeatures).parse(doc);
}
- public static void finnishUp() {
+ public static void finishUp() {
ServiceFrameParser.logSummary();
}
@@ -74,8 +78,8 @@ private void parseCommonFrame(Common_VersionFrameStructure value) {
} else if (value instanceof ServiceFrame) {
parse((ServiceFrame) value, new ServiceFrameParser(netexIndex.flexibleStopPlaceById));
} else if (value instanceof SiteFrame) {
- parse((SiteFrame) value, new SiteFrameParser());
- } else if (!ignoreFareFrame && value instanceof FareFrame) {
+ parse((SiteFrame) value, new SiteFrameParser(ignoredFeatures));
+ } else if (!ignoredFeatures.contains(FARE_FRAME) && value instanceof FareFrame) {
parse((FareFrame) value, new FareFrameParser());
} else if (value instanceof CompositeFrame) {
// We recursively parse composite frames and content until there
diff --git a/src/main/java/org/opentripplanner/netex/loader/parser/NetexParser.java b/src/main/java/org/opentripplanner/netex/loader/parser/NetexParser.java
index 54b0043e072..3c24562ef6f 100644
--- a/src/main/java/org/opentripplanner/netex/loader/parser/NetexParser.java
+++ b/src/main/java/org/opentripplanner/netex/loader/parser/NetexParser.java
@@ -16,7 +16,7 @@
abstract class NetexParser {
/**
- * Currently a lot of elements on a frame is skipped. If any of these elements are pressent we
+ * Currently a lot of elements on a frame is skipped. If any of these elements are present we
* print a warning for elements that might be relevant for OTP and an info message for none
* relevant elements.
*/
@@ -39,10 +39,10 @@ static void verifyCommonUnusedPropertiesIsNotSet(Logger log, VersionFrame_Versio
/**
* Log a warning for Netex elements which is not mapped. There might be something wrong with the
* data or there might be something wrong with the Netex data import(ignoring these elements). The
- * element should be relevant to OTP. OTP do not support Netex 100%, but elements in Nordic
+ * element should be relevant to OTP. OTP does not support NeTEx 100%, but elements in the Nordic
* profile, see https://enturas.atlassian.net/wiki/spaces/PUBLIC/overview should be supported.
*
- * If you get this warning and think the element should be mapped, please feel free to report an
+ * If you see this warning and think the element should be mapped, please feel free to report an
* issue on GitHub.
*/
static void warnOnMissingMapping(Logger log, Object rel) {
diff --git a/src/main/java/org/opentripplanner/netex/loader/parser/SiteFrameParser.java b/src/main/java/org/opentripplanner/netex/loader/parser/SiteFrameParser.java
index 8cbe0c8aee6..38cd91ded98 100644
--- a/src/main/java/org/opentripplanner/netex/loader/parser/SiteFrameParser.java
+++ b/src/main/java/org/opentripplanner/netex/loader/parser/SiteFrameParser.java
@@ -1,14 +1,19 @@
package org.opentripplanner.netex.loader.parser;
+import static org.opentripplanner.netex.config.IgnorableFeature.PARKING;
+
import jakarta.xml.bind.JAXBElement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.Set;
import org.opentripplanner.framework.application.OTPFeature;
+import org.opentripplanner.netex.config.IgnorableFeature;
import org.opentripplanner.netex.index.NetexEntityIndex;
import org.opentripplanner.netex.support.JAXBUtils;
import org.rutebanken.netex.model.FlexibleStopPlace;
import org.rutebanken.netex.model.GroupOfStopPlaces;
+import org.rutebanken.netex.model.Parking;
import org.rutebanken.netex.model.Quay;
import org.rutebanken.netex.model.Quays_RelStructure;
import org.rutebanken.netex.model.Site_VersionFrameStructure;
@@ -36,6 +41,14 @@ class SiteFrameParser extends NetexParser {
private final Collection quays = new ArrayList<>();
+ private final Collection parkings = new ArrayList<>(0);
+
+ private final Set ignoredFeatures;
+
+ SiteFrameParser(Set ignoredFeatures) {
+ this.ignoredFeatures = ignoredFeatures;
+ }
+
@Override
public void parse(Site_VersionFrameStructure frame) {
if (frame.getStopPlaces() != null) {
@@ -51,6 +64,10 @@ public void parse(Site_VersionFrameStructure frame) {
if (frame.getTariffZones() != null) {
parseTariffZones(frame.getTariffZones().getTariffZone());
}
+
+ if (!ignoredFeatures.contains(PARKING) && frame.getParkings() != null) {
+ parseParkings(frame.getParkings().getParking());
+ }
// Keep list sorted alphabetically
warnOnMissingMapping(LOG, frame.getAccesses());
warnOnMissingMapping(LOG, frame.getAddresses());
@@ -59,7 +76,6 @@ public void parse(Site_VersionFrameStructure frame) {
warnOnMissingMapping(LOG, frame.getCheckConstraintDelays());
warnOnMissingMapping(LOG, frame.getCheckConstraintThroughputs());
warnOnMissingMapping(LOG, frame.getNavigationPaths());
- warnOnMissingMapping(LOG, frame.getParkings());
warnOnMissingMapping(LOG, frame.getPathJunctions());
warnOnMissingMapping(LOG, frame.getPathLinks());
warnOnMissingMapping(LOG, frame.getPointsOfInterest());
@@ -79,6 +95,7 @@ void setResultOnIndex(NetexEntityIndex netexIndex) {
netexIndex.stopPlaceById.addAll(stopPlaces);
netexIndex.tariffZonesById.addAll(tariffZones);
netexIndex.quayById.addAll(quays);
+ netexIndex.parkings.addAll(parkings);
}
private void parseFlexibleStopPlaces(Collection flexibleStopPlacesList) {
@@ -89,6 +106,10 @@ private void parseGroupsOfStopPlaces(Collection groupsOfStopP
groupsOfStopPlaces.addAll(groupsOfStopPlacesList);
}
+ private void parseParkings(List parking) {
+ parkings.addAll(parking);
+ }
+
private void parseStopPlaces(List> stopPlaceList) {
for (JAXBElement extends Site_VersionStructure> jaxBStopPlace : stopPlaceList) {
StopPlace stopPlace = (StopPlace) jaxBStopPlace.getValue();
diff --git a/src/main/java/org/opentripplanner/netex/mapping/MultiModalStationMapper.java b/src/main/java/org/opentripplanner/netex/mapping/MultiModalStationMapper.java
index ef4b0a25cfd..5a4fb23bbb9 100644
--- a/src/main/java/org/opentripplanner/netex/mapping/MultiModalStationMapper.java
+++ b/src/main/java/org/opentripplanner/netex/mapping/MultiModalStationMapper.java
@@ -1,6 +1,7 @@
package org.opentripplanner.netex.mapping;
import java.util.Collection;
+import javax.annotation.Nullable;
import org.opentripplanner.framework.geometry.WgsCoordinate;
import org.opentripplanner.framework.i18n.NonLocalizedString;
import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore;
@@ -21,6 +22,7 @@ public MultiModalStationMapper(DataImportIssueStore issueStore, FeedScopedIdFact
this.idFactory = idFactory;
}
+ @Nullable
MultiModalStation map(StopPlace stopPlace, Collection childStations) {
MultiModalStationBuilder multiModalStation = MultiModalStation
.of(idFactory.createId(stopPlace.getId()))
@@ -34,13 +36,13 @@ MultiModalStation map(StopPlace stopPlace, Collection childStations) {
if (coordinate == null) {
issueStore.add(
"MultiModalStationWithoutCoordinates",
- "MultiModal station {} does not contain any coordinates.",
+ "MultiModal station %s does not contain any coordinates.",
multiModalStation.getId()
);
+ return null;
} else {
multiModalStation.withCoordinate(coordinate);
+ return multiModalStation.build();
}
-
- return multiModalStation.build();
}
}
diff --git a/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java b/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java
index c3c9ad2d0ae..025a2349874 100644
--- a/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java
+++ b/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java
@@ -77,9 +77,9 @@ public class NetexMapper {
/**
* Shared/cached entity index, used by more than one mapper. This index provides alternative
- * indexes to netex entites, as well as global indexes to OTP domain objects needed in the mapping
+ * indexes to netex entities, as well as global indexes to OTP domain objects needed in the mapping
* process. Some of these indexes are feed scoped, and some are file group level scoped. As a rule
- * of tomb the indexes for OTP Model entities are global(small memory overhead), while the indexes
+ * of thumb the indexes for OTP Model entities are global(small memory overhead), while the indexes
* for the Netex entities follow the main index {@link #currentNetexIndex}, hence sopped by file
* group.
*/
@@ -158,7 +158,7 @@ public void finishUp() {
/**
*
- * This method mapes the last Netex file imported using the *local* entities in the hierarchical
+ * This method maps the last Netex file imported using the *local* entities in the hierarchical
* {@link NetexEntityIndexReadOnlyView}.
*
*
@@ -199,6 +199,8 @@ public void mapNetexToOtp(NetexEntityIndexReadOnlyView netexIndex) {
mapTripPatterns(serviceIds);
mapNoticeAssignments();
+ mapVehicleParkings();
+
addEntriesToGroupMapperForPostProcessingLater();
}
@@ -332,8 +334,9 @@ private void mapMultiModalStopPlaces() {
.getStationsByMultiModalStationRfs()
.get(multiModalStopPlace.getId());
var multiModalStation = mapper.map(multiModalStopPlace, stations);
-
- transitBuilder.stopModel().withMultiModalStation(multiModalStation);
+ if (multiModalStation != null) {
+ transitBuilder.stopModel().withMultiModalStation(multiModalStation);
+ }
}
}
@@ -519,6 +522,19 @@ private void addEntriesToGroupMapperForPostProcessingLater() {
}
}
+ private void mapVehicleParkings() {
+ var mapper = new VehicleParkingMapper(idFactory, issueStore);
+ currentNetexIndex
+ .getParkingsById()
+ .localKeys()
+ .forEach(id -> {
+ var parking = mapper.map(currentNetexIndex.getParkingsById().lookup(id));
+ if (parking != null) {
+ transitBuilder.vehicleParkings().add(parking);
+ }
+ });
+ }
+
/**
* The start of period is used to find the valid entities based on the current time. This should
* probably be configurable in the future, or even better incorporate the version number into the
diff --git a/src/main/java/org/opentripplanner/netex/mapping/VehicleParkingMapper.java b/src/main/java/org/opentripplanner/netex/mapping/VehicleParkingMapper.java
new file mode 100644
index 00000000000..862c5f0c648
--- /dev/null
+++ b/src/main/java/org/opentripplanner/netex/mapping/VehicleParkingMapper.java
@@ -0,0 +1,103 @@
+package org.opentripplanner.netex.mapping;
+
+import static org.rutebanken.netex.model.ParkingVehicleEnumeration.CYCLE;
+import static org.rutebanken.netex.model.ParkingVehicleEnumeration.E_CYCLE;
+import static org.rutebanken.netex.model.ParkingVehicleEnumeration.PEDAL_CYCLE;
+
+import java.util.Set;
+import javax.annotation.Nullable;
+import org.opentripplanner.framework.i18n.NonLocalizedString;
+import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore;
+import org.opentripplanner.netex.mapping.support.FeedScopedIdFactory;
+import org.opentripplanner.routing.vehicle_parking.VehicleParking;
+import org.opentripplanner.routing.vehicle_parking.VehicleParkingSpaces;
+import org.rutebanken.netex.model.Parking;
+import org.rutebanken.netex.model.ParkingVehicleEnumeration;
+
+/**
+ * Maps from NeTEx Parking to an internal {@link VehicleParking}.
+ */
+class VehicleParkingMapper {
+
+ private final FeedScopedIdFactory idFactory;
+
+ private static final Set BICYCLE_TYPES = Set.of(
+ PEDAL_CYCLE,
+ E_CYCLE,
+ CYCLE
+ );
+ private final DataImportIssueStore issueStore;
+
+ VehicleParkingMapper(FeedScopedIdFactory idFactory, DataImportIssueStore issueStore) {
+ this.idFactory = idFactory;
+ this.issueStore = issueStore;
+ }
+
+ @Nullable
+ VehicleParking map(Parking parking) {
+ if (parking.getTotalCapacity() == null) {
+ issueStore.add(
+ "MissingParkingCapacity",
+ "NeTEx Parking '%s' does not contain totalCapacity",
+ parkingDebugId(parking)
+ );
+ return null;
+ }
+ return VehicleParking
+ .builder()
+ .id(idFactory.createId(parking.getId()))
+ .name(NonLocalizedString.ofNullable(parking.getName().getValue()))
+ .coordinate(WgsCoordinateMapper.mapToDomain(parking.getCentroid()))
+ .capacity(mapCapacity(parking))
+ .bicyclePlaces(hasBikes(parking))
+ .carPlaces(!hasBikes(parking))
+ .entrance(mapEntrance(parking))
+ .build();
+ }
+
+ /**
+ * In the Nordic profile many fields of {@link Parking} are optional so even adding the ID to the
+ * issue store can lead to NPEs. For this reason we have a lot of fallbacks.
+ */
+ private static String parkingDebugId(Parking parking) {
+ if (parking.getId() != null) {
+ return parking.getId();
+ } else if (parking.getName() != null) {
+ return parking.getName().getValue();
+ } else if (parking.getCentroid() != null) {
+ return parking.getCentroid().toString();
+ } else {
+ return parking.toString();
+ }
+ }
+
+ private VehicleParking.VehicleParkingEntranceCreator mapEntrance(Parking parking) {
+ return builder ->
+ builder
+ .entranceId(idFactory.createId(parking.getId() + "/entrance"))
+ .coordinate(WgsCoordinateMapper.mapToDomain(parking.getCentroid()))
+ .walkAccessible(true)
+ .carAccessible(true);
+ }
+
+ private static VehicleParkingSpaces mapCapacity(Parking parking) {
+ var builder = VehicleParkingSpaces.builder();
+ int capacity = parking.getTotalCapacity().intValue();
+
+ // we assume that if we have something bicycle-like in the vehicle types it's a bicycle parking
+ // lot
+ // it's not possible in NeTEx to split the spaces between the types, so if you want that
+ // you have to define two parking lots with the same coordinates
+ if (hasBikes(parking)) {
+ builder.bicycleSpaces(capacity);
+ } else {
+ builder.carSpaces(capacity);
+ }
+
+ return builder.build();
+ }
+
+ private static boolean hasBikes(Parking parking) {
+ return parking.getParkingVehicleTypes().stream().anyMatch(BICYCLE_TYPES::contains);
+ }
+}
diff --git a/src/main/java/org/opentripplanner/netex/support/NetexVersionHelper.java b/src/main/java/org/opentripplanner/netex/support/NetexVersionHelper.java
index 4e6cf4967c0..8f7a7f14913 100644
--- a/src/main/java/org/opentripplanner/netex/support/NetexVersionHelper.java
+++ b/src/main/java/org/opentripplanner/netex/support/NetexVersionHelper.java
@@ -16,6 +16,15 @@
*/
public class NetexVersionHelper {
+ /**
+ * @see NetexVersionHelper#versionOf(EntityInVersionStructure)
+ */
+ private static final String ANY = "any";
+ /**
+ * A special value that represents an unknown version.
+ */
+ private static final int UNKNOWN_VERSION = -1;
+
/**
* private constructor to prevent instantiation of utility class
*/
@@ -23,10 +32,19 @@ private NetexVersionHelper() {}
/**
* According to the Norwegian Netex profile the version number must be a positive
- * increasing integer. A bigger value indicate a later version.
+ * increasing integer. A bigger value indicates a later version.
+ * However, the special value "any" is also supported and returns a constant meaning "unknown".
+ * The EPIP profile at
+ * http://netex.uk/netex/doc/2019.05.07-v1.1_FinalDraft/prCEN_TS_16614-PI_Profile_FV_%28E%29-2019-Final-Draft-v3.pdf (page 33)
+ * defines this as follows: "Use "any" if the VERSION is unknown (note that this will trigger NeTEx's
+ * XML automatic consistency check)."
*/
public static int versionOf(EntityInVersionStructure e) {
- return Integer.parseInt(e.getVersion());
+ if (e.getVersion().equals(ANY)) {
+ return UNKNOWN_VERSION;
+ } else {
+ return Integer.parseInt(e.getVersion());
+ }
}
/**
@@ -34,7 +52,7 @@ public static int versionOf(EntityInVersionStructure e) {
* elements exist in the collection {@code -1} is returned.
*/
public static int latestVersionIn(Collection extends EntityInVersionStructure> list) {
- return list.stream().mapToInt(NetexVersionHelper::versionOf).max().orElse(-1);
+ return list.stream().mapToInt(NetexVersionHelper::versionOf).max().orElse(UNKNOWN_VERSION);
}
/**
diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMNode.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMNode.java
index a1897d87124..d181cde4564 100644
--- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMNode.java
+++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMNode.java
@@ -38,7 +38,8 @@ public boolean hasCrossingTrafficLight() {
return hasTag("crossing") && "traffic_signals".equals(getTag("crossing"));
}
- /* Checks if this node is a barrier which prevents motor vehicle traffic
+ /**
+ * Checks if this node is a barrier which prevents motor vehicle traffic.
*
* @return true if it is
*/
diff --git a/src/main/java/org/opentripplanner/raptor/api/request/MultiCriteriaRequest.java b/src/main/java/org/opentripplanner/raptor/api/request/MultiCriteriaRequest.java
index 368b4660922..683c9807af5 100644
--- a/src/main/java/org/opentripplanner/raptor/api/request/MultiCriteriaRequest.java
+++ b/src/main/java/org/opentripplanner/raptor/api/request/MultiCriteriaRequest.java
@@ -18,7 +18,7 @@ public class MultiCriteriaRequest {
private final RelaxFunction relaxC1;
@Nullable
- private final RaptorTransitGroupCalculator transitPriorityCalculator;
+ private final RaptorTransitGroupPriorityCalculator transitPriorityCalculator;
private final List passThroughPoints;
@@ -63,7 +63,7 @@ public RelaxFunction relaxC1() {
return relaxC1;
}
- public Optional transitPriorityCalculator() {
+ public Optional transitPriorityCalculator() {
return Optional.ofNullable(transitPriorityCalculator);
}
@@ -140,7 +140,7 @@ public static class Builder {
private final MultiCriteriaRequest original;
private RelaxFunction relaxC1;
- private RaptorTransitGroupCalculator transitPriorityCalculator;
+ private RaptorTransitGroupPriorityCalculator transitPriorityCalculator;
private List passThroughPoints;
private Double relaxCostAtDestination;
@@ -163,11 +163,11 @@ public Builder withRelaxC1(RelaxFunction relaxC1) {
}
@Nullable
- public RaptorTransitGroupCalculator transitPriorityCalculator() {
+ public RaptorTransitGroupPriorityCalculator transitPriorityCalculator() {
return transitPriorityCalculator;
}
- public Builder withTransitPriorityCalculator(RaptorTransitGroupCalculator value) {
+ public Builder withTransitPriorityCalculator(RaptorTransitGroupPriorityCalculator value) {
transitPriorityCalculator = value;
return this;
}
diff --git a/src/main/java/org/opentripplanner/raptor/api/request/RaptorTransitGroupCalculator.java b/src/main/java/org/opentripplanner/raptor/api/request/RaptorTransitGroupPriorityCalculator.java
similarity index 85%
rename from src/main/java/org/opentripplanner/raptor/api/request/RaptorTransitGroupCalculator.java
rename to src/main/java/org/opentripplanner/raptor/api/request/RaptorTransitGroupPriorityCalculator.java
index b5f0598415e..2b96a7b1470 100644
--- a/src/main/java/org/opentripplanner/raptor/api/request/RaptorTransitGroupCalculator.java
+++ b/src/main/java/org/opentripplanner/raptor/api/request/RaptorTransitGroupPriorityCalculator.java
@@ -2,7 +2,7 @@
import org.opentripplanner.raptor.api.model.DominanceFunction;
-public interface RaptorTransitGroupCalculator {
+public interface RaptorTransitGroupPriorityCalculator {
/**
* Merge in the transit group id with an existing set. Note! Both the set
* and the group id type is {@code int}.
@@ -11,7 +11,7 @@ public interface RaptorTransitGroupCalculator {
* @param boardingGroupId the transit group id to add to the given set.
* @return the new computed set of groupIds
*/
- int mergeGroupIds(int currentGroupIds, int boardingGroupId);
+ int mergeInGroupId(int currentGroupIds, int boardingGroupId);
/**
* This is the dominance function to use for comparing transit-groups.
diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/configure/McRangeRaptorConfig.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/configure/McRangeRaptorConfig.java
index 8eef90950dd..3673e78ee47 100644
--- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/configure/McRangeRaptorConfig.java
+++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/configure/McRangeRaptorConfig.java
@@ -6,7 +6,7 @@
import org.opentripplanner.raptor.api.model.DominanceFunction;
import org.opentripplanner.raptor.api.model.RaptorTripSchedule;
import org.opentripplanner.raptor.api.request.MultiCriteriaRequest;
-import org.opentripplanner.raptor.api.request.RaptorTransitGroupCalculator;
+import org.opentripplanner.raptor.api.request.RaptorTransitGroupPriorityCalculator;
import org.opentripplanner.raptor.rangeraptor.context.SearchContext;
import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics;
import org.opentripplanner.raptor.rangeraptor.internalapi.ParetoSetCost;
@@ -201,7 +201,7 @@ private DominanceFunction dominanceFunctionC2() {
return null;
}
- private RaptorTransitGroupCalculator getTransitGroupPriorityCalculator() {
+ private RaptorTransitGroupPriorityCalculator getTransitGroupPriorityCalculator() {
return mcRequest().transitPriorityCalculator().orElseThrow();
}
diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/ride/c2/TransitGroupPriorityRideFactory.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/ride/c2/TransitGroupPriorityRideFactory.java
index 5d65c40d021..323067cc240 100644
--- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/ride/c2/TransitGroupPriorityRideFactory.java
+++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/ride/c2/TransitGroupPriorityRideFactory.java
@@ -2,7 +2,7 @@
import org.opentripplanner.raptor.api.model.RaptorTripPattern;
import org.opentripplanner.raptor.api.model.RaptorTripSchedule;
-import org.opentripplanner.raptor.api.request.RaptorTransitGroupCalculator;
+import org.opentripplanner.raptor.api.request.RaptorTransitGroupPriorityCalculator;
import org.opentripplanner.raptor.rangeraptor.multicriteria.arrivals.McStopArrival;
import org.opentripplanner.raptor.rangeraptor.multicriteria.ride.PatternRide;
import org.opentripplanner.raptor.rangeraptor.multicriteria.ride.PatternRideFactory;
@@ -15,10 +15,10 @@ public class TransitGroupPriorityRideFactory
implements PatternRideFactory> {
private int currentPatternGroupPriority;
- private final RaptorTransitGroupCalculator transitGroupPriorityCalculator;
+ private final RaptorTransitGroupPriorityCalculator transitGroupPriorityCalculator;
public TransitGroupPriorityRideFactory(
- RaptorTransitGroupCalculator transitGroupPriorityCalculator
+ RaptorTransitGroupPriorityCalculator transitGroupPriorityCalculator
) {
this.transitGroupPriorityCalculator = transitGroupPriorityCalculator;
}
@@ -55,6 +55,6 @@ public void prepareForTransitWith(RaptorTripPattern pattern) {
* Currently transit-group-priority is the only usage of c2
*/
private int calculateC2(int c2) {
- return transitGroupPriorityCalculator.mergeGroupIds(c2, currentPatternGroupPriority);
+ return transitGroupPriorityCalculator.mergeInGroupId(c2, currentPatternGroupPriority);
}
}
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java b/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java
index 06ceaeebeb2..7ac3dd1caf6 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java
@@ -16,6 +16,7 @@
import org.opentripplanner.framework.application.OTPRequestTimeoutException;
import org.opentripplanner.framework.time.ServiceDateUtils;
import org.opentripplanner.model.plan.Itinerary;
+import org.opentripplanner.model.plan.grouppriority.TransitGroupPriorityItineraryDecorator;
import org.opentripplanner.model.plan.paging.cursor.PageCursorInput;
import org.opentripplanner.raptor.api.request.RaptorTuningParameters;
import org.opentripplanner.raptor.api.request.SearchParams;
@@ -36,6 +37,7 @@
import org.opentripplanner.routing.framework.DebugTimingAggregator;
import org.opentripplanner.service.paging.PagingService;
import org.opentripplanner.standalone.api.OtpServerRequestContext;
+import org.opentripplanner.transit.model.network.grouppriority.TransitGroupPriorityService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -64,6 +66,7 @@ public class RoutingWorker {
*/
private final ZonedDateTime transitSearchTimeZero;
private final AdditionalSearchDays additionalSearchDays;
+ private final TransitGroupPriorityService transitGroupPriorityService;
private SearchParams raptorSearchParamsUsed = null;
private PageCursorInput pageCursorInput = null;
@@ -79,6 +82,12 @@ public RoutingWorker(OtpServerRequestContext serverContext, RouteRequest request
this.transitSearchTimeZero = ServiceDateUtils.asStartOfService(request.dateTime(), zoneId);
this.additionalSearchDays =
createAdditionalSearchDays(serverContext.raptorTuningParameters(), zoneId, request);
+ this.transitGroupPriorityService =
+ TransitGroupPriorityService.of(
+ request.preferences().transit().relaxTransitGroupPriority(),
+ request.journey().transit().priorityGroupsByAgency(),
+ request.journey().transit().priorityGroupsGlobal()
+ );
}
public RoutingResponse route() {
@@ -122,6 +131,9 @@ public RoutingResponse route() {
routeTransit(itineraries, routingErrors);
}
+ // Set C2 value for Street and FLEX if transit-group-priority is used
+ new TransitGroupPriorityItineraryDecorator(transitGroupPriorityService).decorate(itineraries);
+
debugTimingAggregator.finishedRouting();
// Filter itineraries
@@ -258,6 +270,7 @@ private Void routeTransit(List itineraries, Collection
var transitResults = TransitRouter.route(
request,
serverContext,
+ transitGroupPriorityService,
transitSearchTimeZero,
additionalSearchDays,
debugTimingAggregator
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 e8b8ed43c1c..071814a7abf 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java
@@ -12,6 +12,7 @@
import java.util.function.Function;
import javax.annotation.Nullable;
import org.opentripplanner.ext.accessibilityscore.DecorateWithAccessibilityScore;
+import org.opentripplanner.framework.application.OTPFeature;
import org.opentripplanner.framework.collection.ListSection;
import org.opentripplanner.framework.lang.Sandbox;
import org.opentripplanner.model.plan.Itinerary;
@@ -27,6 +28,8 @@
import org.opentripplanner.routing.algorithm.filterchain.filters.system.NumItinerariesFilter;
import org.opentripplanner.routing.algorithm.filterchain.filters.system.OutsideSearchWindowFilter;
import org.opentripplanner.routing.algorithm.filterchain.filters.system.PagingFilter;
+import org.opentripplanner.routing.algorithm.filterchain.filters.system.SingleCriteriaComparator;
+import org.opentripplanner.routing.algorithm.filterchain.filters.system.mcmax.McMaxLimitFilter;
import org.opentripplanner.routing.algorithm.filterchain.filters.transit.DecorateTransitAlert;
import org.opentripplanner.routing.algorithm.filterchain.filters.transit.KeepItinerariesWithFewestTransfers;
import org.opentripplanner.routing.algorithm.filterchain.filters.transit.RemoveItinerariesWithShortStreetLeg;
@@ -64,7 +67,6 @@ public class ItineraryListFilterChainBuilder {
private static final int NOT_SET = -1;
private final SortOrder sortOrder;
private final List groupBySimilarity = new ArrayList<>();
-
private ItineraryFilterDebugProfile debug = ItineraryFilterDebugProfile.OFF;
private int maxNumberOfItineraries = NOT_SET;
private ListSection maxNumberOfItinerariesCropSection = ListSection.TAIL;
@@ -86,6 +88,7 @@ public class ItineraryListFilterChainBuilder {
private double minBikeParkingDistance;
private boolean removeTransitIfWalkingIsBetter = true;
private ItinerarySortKey itineraryPageCut;
+ private boolean transitGroupPriorityUsed = false;
/**
* Sandbox filters which decorate the itineraries with extra information.
@@ -292,6 +295,15 @@ public ItineraryListFilterChainBuilder withPagingDeduplicationFilter(
return this;
}
+ /**
+ * Adjust filters to include multi-criteria parameter c2 and treat it as the
+ * transit-group.
+ */
+ public ItineraryListFilterChainBuilder withTransitGroupPriority() {
+ this.transitGroupPriorityUsed = true;
+ return this;
+ }
+
/**
* If set, walk-all-the-way itineraries are removed. This happens AFTER e.g. the group-by and
* remove-transit-with-higher-cost-than-best-on-street-only filter. This make sure that poor
@@ -531,7 +543,7 @@ private ItineraryListFilter buildGroupBySameRoutesAndStopsFilter() {
GroupBySameRoutesAndStops::new,
List.of(
new SortingFilter(SortOrderComparator.comparator(sortOrder)),
- new RemoveFilter(new MaxLimit(GroupBySameRoutesAndStops.TAG, 1))
+ new RemoveFilter(createMaxLimitFilter(GroupBySameRoutesAndStops.TAG, 1))
)
);
}
@@ -574,7 +586,7 @@ private List buildGroupByTripIdAndDistanceFilters() {
GroupByAllSameStations::new,
List.of(
new SortingFilter(generalizedCostComparator()),
- new RemoveFilter(new MaxLimit(innerGroupName, 1))
+ new RemoveFilter(createMaxLimitFilter(innerGroupName, 1))
)
)
);
@@ -587,7 +599,7 @@ private List buildGroupByTripIdAndDistanceFilters() {
}
addSort(nested, generalizedCostComparator());
- addRemoveFilter(nested, new MaxLimit(tag, group.maxNumOfItinerariesPerGroup));
+ addRemoveFilter(nested, createMaxLimitFilter(tag, group.maxNumOfItinerariesPerGroup));
nested.add(new KeepItinerariesWithFewestTransfers(sysTags));
@@ -620,4 +632,20 @@ private static void addDecorateFilter(
) {
filters.add(new DecorateFilter(decorator));
}
+
+ private RemoveItineraryFlagger createMaxLimitFilter(String filterName, int maxLimit) {
+ if (OTPFeature.MultiCriteriaGroupMaxFilter.isOn()) {
+ List comparators = new ArrayList<>();
+ comparators.add(SingleCriteriaComparator.compareGeneralizedCost());
+ comparators.add(SingleCriteriaComparator.compareNumTransfers());
+ if (transitGroupPriorityUsed) {
+ comparators.add(SingleCriteriaComparator.compareTransitGroupsPriority());
+ }
+ return new McMaxLimitFilter(filterName, maxLimit, comparators);
+ }
+ // Default is to just use a "hard" max limit
+ else {
+ return new MaxLimit(filterName, maxLimit);
+ }
+ }
}
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/SingleCriteriaComparator.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/SingleCriteriaComparator.java
new file mode 100644
index 00000000000..fdaaf0d7657
--- /dev/null
+++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/SingleCriteriaComparator.java
@@ -0,0 +1,69 @@
+package org.opentripplanner.routing.algorithm.filterchain.filters.system;
+
+import java.util.Comparator;
+import java.util.function.ToIntFunction;
+import org.opentripplanner.model.plan.Itinerary;
+import org.opentripplanner.transit.model.network.grouppriority.DefaultTransitGroupPriorityCalculator;
+
+/**
+ * Comparator used to compare a SINGLE criteria for dominance. The difference between this and the
+ * {@link org.opentripplanner.raptor.util.paretoset.ParetoComparator} is that:
+ *
+ * - This applies to one criteria, not multiple.
+ * - This interface applies to itineraries; It is not generic.
+ *
+ * A set of instances of this interface can be used to create a pareto-set. See
+ * {@link org.opentripplanner.raptor.util.paretoset.ParetoSet} and
+ * {@link org.opentripplanner.raptor.util.paretoset.ParetoComparator}.
+ *
+ * This interface extends {@link Comparator} so elements can be sorted as well. Not all criteria
+ * can be sorted, if so the {@link #strictOrder()} should return false (this is the default).
+ */
+@FunctionalInterface
+public interface SingleCriteriaComparator {
+ DefaultTransitGroupPriorityCalculator GROUP_PRIORITY_CALCULATOR = new DefaultTransitGroupPriorityCalculator();
+
+ /**
+ * The left criteria dominates the right criteria. Note! The right criteria may dominate
+ * the left criteria if there is no {@link #strictOrder()}. If left and right are equals, then
+ * there is no dominance.
+ */
+ boolean leftDominanceExist(Itinerary left, Itinerary right);
+
+ /**
+ * Return true if the criteria can be deterministically sorted.
+ */
+ default boolean strictOrder() {
+ return false;
+ }
+
+ static SingleCriteriaComparator compareNumTransfers() {
+ return compareLessThan(Itinerary::getNumberOfTransfers);
+ }
+
+ static SingleCriteriaComparator compareGeneralizedCost() {
+ return compareLessThan(Itinerary::getGeneralizedCost);
+ }
+
+ @SuppressWarnings("OptionalGetWithoutIsPresent")
+ static SingleCriteriaComparator compareTransitGroupsPriority() {
+ return (left, right) ->
+ GROUP_PRIORITY_CALCULATOR
+ .dominanceFunction()
+ .leftDominateRight(left.getGeneralizedCost2().get(), right.getGeneralizedCost2().get());
+ }
+
+ static SingleCriteriaComparator compareLessThan(final ToIntFunction op) {
+ return new SingleCriteriaComparator() {
+ @Override
+ public boolean leftDominanceExist(Itinerary left, Itinerary right) {
+ return op.applyAsInt(left) < op.applyAsInt(right);
+ }
+
+ @Override
+ public boolean strictOrder() {
+ return true;
+ }
+ };
+ }
+}
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/Group.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/Group.java
new file mode 100644
index 00000000000..7bfdad83e8f
--- /dev/null
+++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/Group.java
@@ -0,0 +1,55 @@
+package org.opentripplanner.routing.algorithm.filterchain.filters.system.mcmax;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * The purpose of a group is to maintain a list of items, all optimal for a single
+ * criteria/comparator. After the group is created, then the criteria is no longer needed, so we do
+ * not keep a reference to the original criteria.
+ */
+class Group implements Iterable- {
+
+ private final List
- items = new ArrayList<>();
+
+ public Group(Item firstItem) {
+ add(firstItem);
+ }
+
+ Item first() {
+ return items.getFirst();
+ }
+
+ boolean isEmpty() {
+ return items.isEmpty();
+ }
+
+ boolean isSingleItemGroup() {
+ return items.size() == 1;
+ }
+
+ void add(Item item) {
+ item.incGroupCount();
+ items.add(item);
+ }
+
+ void removeAllItems() {
+ items.forEach(Item::decGroupCount);
+ items.clear();
+ }
+
+ void addNewDominantItem(Item item) {
+ removeAllItems();
+ add(item);
+ }
+
+ boolean contains(Item item) {
+ return this.items.contains(item);
+ }
+
+ @Override
+ public Iterator
- iterator() {
+ return items.iterator();
+ }
+}
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/Item.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/Item.java
new file mode 100644
index 00000000000..36c3d662493
--- /dev/null
+++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/Item.java
@@ -0,0 +1,47 @@
+package org.opentripplanner.routing.algorithm.filterchain.filters.system.mcmax;
+
+import org.opentripplanner.model.plan.Itinerary;
+
+/**
+ * An item is a decorated itinerary. The extra information added is the index in the input list
+ * (sort order) and a groupCount. The sort order is used to break ties, while the group-count is
+ * used to select the itinerary witch exist in the highest number of groups. The group dynamically
+ * updates the group-count; The count is incremented when an item is added to a group, and
+ * decremented when the group is removed from the State.
+ */
+class Item {
+
+ private final Itinerary item;
+ private final int index;
+ private int groupCount = 0;
+
+ Item(Itinerary item, int index) {
+ this.item = item;
+ this.index = index;
+ }
+
+ /**
+ * An item is better than another if the groupCount is higher, and in case of a tie, if the sort
+ * index is lower.
+ */
+ public boolean betterThan(Item o) {
+ return groupCount != o.groupCount ? groupCount > o.groupCount : index < o.index;
+ }
+
+ Itinerary item() {
+ return item;
+ }
+
+ void incGroupCount() {
+ ++this.groupCount;
+ }
+
+ void decGroupCount() {
+ --this.groupCount;
+ }
+
+ @Override
+ public String toString() {
+ return "Item #%d {count:%d, %s}".formatted(index, groupCount, item.toStr());
+ }
+}
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/McMaxLimitFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/McMaxLimitFilter.java
new file mode 100644
index 00000000000..c0b07e7400a
--- /dev/null
+++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/McMaxLimitFilter.java
@@ -0,0 +1,105 @@
+package org.opentripplanner.routing.algorithm.filterchain.filters.system.mcmax;
+
+import java.util.List;
+import java.util.function.Predicate;
+import org.opentripplanner.model.plan.Itinerary;
+import org.opentripplanner.routing.algorithm.filterchain.filters.system.SingleCriteriaComparator;
+import org.opentripplanner.routing.algorithm.filterchain.framework.spi.RemoveItineraryFlagger;
+
+/**
+ * This filter is used to reduce a set of itineraries down to the specified limit, if possible.
+ * The filter is guaranteed to keep at least the given {@code minNumItineraries} and/or the best
+ * itinerary for each criterion. The criterion is defined using the list of {@code comparators}.
+ *
+ * The main usage of this filter is to combine it with a transit grouping filter and for each group
+ * make sure there is at least {@code minNumItineraries} and that the best itinerary with respect
+ * to each criterion is kept. So, if the grouping is based on time and riding common trips, then
+ * this filter will use the remaining criterion (transfers, generalized-cost,
+ * [transit-group-priority]) to filter the grouped set of itineraries. DO NOT INCLUDE CRITERIA
+ * USED TO GROUP THE ITINERARIES, ONLY THE REMAINING CRITERION USED IN THE RAPTOR SEARCH.
+ *
+ * IMPLEMENTATION DETAILS
+ *
+ * This is not a trivial problem. In most cases, the best itinerary for a given criteria is unique,
+ * but there might be ties - same number of transfers, same cost, and/or different priority groups.
+ * In case of a tie, we will look if an itinerary is "best-in-group" for more than one criterion,
+ * if so we pick the one which is best in the highest number of groups. Again, if there is a tie
+ * (best in the same number of groups), then we fall back to the given itinerary sorting order.
+ *
+ * This filter will use the order of the input itineraries to break ties. So, make sure to call the
+ * appropriate sort function before this filter is invoked.
+ *
+ * Note! For criteria like num-of-transfers or generalized-cost, there is only one set of "best"
+ * itineraries, and usually there are only one or a few itineraries. In case there is more than one,
+ * picking just one is fine. But, for transit-group-priority there might be more than one optimal
+ * set of itineraries. For each set, we need to pick one itinerary for the final result. Each of
+ * these sets may or may not have more than one itinerary. If you group by agency, then there will
+ * be at least one itinerary for each agency present in the result (simplified, an itinerary may
+ * consist of legs with different agencies). The transit-group-priority pareto-function used by
+ * Raptor is reused, so we do not need to worry about the logic here.
+ *
+ * Let's discuss an example (this example also exists as a unit-test case):
+ *
+ * minNumItineraries = 4
+ * comparators = [ generalized-cost, min-num-transfers, transit-group-priority ]
+ * itineraries: [
+ * #0 : [ 1000, 2, (a) ]
+ * #1 : [ 1000, 3, (a,b) ]
+ * #2 : [ 1000, 3, (b) ]
+ * #3 : [ 1200, 1, (a,b) ]
+ * #4 : [ 1200, 1, (a) ]
+ * #5 : [ 1300, 2, (c) ]
+ * #6 : [ 1300, 3, (c) ]
+ * ]
+ *
+ * The best itineraries by generalized-cost are (#0, #1, #2). The best itineraries by
+ * min-num-transfers are (#3, #4). The best itineraries by transit-group-priority are
+ * (a:(#0, #4), b:(#2), c:(#5, #6)).
+ *
+ * So we need to pick one from each group (#0, #1, #2), (#3, #4), (#0, #4), (#2), and (#5, #6).
+ * Since #2 is a single, we pick it first. Itinerary #2 is also one of the best
+ * generalized-cost itineraries - so we are done with generalized-cost itineraries as well. The two
+ * groups left are (#3, #4), (#0, #4), and (#5, #6). #4 exists in 2 groups, so we pick it next. Now
+ * we are left with (#5, #6). To break the tie, we look at the sort-order. We pick
+ * itinerary #5. Result: #2, #4, and #5.
+ *
+ * The `minNumItineraries` limit is not met, so we need to pick another itinerary, we use the
+ * sort-order again and add itinerary #0. The result returned is: [#0, #2, #4, #5]
+ */
+public class McMaxLimitFilter implements RemoveItineraryFlagger {
+
+ private final String name;
+ private final int minNumItineraries;
+ private final List comparators;
+
+ public McMaxLimitFilter(
+ String name,
+ int minNumItineraries,
+ List comparators
+ ) {
+ this.name = name;
+ this.minNumItineraries = minNumItineraries;
+ this.comparators = comparators;
+ }
+
+ @Override
+ public String name() {
+ return name;
+ }
+
+ @Override
+ public List flagForRemoval(List itineraries) {
+ if (itineraries.size() <= minNumItineraries) {
+ return List.of();
+ }
+ var state = new State(itineraries, comparators);
+ state.findAllSingleItemGroupsAndAddTheItemToTheResult();
+ state.findTheBestItemsUntilAllGroupsAreRepresentedInTheResult();
+ state.fillUpTheResultWithMinimumNumberOfItineraries(minNumItineraries);
+
+ // We now have the itineraries we want, but we must invert this and return the
+ // list of itineraries to drop - keeping the original order
+ var ok = state.getResult();
+ return itineraries.stream().filter(Predicate.not(ok::contains)).toList();
+ }
+}
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/State.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/State.java
new file mode 100644
index 00000000000..93b8b1097c9
--- /dev/null
+++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/State.java
@@ -0,0 +1,201 @@
+package org.opentripplanner.routing.algorithm.filterchain.filters.system.mcmax;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import javax.annotation.Nullable;
+import org.opentripplanner.model.plan.Itinerary;
+import org.opentripplanner.routing.algorithm.filterchain.filters.system.SingleCriteriaComparator;
+
+/**
+ * Keep a list of items, groups and the result in progress. This is just a class for
+ * simple bookkeeping for the state of the filter.
+ */
+class State {
+
+ private final List- items;
+ private final List groups;
+ private final List
- result = new ArrayList<>();
+
+ /**
+ * Initialize the state by wrapping each itinerary in an item (with index) and create groups for
+ * each criterion with the best itineraries (can be more than one with, for example, the same
+ * cost). There should be at least one itinerary from each group surviving the filtering process.
+ * The same itinerary can exist in multiple groups.
+ */
+ State(List itineraries, List comparators) {
+ this.items = createListOfItems(itineraries);
+ this.groups = createGroups(items, comparators);
+ }
+
+ List getResult() {
+ return result.stream().map(Item::item).toList();
+ }
+
+ /**
+ * Find and add all groups with a single item in them and add them to the result
+ */
+ void findAllSingleItemGroupsAndAddTheItemToTheResult() {
+ var item = findItemInFirstSingleItemGroup(groups);
+ while (item != null) {
+ addToResult(item);
+ item = findItemInFirstSingleItemGroup(groups);
+ }
+ }
+
+ /**
+ * Find the items with the highest group count and the lowest index. Theoretically, there might be
+ * a smaller set of itineraries that TOGETHER represent all groups than what we achieve here, but
+ * it is far more complicated to compute - so this is probably good enough.
+ */
+ void findTheBestItemsUntilAllGroupsAreRepresentedInTheResult() {
+ while (!groups.isEmpty()) {
+ addToResult(findBestItem(groups));
+ }
+ }
+
+ /**
+ * Fill up with itineraries until the minimum number of itineraries is reached
+ */
+ void fillUpTheResultWithMinimumNumberOfItineraries(int minNumItineraries) {
+ int end = Math.min(items.size(), minNumItineraries);
+ for (int i = 0; result.size() < end; ++i) {
+ var it = items.get(i);
+ if (!result.contains(it)) {
+ result.add(it);
+ }
+ }
+ }
+
+ private void addToResult(Item item) {
+ result.add(item);
+ removeGroupsWitchContainsItem(item);
+ }
+
+ /**
+ * If an itinerary is accepted into the final result, then all groups that contain that itinerary
+ * can be removed. In addition, the item groupCount should be decremented if a group is dropped.
+ * This makes sure that the groups represented in the final result do not count when selecting the
+ * next item.
+ */
+ private void removeGroupsWitchContainsItem(Item item) {
+ for (Group group : groups) {
+ if (group.contains(item)) {
+ group.removeAllItems();
+ }
+ }
+ groups.removeIf(Group::isEmpty);
+ }
+
+ /**
+ * The best item is the one which exists in most groups, and in case of a tie, the sort order/
+ * itinerary index is used.
+ */
+ private static Item findBestItem(List groups) {
+ var candidate = groups.getFirst().first();
+ for (Group group : groups) {
+ for (Item item : group) {
+ if (item.betterThan(candidate)) {
+ candidate = item;
+ }
+ }
+ }
+ return candidate;
+ }
+
+ /**
+ * Search through all groups and return all items witch comes from groups with only one item.
+ */
+ @Nullable
+ private static Item findItemInFirstSingleItemGroup(List groups) {
+ return groups
+ .stream()
+ .filter(Group::isSingleItemGroup)
+ .findFirst()
+ .map(Group::first)
+ .orElse(null);
+ }
+
+ private static ArrayList
- createListOfItems(List itineraries) {
+ var items = new ArrayList
- ();
+ for (int i = 0; i < itineraries.size(); i++) {
+ items.add(new Item(itineraries.get(i), i));
+ }
+ return items;
+ }
+
+ private static List createGroups(
+ Collection
- items,
+ List comparators
+ ) {
+ List groups = new ArrayList<>();
+ for (SingleCriteriaComparator comparator : comparators) {
+ if (comparator.strictOrder()) {
+ groups.add(createOrderedGroup(items, comparator));
+ } else {
+ groups.addAll(createUnorderedGroups(items, comparator));
+ }
+ }
+ return groups;
+ }
+
+ /**
+ * In a strict ordered group only one optimal value exist for the criteria defined by the given
+ * {@code comparator}. All items that have this value should be included in the group created.
+ */
+ private static Group createOrderedGroup(
+ Collection
- items,
+ SingleCriteriaComparator comparator
+ ) {
+ Group group = null;
+ for (Item item : items) {
+ if (group == null) {
+ group = new Group(item);
+ continue;
+ }
+ var current = group.first();
+ if (comparator.leftDominanceExist(item.item(), current.item())) {
+ group.addNewDominantItem(item);
+ } else if (!comparator.leftDominanceExist(current.item(), item.item())) {
+ group.add(item);
+ }
+ }
+ return group;
+ }
+
+ /**
+ * For a none strict ordered criteria, multiple optimal values exist. The criterion is defined by
+ * the given {@code comparator}. This method will create a group for each optimal value found in
+ * the given set of items.
+ *
+ * @see #createOrderedGroup(Collection, SingleCriteriaComparator)
+ */
+ private static Collection extends Group> createUnorderedGroups(
+ Collection
- items,
+ SingleCriteriaComparator comparator
+ ) {
+ List result = new ArrayList<>();
+
+ for (Item item : items) {
+ int groupCount = result.size();
+ for (Group group : result) {
+ var groupItem = group.first().item();
+ if (comparator.leftDominanceExist(groupItem, item.item())) {
+ if (comparator.leftDominanceExist(item.item(), groupItem)) {
+ // Mutual dominance => the item belong in another group
+ --groupCount;
+ }
+ } else {
+ if (comparator.leftDominanceExist(item.item(), groupItem)) {
+ group.removeAllItems();
+ }
+ group.add(item);
+ }
+ }
+ if (groupCount == 0) {
+ result.add(new Group(item));
+ }
+ }
+ return result;
+ }
+}
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 651d94b4eac..c1fab68f999 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java
@@ -94,6 +94,10 @@ public static ItineraryListFilterChain createFilterChain(
.withRemoveTransitIfWalkingIsBetter(true)
.withDebugEnabled(params.debug());
+ if (!request.preferences().transit().relaxTransitGroupPriority().isNormal()) {
+ builder.withTransitGroupPriority();
+ }
+
var fareService = context.graph().getFareService();
if (fareService != null) {
builder.withFareDecorator(new DecorateWithFare(fareService));
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java
index 06f4ff1cf45..37bb7270902 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java
@@ -41,6 +41,7 @@
import org.opentripplanner.routing.framework.DebugTimingAggregator;
import org.opentripplanner.standalone.api.OtpServerRequestContext;
import org.opentripplanner.street.search.TemporaryVerticesContainer;
+import org.opentripplanner.transit.model.network.grouppriority.TransitGroupPriorityService;
public class TransitRouter {
@@ -48,6 +49,7 @@ public class TransitRouter {
private final RouteRequest request;
private final OtpServerRequestContext serverContext;
+ private final TransitGroupPriorityService transitGroupPriorityService;
private final DebugTimingAggregator debugTimingAggregator;
private final ZonedDateTime transitSearchTimeZero;
private final AdditionalSearchDays additionalSearchDays;
@@ -56,12 +58,14 @@ public class TransitRouter {
private TransitRouter(
RouteRequest request,
OtpServerRequestContext serverContext,
+ TransitGroupPriorityService transitGroupPriorityService,
ZonedDateTime transitSearchTimeZero,
AdditionalSearchDays additionalSearchDays,
DebugTimingAggregator debugTimingAggregator
) {
this.request = request;
this.serverContext = serverContext;
+ this.transitGroupPriorityService = transitGroupPriorityService;
this.transitSearchTimeZero = transitSearchTimeZero;
this.additionalSearchDays = additionalSearchDays;
this.debugTimingAggregator = debugTimingAggregator;
@@ -71,6 +75,7 @@ private TransitRouter(
public static TransitRouterResult route(
RouteRequest request,
OtpServerRequestContext serverContext,
+ TransitGroupPriorityService priorityGroupConfigurator,
ZonedDateTime transitSearchTimeZero,
AdditionalSearchDays additionalSearchDays,
DebugTimingAggregator debugTimingAggregator
@@ -78,6 +83,7 @@ public static TransitRouterResult route(
TransitRouter transitRouter = new TransitRouter(
request,
serverContext,
+ priorityGroupConfigurator,
transitSearchTimeZero,
additionalSearchDays,
debugTimingAggregator
@@ -309,6 +315,7 @@ private RaptorRoutingRequestTransitData createRequestTransitDataProvider(
) {
return new RaptorRoutingRequestTransitData(
transitLayer,
+ transitGroupPriorityService,
transitSearchTimeZero,
additionalSearchDays.additionalSearchDaysInPast(),
additionalSearchDays.additionalSearchDaysInFuture(),
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 879536fdcd0..948b132e408 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
@@ -21,10 +21,10 @@
import org.opentripplanner.raptor.rangeraptor.SystemErrDebugLogger;
import org.opentripplanner.routing.algorithm.raptoradapter.router.performance.PerformanceTimersForRaptor;
import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.RaptorCostConverter;
-import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.grouppriority.TransitGroupPriority32n;
import org.opentripplanner.routing.api.request.DebugEventType;
import org.opentripplanner.routing.api.request.RouteRequest;
import org.opentripplanner.routing.api.request.framework.CostLinearFunction;
+import org.opentripplanner.transit.model.network.grouppriority.DefaultTransitGroupPriorityCalculator;
import org.opentripplanner.transit.model.site.StopLocation;
public class RaptorRequestMapper {
@@ -119,7 +119,7 @@ private RaptorRequest doMap() {
mcBuilder.withPassThroughPoints(mapPassThroughPoints());
r.relaxGeneralizedCostAtDestination().ifPresent(mcBuilder::withRelaxCostAtDestination);
} else if (!pt.relaxTransitGroupPriority().isNormal()) {
- mcBuilder.withTransitPriorityCalculator(TransitGroupPriority32n.priorityCalculator());
+ mcBuilder.withTransitPriorityCalculator(new DefaultTransitGroupPriorityCalculator());
mcBuilder.withRelaxC1(mapRelaxCost(pt.relaxTransitGroupPriority()));
}
});
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TransfersMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TransfersMapper.java
index 74a5d7a6352..c7e9ea05320 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TransfersMapper.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TransfersMapper.java
@@ -6,7 +6,7 @@
import org.opentripplanner.routing.algorithm.raptoradapter.transit.Transfer;
import org.opentripplanner.transit.model.site.RegularStop;
import org.opentripplanner.transit.service.StopModel;
-import org.opentripplanner.transit.service.TransitModel;
+import org.opentripplanner.transit.service.TransitService;
class TransfersMapper {
@@ -14,7 +14,7 @@ class TransfersMapper {
* Copy pre-calculated transfers from the original graph
* @return a list where each element is a list of transfers for the corresponding stop index
*/
- static List
> mapTransfers(StopModel stopModel, TransitModel transitModel) {
+ static List> mapTransfers(StopModel stopModel, TransitService transitService) {
List> transferByStopIndex = new ArrayList<>();
for (int i = 0; i < stopModel.stopIndexSize(); ++i) {
@@ -26,7 +26,7 @@ static List> mapTransfers(StopModel stopModel, TransitModel trans
ArrayList list = new ArrayList<>();
- for (PathTransfer pathTransfer : transitModel.getTransfersByStop(stop)) {
+ for (PathTransfer pathTransfer : transitService.getTransfersByStop(stop)) {
if (pathTransfer.to instanceof RegularStop) {
int toStopIndex = pathTransfer.to.getIndex();
Transfer newTransfer;
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TransitLayerMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TransitLayerMapper.java
index 33a076bb8d6..8ce328fe1b6 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TransitLayerMapper.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TransitLayerMapper.java
@@ -27,8 +27,10 @@
import org.opentripplanner.transit.model.network.TripPattern;
import org.opentripplanner.transit.model.site.StopTransferPriority;
import org.opentripplanner.transit.model.timetable.TripTimes;
+import org.opentripplanner.transit.service.DefaultTransitService;
import org.opentripplanner.transit.service.StopModel;
import org.opentripplanner.transit.service.TransitModel;
+import org.opentripplanner.transit.service.TransitService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -47,10 +49,12 @@ public class TransitLayerMapper {
private static final Logger LOG = LoggerFactory.getLogger(TransitLayerMapper.class);
- private final TransitModel transitModel;
+ private final TransitService transitService;
+ private final StopModel stopModel;
private TransitLayerMapper(TransitModel transitModel) {
- this.transitModel = transitModel;
+ this.transitService = new DefaultTransitService(transitModel);
+ this.stopModel = transitModel.getStopModel();
}
public static TransitLayer map(
@@ -74,20 +78,19 @@ private TransitLayer map(TransitTuningParameters tuningParameters) {
HashMap> tripPatternsByStopByDate;
List> transferByStopIndex;
ConstrainedTransfersForPatterns constrainedTransfers = null;
- StopModel stopModel = transitModel.getStopModel();
LOG.info("Mapping transitLayer from TransitModel...");
- Collection allTripPatterns = transitModel.getAllTripPatterns();
+ Collection allTripPatterns = transitService.getAllTripPatterns();
tripPatternsByStopByDate = mapTripPatterns(allTripPatterns);
- transferByStopIndex = mapTransfers(stopModel, transitModel);
+ transferByStopIndex = mapTransfers(stopModel, transitService);
TransferIndexGenerator transferIndexGenerator = null;
if (OTPFeature.TransferConstraints.isOn()) {
transferIndexGenerator =
- new TransferIndexGenerator(transitModel.getTransferService().listAll(), allTripPatterns);
+ new TransferIndexGenerator(transitService.getTransferService().listAll(), allTripPatterns);
constrainedTransfers = transferIndexGenerator.generateTransfers();
}
@@ -98,9 +101,9 @@ private TransitLayer map(TransitTuningParameters tuningParameters) {
return new TransitLayer(
tripPatternsByStopByDate,
transferByStopIndex,
- transitModel.getTransferService(),
+ transitService.getTransferService(),
stopModel,
- transitModel.getTimeZone(),
+ transitService.getTimeZone(),
transferCache,
constrainedTransfers,
transferIndexGenerator,
@@ -118,13 +121,10 @@ private HashMap> mapTripPatterns(
Collection allTripPatterns
) {
TripPatternForDateMapper tripPatternForDateMapper = new TripPatternForDateMapper(
- transitModel.getTransitModelIndex().getServiceCodesRunningForDate()
+ transitService.getServiceCodesRunningForDate()
);
- Set allServiceDates = transitModel
- .getTransitModelIndex()
- .getServiceCodesRunningForDate()
- .keySet();
+ Set allServiceDates = transitService.getAllServiceCodes();
List tripPatternForDates = Collections.synchronizedList(new ArrayList<>());
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TransitLayerUpdater.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TransitLayerUpdater.java
index 5188bdef8b1..934bec39c11 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TransitLayerUpdater.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TransitLayerUpdater.java
@@ -2,7 +2,6 @@
import com.google.common.collect.HashMultimap;
import com.google.common.collect.SetMultimap;
-import gnu.trove.set.TIntSet;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collection;
@@ -20,7 +19,7 @@
import org.opentripplanner.transit.model.network.TripPattern;
import org.opentripplanner.transit.model.timetable.TripIdAndServiceDate;
import org.opentripplanner.transit.model.timetable.TripTimes;
-import org.opentripplanner.transit.service.TransitModel;
+import org.opentripplanner.transit.service.TransitEditorService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -40,9 +39,7 @@ public class TransitLayerUpdater {
private static final Logger LOG = LoggerFactory.getLogger(TransitLayerUpdater.class);
- private final TransitModel transitModel;
-
- private final Map serviceCodesRunningForDate;
+ private final TransitEditorService transitService;
/**
* Cache the TripPatternForDates indexed on the original TripPatterns in order to avoid this
@@ -58,19 +55,15 @@ public class TransitLayerUpdater {
private final Map> tripPatternsRunningOnDateMapCache = new HashMap<>();
- public TransitLayerUpdater(
- TransitModel transitModel,
- Map serviceCodesRunningForDate
- ) {
- this.transitModel = transitModel;
- this.serviceCodesRunningForDate = serviceCodesRunningForDate;
+ public TransitLayerUpdater(TransitEditorService transitService) {
+ this.transitService = transitService;
}
public void update(
Set updatedTimetables,
Map> timetables
) {
- if (!transitModel.hasRealtimeTransitLayer()) {
+ if (!transitService.hasRealtimeTransitLayer()) {
return;
}
@@ -78,11 +71,11 @@ public void update(
// Make a shallow copy of the realtime transit layer. Only the objects that are copied will be
// changed during this update process.
- TransitLayer realtimeTransitLayer = new TransitLayer(transitModel.getRealtimeTransitLayer());
+ TransitLayer realtimeTransitLayer = new TransitLayer(transitService.getRealtimeTransitLayer());
// Instantiate a TripPatternForDateMapper with the new TripPattern mappings
TripPatternForDateMapper tripPatternForDateMapper = new TripPatternForDateMapper(
- serviceCodesRunningForDate
+ transitService.getServiceCodesRunningForDate()
);
Set datesToBeUpdated = new HashSet<>();
@@ -229,7 +222,7 @@ public void update(
// Switch out the reference with the updated realtimeTransitLayer. This is synchronized to
// guarantee that the reference is set after all the fields have been updated.
- transitModel.setRealtimeTransitLayer(realtimeTransitLayer);
+ transitService.setRealtimeTransitLayer(realtimeTransitLayer);
LOG.debug(
"UPDATING {} tripPatterns took {} ms",
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TripPatternForDateMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TripPatternForDateMapper.java
index 30551421138..cd00b9356dc 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TripPatternForDateMapper.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TripPatternForDateMapper.java
@@ -41,7 +41,7 @@ public class TripPatternForDateMapper {
* @param serviceCodesRunningForDate - READ ONLY
*/
TripPatternForDateMapper(Map serviceCodesRunningForDate) {
- this.serviceCodesRunningForDate = Collections.unmodifiableMap(serviceCodesRunningForDate);
+ this.serviceCodesRunningForDate = serviceCodesRunningForDate;
}
/**
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
deleted file mode 100644
index 6ef82786b99..00000000000
--- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfigurator.java
+++ /dev/null
@@ -1,144 +0,0 @@
-package org.opentripplanner.routing.algorithm.raptoradapter.transit.request;
-
-import gnu.trove.impl.Constants;
-import gnu.trove.map.TObjectIntMap;
-import gnu.trove.map.hash.TObjectIntHashMap;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.stream.Stream;
-import org.opentripplanner.framework.lang.ArrayUtils;
-import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.grouppriority.TransitGroupPriority32n;
-import org.opentripplanner.routing.api.request.request.filter.TransitGroupSelect;
-import org.opentripplanner.transit.model.framework.FeedScopedId;
-import org.opentripplanner.transit.model.network.TripPattern;
-
-/**
- * This class dynamically builds an index of transit-group-ids from the
- * provided {@link TransitGroupSelect}s while serving the caller with
- * group-ids for each requested pattern. It is made for optimal
- * performance, since it is used in request scope.
- *
- * THIS CLASS IS NOT THREAD-SAFE.
- */
-public class PriorityGroupConfigurator {
-
- /**
- * There are two ways we can treat the base (local-traffic) transit priority group:
- *
- * - We can assign group id 1 (one) to the base group and it will be treated as any other group.
- *
- We can assign group id 0 (zero) to the base and it will not be added to the set of groups
- * a given path has.
- *
- * When we compare paths we compare sets of group ids. A set is dominating another set if it is
- * a smaller subset or different from the other set.
- *
- * Example - base-group-id = 0 (zero)
- *
- * Let B be the base and G be concrete group. Then: (B) dominates (G), (G) dominates (B), (B)
- * dominates (BG), but (G) does not dominate (BG). In other words, paths with only agency
- * X (group G) is not given an advantage in the routing over paths with a combination of agency
- * X (group G) and local traffic (group B).
- *
- * TODO: Experiment with base-group-id=0 and make it configurable.
- */
- private static final int GROUP_INDEX_COUNTER_START = 1;
-
- private final int baseGroupId = TransitGroupPriority32n.groupId(GROUP_INDEX_COUNTER_START);
- private int groupIndexCounter = GROUP_INDEX_COUNTER_START;
- private final boolean enabled;
- private final PriorityGroupMatcher[] agencyMatchers;
- private final PriorityGroupMatcher[] globalMatchers;
-
- // Index matchers and ids
- private final List agencyMatchersIds;
- private final List globalMatchersIds;
-
- private PriorityGroupConfigurator() {
- this.enabled = false;
- this.agencyMatchers = null;
- this.globalMatchers = null;
- this.agencyMatchersIds = List.of();
- this.globalMatchersIds = List.of();
- }
-
- private PriorityGroupConfigurator(
- Collection byAgency,
- Collection global
- ) {
- this.agencyMatchers = PriorityGroupMatcher.of(byAgency);
- this.globalMatchers = PriorityGroupMatcher.of(global);
- 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
- this.agencyMatchersIds = Arrays.stream(agencyMatchers).map(MatcherAgencyAndIds::new).toList();
- }
-
- public static PriorityGroupConfigurator empty() {
- return new PriorityGroupConfigurator();
- }
-
- public static PriorityGroupConfigurator of(
- Collection byAgency,
- Collection global
- ) {
- if (Stream.of(byAgency, global).allMatch(Collection::isEmpty)) {
- return empty();
- }
- return new PriorityGroupConfigurator(byAgency, global);
- }
-
- /**
- * Fetch/lookup the transit-group-id for the given pattern.
- *
- * @throws IllegalArgumentException if more than 32 group-ids are requested.
- */
- public int lookupTransitGroupPriorityId(TripPattern tripPattern) {
- if (!enabled || tripPattern == null) {
- return baseGroupId;
- }
-
- for (var it : agencyMatchersIds) {
- if (it.matcher().match(tripPattern)) {
- var agencyId = tripPattern.getRoute().getAgency().getId();
- int groupId = it.ids().get(agencyId);
-
- if (groupId < 0) {
- groupId = nextGroupId();
- it.ids.put(agencyId, groupId);
- }
- return groupId;
- }
- }
-
- for (var it : globalMatchersIds) {
- if (it.matcher.match(tripPattern)) {
- return it.groupId();
- }
- }
- // Fallback to base-group-id
- return baseGroupId;
- }
-
- public int baseGroupId() {
- return baseGroupId;
- }
-
- private int nextGroupId() {
- return TransitGroupPriority32n.groupId(++groupIndexCounter);
- }
-
- /** Pair of matcher and groupId. Used only inside this class. */
- record MatcherAndId(PriorityGroupMatcher matcher, int groupId) {}
-
- /** Matcher with map of ids by agency. */
- record MatcherAgencyAndIds(PriorityGroupMatcher matcher, TObjectIntMap ids) {
- MatcherAgencyAndIds(PriorityGroupMatcher matcher) {
- this(
- matcher,
- new TObjectIntHashMap<>(Constants.DEFAULT_CAPACITY, Constants.DEFAULT_LOAD_FACTOR, -1)
- );
- }
- }
-}
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 19b5ccf8502..5b9d81fa6c3 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
@@ -30,10 +30,11 @@
import org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.GeneralizedCostParametersMapper;
import org.opentripplanner.routing.api.request.RouteRequest;
import org.opentripplanner.transit.model.network.RoutingTripPattern;
+import org.opentripplanner.transit.model.network.grouppriority.TransitGroupPriorityService;
/**
* This is the data provider for the Range Raptor search engine. It uses data from the TransitLayer,
- * but filters it by dates and modes per request. Transfers durations are pre-calculated per request
+ * but filters it by dates and modes per request. Transfer durations are pre-calculated per request
* based on walk speed.
*/
public class RaptorRoutingRequestTransitData implements RaptorTransitDataProvider {
@@ -71,6 +72,7 @@ public class RaptorRoutingRequestTransitData implements RaptorTransitDataProvide
public RaptorRoutingRequestTransitData(
TransitLayer transitLayer,
+ TransitGroupPriorityService transitGroupPriorityService,
ZonedDateTime transitSearchTimeZero,
int additionalPastSearchDays,
int additionalFutureSearchDays,
@@ -82,7 +84,7 @@ public RaptorRoutingRequestTransitData(
this.transitSearchTimeZero = transitSearchTimeZero;
// Delegate to the creator to construct the needed data structures. The code is messy so
- // it is nice to NOT have it in the class. It isolate this code to only be available at
+ // it is nice to NOT have it in the class. It isolates this code to only be available at
// the time of construction
var transitDataCreator = new RaptorRoutingRequestTransitDataCreator(
transitLayer,
@@ -92,7 +94,7 @@ public RaptorRoutingRequestTransitData(
additionalPastSearchDays,
additionalFutureSearchDays,
filter,
- createTransitGroupPriorityConfigurator(request)
+ transitGroupPriorityService
);
this.patternIndex = transitDataCreator.createPatternIndex(tripPatterns);
this.activeTripPatternsPerStop = transitDataCreator.createTripPatternsPerStop(tripPatterns);
@@ -242,15 +244,4 @@ public RaptorConstrainedBoardingSearch transferConstraintsReverseS
}
return new ConstrainedBoardingSearch(false, toStopTransfers, fromStopTransfers);
}
-
- private PriorityGroupConfigurator createTransitGroupPriorityConfigurator(RouteRequest request) {
- if (request.preferences().transit().relaxTransitGroupPriority().isNormal()) {
- return PriorityGroupConfigurator.empty();
- }
- var transitRequest = request.journey().transit();
- return PriorityGroupConfigurator.of(
- transitRequest.priorityGroupsByAgency(),
- transitRequest.priorityGroupsGlobal()
- );
- }
}
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreator.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreator.java
index f987e4f7a21..815bf839e31 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreator.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreator.java
@@ -19,6 +19,7 @@
import org.opentripplanner.routing.algorithm.raptoradapter.transit.TransitLayer;
import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripPatternForDate;
import org.opentripplanner.transit.model.network.RoutingTripPattern;
+import org.opentripplanner.transit.model.network.grouppriority.TransitGroupPriorityService;
import org.opentripplanner.transit.model.timetable.TripTimes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -93,7 +94,7 @@ static List merge(
ZonedDateTime transitSearchTimeZero,
List patternForDateList,
TransitDataProviderFilter filter,
- PriorityGroupConfigurator priorityGroupConfigurator
+ TransitGroupPriorityService transitGroupPriorityService
) {
// Group TripPatternForDate objects by TripPattern.
// This is done in a loop to increase performance.
@@ -147,7 +148,7 @@ static List merge(
tripPattern.getAlightingPossible(),
BoardAlight.ALIGHT
),
- priorityGroupConfigurator.lookupTransitGroupPriorityId(tripPattern.getPattern())
+ transitGroupPriorityService.lookupTransitGroupPriorityId(tripPattern.getPattern())
)
);
}
@@ -159,7 +160,7 @@ List createTripPatterns(
int additionalPastSearchDays,
int additionalFutureSearchDays,
TransitDataProviderFilter filter,
- PriorityGroupConfigurator priorityGroupConfigurator
+ TransitGroupPriorityService transitGroupPriorityService
) {
List tripPatternForDates = getTripPatternsForDateRange(
additionalPastSearchDays,
@@ -167,7 +168,7 @@ List createTripPatterns(
filter
);
- return merge(transitSearchTimeZero, tripPatternForDates, filter, priorityGroupConfigurator);
+ return merge(transitSearchTimeZero, tripPatternForDates, filter, transitGroupPriorityService);
}
private static List filterActiveTripPatterns(
diff --git a/src/main/java/org/opentripplanner/routing/graph/Graph.java b/src/main/java/org/opentripplanner/routing/graph/Graph.java
index 4b6c9938317..fa3d12b92f7 100644
--- a/src/main/java/org/opentripplanner/routing/graph/Graph.java
+++ b/src/main/java/org/opentripplanner/routing/graph/Graph.java
@@ -16,7 +16,6 @@
import javax.annotation.Nullable;
import org.locationtech.jts.geom.Geometry;
import org.opentripplanner.ext.dataoverlay.configuration.DataOverlayParameterBindings;
-import org.opentripplanner.ext.geocoder.LuceneIndex;
import org.opentripplanner.framework.geometry.CompactElevationProfile;
import org.opentripplanner.framework.geometry.GeometryUtils;
import org.opentripplanner.model.calendar.openinghours.OpeningHoursCalendarService;
@@ -90,12 +89,6 @@ public class Graph implements Serializable {
/** True if OSM data was loaded into this Graph. */
public boolean hasStreets = false;
- /**
- * Have bike parks already been linked to the graph. As the linking happens twice if a base graph
- * is used, we store information on whether bike park linking should be skipped.
- */
-
- public boolean hasLinkedBikeParks = false;
/**
* The difference in meters between the WGS84 ellipsoid height and geoid height at the graph's
* center
@@ -136,7 +129,6 @@ public class Graph implements Serializable {
* creating the data overlay context when routing.
*/
public DataOverlayParameterBindings dataOverlayParameterBindings;
- private LuceneIndex luceneIndex;
@Inject
public Graph(
@@ -384,14 +376,6 @@ public void setFareService(FareService fareService) {
this.fareService = fareService;
}
- public LuceneIndex getLuceneIndex() {
- return luceneIndex;
- }
-
- public void setLuceneIndex(LuceneIndex luceneIndex) {
- this.luceneIndex = luceneIndex;
- }
-
private void indexIfNotIndexed(StopModel stopModel) {
if (streetIndex == null) {
index(stopModel);
diff --git a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java
index 6ebd34e1287..d5dfc4ce8c1 100644
--- a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java
+++ b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java
@@ -308,6 +308,7 @@ public boolean equals(Object o) {
public String toString() {
return ToStringBuilder
.of(VehicleParking.class)
+ .addStr("id", id.toString())
.addStr("name", name.toString())
.addObj("coordinate", coordinate)
.toString();
diff --git a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParkingHelper.java b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParkingHelper.java
index 507ce9329ed..257f2805ca2 100644
--- a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParkingHelper.java
+++ b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParkingHelper.java
@@ -2,7 +2,6 @@
import java.util.List;
import java.util.Objects;
-import java.util.stream.Collectors;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.street.model.edge.StreetVehicleParkingLink;
import org.opentripplanner.street.model.edge.VehicleParkingEdge;
@@ -30,7 +29,7 @@ public List createVehicleParkingVertices(
.getEntrances()
.stream()
.map(vertexFactory::vehicleParkingEntrance)
- .collect(Collectors.toList());
+ .toList();
}
public static void linkVehicleParkingEntrances(
diff --git a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParkingSpaces.java b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParkingSpaces.java
index de25fc75521..8b21cc21f2c 100644
--- a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParkingSpaces.java
+++ b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParkingSpaces.java
@@ -2,6 +2,7 @@
import java.io.Serializable;
import java.util.Objects;
+import org.opentripplanner.framework.tostring.ToStringBuilder;
/**
* The number of spaces by type. {@code null} if unknown.
@@ -70,6 +71,16 @@ public boolean equals(Object o) {
);
}
+ @Override
+ public String toString() {
+ return ToStringBuilder
+ .of(VehicleParkingSpaces.class)
+ .addNum("carSpaces", carSpaces)
+ .addNum("wheelchairAccessibleCarSpaces", wheelchairAccessibleCarSpaces)
+ .addNum("bicycleSpaces", bicycleSpaces)
+ .toString();
+ }
+
public static class VehicleParkingSpacesBuilder {
private Integer bicycleSpaces;
diff --git a/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java b/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java
index 6552d82770f..3a577611617 100644
--- a/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java
+++ b/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java
@@ -8,6 +8,7 @@
import org.opentripplanner.ext.dataoverlay.routing.DataOverlayContext;
import org.opentripplanner.ext.emissions.EmissionsService;
import org.opentripplanner.ext.flex.FlexParameters;
+import org.opentripplanner.ext.geocoder.LuceneIndex;
import org.opentripplanner.ext.ridehailing.RideHailingService;
import org.opentripplanner.ext.stopconsolidation.StopConsolidationService;
import org.opentripplanner.framework.application.OTPFeature;
@@ -136,4 +137,7 @@ default DataOverlayContext dataOverlayContext(RouteRequest request) {
)
);
}
+
+ @Nullable
+ LuceneIndex lucenceIndex();
}
diff --git a/src/main/java/org/opentripplanner/standalone/config/buildconfig/NetexConfig.java b/src/main/java/org/opentripplanner/standalone/config/buildconfig/NetexConfig.java
index d4cd4521e54..52bccf605d8 100644
--- a/src/main/java/org/opentripplanner/standalone/config/buildconfig/NetexConfig.java
+++ b/src/main/java/org/opentripplanner/standalone/config/buildconfig/NetexConfig.java
@@ -3,6 +3,7 @@
import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_0;
import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_2;
import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_3;
+import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_6;
import org.opentripplanner.netex.config.NetexFeedParameters;
import org.opentripplanner.standalone.config.framework.json.NodeAdapter;
@@ -164,6 +165,14 @@ private static NetexFeedParameters.Builder mapFilePatternParameters(
.summary("Ignore contents of the FareFrame")
.docDefaultValue(base.ignoreFareFrame())
.asBoolean(base.ignoreFareFrame())
+ )
+ .withIgnoreParking(
+ config
+ .of("ignoreParking")
+ .since(V2_6)
+ .summary("Ignore Parking elements.")
+ .docDefaultValue(base.ignoreParking())
+ .asBoolean(base.ignoreParking())
);
}
diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java
index 6fa33aac267..dc91388458a 100644
--- a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java
+++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java
@@ -514,7 +514,7 @@ the access legs used. In other cases where the access(CAR) is faster than transi
guaranteed to be optimal. Use itinerary-filters to limit what is presented to the client. The
duration can be set per mode(`maxDurationForMode`), because some street modes searches
are much more resource intensive than others. A default value is applied if the mode specific value
-do not exist.
+does not exist.
"""
)
.asDuration(dftAccessEgress.maxDuration().defaultValue()),
@@ -554,7 +554,7 @@ duration can be set per mode(`maxDurationForMode`), because some street modes se
guaranteed to be optimal. Use itinerary-filters to limit what is presented to the client. The
duration can be set per mode(`maxDirectStreetDurationForMode`), because some street modes searches
are much more resource intensive than others. A default value is applied if the mode specific value
-do not exist."
+does not exist."
"""
)
.asDuration(dft.maxDirectDuration().defaultValue()),
diff --git a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java
index b1bc6888753..52f7970ccd7 100644
--- a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java
+++ b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java
@@ -5,7 +5,6 @@
import org.opentripplanner.apis.transmodel.TransmodelAPI;
import org.opentripplanner.datastore.api.DataSource;
import org.opentripplanner.ext.emissions.EmissionsDataModel;
-import org.opentripplanner.ext.geocoder.LuceneIndex;
import org.opentripplanner.ext.stopconsolidation.StopConsolidationRepository;
import org.opentripplanner.framework.application.LogMDCSupport;
import org.opentripplanner.framework.application.OTPFeature;
@@ -33,6 +32,7 @@
import org.opentripplanner.standalone.server.OTPWebApplication;
import org.opentripplanner.street.model.StreetLimitationParameters;
import org.opentripplanner.street.model.elevation.ElevationUtils;
+import org.opentripplanner.transit.service.DefaultTransitService;
import org.opentripplanner.transit.service.TransitModel;
import org.opentripplanner.updater.configure.UpdaterConfigurator;
import org.opentripplanner.visualizer.GraphVisualizer;
@@ -180,8 +180,9 @@ private void setupTransitRoutingServer() {
}
if (OTPFeature.SandboxAPIGeocoder.isOn()) {
- LOG.info("Creating debug client geocoder lucene index");
- LuceneIndex.forServer(createServerContext());
+ LOG.info("Initializing geocoder");
+ // eagerly initialize the geocoder
+ this.factory.luceneIndex();
}
}
@@ -202,7 +203,7 @@ public static void creatTransitLayerForRaptor(
TransitModel transitModel,
TransitTuningParameters tuningParameters
) {
- if (!transitModel.hasTransit() || transitModel.getTransitModelIndex() == null) {
+ if (!transitModel.hasTransit() || !transitModel.isIndexed()) {
LOG.warn(
"Cannot create Raptor data, that requires the graph to have transit data and be indexed."
);
@@ -211,10 +212,7 @@ public static void creatTransitLayerForRaptor(
transitModel.setTransitLayer(TransitLayerMapper.map(tuningParameters, transitModel));
transitModel.setRealtimeTransitLayer(new TransitLayer(transitModel.getTransitLayer()));
transitModel.setTransitLayerUpdater(
- new TransitLayerUpdater(
- transitModel,
- transitModel.getTransitModelIndex().getServiceCodesRunningForDate()
- )
+ new TransitLayerUpdater(new DefaultTransitService(transitModel))
);
}
diff --git a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java
index fe95fe0447d..b307776ef52 100644
--- a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java
+++ b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java
@@ -6,6 +6,8 @@
import javax.annotation.Nullable;
import org.opentripplanner.ext.emissions.EmissionsDataModel;
import org.opentripplanner.ext.emissions.EmissionsServiceModule;
+import org.opentripplanner.ext.geocoder.LuceneIndex;
+import org.opentripplanner.ext.geocoder.configure.GeocoderModule;
import org.opentripplanner.ext.interactivelauncher.configuration.InteractiveLauncherModule;
import org.opentripplanner.ext.ridehailing.configure.RideHailingServicesModule;
import org.opentripplanner.ext.stopconsolidation.StopConsolidationRepository;
@@ -56,6 +58,7 @@
StopConsolidationServiceModule.class,
InteractiveLauncherModule.class,
StreetLimitationParametersServiceModule.class,
+ GeocoderModule.class,
}
)
public interface ConstructApplicationFactory {
@@ -87,6 +90,9 @@ public interface ConstructApplicationFactory {
StreetLimitationParameters streetLimitationParameters();
+ @Nullable
+ LuceneIndex luceneIndex();
+
@Component.Builder
interface Builder {
@BindsInstance
diff --git a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java
index eb244ce726c..6c830054c49 100644
--- a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java
+++ b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java
@@ -7,6 +7,7 @@
import javax.annotation.Nullable;
import org.opentripplanner.astar.spi.TraverseVisitor;
import org.opentripplanner.ext.emissions.EmissionsService;
+import org.opentripplanner.ext.geocoder.LuceneIndex;
import org.opentripplanner.ext.interactivelauncher.api.LauncherRequestDecorator;
import org.opentripplanner.ext.ridehailing.RideHailingService;
import org.opentripplanner.ext.stopconsolidation.StopConsolidationService;
@@ -40,7 +41,8 @@ OtpServerRequestContext providesServerContext(
StreetLimitationParametersService streetLimitationParametersService,
@Nullable TraverseVisitor, ?> traverseVisitor,
EmissionsService emissionsService,
- LauncherRequestDecorator launcherRequestDecorator
+ LauncherRequestDecorator launcherRequestDecorator,
+ @Nullable LuceneIndex luceneIndex
) {
var defaultRequest = launcherRequestDecorator.intercept(routerConfig.routingRequestDefaults());
@@ -60,7 +62,8 @@ OtpServerRequestContext providesServerContext(
rideHailingServices,
stopConsolidationService,
streetLimitationParametersService,
- traverseVisitor
+ traverseVisitor,
+ luceneIndex
);
}
diff --git a/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java b/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java
index 7a4ccea9247..0e81193d787 100644
--- a/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java
+++ b/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java
@@ -7,6 +7,7 @@
import org.opentripplanner.astar.spi.TraverseVisitor;
import org.opentripplanner.ext.emissions.EmissionsService;
import org.opentripplanner.ext.flex.FlexParameters;
+import org.opentripplanner.ext.geocoder.LuceneIndex;
import org.opentripplanner.ext.ridehailing.RideHailingService;
import org.opentripplanner.ext.stopconsolidation.StopConsolidationService;
import org.opentripplanner.inspector.raster.TileRendererManager;
@@ -49,6 +50,7 @@ public class DefaultServerRequestContext implements OtpServerRequestContext {
private final EmissionsService emissionsService;
private final StopConsolidationService stopConsolidationService;
private final StreetLimitationParametersService streetLimitationParametersService;
+ private final LuceneIndex luceneIndex;
/**
* Make sure all mutable components are copied/cloned before calling this constructor.
@@ -70,7 +72,8 @@ private DefaultServerRequestContext(
StopConsolidationService stopConsolidationService,
StreetLimitationParametersService streetLimitationParametersService,
FlexParameters flexParameters,
- TraverseVisitor traverseVisitor
+ TraverseVisitor traverseVisitor,
+ @Nullable LuceneIndex luceneIndex
) {
this.graph = graph;
this.transitService = transitService;
@@ -89,6 +92,7 @@ private DefaultServerRequestContext(
this.emissionsService = emissionsService;
this.stopConsolidationService = stopConsolidationService;
this.streetLimitationParametersService = streetLimitationParametersService;
+ this.luceneIndex = luceneIndex;
}
/**
@@ -110,7 +114,8 @@ public static DefaultServerRequestContext create(
List rideHailingServices,
@Nullable StopConsolidationService stopConsolidationService,
StreetLimitationParametersService streetLimitationParametersService,
- @Nullable TraverseVisitor traverseVisitor
+ @Nullable TraverseVisitor traverseVisitor,
+ @Nullable LuceneIndex luceneIndex
) {
return new DefaultServerRequestContext(
graph,
@@ -129,7 +134,8 @@ public static DefaultServerRequestContext create(
stopConsolidationService,
streetLimitationParametersService,
flexParameters,
- traverseVisitor
+ traverseVisitor,
+ luceneIndex
);
}
@@ -235,6 +241,12 @@ public VectorTileConfig vectorTileConfig() {
return vectorTileConfig;
}
+ @Nullable
+ @Override
+ public LuceneIndex lucenceIndex() {
+ return luceneIndex;
+ }
+
@Override
public EmissionsService emissionsService() {
return emissionsService;
diff --git a/src/main/java/org/opentripplanner/street/model/edge/StreetVehicleParkingLink.java b/src/main/java/org/opentripplanner/street/model/edge/StreetVehicleParkingLink.java
index e682a7bfac1..2ad9d0f39c4 100644
--- a/src/main/java/org/opentripplanner/street/model/edge/StreetVehicleParkingLink.java
+++ b/src/main/java/org/opentripplanner/street/model/edge/StreetVehicleParkingLink.java
@@ -2,6 +2,7 @@
import javax.annotation.Nonnull;
import org.locationtech.jts.geom.LineString;
+import org.opentripplanner.framework.geometry.GeometryUtils;
import org.opentripplanner.framework.i18n.I18NString;
import org.opentripplanner.framework.tostring.ToStringBuilder;
import org.opentripplanner.routing.api.request.preference.VehicleParkingPreferences;
@@ -95,11 +96,8 @@ public I18NString getName() {
return vehicleParkingEntranceVertex.getName();
}
+ @Override
public LineString getGeometry() {
- return null;
- }
-
- public double getDistanceMeters() {
- return 0;
+ return GeometryUtils.makeLineString(fromv.getCoordinate(), tov.getCoordinate());
}
}
diff --git a/src/main/java/org/opentripplanner/street/model/vertex/VehicleParkingEntranceVertex.java b/src/main/java/org/opentripplanner/street/model/vertex/VehicleParkingEntranceVertex.java
index a3ab0aa17b0..ba7a1540715 100644
--- a/src/main/java/org/opentripplanner/street/model/vertex/VehicleParkingEntranceVertex.java
+++ b/src/main/java/org/opentripplanner/street/model/vertex/VehicleParkingEntranceVertex.java
@@ -1,10 +1,12 @@
package org.opentripplanner.street.model.vertex;
+import java.util.Collection;
import java.util.Objects;
import javax.annotation.Nonnull;
import org.opentripplanner.framework.i18n.I18NString;
import org.opentripplanner.routing.vehicle_parking.VehicleParking;
import org.opentripplanner.routing.vehicle_parking.VehicleParkingEntrance;
+import org.opentripplanner.street.model.edge.Edge;
import org.opentripplanner.street.model.edge.StreetVehicleParkingLink;
import org.opentripplanner.street.model.edge.VehicleParkingEdge;
@@ -49,4 +51,15 @@ public boolean isCarAccessible() {
public boolean isWalkAccessible() {
return parkingEntrance.isWalkAccessible();
}
+
+ /**
+ * Is this vertex already linked to the graph with a {@link StreetVehicleParkingLink}?
+ */
+ public boolean isLinkedToGraph() {
+ return hasLink(getIncoming()) || hasLink(getOutgoing());
+ }
+
+ private boolean hasLink(Collection edges) {
+ return edges.stream().anyMatch(StreetVehicleParkingLink.class::isInstance);
+ }
}
diff --git a/src/main/java/org/opentripplanner/transit/model/network/TripPattern.java b/src/main/java/org/opentripplanner/transit/model/network/TripPattern.java
index f9734c1cb1a..57c71d06113 100644
--- a/src/main/java/org/opentripplanner/transit/model/network/TripPattern.java
+++ b/src/main/java/org/opentripplanner/transit/model/network/TripPattern.java
@@ -331,17 +331,6 @@ public boolean isBoardAndAlightAt(int stopIndex, PickDrop value) {
/* METHODS THAT DELEGATE TO THE SCHEDULED TIMETABLE */
- // TODO: These should probably be deprecated. That would require grabbing the scheduled timetable,
- // and would avoid mistakes where real-time updates are accidentally not taken into account.
-
- public boolean stopPatternIsEqual(TripPattern other) {
- return stopPattern.equals(other.stopPattern);
- }
-
- public Trip getTrip(int tripIndex) {
- return scheduledTimetable.getTripTimes(tripIndex).getTrip();
- }
-
// TODO OTP2 this method modifies the state, it will be refactored in a subsequent step
/**
* Add the given tripTimes to this pattern's scheduled timetable, recording the corresponding trip
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/BinarySetOperator.java b/src/main/java/org/opentripplanner/transit/model/network/grouppriority/BinarySetOperator.java
similarity index 79%
rename from src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/BinarySetOperator.java
rename to src/main/java/org/opentripplanner/transit/model/network/grouppriority/BinarySetOperator.java
index 35e5b8c0918..4d8ce2c8fd7 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/BinarySetOperator.java
+++ b/src/main/java/org/opentripplanner/transit/model/network/grouppriority/BinarySetOperator.java
@@ -1,4 +1,4 @@
-package org.opentripplanner.routing.algorithm.raptoradapter.transit.request;
+package org.opentripplanner.transit.model.network.grouppriority;
/**
* Used to concatenate matches with either the logical "AND" or "OR" operator.
diff --git a/src/main/java/org/opentripplanner/transit/model/network/grouppriority/DefaultTransitGroupPriorityCalculator.java b/src/main/java/org/opentripplanner/transit/model/network/grouppriority/DefaultTransitGroupPriorityCalculator.java
new file mode 100644
index 00000000000..3e96cf18f95
--- /dev/null
+++ b/src/main/java/org/opentripplanner/transit/model/network/grouppriority/DefaultTransitGroupPriorityCalculator.java
@@ -0,0 +1,26 @@
+package org.opentripplanner.transit.model.network.grouppriority;
+
+import org.opentripplanner.raptor.api.model.DominanceFunction;
+import org.opentripplanner.raptor.api.request.RaptorTransitGroupPriorityCalculator;
+
+/**
+ * Implement {@link RaptorTransitGroupPriorityCalculator}.
+ */
+public final class DefaultTransitGroupPriorityCalculator
+ implements RaptorTransitGroupPriorityCalculator {
+
+ @Override
+ public int mergeInGroupId(int currentGroupIds, int boardingGroupId) {
+ return TransitGroupPriority32n.mergeInGroupId(currentGroupIds, boardingGroupId);
+ }
+
+ @Override
+ public DominanceFunction dominanceFunction() {
+ return TransitGroupPriority32n::dominate;
+ }
+
+ @Override
+ public String toString() {
+ return "DefaultTransitGroupCalculator{Using TGP32n}";
+ }
+}
diff --git a/src/main/java/org/opentripplanner/transit/model/network/grouppriority/EntityAdapter.java b/src/main/java/org/opentripplanner/transit/model/network/grouppriority/EntityAdapter.java
new file mode 100644
index 00000000000..760da3d87ca
--- /dev/null
+++ b/src/main/java/org/opentripplanner/transit/model/network/grouppriority/EntityAdapter.java
@@ -0,0 +1,16 @@
+package org.opentripplanner.transit.model.network.grouppriority;
+
+import org.opentripplanner.transit.model.basic.TransitMode;
+import org.opentripplanner.transit.model.framework.FeedScopedId;
+
+/**
+ * These are the keys used to group transit trips and trip-patterns. This is used to calculate a
+ * unique groupId based on the request config. We use the adapter pattern to be able to generate
+ * the groupId based on different input types (TripPattern and Trip).
+ */
+interface EntityAdapter {
+ TransitMode mode();
+ String subMode();
+ FeedScopedId agencyId();
+ FeedScopedId routeId();
+}
diff --git a/src/main/java/org/opentripplanner/transit/model/network/grouppriority/Matcher.java b/src/main/java/org/opentripplanner/transit/model/network/grouppriority/Matcher.java
new file mode 100644
index 00000000000..bb5b4075364
--- /dev/null
+++ b/src/main/java/org/opentripplanner/transit/model/network/grouppriority/Matcher.java
@@ -0,0 +1,9 @@
+package org.opentripplanner.transit.model.network.grouppriority;
+
+interface Matcher {
+ boolean match(EntityAdapter entity);
+
+ default boolean isEmpty() {
+ return false;
+ }
+}
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcher.java b/src/main/java/org/opentripplanner/transit/model/network/grouppriority/Matchers.java
similarity index 54%
rename from src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcher.java
rename to src/main/java/org/opentripplanner/transit/model/network/grouppriority/Matchers.java
index c017f2862ab..7e1e7e6853a 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcher.java
+++ b/src/main/java/org/opentripplanner/transit/model/network/grouppriority/Matchers.java
@@ -1,7 +1,7 @@
-package org.opentripplanner.routing.algorithm.raptoradapter.transit.request;
+package org.opentripplanner.transit.model.network.grouppriority;
-import static org.opentripplanner.routing.algorithm.raptoradapter.transit.request.BinarySetOperator.AND;
-import static org.opentripplanner.routing.algorithm.raptoradapter.transit.request.BinarySetOperator.OR;
+import static org.opentripplanner.transit.model.network.grouppriority.BinarySetOperator.AND;
+import static org.opentripplanner.transit.model.network.grouppriority.BinarySetOperator.OR;
import java.util.ArrayList;
import java.util.Arrays;
@@ -18,7 +18,6 @@
import org.opentripplanner.routing.api.request.request.filter.TransitGroupSelect;
import org.opentripplanner.transit.model.basic.TransitMode;
import org.opentripplanner.transit.model.framework.FeedScopedId;
-import org.opentripplanner.transit.model.network.TripPattern;
/**
* This class turns a {@link TransitGroupSelect} into a matcher.
@@ -28,49 +27,38 @@
* a `CompositeMatcher`. So, a new matcher is only created if the field in the
* select is present.
*/
-public abstract class PriorityGroupMatcher {
+final class Matchers {
- private static final PriorityGroupMatcher NOOP = new PriorityGroupMatcher() {
- @Override
- boolean match(TripPattern pattern) {
- return false;
- }
+ private static final Matcher NOOP = new EmptyMatcher();
- @Override
- boolean isEmpty() {
- return true;
- }
- };
-
- public static PriorityGroupMatcher of(TransitGroupSelect select) {
+ static Matcher of(TransitGroupSelect select) {
if (select.isEmpty()) {
return NOOP;
}
- List list = new ArrayList<>();
+ List list = new ArrayList<>();
if (!select.modes().isEmpty()) {
list.add(new ModeMatcher(select.modes()));
}
if (!select.subModeRegexp().isEmpty()) {
- list.add(
- new RegExpMatcher("SubMode", select.subModeRegexp(), p -> p.getNetexSubmode().name())
- );
+ list.add(new RegExpMatcher("SubMode", select.subModeRegexp(), EntityAdapter::subMode));
}
if (!select.agencyIds().isEmpty()) {
- list.add(new IdMatcher("Agency", select.agencyIds(), p -> p.getRoute().getAgency().getId()));
+ list.add(new IdMatcher("Agency", select.agencyIds(), EntityAdapter::agencyId));
}
if (!select.routeIds().isEmpty()) {
- list.add(new IdMatcher("Route", select.routeIds(), p -> p.getRoute().getId()));
+ list.add(new IdMatcher("Route", select.routeIds(), EntityAdapter::routeId));
}
return andOf(list);
}
- static PriorityGroupMatcher[] of(Collection selectors) {
+ @SuppressWarnings("unchecked")
+ static Matcher[] of(Collection selectors) {
return selectors
.stream()
- .map(PriorityGroupMatcher::of)
- .filter(Predicate.not(PriorityGroupMatcher::isEmpty))
- .toArray(PriorityGroupMatcher[]::new);
+ .map(Matchers::of)
+ .filter(Predicate.not(Matcher::isEmpty))
+ .toArray(Matcher[]::new);
}
private static String arrayToString(BinarySetOperator op, T[] values) {
@@ -81,9 +69,9 @@ private static String colToString(BinarySetOperator op, Collection values
return values.stream().map(Objects::toString).collect(Collectors.joining(" " + op + " "));
}
- private static PriorityGroupMatcher andOf(List list) {
+ private static Matcher andOf(List list) {
// Remove empty/noop matchers
- list = list.stream().filter(Predicate.not(PriorityGroupMatcher::isEmpty)).toList();
+ list = list.stream().filter(Predicate.not(Matcher::isEmpty)).toList();
if (list.isEmpty()) {
return NOOP;
@@ -94,13 +82,25 @@ private static PriorityGroupMatcher andOf(List list) {
return new AndMatcher(list);
}
- abstract boolean match(TripPattern pattern);
+ private static final class EmptyMatcher implements Matcher {
+
+ @Override
+ public boolean match(EntityAdapter entity) {
+ return false;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return true;
+ }
- boolean isEmpty() {
- return false;
+ @Override
+ public String toString() {
+ return "Empty";
+ }
}
- private static final class ModeMatcher extends PriorityGroupMatcher {
+ private static final class ModeMatcher implements Matcher {
private final Set modes;
@@ -109,8 +109,8 @@ public ModeMatcher(List modes) {
}
@Override
- boolean match(TripPattern pattern) {
- return modes.contains(pattern.getMode());
+ public boolean match(EntityAdapter entity) {
+ return modes.contains(entity.mode());
}
@Override
@@ -119,26 +119,26 @@ public String toString() {
}
}
- private static final class RegExpMatcher extends PriorityGroupMatcher {
+ private static final class RegExpMatcher implements Matcher {
private final String typeName;
- private final Pattern[] subModeRegexp;
- private final Function toValue;
+ private final Pattern[] patterns;
+ private final Function toValue;
public RegExpMatcher(
String typeName,
- List subModeRegexp,
- Function toValue
+ List regexps,
+ Function toValue
) {
this.typeName = typeName;
- this.subModeRegexp = subModeRegexp.stream().map(Pattern::compile).toArray(Pattern[]::new);
+ this.patterns = regexps.stream().map(Pattern::compile).toArray(Pattern[]::new);
this.toValue = toValue;
}
@Override
- boolean match(TripPattern pattern) {
- var value = toValue.apply(pattern);
- for (Pattern p : subModeRegexp) {
+ public boolean match(EntityAdapter entity) {
+ var value = toValue.apply(entity);
+ for (Pattern p : patterns) {
if (p.matcher(value).matches()) {
return true;
}
@@ -148,20 +148,20 @@ boolean match(TripPattern pattern) {
@Override
public String toString() {
- return typeName + "Regexp(" + arrayToString(OR, subModeRegexp) + ')';
+ return typeName + "Regexp(" + arrayToString(OR, patterns) + ')';
}
}
- private static final class IdMatcher extends PriorityGroupMatcher {
+ private static final class IdMatcher implements Matcher {
private final String typeName;
private final Set ids;
- private final Function idProvider;
+ private final Function idProvider;
public IdMatcher(
String typeName,
List