-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #5910 from entur/otp2_add_generalized-cost-includi…
…ng-penalty Add timePenalty to Transmodel API
- Loading branch information
Showing
8 changed files
with
247 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
79 changes: 79 additions & 0 deletions
79
src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripPatternTimePenaltyType.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package org.opentripplanner.apis.transmodel.model.plan; | ||
|
||
import graphql.Scalars; | ||
import graphql.schema.DataFetchingEnvironment; | ||
import graphql.schema.GraphQLFieldDefinition; | ||
import graphql.schema.GraphQLObjectType; | ||
import org.opentripplanner.framework.time.DurationUtils; | ||
|
||
public class TripPatternTimePenaltyType { | ||
|
||
public static GraphQLObjectType create() { | ||
return GraphQLObjectType | ||
.newObject() | ||
.name("TimePenaltyWithCost") | ||
.description( | ||
""" | ||
The time-penalty is applied to either the access-legs and/or egress-legs. Both access and | ||
egress may contain more than one leg; Hence, the penalty is not a field on leg. | ||
Note! This is for debugging only. This type can change without notice. | ||
""" | ||
) | ||
.field( | ||
GraphQLFieldDefinition | ||
.newFieldDefinition() | ||
.name("appliedTo") | ||
.description( | ||
""" | ||
The time-penalty is applied to either the access-legs and/or egress-legs. Both access | ||
and egress may contain more than one leg; Hence, the penalty is not a field on leg. The | ||
`appliedTo` describe witch part of the itinerary that this instance applies to. | ||
""" | ||
) | ||
.type(Scalars.GraphQLString) | ||
.dataFetcher(environment -> penalty(environment).appliesTo()) | ||
.build() | ||
) | ||
.field( | ||
GraphQLFieldDefinition | ||
.newFieldDefinition() | ||
.name("timePenalty") | ||
.description( | ||
""" | ||
The time-penalty added to the actual time/duration when comparing the itinerary with | ||
other itineraries. This is used to decide witch is the best option, but is not visible | ||
- the actual departure and arrival-times are not modified. | ||
""" | ||
) | ||
.type(Scalars.GraphQLString) | ||
.dataFetcher(environment -> | ||
DurationUtils.durationToStr(penalty(environment).penalty().time()) | ||
) | ||
.build() | ||
) | ||
.field( | ||
GraphQLFieldDefinition | ||
.newFieldDefinition() | ||
.name("generalizedCostDelta") | ||
.description( | ||
""" | ||
The time-penalty does also propagate to the `generalizedCost`. As a result of the given | ||
time-penalty, the generalized-cost also increased by the given amount. This delta is | ||
included in the itinerary generalized-cost. In some cases the generalized-cost-delta is | ||
excluded when comparing itineraries - that happens if one of the itineraries is a | ||
"direct/street-only" itinerary. Time-penalty can not be set for direct searches, so it | ||
needs to be excluded from such comparison to be fair. The unit is transit-seconds. | ||
""" | ||
) | ||
.type(Scalars.GraphQLInt) | ||
.dataFetcher(environment -> penalty(environment).penalty().cost().toSeconds()) | ||
.build() | ||
) | ||
.build(); | ||
} | ||
|
||
static TripPlanTimePenaltyDto penalty(DataFetchingEnvironment environment) { | ||
return environment.getSource(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
32 changes: 32 additions & 0 deletions
32
src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripPlanTimePenaltyDto.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package org.opentripplanner.apis.transmodel.model.plan; | ||
|
||
import java.util.List; | ||
import java.util.Objects; | ||
import java.util.stream.Stream; | ||
import javax.annotation.Nullable; | ||
import org.opentripplanner.framework.model.TimeAndCost; | ||
import org.opentripplanner.model.plan.Itinerary; | ||
|
||
/** | ||
* A simple data-transfer-object used to map from an itinerary to the API specific | ||
* type. It is needed because we need to pass in the "appliedTo" field, which does not | ||
* exist in the domain model. | ||
*/ | ||
public record TripPlanTimePenaltyDto(String appliesTo, TimeAndCost penalty) { | ||
static List<TripPlanTimePenaltyDto> of(Itinerary itinerary) { | ||
return Stream | ||
.of(of("access", itinerary.getAccessPenalty()), of("egress", itinerary.getEgressPenalty())) | ||
.filter(Objects::nonNull) | ||
.toList(); | ||
} | ||
|
||
/** | ||
* Package local to be unit-testable. | ||
*/ | ||
@Nullable | ||
static TripPlanTimePenaltyDto of(String appliedTo, TimeAndCost penalty) { | ||
return penalty == null || penalty.isZero() | ||
? null | ||
: new TripPlanTimePenaltyDto(appliedTo, penalty); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
66 changes: 66 additions & 0 deletions
66
src/test/java/org/opentripplanner/apis/transmodel/model/plan/TripPlanTimePenaltyDtoTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package org.opentripplanner.apis.transmodel.model.plan; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
import static org.junit.jupiter.api.Assertions.assertNull; | ||
|
||
import java.util.List; | ||
import org.junit.jupiter.api.Test; | ||
import org.opentripplanner.framework.model.Cost; | ||
import org.opentripplanner.framework.model.TimeAndCost; | ||
import org.opentripplanner.framework.time.DurationUtils; | ||
import org.opentripplanner.model.plan.Itinerary; | ||
import org.opentripplanner.model.plan.Place; | ||
import org.opentripplanner.model.plan.TestItineraryBuilder; | ||
import org.opentripplanner.transit.model._data.TransitModelForTest; | ||
|
||
class TripPlanTimePenaltyDtoTest { | ||
|
||
private static final TimeAndCost PENALTY = new TimeAndCost( | ||
DurationUtils.duration("20m30s"), | ||
Cost.costOfSeconds(21) | ||
); | ||
|
||
private final TransitModelForTest testModel = TransitModelForTest.of(); | ||
private final Place placeA = Place.forStop(testModel.stop("A").build()); | ||
private final Place placeB = Place.forStop(testModel.stop("B").build()); | ||
|
||
@Test | ||
void testCreateFromSingeEntry() { | ||
assertNull(TripPlanTimePenaltyDto.of("access", null)); | ||
assertNull(TripPlanTimePenaltyDto.of("access", TimeAndCost.ZERO)); | ||
assertEquals( | ||
new TripPlanTimePenaltyDto("access", PENALTY), | ||
TripPlanTimePenaltyDto.of("access", PENALTY) | ||
); | ||
} | ||
|
||
@Test | ||
void testCreateFromItineraryWithNoPenalty() { | ||
var i = itinerary(); | ||
assertEquals(List.of(), TripPlanTimePenaltyDto.of(i)); | ||
} | ||
|
||
@Test | ||
void testCreateFromItineraryWithAccess() { | ||
var i = itinerary(); | ||
i.setAccessPenalty(PENALTY); | ||
assertEquals( | ||
List.of(new TripPlanTimePenaltyDto("access", PENALTY)), | ||
TripPlanTimePenaltyDto.of(i) | ||
); | ||
} | ||
|
||
@Test | ||
void testCreateFromItineraryWithEgress() { | ||
var i = itinerary(); | ||
i.setEgressPenalty(PENALTY); | ||
assertEquals( | ||
List.of(new TripPlanTimePenaltyDto("egress", PENALTY)), | ||
TripPlanTimePenaltyDto.of(i) | ||
); | ||
} | ||
|
||
private Itinerary itinerary() { | ||
return TestItineraryBuilder.newItinerary(placeA).drive(100, 200, placeB).build(); | ||
} | ||
} |