diff --git a/application/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java b/application/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java index c26ec7c636a..03695a30350 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java @@ -17,6 +17,7 @@ import org.opentripplanner.framework.model.Grams; import org.opentripplanner.model.StopTime; import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.model.plan.LegConstructionSupport; import org.opentripplanner.model.plan.ScheduledTransitLeg; import org.opentripplanner.model.plan.ScheduledTransitLegBuilder; import org.opentripplanner.model.plan.StreetLeg; @@ -146,6 +147,7 @@ private ScheduledTransitLeg createTransitLeg(Route route) { .withEndTime(TIME.plusMinutes(10)) .withServiceDate(TIME.toLocalDate()) .withZoneId(ZoneIds.BERLIN) + .withDistanceMeters(LegConstructionSupport.computeDistanceMeters(pattern, 0,2)) .build(); } } diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/impl/FareModelForTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/impl/FareModelForTest.java index ccd2303f366..eedf20b95d9 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/impl/FareModelForTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/impl/FareModelForTest.java @@ -3,10 +3,13 @@ import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.OTHER_FEED_AGENCY; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import java.util.List; import org.opentripplanner.ext.fares.model.FareAttribute; import org.opentripplanner.ext.fares.model.FareRuleSet; import org.opentripplanner.framework.geometry.WgsCoordinate; import org.opentripplanner.framework.i18n.I18NString; +import org.opentripplanner.model.fare.FareProduct; +import org.opentripplanner.model.fare.FareProductUse; import org.opentripplanner.transit.model.basic.Money; import org.opentripplanner.transit.model.basic.TransitMode; import org.opentripplanner.transit.model.framework.FeedScopedId; @@ -82,6 +85,16 @@ public class FareModelForTest { .setTransfers(1) .setAgency(OTHER_FEED_AGENCY.getId()) .build(); + public static final FareProduct FARE_PRODUCT = new FareProduct( + id("fp"), + "fare product", + Money.euros(10.00f), + null, + null, + null + ); + public static final FareProductUse FARE_PRODUCT_USE = + new FareProductUse("c1a04702-1fb6-32d4-ba02-483bf68111ed", FARE_PRODUCT); // Fare rule sets static FareRuleSet AIRPORT_TO_CITY_CENTER_SET = new FareRuleSet(TEN_DOLLARS); diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexibleTransitLegBuilderTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexibleTransitLegBuilderTest.java new file mode 100644 index 00000000000..c9571c597a6 --- /dev/null +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexibleTransitLegBuilderTest.java @@ -0,0 +1,85 @@ +package org.opentripplanner.ext.flex; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.opentripplanner.ext.fares.impl.FareModelForTest.FARE_PRODUCT_USE; +import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; + +import java.time.Duration; +import java.time.LocalDate; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Set; +import org.junit.jupiter.api.Test; +import org.opentripplanner.ext.flex.edgetype.FlexTripEdge; +import org.opentripplanner.ext.flex.flexpathcalculator.FlexPath; +import org.opentripplanner.framework.geometry.GeometryUtils; +import org.opentripplanner.framework.i18n.I18NString; +import org.opentripplanner.model.plan.PlanTestConstants; +import org.opentripplanner.routing.alertpatch.TransitAlert; +import org.opentripplanner.street.model._data.StreetModelForTest; + +class FlexibleTransitLegBuilderTest implements PlanTestConstants { + + private static final FlexTripEdge EDGE = new FlexTripEdge( + StreetModelForTest.intersectionVertex(1,1), + StreetModelForTest.intersectionVertex(2,2), + A.stop, + B.stop, + null, + 1, + 2, + LocalDate.of(2025, 1, 15), + new FlexPath(1000, 600, () -> GeometryUtils.makeLineString(1,1,2,2)) + ); + private static final TransitAlert ALERT = TransitAlert.of(id("alert")).withHeaderText(I18NString.of("alert 1")).build(); + private static final Duration TIME_SHIFT = Duration.ofHours(5); + private static final ZonedDateTime START_TIME = ZonedDateTime.parse( + "2025-01-14T14:01:21+01:00" + ); + private static final ZonedDateTime END_TIME = START_TIME.plusHours(3); + + @Test + void listsAreInitialized(){ + var leg = new FlexibleTransitLegBuilder().withStartTime(START_TIME).withEndTime(END_TIME).withFlexTripEdge(EDGE).build(); + assertNotNull(leg.fareProducts()); + assertNotNull(leg.getTransitAlerts()); + } + + @Test + void everythingIsNonNull(){ + var expectedType = RuntimeException.class; + assertThrows(expectedType,()-> new FlexibleTransitLegBuilder().withStartTime(null).build()); + assertThrows(expectedType,()-> new FlexibleTransitLegBuilder().withEndTime(null).build()); + assertThrows(expectedType,()-> new FlexibleTransitLegBuilder().withFlexTripEdge(null).build()); + assertThrows(expectedType,()-> new FlexibleTransitLegBuilder().withAlerts(null).build()); + assertThrows(expectedType,()-> new FlexibleTransitLegBuilder().withFareProducts(null).build()); + } + + @Test + void copy(){ + var leg = new FlexibleTransitLegBuilder().withStartTime(START_TIME).withEndTime(END_TIME).withFlexTripEdge(EDGE) + .withFareProducts(List.of(FARE_PRODUCT_USE)).withAlerts(Set.of(ALERT)).build(); + + var copy = leg.copy().build(); + + assertEquals(copy.flexTripEdge(), EDGE); + assertEquals(copy.getStartTime(), START_TIME); + assertEquals(copy.getEndTime(), END_TIME); + assertEquals(copy.getTransitAlerts(), Set.of(ALERT)); + assertEquals(copy.fareProducts(), List.of(FARE_PRODUCT_USE)); + } + + @Test + void timeShift(){ + var leg = new FlexibleTransitLegBuilder().withStartTime(START_TIME).withEndTime(END_TIME).withFlexTripEdge(EDGE).withFareProducts(List.of(FARE_PRODUCT_USE)).withAlerts(Set.of(ALERT)).build(); + + var shifted = leg.withTimeShift(TIME_SHIFT); + + assertEquals(START_TIME.plus(TIME_SHIFT), shifted.getStartTime()); + assertEquals(END_TIME.plus(TIME_SHIFT), shifted.getEndTime()); + assertEquals(List.of(FARE_PRODUCT_USE), shifted.fareProducts()); + assertEquals(Set.of(ALERT), shifted.getTransitAlerts()); + } +} \ No newline at end of file diff --git a/application/src/ext-test/java/org/opentripplanner/ext/stopconsolidation/DecorateConsolidatedStopNamesTest.java b/application/src/ext-test/java/org/opentripplanner/ext/stopconsolidation/DecorateConsolidatedStopNamesTest.java index bcbc84fc4b8..fc93c5e3292 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/stopconsolidation/DecorateConsolidatedStopNamesTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/stopconsolidation/DecorateConsolidatedStopNamesTest.java @@ -1,41 +1,29 @@ package org.opentripplanner.ext.stopconsolidation; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.ext.fares.impl.FareModelForTest.FARE_PRODUCT_USE; import static org.opentripplanner.ext.stopconsolidation.TestStopConsolidationModel.STOP_C; import static org.opentripplanner.ext.stopconsolidation.TestStopConsolidationModel.STOP_D; import static org.opentripplanner.model.plan.PlanTestConstants.T11_05; import static org.opentripplanner.model.plan.PlanTestConstants.T11_12; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.Test; +import org.opentripplanner.ext.fares.impl.FareModelForTest; import org.opentripplanner.ext.stopconsolidation.internal.DefaultStopConsolidationRepository; import org.opentripplanner.ext.stopconsolidation.internal.DefaultStopConsolidationService; import org.opentripplanner.ext.stopconsolidation.model.ConsolidatedStopGroup; import org.opentripplanner.ext.stopconsolidation.model.ConsolidatedStopLeg; -import org.opentripplanner.model.fare.FareProduct; -import org.opentripplanner.model.fare.FareProductUse; import org.opentripplanner.model.plan.Leg; import org.opentripplanner.model.plan.Place; import org.opentripplanner.model.plan.PlanTestConstants; import org.opentripplanner.model.plan.ScheduledTransitLeg; import org.opentripplanner.model.plan.StreetLeg; import org.opentripplanner.model.plan.TestItineraryBuilder; -import org.opentripplanner.transit.model.basic.Money; class DecorateConsolidatedStopNamesTest { - private static final FareProduct fp = new FareProduct( - id("fp"), - "fare product", - Money.euros(10.00f), - null, - null, - null - ); - private static final List fpu = List.of( - new FareProductUse("c1a04702-1fb6-32d4-ba02-483bf68111ed", fp) - ); private static final List GROUPS = List.of( new ConsolidatedStopGroup(STOP_C.getId(), List.of(STOP_D.getId())) ); @@ -52,7 +40,12 @@ void changeNames() { .bus(1, T11_05, T11_12, PlanTestConstants.F) .build(); - itinerary.getLegs().getFirst().setFareProducts(fpu); + var first = (ScheduledTransitLeg)itinerary.getLegs().getFirst(); + var withFp = first.copy().withFareProducts(List.of(FARE_PRODUCT_USE)).build(); + var legs = new ArrayList<>(itinerary.getLegs()); + legs.set(0, withFp); + + itinerary.setLegs(legs); filter.decorate(itinerary); @@ -61,7 +54,7 @@ void changeNames() { assertEquals(STOP_D.getName(), updatedLeg.getTo().name); // Check that the fares were carried over - assertEquals(fpu, updatedLeg.fareProducts()); + assertEquals(List.of(FARE_PRODUCT_USE), updatedLeg.fareProducts()); } @Test diff --git a/application/src/ext-test/java/org/opentripplanner/ext/stopconsolidation/model/ConsolidatedStopLegBuilderTest.java b/application/src/ext-test/java/org/opentripplanner/ext/stopconsolidation/model/ConsolidatedStopLegBuilderTest.java new file mode 100644 index 00000000000..0dadc2017d4 --- /dev/null +++ b/application/src/ext-test/java/org/opentripplanner/ext/stopconsolidation/model/ConsolidatedStopLegBuilderTest.java @@ -0,0 +1,85 @@ +package org.opentripplanner.ext.stopconsolidation.model; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.ext.fares.impl.FareModelForTest.FARE_PRODUCT_USE; +import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; + +import java.time.LocalDate; +import java.util.List; +import java.util.Set; +import org.junit.jupiter.api.Test; +import org.opentripplanner._support.time.ZoneIds; +import org.opentripplanner.framework.i18n.I18NString; +import org.opentripplanner.model.fare.FareProductUse; +import org.opentripplanner.model.plan.PlanTestConstants; +import org.opentripplanner.model.plan.ScheduledTransitLeg; +import org.opentripplanner.model.plan.ScheduledTransitLegBuilder; +import org.opentripplanner.routing.alertpatch.TransitAlert; +import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; +import org.opentripplanner.transit.model.basic.TransitMode; +import org.opentripplanner.transit.model.network.TripPattern; + +class ConsolidatedStopLegBuilderTest implements PlanTestConstants{ + private static final Set ALERTS = Set.of(TransitAlert + .of(id("alert")) + .withDescriptionText(I18NString.of("alert")) + .build()); + private static final TripPattern PATTERN = TimetableRepositoryForTest + .of() + .pattern(TransitMode.BUS) + .build(); + private static final ScheduledTransitLeg SCHEDULED_TRANSIT_LEG = new ScheduledTransitLegBuilder<>() + .withZoneId(ZoneIds.BERLIN) + .withServiceDate(LocalDate.of(2025, 1, 15)) + .withTripPattern(PATTERN) + .withBoardStopIndexInPattern(0) + .withDistanceMeters(1000) + .withAlightStopIndexInPattern(1).build(); + private static final List FARES = List.of(FARE_PRODUCT_USE); + + @Test + void build(){ + var leg = new ConsolidatedStopLegBuilder(SCHEDULED_TRANSIT_LEG).withFrom(E.stop).withTo(F.stop).build(); + assertEquals(E.stop, leg.getFrom().stop); + assertEquals(F.stop, leg.getTo().stop); + } + + @Test + void copyAttributesFromConsolidatedStopLeg(){ + var leg = new ConsolidatedStopLegBuilder(SCHEDULED_TRANSIT_LEG).withFrom(E.stop).withTo(F.stop).build(); + + var copy = leg.copy().withAccessibilityScore(4f).withFareProducts(FARES).withAlerts(Set.of(ALERTS)).build(); + + assertEquals(leg.getFrom().stop, copy.getFrom().stop); + assertEquals(leg.getTo().stop, copy.getTo().stop); + assertEquals(Set.of(ALERTS), copy.getTransitAlerts()); + assertEquals(FARES, copy.fareProducts()); + assertEquals(ZoneIds.BERLIN, copy.getZoneId()); + + } + + @Test + void copyConsolidatedLeg(){ + var leg = new ConsolidatedStopLegBuilder(SCHEDULED_TRANSIT_LEG).withFrom(E.stop).withTo(F.stop).withAlerts(ALERTS).build(); + + var copy = leg.copy().build(); + + assertEquals(E.stop, copy.getFrom().stop); + assertEquals(F.stop, copy.getTo().stop); + assertEquals(ALERTS, copy.getTransitAlerts()); + } + + @Test + void copyAttributesFromScheduledLeg(){ + var leg = SCHEDULED_TRANSIT_LEG.copy().withFareProducts(FARES).withAlerts(Set.of(ALERTS)).build(); + + var copy = new ConsolidatedStopLegBuilder(leg).withFrom(C.stop).withTo(G.stop).build(); + + assertEquals(C.stop, copy.getFrom().stop); + assertEquals(G.stop, copy.getTo().stop); + assertEquals(Set.of(ALERTS), copy.getTransitAlerts()); + assertEquals(FARES, copy.fareProducts()); + assertEquals(ZoneIds.BERLIN, copy.getZoneId()); + + } +} \ No newline at end of file diff --git a/application/src/ext/java/org/opentripplanner/ext/accessibilityscore/DecorateWithAccessibilityScore.java b/application/src/ext/java/org/opentripplanner/ext/accessibilityscore/DecorateWithAccessibilityScore.java index d95f2c4c0cd..aaa41506e2b 100644 --- a/application/src/ext/java/org/opentripplanner/ext/accessibilityscore/DecorateWithAccessibilityScore.java +++ b/application/src/ext/java/org/opentripplanner/ext/accessibilityscore/DecorateWithAccessibilityScore.java @@ -115,7 +115,7 @@ private Itinerary addAccessibilityScore(Itinerary i) { .stream() .map(leg -> { if (leg instanceof ScheduledTransitLeg transitLeg) { - return transitLeg.withAccessibilityScore(compute(transitLeg)); + return transitLeg.copy().withAccessibilityScore(compute(transitLeg)).build(); } else if (leg instanceof StreetLeg streetLeg && leg.isWalkingLeg()) { return streetLeg.withAccessibilityScore(compute(streetLeg)); } else { diff --git a/application/src/ext/java/org/opentripplanner/ext/fares/FaresToItineraryMapper.java b/application/src/ext/java/org/opentripplanner/ext/fares/FaresToItineraryMapper.java index b936e458c00..a9e09d4466b 100644 --- a/application/src/ext/java/org/opentripplanner/ext/fares/FaresToItineraryMapper.java +++ b/application/src/ext/java/org/opentripplanner/ext/fares/FaresToItineraryMapper.java @@ -2,7 +2,9 @@ import org.opentripplanner.model.fare.FareProductUse; import org.opentripplanner.model.fare.ItineraryFares; +import org.opentripplanner.model.plan.FareProductAware; import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.model.plan.TransitLeg; import org.opentripplanner.utils.collection.ListUtils; /** @@ -22,8 +24,12 @@ public static void addFaresToLegs(ItineraryFares fares, Itinerary i) { i.transformTransitLegs(leg -> { var legUses = fares.getLegProducts().get(leg); - leg.setFareProducts(ListUtils.combine(itineraryFareUses, legUses)); - return leg; + var allUses = ListUtils.combine(itineraryFareUses, legUses); + if(leg instanceof FareProductAware fpa) { + return fpa.decorateWithFareProducts(allUses); + } else { + return leg; + } }); } } diff --git a/application/src/ext/java/org/opentripplanner/ext/fares/impl/CombinedInterlinedTransitLeg.java b/application/src/ext/java/org/opentripplanner/ext/fares/impl/CombinedInterlinedTransitLeg.java index 60d03e484df..22dcec38706 100644 --- a/application/src/ext/java/org/opentripplanner/ext/fares/impl/CombinedInterlinedTransitLeg.java +++ b/application/src/ext/java/org/opentripplanner/ext/fares/impl/CombinedInterlinedTransitLeg.java @@ -11,8 +11,10 @@ import org.opentripplanner.model.plan.Leg; import org.opentripplanner.model.plan.LegCallTime; import org.opentripplanner.model.plan.Place; +import org.opentripplanner.model.plan.ScheduledTransitLegBuilder; import org.opentripplanner.model.plan.StopArrival; import org.opentripplanner.model.plan.TransitLeg; +import org.opentripplanner.routing.alertpatch.TransitAlert; import org.opentripplanner.transit.model.basic.TransitMode; import org.opentripplanner.transit.model.network.Route; import org.opentripplanner.transit.model.organization.Agency; @@ -100,6 +102,11 @@ public LineString getLegGeometry() { return null; } + @Override + public Set getTransitAlerts() { + return Set.of(); + } + @Override public int getGeneralizedCost() { if (first.getGeneralizedCost() == UNKNOWN) { @@ -119,9 +126,6 @@ public Set getFareZones() { return fareZones; } - @Override - public void setFareProducts(List products) {} - @Override public List fareProducts() { return List.of(); @@ -133,4 +137,14 @@ public List fareProducts() { public List originalLegs() { return List.of(first, second); } + + @Override + public TransitLeg decorateWithAlerts(Set alerts) { + throw new UnsupportedOperationException(); + } + + @Override + public TransitLeg decorateWithFareProducts(List fares) { + throw new UnsupportedOperationException(); + } } diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/FlexibleTransitLeg.java b/application/src/ext/java/org/opentripplanner/ext/flex/FlexibleTransitLeg.java index 769407aa05a..eddf2969a19 100644 --- a/application/src/ext/java/org/opentripplanner/ext/flex/FlexibleTransitLeg.java +++ b/application/src/ext/java/org/opentripplanner/ext/flex/FlexibleTransitLeg.java @@ -3,8 +3,8 @@ import java.time.Duration; import java.time.LocalDate; import java.time.ZonedDateTime; -import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Set; import javax.annotation.Nullable; import org.locationtech.jts.geom.LineString; @@ -41,23 +41,27 @@ public class FlexibleTransitLeg implements TransitLeg { private final ZonedDateTime endTime; - private final Set transitAlerts = new HashSet<>(); + private final Set transitAlerts; private final int generalizedCost; - private List fareProducts; + private final List fareProducts; - public FlexibleTransitLeg( - FlexTripEdge flexTripEdge, - ZonedDateTime startTime, - ZonedDateTime endTime, - int generalizedCost + FlexibleTransitLeg( + FlexibleTransitLegBuilder builder ) { - this.edge = flexTripEdge; - - this.startTime = startTime; - this.endTime = endTime; + this.edge = Objects.requireNonNull(builder.flexTripEdge()); + this.startTime = Objects.requireNonNull(builder.startTime()); + this.endTime = Objects.requireNonNull(builder.endTime()); + this.generalizedCost = builder.generalizedCost(); + this.transitAlerts = Set.copyOf(builder.alerts()); + this.fareProducts = List.copyOf(builder.fareProducts()); + } - this.generalizedCost = generalizedCost; + /** + * Return an empty builder for {@link FlexibleTransitLeg}. + */ + public static FlexibleTransitLegBuilder of() { + return new FlexibleTransitLegBuilder(); } @Override @@ -161,6 +165,16 @@ public Set getTransitAlerts() { return transitAlerts; } + @Override + public TransitLeg decorateWithAlerts(Set alerts) { + return copy().withAlerts(alerts).build(); + } + + @Override + public TransitLeg decorateWithFareProducts(List fares) { + return copy().withFareProducts(fares).build(); + } + @Override public PickDrop getBoardRule() { return edge.getFlexTrip().getBoardRule(getBoardStopPosInPattern()); @@ -196,30 +210,9 @@ public int getGeneralizedCost() { return generalizedCost; } - @Override - public void addAlert(TransitAlert alert) { - transitAlerts.add(alert); - } - @Override public Leg withTimeShift(Duration duration) { - FlexibleTransitLeg copy = new FlexibleTransitLeg( - edge, - startTime.plus(duration), - endTime.plus(duration), - generalizedCost - ); - - for (TransitAlert alert : transitAlerts) { - copy.addAlert(alert); - } - - return copy; - } - - @Override - public void setFareProducts(List products) { - this.fareProducts = List.copyOf(products); + return copy().withStartTime(startTime.plus(duration)).withEndTime(endTime.plus(duration)).build(); } @Override @@ -227,6 +220,10 @@ public List fareProducts() { return fareProducts; } + public FlexibleTransitLegBuilder copy() { + return new FlexibleTransitLegBuilder(this); + } + /** * Should be used for debug logging only */ @@ -254,4 +251,9 @@ public String toString() { .addObj("dropOffBookingInfo", getDropOffBookingInfo()) .toString(); } + + FlexTripEdge flexTripEdge() { + return edge; + } + } diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/FlexibleTransitLegBuilder.java b/application/src/ext/java/org/opentripplanner/ext/flex/FlexibleTransitLegBuilder.java new file mode 100644 index 00000000000..abd4d9858b1 --- /dev/null +++ b/application/src/ext/java/org/opentripplanner/ext/flex/FlexibleTransitLegBuilder.java @@ -0,0 +1,89 @@ +package org.opentripplanner.ext.flex; + +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.opentripplanner.ext.flex.edgetype.FlexTripEdge; +import org.opentripplanner.model.fare.FareProductUse; +import org.opentripplanner.routing.alertpatch.TransitAlert; + +public class FlexibleTransitLegBuilder { + private FlexTripEdge flexTripEdge; + private ZonedDateTime startTime; + private ZonedDateTime endTime; + private int generalizedCost; + private Set transitAlerts = new HashSet<>(); + private List fareProducts = new ArrayList<>(); + + FlexibleTransitLegBuilder() {} + + FlexibleTransitLegBuilder(FlexibleTransitLeg original) { + flexTripEdge = original.flexTripEdge(); + startTime = original.getStartTime(); + endTime = original.getEndTime(); + generalizedCost = original.getGeneralizedCost(); + transitAlerts = original.getTransitAlerts(); + fareProducts = original.fareProducts(); + } + + public FlexibleTransitLegBuilder withFlexTripEdge(FlexTripEdge flexTripEdge) { + this.flexTripEdge = flexTripEdge; + return this; + } + public FlexTripEdge flexTripEdge() { + return flexTripEdge; + } + + public FlexibleTransitLegBuilder withStartTime(ZonedDateTime startTime) { + this.startTime = startTime; + return this; + } + + public ZonedDateTime startTime() { + return startTime; + } + + public FlexibleTransitLegBuilder withEndTime(ZonedDateTime endTime) { + this.endTime = endTime; + return this; + } + + public ZonedDateTime endTime() { + return endTime; + } + + public FlexibleTransitLegBuilder withGeneralizedCost(int generalizedCost) { + this.generalizedCost = generalizedCost; + return this; + } + + public int generalizedCost() { + return generalizedCost; + } + + public FlexibleTransitLegBuilder withAlerts(Collection alerts) { + this.transitAlerts = Set.copyOf(alerts); + return this; + } + + public Set alerts() { + return transitAlerts; + } + + public FlexibleTransitLegBuilder withFareProducts(List allUses) { + this.fareProducts = List.copyOf(allUses); + return this; + } + + public List fareProducts() { + return fareProducts; + } + + public FlexibleTransitLeg build() { + return new FlexibleTransitLeg(this); + } + +} \ No newline at end of file diff --git a/application/src/ext/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolver.java b/application/src/ext/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolver.java index d03ba1a2fc5..c12e131c398 100644 --- a/application/src/ext/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolver.java +++ b/application/src/ext/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolver.java @@ -54,13 +54,11 @@ private static Leg combineReferenceWithOriginal( ScheduledTransitLeg reference, ScheduledTransitLeg original ) { - var leg = new ScheduledTransitLegBuilder<>(reference) + return new ScheduledTransitLegBuilder<>(reference) .withTransferFromPreviousLeg(original.getTransferFromPrevLeg()) .withTransferToNextLeg(original.getTransferToNextLeg()) .withGeneralizedCost(original.getGeneralizedCost()) .withAccessibilityScore(original.accessibilityScore()) .build(); - reference.getTransitAlerts().forEach(leg::addAlert); - return leg; } } diff --git a/application/src/ext/java/org/opentripplanner/ext/stopconsolidation/DecorateConsolidatedStopNames.java b/application/src/ext/java/org/opentripplanner/ext/stopconsolidation/DecorateConsolidatedStopNames.java index 6def5c85fa1..b3569d2df3b 100644 --- a/application/src/ext/java/org/opentripplanner/ext/stopconsolidation/DecorateConsolidatedStopNames.java +++ b/application/src/ext/java/org/opentripplanner/ext/stopconsolidation/DecorateConsolidatedStopNames.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.Objects; import org.opentripplanner.ext.stopconsolidation.model.ConsolidatedStopLeg; +import org.opentripplanner.ext.stopconsolidation.model.ConsolidatedStopLegBuilder; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.Leg; import org.opentripplanner.model.plan.ScheduledTransitLeg; @@ -48,7 +49,7 @@ private void replaceConsolidatedStops(Itinerary i) { var from = service.primaryStop(stl.getFrom().stop.getId()).orElse(stl.getFrom().stop); // to show the name that's on the display inside the vehicle we use the agency-specific name var to = service.agencySpecificStop(stl.getTo().stop, agency); - return new ConsolidatedStopLeg(stl, from, to); + return ConsolidatedStopLeg.of(stl).withFrom(from).withTo(to).build(); } else { return leg; } diff --git a/application/src/ext/java/org/opentripplanner/ext/stopconsolidation/model/ConsolidatedStopLeg.java b/application/src/ext/java/org/opentripplanner/ext/stopconsolidation/model/ConsolidatedStopLeg.java index cf39b0d2bf1..03b1d021aad 100644 --- a/application/src/ext/java/org/opentripplanner/ext/stopconsolidation/model/ConsolidatedStopLeg.java +++ b/application/src/ext/java/org/opentripplanner/ext/stopconsolidation/model/ConsolidatedStopLeg.java @@ -8,23 +8,32 @@ public class ConsolidatedStopLeg extends ScheduledTransitLeg { - private final StopLocation from; - private final StopLocation to; - - public ConsolidatedStopLeg(ScheduledTransitLeg original, StopLocation from, StopLocation to) { - super(new ScheduledTransitLegBuilder<>(original)); - this.from = Objects.requireNonNull(from); - this.to = Objects.requireNonNull(to); - this.setFareProducts(original.fareProducts()); + private final Place from; + private final Place to; + + ConsolidatedStopLeg(ConsolidatedStopLegBuilder builder) { + super(builder); + this.from = Objects.requireNonNull(builder.from()); + this.to = Objects.requireNonNull(builder.to()); + } + + public static ConsolidatedStopLegBuilder of(ScheduledTransitLeg stl) { + return new ConsolidatedStopLegBuilder(stl); } @Override public Place getFrom() { - return Place.forStop(from); + return from; } @Override public Place getTo() { - return Place.forStop(to); + return to; } + + @Override + public ScheduledTransitLegBuilder copy() { + return new ConsolidatedStopLegBuilder(this); + } + } diff --git a/application/src/ext/java/org/opentripplanner/ext/stopconsolidation/model/ConsolidatedStopLegBuilder.java b/application/src/ext/java/org/opentripplanner/ext/stopconsolidation/model/ConsolidatedStopLegBuilder.java new file mode 100644 index 00000000000..82148f862a1 --- /dev/null +++ b/application/src/ext/java/org/opentripplanner/ext/stopconsolidation/model/ConsolidatedStopLegBuilder.java @@ -0,0 +1,46 @@ +package org.opentripplanner.ext.stopconsolidation.model; + +import org.opentripplanner.model.plan.Place; +import org.opentripplanner.model.plan.ScheduledTransitLeg; +import org.opentripplanner.model.plan.ScheduledTransitLegBuilder; +import org.opentripplanner.transit.model.site.StopLocation; + +public class ConsolidatedStopLegBuilder extends ScheduledTransitLegBuilder { + private Place from; + private Place to; + + ConsolidatedStopLegBuilder(ScheduledTransitLeg leg) { + super(leg); + } + + ConsolidatedStopLegBuilder(ConsolidatedStopLeg consolidatedStopLeg) { + super(consolidatedStopLeg); + this.from = consolidatedStopLeg.getFrom(); + this.to = consolidatedStopLeg.getTo(); + } + + public ConsolidatedStopLegBuilder withFrom(StopLocation stop) { + this.from = Place.forStop(stop); + return this; + } + + public Place from() { + return from; + } + + public ConsolidatedStopLegBuilder withTo(StopLocation stop) { + this.to = Place.forStop(stop); + return this; + } + + public Place to() { + return to; + } + + @Override + public ConsolidatedStopLeg build() { + return new ConsolidatedStopLeg(this); + } + + +} \ No newline at end of file diff --git a/application/src/main/java/org/opentripplanner/framework/geometry/GeometryUtils.java b/application/src/main/java/org/opentripplanner/framework/geometry/GeometryUtils.java index 85915cfeb48..1c036035f4f 100644 --- a/application/src/main/java/org/opentripplanner/framework/geometry/GeometryUtils.java +++ b/application/src/main/java/org/opentripplanner/framework/geometry/GeometryUtils.java @@ -282,4 +282,15 @@ public static Stream toEnvelopes(LineString ls) { return Arrays.stream(envelopes); } + + /** + * Returns the sum of the distances in between the pairs of coordinates in meters. + */ + public static double sumDistances(List coordinates) { + double distance = 0; + for (int i = 1; i < coordinates.size(); i++) { + distance += SphericalDistanceLibrary.distance(coordinates.get(i - 1), coordinates.get(i)); + } + return distance; + } } diff --git a/application/src/main/java/org/opentripplanner/model/plan/AlertsAware.java b/application/src/main/java/org/opentripplanner/model/plan/AlertsAware.java new file mode 100644 index 00000000000..2408e71c603 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/model/plan/AlertsAware.java @@ -0,0 +1,15 @@ +package org.opentripplanner.model.plan; + +import java.util.Set; +import org.opentripplanner.routing.alertpatch.TransitAlert; + +/** + * An interface to signal that an entity (typically a leg) can have alerts attached to it. + */ +public interface AlertsAware { + Set getTransitAlerts(); + /** + * Returns a copy of the entity with alerts added to it. + */ + T decorateWithAlerts(Set alerts); +} diff --git a/application/src/main/java/org/opentripplanner/model/plan/FareProductAware.java b/application/src/main/java/org/opentripplanner/model/plan/FareProductAware.java new file mode 100644 index 00000000000..14047288ef4 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/model/plan/FareProductAware.java @@ -0,0 +1,14 @@ +package org.opentripplanner.model.plan; + +import java.util.List; +import org.opentripplanner.model.fare.FareProductUse; + +/** + * An interface to signal that an entity (typically a leg) can have fares attached to it. + */ +public interface FareProductAware { + /** + * Returns a copy of the entity with fares added to it. + */ + T decorateWithFareProducts(List fares); +} diff --git a/application/src/main/java/org/opentripplanner/model/plan/FrequencyTransitLeg.java b/application/src/main/java/org/opentripplanner/model/plan/FrequencyTransitLeg.java index 6226b0d2b01..90996f18378 100644 --- a/application/src/main/java/org/opentripplanner/model/plan/FrequencyTransitLeg.java +++ b/application/src/main/java/org/opentripplanner/model/plan/FrequencyTransitLeg.java @@ -64,9 +64,4 @@ public List getIntermediateStops() { } return visits; } - - @Override - public ScheduledTransitLeg withAccessibilityScore(Float score) { - return new FrequencyTransitLegBuilder(this).withAccessibilityScore(score).build(); - } } diff --git a/application/src/main/java/org/opentripplanner/model/plan/Leg.java b/application/src/main/java/org/opentripplanner/model/plan/Leg.java index 27e67400daf..b5ec621a49a 100644 --- a/application/src/main/java/org/opentripplanner/model/plan/Leg.java +++ b/application/src/main/java/org/opentripplanner/model/plan/Leg.java @@ -381,9 +381,7 @@ default Set getStreetNotes() { return Set.of(); } - default Set getTransitAlerts() { - return Set.of(); - } + Set getTransitAlerts(); @Nullable default PickDrop getBoardRule() { @@ -485,10 +483,6 @@ default LegReference getLegReference() { return null; } - default void addAlert(TransitAlert alert) { - throw new UnsupportedOperationException(); - } - default Leg withTimeShift(Duration duration) { throw new UnsupportedOperationException(); } @@ -504,13 +498,6 @@ default Set getFareZones() { return Stream.of(intermediate, start, end).flatMap(s -> s).collect(Collectors.toSet()); } - /** - * Set {@link FareProductUse} for this leg. Their use-id can identify them across several - * legs. - */ - @Sandbox - void setFareProducts(List products); - /** * Get the {@link FareProductUse} for this leg. */ diff --git a/application/src/main/java/org/opentripplanner/model/plan/LegConstructionSupport.java b/application/src/main/java/org/opentripplanner/model/plan/LegConstructionSupport.java new file mode 100644 index 00000000000..a488cd2523d --- /dev/null +++ b/application/src/main/java/org/opentripplanner/model/plan/LegConstructionSupport.java @@ -0,0 +1,50 @@ +package org.opentripplanner.model.plan; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.locationtech.jts.geom.Coordinate; +import org.opentripplanner.framework.geometry.GeometryUtils; +import org.opentripplanner.transit.model.network.TripPattern; + +/** + * Utility methods for constructing legs. + */ +public class LegConstructionSupport { + + /** + * Given a pattern, board and alight stop index compute the distance in meters. + */ + public static double computeDistanceMeters( + TripPattern tripPattern, + int boardStopIndexInPattern, + int alightStopIndexInPattern + ) { + List transitLegCoordinates = extractTransitLegCoordinates( + tripPattern, + boardStopIndexInPattern, + alightStopIndexInPattern + ); + return GeometryUtils.sumDistances(transitLegCoordinates); + } + + /** + * Given a pattern, board and alight stop index compute the list of coordinates that this + * segment of the pattern visits. + */ + public static List extractTransitLegCoordinates( + TripPattern tripPattern, + int boardStopIndexInPattern, + int alightStopIndexInPattern + ) { + List transitLegCoordinates = new ArrayList<>(); + + for (int i = boardStopIndexInPattern + 1; i <= alightStopIndexInPattern; i++) { + transitLegCoordinates.addAll( + Arrays.asList(tripPattern.getHopGeometry(i - 1).getCoordinates()) + ); + } + + return transitLegCoordinates; + } +} diff --git a/application/src/main/java/org/opentripplanner/model/plan/ScheduledTransitLeg.java b/application/src/main/java/org/opentripplanner/model/plan/ScheduledTransitLeg.java index ab912eae85b..387cfd673ef 100644 --- a/application/src/main/java/org/opentripplanner/model/plan/ScheduledTransitLeg.java +++ b/application/src/main/java/org/opentripplanner/model/plan/ScheduledTransitLeg.java @@ -5,8 +5,6 @@ import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; @@ -14,7 +12,6 @@ import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.LineString; import org.opentripplanner.framework.geometry.GeometryUtils; -import org.opentripplanner.framework.geometry.SphericalDistanceLibrary; import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.model.PickDrop; import org.opentripplanner.model.fare.FareProductUse; @@ -36,6 +33,7 @@ import org.opentripplanner.transit.model.timetable.TripTimes; import org.opentripplanner.transit.model.timetable.booking.BookingInfo; import org.opentripplanner.utils.lang.DoubleUtils; +import org.opentripplanner.utils.lang.Sandbox; import org.opentripplanner.utils.time.ServiceDateUtils; import org.opentripplanner.utils.tostring.ToStringBuilder; @@ -51,19 +49,19 @@ public class ScheduledTransitLeg implements TransitLeg { private final ZonedDateTime startTime; private final ZonedDateTime endTime; private final LineString legGeometry; - private final Set transitAlerts = new HashSet<>(); + private final Set transitAlerts; private final ConstrainedTransfer transferFromPrevLeg; private final ConstrainedTransfer transferToNextLeg; - protected final Integer boardStopPosInPattern; - protected final Integer alightStopPosInPattern; + protected final int boardStopPosInPattern; + protected final int alightStopPosInPattern; private final int generalizedCost; protected final LocalDate serviceDate; protected final ZoneId zoneId; private final TripOnServiceDate tripOnServiceDate; - private double distanceMeters; + private final double distanceMeters; private final double directDistanceMeters; private final Float accessibilityScore; - private List fareProducts = List.of(); + private final List fareProducts; protected ScheduledTransitLeg(ScheduledTransitLegBuilder builder) { this.tripTimes = builder.tripTimes(); @@ -76,7 +74,7 @@ protected ScheduledTransitLeg(ScheduledTransitLegBuilder builder) { this.endTime = builder.endTime(); this.serviceDate = builder.serviceDate(); - this.zoneId = Objects.requireNonNull(builder.zoneId()); + this.zoneId = Objects.requireNonNull(builder.zoneId(), "zoneId"); this.tripOnServiceDate = builder.tripOnServiceDate(); @@ -86,19 +84,23 @@ protected ScheduledTransitLeg(ScheduledTransitLegBuilder builder) { this.generalizedCost = builder.generalizedCost(); this.accessibilityScore = builder.accessibilityScore(); - List transitLegCoordinates = extractTransitLegCoordinates( + List transitLegCoordinates = LegConstructionSupport.extractTransitLegCoordinates( tripPattern, builder.boardStopIndexInPattern(), builder.alightStopIndexInPattern() ); this.legGeometry = GeometryUtils.makeLineString(transitLegCoordinates); - setDistanceMeters(getDistanceFromCoordinates(transitLegCoordinates)); + this.distanceMeters = + DoubleUtils.roundTo2Decimals( + Objects.requireNonNull(builder.distanceMeters(), "distanceMeters") + ); this.directDistanceMeters = - getDistanceFromCoordinates( + GeometryUtils.sumDistances( List.of(transitLegCoordinates.getFirst(), transitLegCoordinates.getLast()) ); - this.transitAlerts.addAll(builder.alerts()); + this.transitAlerts = Set.copyOf(builder.alerts()); + this.fareProducts = List.copyOf(builder.fareProducts()); } public ZoneId getZoneId() { @@ -232,11 +234,6 @@ public double getDistanceMeters() { return distanceMeters; } - /** Only for testing purposes */ - protected void setDistanceMeters(double distanceMeters) { - this.distanceMeters = DoubleUtils.roundTo2Decimals(distanceMeters); - } - public double getDirectDistanceMeters() { return directDistanceMeters; } @@ -295,6 +292,16 @@ public Set getTransitAlerts() { return transitAlerts; } + @Override + public ScheduledTransitLeg decorateWithAlerts(Set alerts) { + return copy().withAlerts(alerts).build(); + } + + @Override + public TransitLeg decorateWithFareProducts(List fares) { + return copy().withFareProducts(fares).build(); + } + @Override @Nullable public PickDrop getBoardRule() { @@ -376,16 +383,6 @@ public LegReference getLegReference() { ); } - @Override - public void addAlert(TransitAlert alert) { - transitAlerts.add(alert); - } - - @Override - public void setFareProducts(List products) { - this.fareProducts = List.copyOf(products); - } - @Override public List fareProducts() { return fareProducts; @@ -393,12 +390,13 @@ public List fareProducts() { @Override @Nullable + @Sandbox public Float accessibilityScore() { return accessibilityScore; } - public ScheduledTransitLeg withAccessibilityScore(Float score) { - return new ScheduledTransitLegBuilder<>(this).withAccessibilityScore(score).build(); + public ScheduledTransitLegBuilder copy() { + return new ScheduledTransitLegBuilder<>(this); } /** @@ -438,28 +436,4 @@ public String toString() { private Trip trip() { return tripTimes.getTrip(); } - - private List extractTransitLegCoordinates( - TripPattern tripPattern, - int boardStopIndexInPattern, - int alightStopIndexInPattern - ) { - List transitLegCoordinates = new ArrayList<>(); - - for (int i = boardStopIndexInPattern + 1; i <= alightStopIndexInPattern; i++) { - transitLegCoordinates.addAll( - Arrays.asList(tripPattern.getHopGeometry(i - 1).getCoordinates()) - ); - } - - return transitLegCoordinates; - } - - private double getDistanceFromCoordinates(List coordinates) { - double distance = 0; - for (int i = 1; i < coordinates.size(); i++) { - distance += SphericalDistanceLibrary.distance(coordinates.get(i), coordinates.get(i - 1)); - } - return distance; - } } diff --git a/application/src/main/java/org/opentripplanner/model/plan/ScheduledTransitLegBuilder.java b/application/src/main/java/org/opentripplanner/model/plan/ScheduledTransitLegBuilder.java index e132137e982..a02cec02e83 100644 --- a/application/src/main/java/org/opentripplanner/model/plan/ScheduledTransitLegBuilder.java +++ b/application/src/main/java/org/opentripplanner/model/plan/ScheduledTransitLegBuilder.java @@ -3,8 +3,11 @@ import java.time.LocalDate; import java.time.ZoneId; import java.time.ZonedDateTime; -import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.OptionalDouble; import java.util.Set; +import org.opentripplanner.model.fare.FareProductUse; import org.opentripplanner.model.transfer.ConstrainedTransfer; import org.opentripplanner.routing.alertpatch.TransitAlert; import org.opentripplanner.transit.model.network.TripPattern; @@ -26,7 +29,9 @@ public class ScheduledTransitLegBuilder> private ConstrainedTransfer transferToNextLeg; private int generalizedCost; private Float accessibilityScore; - private Set alerts = new HashSet<>(); + private Set alerts = Set.of(); + private Double distanceMeters; + private List fareProducts = List.of(); public ScheduledTransitLegBuilder() {} @@ -45,6 +50,8 @@ public ScheduledTransitLegBuilder(ScheduledTransitLeg original) { accessibilityScore = original.accessibilityScore(); zoneId = original.getZoneId(); alerts = original.getTransitAlerts(); + distanceMeters = original.getDistanceMeters(); + fareProducts = original.fareProducts(); } public B withTripTimes(TripTimes tripTimes) { @@ -164,10 +171,33 @@ public Float accessibilityScore() { return accessibilityScore; } + public B withAlerts(Set alerts) { + this.alerts = Objects.requireNonNull(alerts); + return instance(); + } + public Set alerts() { return alerts; } + public B withDistanceMeters(double distance) { + this.distanceMeters = distance; + return instance(); + } + + public Double distanceMeters() { + return distanceMeters; + } + + public List fareProducts() { + return fareProducts; + } + + public B withFareProducts(List fareProducts) { + this.fareProducts = Objects.requireNonNull(fareProducts); + return instance(); + } + public ScheduledTransitLeg build() { return new ScheduledTransitLeg(this); } diff --git a/application/src/main/java/org/opentripplanner/model/plan/StreetLeg.java b/application/src/main/java/org/opentripplanner/model/plan/StreetLeg.java index cb577b9ec49..2e3e58a275c 100644 --- a/application/src/main/java/org/opentripplanner/model/plan/StreetLeg.java +++ b/application/src/main/java/org/opentripplanner/model/plan/StreetLeg.java @@ -8,6 +8,7 @@ import javax.annotation.Nullable; import org.locationtech.jts.geom.LineString; import org.opentripplanner.model.fare.FareProductUse; +import org.opentripplanner.routing.alertpatch.TransitAlert; import org.opentripplanner.street.model.note.StreetNote; import org.opentripplanner.street.search.TraverseMode; import org.opentripplanner.utils.lang.DoubleUtils; @@ -125,6 +126,11 @@ public Set getStreetNotes() { return streetNotes; } + @Override + public Set getTransitAlerts() { + return Set.of(); + } + @Override public Boolean getWalkingBike() { return walkingBike; @@ -175,11 +181,6 @@ public Leg withTimeShift(Duration duration) { .build(); } - @Override - public void setFareProducts(List products) { - throw new UnsupportedOperationException(); - } - @Override public List fareProducts() { return List.of(); diff --git a/application/src/main/java/org/opentripplanner/model/plan/TransitLeg.java b/application/src/main/java/org/opentripplanner/model/plan/TransitLeg.java index f7ac3e42544..d999544e33d 100644 --- a/application/src/main/java/org/opentripplanner/model/plan/TransitLeg.java +++ b/application/src/main/java/org/opentripplanner/model/plan/TransitLeg.java @@ -2,7 +2,7 @@ import org.opentripplanner.transit.model.basic.TransitMode; -public interface TransitLeg extends Leg { +public interface TransitLeg extends Leg, AlertsAware, FareProductAware { /** * The mode (e.g., BUS) used when traversing this leg. */ diff --git a/application/src/main/java/org/opentripplanner/model/plan/UnknownTransitPathLeg.java b/application/src/main/java/org/opentripplanner/model/plan/UnknownTransitPathLeg.java index 443621b750a..8027857e02e 100644 --- a/application/src/main/java/org/opentripplanner/model/plan/UnknownTransitPathLeg.java +++ b/application/src/main/java/org/opentripplanner/model/plan/UnknownTransitPathLeg.java @@ -4,10 +4,12 @@ import java.time.ZonedDateTime; import java.util.List; +import java.util.Set; import javax.annotation.Nullable; import org.locationtech.jts.geom.LineString; import org.opentripplanner.model.fare.FareProductUse; import org.opentripplanner.raptor.spi.RaptorCostCalculator; +import org.opentripplanner.routing.alertpatch.TransitAlert; import org.opentripplanner.utils.time.DurationUtils; import org.opentripplanner.utils.tostring.ToStringBuilder; @@ -91,13 +93,13 @@ public LineString getLegGeometry() { } @Override - public int getGeneralizedCost() { - return RaptorCostCalculator.ZERO_COST; + public Set getTransitAlerts() { + return Set.of(); } @Override - public void setFareProducts(List products) { - throw new UnsupportedOperationException(); + public int getGeneralizedCost() { + return RaptorCostCalculator.ZERO_COST; } @Override diff --git a/application/src/main/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReference.java b/application/src/main/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReference.java index 5defff73cec..eec95a8028c 100644 --- a/application/src/main/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReference.java +++ b/application/src/main/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReference.java @@ -4,6 +4,7 @@ import java.time.ZoneId; import javax.annotation.Nullable; import org.opentripplanner.model.Timetable; +import org.opentripplanner.model.plan.LegConstructionSupport; import org.opentripplanner.model.plan.ScheduledTransitLeg; import org.opentripplanner.model.plan.ScheduledTransitLegBuilder; import org.opentripplanner.routing.algorithm.mapping.AlertToLegMapper; @@ -171,17 +172,22 @@ public ScheduledTransitLeg getLeg(TransitService transitService) { .withServiceDate(serviceDate) .withTripOnServiceDate(tripOnServiceDate) .withZoneId(timeZone) + .withDistanceMeters( + LegConstructionSupport.computeDistanceMeters( + tripPattern, + fromStopPositionInPattern, + toStopPositionInPattern + ) + ) // TODO: What should we have here .withGeneralizedCost(0) .build(); - new AlertToLegMapper( + return (ScheduledTransitLeg) new AlertToLegMapper( transitService.getTransitAlertService(), transitService::findMultiModalStation ) - .addTransitAlertsToLeg(leg, false); - - return leg; + .decorateWithAlerts(leg, false); } /** diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/DecorateTransitAlert.java b/application/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/DecorateTransitAlert.java index ccc2c9b26f0..4926ba66029 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/DecorateTransitAlert.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/DecorateTransitAlert.java @@ -2,12 +2,12 @@ import java.util.function.Function; import org.opentripplanner.model.plan.Itinerary; -import org.opentripplanner.model.plan.Leg; import org.opentripplanner.routing.algorithm.filterchain.framework.spi.ItineraryDecorator; import org.opentripplanner.routing.algorithm.mapping.AlertToLegMapper; import org.opentripplanner.routing.services.TransitAlertService; import org.opentripplanner.transit.model.site.MultiModalStation; import org.opentripplanner.transit.model.site.Station; +import org.opentripplanner.utils.lang.Box; public class DecorateTransitAlert implements ItineraryDecorator { @@ -22,12 +22,15 @@ public DecorateTransitAlert( @Override public void decorate(Itinerary itinerary) { - boolean firstLeg = true; - for (Leg leg : itinerary.getLegs()) { + final var firstLeg = Box.of(true); + itinerary.transformTransitLegs(leg -> { if (leg.isTransitLeg()) { - alertToLegMapper.addTransitAlertsToLeg(leg, firstLeg); - firstLeg = false; + var l = alertToLegMapper.decorateWithAlerts(leg, firstLeg.get()); + firstLeg.set(false); + return l; + } else { + return leg; } - } + }); } } diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/AlertToLegMapper.java b/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/AlertToLegMapper.java index 94630be7600..1fceed4dc11 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/AlertToLegMapper.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/AlertToLegMapper.java @@ -5,10 +5,14 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.function.Function; -import org.opentripplanner.model.plan.Leg; +import org.opentripplanner.ext.flex.FlexibleTransitLeg; +import org.opentripplanner.model.plan.AlertsAware; +import org.opentripplanner.model.plan.ScheduledTransitLeg; import org.opentripplanner.model.plan.StopArrival; +import org.opentripplanner.model.plan.TransitLeg; import org.opentripplanner.routing.alertpatch.StopCondition; import org.opentripplanner.routing.alertpatch.TransitAlert; import org.opentripplanner.routing.services.TransitAlertService; @@ -36,17 +40,12 @@ public AlertToLegMapper( } /** - * Find and add alerts to the leg passed in. + * Takes the (immutable) leg and returns a copy of it with the alerts attached. * * @param isFirstLeg Whether the leg is a first leg of the itinerary. This affects the matched * stop condition. */ - public void addTransitAlertsToLeg(Leg leg, boolean isFirstLeg) { - // Alert alerts are only relevant for transit legs - if (!leg.isTransitLeg()) { - return; - } - + public TransitLeg decorateWithAlerts(TransitLeg leg, boolean isFirstLeg) { ZonedDateTime legStartTime = leg.getStartTime(); ZonedDateTime legEndTime = leg.getEndTime(); StopLocation fromStop = leg.getFrom() == null ? null : leg.getFrom().stop; @@ -56,6 +55,8 @@ public void addTransitAlertsToLeg(Leg leg, boolean isFirstLeg) { FeedScopedId tripId = leg.getTrip().getId(); LocalDate serviceDate = leg.getServiceDate(); + var totalAlerts = new HashSet(); + if (fromStop instanceof RegularStop stop) { Set stopConditions = isFirstLeg ? StopCondition.FIRST_DEPARTURE @@ -66,7 +67,7 @@ public void addTransitAlertsToLeg(Leg leg, boolean isFirstLeg) { alerts.addAll( getAlertsForRelatedStops(stop, id -> transitAlertService.getStopAlerts(id, stopConditions)) ); - addTransitAlertsToLeg(leg, alerts, legStartTime, legEndTime); + totalAlerts.addAll(filterAlertsByTime(alerts, legStartTime, legEndTime)); } if (toStop instanceof RegularStop stop) { Set stopConditions = StopCondition.ARRIVING; @@ -75,7 +76,7 @@ public void addTransitAlertsToLeg(Leg leg, boolean isFirstLeg) { alerts.addAll( getAlertsForRelatedStops(stop, id -> transitAlertService.getStopAlerts(id, stopConditions)) ); - addTransitAlertsToLeg(leg, alerts, legStartTime, legEndTime); + totalAlerts.addAll(filterAlertsByTime(alerts, legStartTime, legEndTime)); } if (leg.getIntermediateStops() != null) { @@ -94,7 +95,7 @@ public void addTransitAlertsToLeg(Leg leg, boolean isFirstLeg) { ZonedDateTime stopArrival = visit.arrival.scheduledTime(); ZonedDateTime stopDeparture = visit.departure.scheduledTime(); - addTransitAlertsToLeg(leg, alerts, stopArrival, stopDeparture); + totalAlerts.addAll(filterAlertsByTime(alerts, stopArrival, stopDeparture)); } } } @@ -103,40 +104,36 @@ public void addTransitAlertsToLeg(Leg leg, boolean isFirstLeg) { // trips alerts = transitAlertService.getTripAlerts(leg.getTrip().getId(), serviceDate); - addTransitAlertsToLeg(leg, alerts, legStartTime, legEndTime); + totalAlerts.addAll(filterAlertsByTime(alerts, legStartTime, legEndTime)); // route alerts = transitAlertService.getRouteAlerts(leg.getRoute().getId()); - addTransitAlertsToLeg(leg, alerts, legStartTime, legEndTime); + totalAlerts.addAll(filterAlertsByTime(alerts, legStartTime, legEndTime)); // agency alerts = transitAlertService.getAgencyAlerts(leg.getAgency().getId()); - addTransitAlertsToLeg(leg, alerts, legStartTime, legEndTime); + totalAlerts.addAll(filterAlertsByTime(alerts, legStartTime, legEndTime)); // Filter alerts when there are multiple timePeriods for each alert - leg - .getTransitAlerts() - .removeIf(alert -> - !alert.displayDuring(leg.getStartTime().toEpochSecond(), leg.getEndTime().toEpochSecond()) - ); + totalAlerts.removeIf(alert -> + !alert.displayDuring(leg.getStartTime().toEpochSecond(), leg.getEndTime().toEpochSecond()) + ); + + return leg.decorateWithAlerts(Set.copyOf(totalAlerts)); } /** - * Add alerts for the leg, if they are valid for the duration of the leg. + * Filter alerts if they are valid for the duration of the leg. */ - private static void addTransitAlertsToLeg( - Leg leg, + private static List filterAlertsByTime( Collection alerts, ZonedDateTime fromTime, ZonedDateTime toTime ) { - if (alerts != null) { - for (TransitAlert alert : alerts) { - if (alert.displayDuring(fromTime.toEpochSecond(), toTime.toEpochSecond())) { - leg.addAlert(alert); - } - } - } + return alerts + .stream() + .filter(alert -> alert.displayDuring(fromTime.toEpochSecond(), toTime.toEpochSecond())) + .toList(); } private Collection getAlertsForStopAndRoute( diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/GraphPathToItineraryMapper.java b/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/GraphPathToItineraryMapper.java index 2e7bf1b7040..484a43da40c 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/GraphPathToItineraryMapper.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/GraphPathToItineraryMapper.java @@ -336,7 +336,13 @@ private Leg generateFlexLeg(List states) { ZonedDateTime endTime = toState.getTime().atZone(timeZone); int generalizedCost = (int) (toState.getWeight() - fromState.getWeight()); - return new FlexibleTransitLeg(flexEdge, startTime, endTime, generalizedCost); + return FlexibleTransitLeg + .of() + .withFlexTripEdge(flexEdge) + .withStartTime(startTime) + .withEndTime(endTime) + .withGeneralizedCost(generalizedCost) + .build(); } /** diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java b/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java index 683acdd1a9e..a58aa32367f 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java @@ -14,6 +14,7 @@ import org.opentripplanner.model.plan.FrequencyTransitLegBuilder; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.Leg; +import org.opentripplanner.model.plan.LegConstructionSupport; import org.opentripplanner.model.plan.Place; import org.opentripplanner.model.plan.ScheduledTransitLegBuilder; import org.opentripplanner.model.plan.StreetLeg; @@ -253,6 +254,11 @@ private Leg mapTransitLeg(Leg prevTransitLeg, TransitPathLeg pathLeg) { TripOnServiceDate tripOnServiceDate = getTripOnServiceDate(tripSchedule); + var distanceMeters = LegConstructionSupport.computeDistanceMeters( + tripSchedule.getOriginalTripPattern(), + boardStopIndexInPattern, + alightStopIndexInPattern + ); return new ScheduledTransitLegBuilder<>() .withTripTimes(tripSchedule.getOriginalTripTimes()) .withTripPattern(tripSchedule.getOriginalTripPattern()) @@ -260,6 +266,7 @@ private Leg mapTransitLeg(Leg prevTransitLeg, TransitPathLeg pathLeg) { .withAlightStopIndexInPattern(alightStopIndexInPattern) .withStartTime(createZonedDateTime(pathLeg.fromTime())) .withEndTime(createZonedDateTime(pathLeg.toTime())) + .withDistanceMeters(distanceMeters) .withServiceDate(tripSchedule.getServiceDate()) .withZoneId(transitSearchTimeZero.getZone().normalized()) .withTripOnServiceDate(tripOnServiceDate) diff --git a/application/src/main/java/org/opentripplanner/routing/alternativelegs/AlternativeLegs.java b/application/src/main/java/org/opentripplanner/routing/alternativelegs/AlternativeLegs.java index f65a045a8e3..8d3d8c55b4a 100644 --- a/application/src/main/java/org/opentripplanner/routing/alternativelegs/AlternativeLegs.java +++ b/application/src/main/java/org/opentripplanner/routing/alternativelegs/AlternativeLegs.java @@ -19,6 +19,7 @@ import org.opentripplanner.model.Timetable; import org.opentripplanner.model.TripTimeOnDate; import org.opentripplanner.model.plan.Leg; +import org.opentripplanner.model.plan.LegConstructionSupport; import org.opentripplanner.model.plan.ScheduledTransitLeg; import org.opentripplanner.model.plan.ScheduledTransitLegBuilder; import org.opentripplanner.transit.model.network.TripPattern; @@ -243,6 +244,9 @@ private static ScheduledTransitLeg mapToLeg( .withServiceDate(serviceDay) .withZoneId(timeZone) .withTripOnServiceDate(tripOnServiceDate) + .withDistanceMeters( + LegConstructionSupport.computeDistanceMeters(pattern, boardingPosition, alightingPosition) + ) .build(); } diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java index 2f190502ccc..4363b8d89f5 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java @@ -26,6 +26,7 @@ import java.time.LocalDate; import java.time.OffsetDateTime; import java.time.temporal.ChronoUnit; +import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; @@ -54,6 +55,7 @@ import org.opentripplanner.model.fare.RiderCategory; import org.opentripplanner.model.plan.Emissions; import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.model.plan.Leg; import org.opentripplanner.model.plan.Place; import org.opentripplanner.model.plan.RelativeDirection; import org.opentripplanner.model.plan.ScheduledTransitLeg; @@ -278,6 +280,21 @@ public Set findRoutes(StopLocation stop) { .withEntrance(entrance) .build(); + var entitySelector = new EntitySelector.Stop(A.stop.getId()); + var alert = TransitAlert + .of(id("an-alert")) + .withHeaderText(I18NString.of("A header")) + .withDescriptionText(I18NString.of("A description")) + .withUrl(I18NString.of("https://example.com")) + .withCause(AlertCause.MAINTENANCE) + .withEffect(AlertEffect.REDUCED_SERVICE) + .withSeverity(AlertSeverity.VERY_SEVERE) + .addEntity(entitySelector) + .addTimePeriod( + new TimePeriod(ALERT_START_TIME.getEpochSecond(), ALERT_END_TIME.getEpochSecond()) + ) + .build(); + Itinerary i1 = newItinerary(A, T11_00) .walk(20, B, List.of(step1, step2, step3)) .bus(busRoute, 122, T11_01, T11_15, C) @@ -289,6 +306,10 @@ public Set findRoutes(StopLocation stop) { var busLeg = i1.getTransitLeg(1); var railLeg = (ScheduledTransitLeg) i1.getTransitLeg(2); + railLeg = railLeg.copy().withAlerts(Set.of(alert)).withAccessibilityScore(3f).build(); + ArrayList legs = new ArrayList<>(i1.getLegs()); + legs.set(2, railLeg); + i1.setLegs(legs); var fares = new ItineraryFares(); @@ -305,25 +326,6 @@ public Set findRoutes(StopLocation stop) { i1.setAccessibilityScore(0.5f); - railLeg.withAccessibilityScore(.3f); - - var entitySelector = new EntitySelector.Stop(A.stop.getId()); - var alert = TransitAlert - .of(id("an-alert")) - .withHeaderText(I18NString.of("A header")) - .withDescriptionText(I18NString.of("A description")) - .withUrl(I18NString.of("https://example.com")) - .withCause(AlertCause.MAINTENANCE) - .withEffect(AlertEffect.REDUCED_SERVICE) - .withSeverity(AlertSeverity.VERY_SEVERE) - .addEntity(entitySelector) - .addTimePeriod( - new TimePeriod(ALERT_START_TIME.getEpochSecond(), ALERT_END_TIME.getEpochSecond()) - ) - .build(); - - railLeg.addAlert(alert); - var emissions = new Emissions(new Grams(123.0)); i1.setEmissionsPerPerson(emissions); diff --git a/application/src/test/java/org/opentripplanner/framework/geometry/GeometryUtilsTest.java b/application/src/test/java/org/opentripplanner/framework/geometry/GeometryUtilsTest.java index 9ea46c22cde..affcd5c9545 100644 --- a/application/src/test/java/org/opentripplanner/framework/geometry/GeometryUtilsTest.java +++ b/application/src/test/java/org/opentripplanner/framework/geometry/GeometryUtilsTest.java @@ -9,6 +9,7 @@ import org.locationtech.jts.geom.CoordinateSequenceFactory; import org.locationtech.jts.geom.GeometryFactory; import org.locationtech.jts.geom.LineString; +import org.opentripplanner._support.geometry.Coordinates; public class GeometryUtilsTest { @@ -266,4 +267,11 @@ void toEnvelopes() { envelopes.toString() ); } + + @Test + void sumDistances() { + var coordinates = List.of(Coordinates.BERLIN, Coordinates.HAMBURG, Coordinates.BERLIN); + var meters = GeometryUtils.sumDistances(coordinates); + assertEquals(510_768, meters, 50); + } } diff --git a/application/src/test/java/org/opentripplanner/model/plan/ScheduledTransitLegBuilderTest.java b/application/src/test/java/org/opentripplanner/model/plan/ScheduledTransitLegBuilderTest.java index c8ae617fd20..1031810a300 100644 --- a/application/src/test/java/org/opentripplanner/model/plan/ScheduledTransitLegBuilderTest.java +++ b/application/src/test/java/org/opentripplanner/model/plan/ScheduledTransitLegBuilderTest.java @@ -1,16 +1,23 @@ package org.opentripplanner.model.plan; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.opentripplanner.ext.fares.impl.FareModelForTest.FARE_PRODUCT_USE; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; import java.time.LocalDate; +import java.util.List; import java.util.Set; import org.junit.jupiter.api.Test; import org.opentripplanner._support.time.ZoneIds; +import org.opentripplanner.ext.fares.impl.FareModelForTest; import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.routing.alertpatch.TransitAlert; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.basic.TransitMode; +import org.opentripplanner.transit.model.network.TripPattern; class ScheduledTransitLegBuilderTest { @@ -18,17 +25,15 @@ class ScheduledTransitLegBuilderTest { .of(id("alert")) .withDescriptionText(I18NString.of("alert")) .build(); + private static final TripPattern PATTERN = TimetableRepositoryForTest + .of() + .pattern(TransitMode.BUS) + .build(); + private static final LocalDate DATE = LocalDate.of(2023, 11, 15); @Test - void transferZoneId() { - var pattern = TimetableRepositoryForTest.of().pattern(TransitMode.BUS).build(); - var leg = new ScheduledTransitLegBuilder<>() - .withZoneId(ZoneIds.BERLIN) - .withServiceDate(LocalDate.of(2023, 11, 15)) - .withTripPattern(pattern) - .withBoardStopIndexInPattern(0) - .withAlightStopIndexInPattern(1) - .build(); + void copyZoneId() { + var leg = completeBuilder().build(); var newLeg = new ScheduledTransitLegBuilder<>(leg); assertEquals(ZoneIds.BERLIN, newLeg.zoneId()); @@ -39,22 +44,59 @@ void transferZoneId() { } @Test - void alerts() { - var pattern = TimetableRepositoryForTest.of().pattern(TransitMode.BUS).build(); + void collectionsAreInitialized() { + var leg = completeBuilder().build(); + + assertNotNull(leg.getTransitAlerts()); + assertNotNull(leg.fareProducts()); + } + + @Test + void nullZoneId() { var leg = new ScheduledTransitLegBuilder<>() - .withZoneId(ZoneIds.BERLIN) - .withServiceDate(LocalDate.of(2023, 11, 15)) - .withTripPattern(pattern) + .withServiceDate(DATE) + .withTripPattern(PATTERN) .withBoardStopIndexInPattern(0) - .withAlightStopIndexInPattern(1) - .build(); + .withAlightStopIndexInPattern(1); - leg.addAlert(ALERT); + assertThrows(RuntimeException.class, leg::build); + } - var newLeg = new ScheduledTransitLegBuilder<>(leg); + @Test + void nullCollectionsThrow() { + assertDoesNotThrow(completeBuilder()::build); + assertThrows(RuntimeException.class, () -> completeBuilder().withFareProducts(null).build()); + assertThrows(RuntimeException.class, () -> completeBuilder().withAlerts(null).build()); + } - var withScore = newLeg.withAccessibilityScore(4f).build(); + @Test + void copyAlerts() { + var leg = completeBuilder().withAlerts(Set.of(ALERT)).build(); + + var withScore = leg.copy().withAccessibilityScore(4f).build(); assertEquals(Set.of(ALERT), withScore.getTransitAlerts()); } + + @Test + void copyFareProducts() { + var leg = completeBuilder().withFareProducts(List.of(FARE_PRODUCT_USE)).build(); + + var copy = leg.copy().build(); + + assertEquals(List.of(FARE_PRODUCT_USE), copy.fareProducts()); + } + + /** + * Returns a builder where all required fields are set. + */ + private static ScheduledTransitLegBuilder completeBuilder() { + return new ScheduledTransitLegBuilder<>() + .withZoneId(ZoneIds.BERLIN) + .withServiceDate(DATE) + .withTripPattern(PATTERN) + .withBoardStopIndexInPattern(0) + .withAlightStopIndexInPattern(1) + .withDistanceMeters(10000); + } } diff --git a/application/src/test/java/org/opentripplanner/model/plan/ScheduledTransitLegTest.java b/application/src/test/java/org/opentripplanner/model/plan/ScheduledTransitLegTest.java index 6980a488d7a..8cb4c499f97 100644 --- a/application/src/test/java/org/opentripplanner/model/plan/ScheduledTransitLegTest.java +++ b/application/src/test/java/org/opentripplanner/model/plan/ScheduledTransitLegTest.java @@ -83,6 +83,7 @@ private static ScheduledTransitLegBuilder builder() { .withEndTime(TIME.plusMinutes(10)) .withServiceDate(TIME.toLocalDate()) .withZoneId(ZoneIds.BERLIN) - .withGeneralizedCost(100); + .withGeneralizedCost(100) + .withDistanceMeters(1000); } } diff --git a/application/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java b/application/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java index 33eeae0a553..7248daa1cef 100644 --- a/application/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java +++ b/application/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java @@ -246,7 +246,13 @@ public TestItineraryBuilder flex(int start, int end, Place to) { flexPath ); - FlexibleTransitLeg leg = new FlexibleTransitLeg(edge, newTime(start), newTime(end), legCost); + FlexibleTransitLeg leg = FlexibleTransitLeg + .of() + .withFlexTripEdge(edge) + .withStartTime(newTime(start)) + .withEndTime(newTime(end)) + .withGeneralizedCost(legCost) + .build(); legs.add(leg); c1 += legCost; @@ -507,6 +513,8 @@ public TestItineraryBuilder transit( ScheduledTransitLeg leg; + var distance = speed(trip.getMode()) * (end - start); + if (headwaySecs != null) { leg = new FrequencyTransitLegBuilder() @@ -520,6 +528,7 @@ public TestItineraryBuilder transit( .withZoneId(UTC) .withTransferFromPreviousLeg(transferFromPreviousLeg) .withGeneralizedCost(legCost) + .withDistanceMeters(distance) .withFrequencyHeadwayInSeconds(headwaySecs) .build(); } else { @@ -535,11 +544,10 @@ public TestItineraryBuilder transit( .withZoneId(UTC) .withTransferFromPreviousLeg(transferFromPreviousLeg) .withGeneralizedCost(legCost) + .withDistanceMeters(distance) .build(); } - leg.setDistanceMeters(speed(leg.getMode()) * (end - start)); - legs.add(leg); c1 += legCost; diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/TransitAlertFilterTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/DecorateTransitAlertTest.java similarity index 50% rename from application/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/TransitAlertFilterTest.java rename to application/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/DecorateTransitAlertTest.java index 8c64d7231d8..b37c90ff2f9 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/TransitAlertFilterTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/DecorateTransitAlertTest.java @@ -10,25 +10,19 @@ import org.opentripplanner.routing.alertpatch.EntitySelector; import org.opentripplanner.routing.alertpatch.TimePeriod; import org.opentripplanner.routing.alertpatch.TransitAlert; +import org.opentripplanner.routing.alertpatch.TransitAlertBuilder; import org.opentripplanner.routing.impl.TransitAlertServiceImpl; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.service.TimetableRepository; -class TransitAlertFilterTest implements PlanTestConstants { +class DecorateTransitAlertTest implements PlanTestConstants { - private static final FeedScopedId ID = new FeedScopedId("FEED", "BUS"); + private static final FeedScopedId ID = new FeedScopedId("FEED", "ALERT"); @Test - void testFilter() { - var transitAlertService = new TransitAlertServiceImpl(new TimetableRepository()); - transitAlertService.setAlerts( - List.of( - TransitAlert - .of(ID) - .addEntity(new EntitySelector.Route(BUS_ROUTE.getId())) - .addTimePeriod(new TimePeriod(0, TimePeriod.OPEN_ENDED)) - .build() - ) + void testRoute() { + final var transitAlertService = buildService( + TransitAlert.of(ID).addEntity(new EntitySelector.Route(BUS_ROUTE.getId())) ); var decorator = new DecorateTransitAlert(transitAlertService, ignore -> null); @@ -38,12 +32,37 @@ void testFilter() { decorator.decorate(i1); // Then: expect correct alerts to be added - assertEquals(1, i1.getLegs().get(0).getTransitAlerts().size()); - assertEquals(ID, i1.getLegs().get(0).getTransitAlerts().iterator().next().getId()); + assertEquals(1, i1.getLegs().getFirst().getTransitAlerts().size()); + assertEquals(ID, i1.getLegs().getFirst().getTransitAlerts().iterator().next().getId()); var i2 = newItinerary(B).rail(21, 0, 30, E).build(); decorator.decorate(i2); - assertEquals(0, i2.getLegs().get(0).getTransitAlerts().size()); + assertEquals(0, i2.getLegs().getFirst().getTransitAlerts().size()); + } + + @Test + void testStop() { + final var transitAlertService = buildService( + TransitAlert.of(ID).addEntity(new EntitySelector.Stop(E.stop.getId())) + ); + + var decorator = new DecorateTransitAlert(transitAlertService, ignore -> null); + + // Given a list with one itinerary + var i1 = newItinerary(A).bus(31, 0, 30, E).build(); + decorator.decorate(i1); + + // Then: expect correct alerts to be added + assertEquals(1, i1.getLegs().getFirst().getTransitAlerts().size()); + assertEquals(ID, i1.getLegs().getFirst().getTransitAlerts().iterator().next().getId()); + } + + private static TransitAlertServiceImpl buildService(TransitAlertBuilder builder) { + var transitAlertService = new TransitAlertServiceImpl(new TimetableRepository()); + transitAlertService.setAlerts( + List.of(builder.addTimePeriod(new TimePeriod(0, TimePeriod.OPEN_ENDED)).build()) + ); + return transitAlertService; } } diff --git a/application/src/test/resources/org/opentripplanner/apis/gtfs/expectations/planConnection-extended.json b/application/src/test/resources/org/opentripplanner/apis/gtfs/expectations/planConnection-extended.json index ab29f172b6f..5543c199682 100644 --- a/application/src/test/resources/org/opentripplanner/apis/gtfs/expectations/planConnection-extended.json +++ b/application/src/test/resources/org/opentripplanner/apis/gtfs/expectations/planConnection-extended.json @@ -279,7 +279,7 @@ } ], "rideHailingEstimate": null, - "accessibilityScore": null, + "accessibilityScore": 3.0, "id": "rO0ABXdBABhTQ0hFRFVMRURfVFJBTlNJVF9MRUdfVjMABUY6NDM5AAoyMDIwLTAyLTAyAAAABQAAAAcAA0Y6QwADRjpEAAA=", "realtimeState": "UPDATED" },