Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add HSL specific fare computation in the sandbox #4414

Merged
merged 26 commits into from
Sep 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
7785264
Add HSL specifc fares implementation
viljaminurminen-cgi Aug 11, 2022
2c21af7
Add tests for HSL fare service
viljaminurminen-cgi Aug 16, 2022
237c932
Add ticket type query to legacy GraphQL API.
viljaminurminen-cgi Aug 16, 2022
6288bbe
Fix itineraries needing multiple tickets
viljaminurminen-cgi Aug 18, 2022
a1369f8
Fix tickets for special routes
viljaminurminen-cgi Aug 23, 2022
b2adb31
Move HSL specific fare rule setup into HSLFareServiceFactory.
viljaminurminen-cgi Aug 25, 2022
4bad0a6
Merge remote-tracking branch 'otp/dev-2.x' into hsl_fares
viljaminurminen-cgi Aug 25, 2022
c626511
Fix errors due to merge
viljaminurminen-cgi Aug 25, 2022
158627f
Fix formatting
vesameskanen Aug 25, 2022
356be37
Fix exception route handling
viljaminurminen-cgi Aug 30, 2022
65d689f
Add documentation to addRouteOriginDestination
viljaminurminen-cgi Aug 30, 2022
7916690
Move TicketType to LegacyGraphQL API sandbox
viljaminurminen-cgi Aug 30, 2022
dd4fe63
Make T3 into a record
viljaminurminen-cgi Aug 30, 2022
85b5bc5
Fix formatting and remove old license header
viljaminurminen-cgi Aug 30, 2022
a9a5fd1
Fix documentation on addRouteOriginDestination
nurmAV Sep 1, 2022
3030595
Convert P3 into RouteOriginDestination record
viljaminurminen-cgi Sep 1, 2022
b8c848c
Update Fares.md
nurmAV Sep 1, 2022
12b296b
Update docs/sandbox/Fares.md
nurmAV Sep 1, 2022
ae84f24
Update src/main/java/org/opentripplanner/routing/core/FareRuleSet.java
nurmAV Sep 1, 2022
4279fb2
Change LegacyGraphQLTicketTypeImpl to operate on FareRuleSet and fix …
viljaminurminen-cgi Sep 1, 2022
7d63633
Fix formatting
viljaminurminen-cgi Sep 1, 2022
5b7dd97
Remove breaking typo and unnecessary type conversion
viljaminurminen-cgi Sep 1, 2022
2cdc76e
Merge remote-tracking branch 'otp/dev-2.x' into hsl_fares
viljaminurminen-cgi Sep 6, 2022
c76337c
Update src/ext/java/org/opentripplanner/ext/legacygraphqlapi/datafetc…
nurmAV Sep 6, 2022
3121cc3
Move RouteOriginDestination and FareRuleSet into ext/fares/model
viljaminurminen-cgi Sep 6, 2022
d4d4869
Fix ClassCastException in LegacyGraphQLfareImpl
viljaminurminen-cgi Sep 8, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/sandbox/Fares.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ The classes and their maintainers are as follows:
| HighestFareInFreeTransferWindowFareServiceDutchFareServiceImpl | IBI Group ([David Emory](mailto:[email protected]) |
| NycFareServiceImpl | unmaintained |
| SFBayFareServiceImpl | unmaintained |
| HSLFareServiceImpl | HSL ([Viljami Nurminen](mailto:[email protected])) |
hannesj marked this conversation as resolved.
Show resolved Hide resolved

## Removed fare calculators

Expand All @@ -61,4 +62,4 @@ they were [removed](https://github.com/opentripplanner/OpenTripPlanner/pull/4273
- SeattleFareServiceImpl
- DutchFareServiceImpl

If you were using these calculators, you're welcome to re-add them to the code base.
If you were using these calculators, you're welcome to re-add them to the code base.
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
package org.opentripplanner.ext.fares.impl;

import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary;
import static org.opentripplanner.transit.model._data.TransitModelForTest.FEED_ID;

import java.util.LinkedList;
import java.util.List;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.opentripplanner.ext.fares.model.FareAttribute;
import org.opentripplanner.ext.fares.model.FareRuleSet;
import org.opentripplanner.model.plan.Itinerary;
import org.opentripplanner.model.plan.Place;
import org.opentripplanner.model.plan.PlanTestConstants;
import org.opentripplanner.routing.core.FareType;
import org.opentripplanner.routing.fares.FareService;
import org.opentripplanner.transit.model.framework.FeedScopedId;
import org.opentripplanner.transit.model.site.FareZone;

public class HSLFareServiceTest implements PlanTestConstants {

@ParameterizedTest(name = "[{index}] {0}")
@MethodSource("createTestCases")
public void canCalculateHSLFares(
String testCaseName, // used to create parameterized test name
FareService fareService,
Itinerary i,
List<String> expectedFareIds
) {
Assertions.assertArrayEquals(
expectedFareIds.toArray(),
fareService.getCost(i).getDetails(FareType.regular).stream().map(f -> f.fareId()).toArray()
);
}

private static List<Arguments> createTestCases() {
List<Arguments> args = new LinkedList<>();

FareZone A = FareZone.of(new FeedScopedId(FEED_ID, "A")).build();
FareZone B = FareZone.of(new FeedScopedId(FEED_ID, "B")).build();
FareZone C = FareZone.of(new FeedScopedId(FEED_ID, "C")).build();
FareZone D = FareZone.of(new FeedScopedId(FEED_ID, "D")).build();

Place A1 = PlanTestConstants.place("A1", 10.0, 12.0, A);
Place A2 = PlanTestConstants.place("A2", 10.0, 12.0, A);

Place B1 = PlanTestConstants.place("B1", 10.0, 12.0, B);
Place B2 = PlanTestConstants.place("B2", 10.0, 12.0, B);

Place C1 = PlanTestConstants.place("C1", 10.0, 12.0, C);
Place C2 = PlanTestConstants.place("C2", 10.0, 12.0, C);

Place D1 = PlanTestConstants.place("D1", 10.0, 12.0, D);
Place D2 = PlanTestConstants.place("D2", 10.0, 12.0, D);

float AB_PRICE = 2.80f;
float BC_PRICE = 2.80f;
float CD_PRICE = 3.20f;
float ABC_PRICE = 4.10f;
float BCD_PRICE = 4.10f;
float ABCD_PRICE = 5.70f;
float D_PRICE = 2.80f;

HSLFareServiceImpl hslFareService = new HSLFareServiceImpl();
int fiveMinutes = 60 * 5;

// Fare attributes

FareAttribute fareAttributeAB = FareAttribute
.of(new FeedScopedId(FEED_ID, "AB"))
.setCurrencyType("EUR")
.setPrice(AB_PRICE)
.setTransferDuration(fiveMinutes)
.build();

FareAttribute fareAttributeBC = FareAttribute
.of(new FeedScopedId(FEED_ID, "BC"))
.setCurrencyType("EUR")
.setPrice(BC_PRICE)
.setTransferDuration(fiveMinutes)
.build();

FareAttribute fareAttributeCD = FareAttribute
.of(new FeedScopedId(FEED_ID, "CD"))
.setCurrencyType("EUR")
.setPrice(CD_PRICE)
.setTransferDuration(fiveMinutes)
.build();

FareAttribute fareAttributeD = FareAttribute
.of(new FeedScopedId(FEED_ID, "D"))
.setCurrencyType("EUR")
.setPrice(D_PRICE)
.setTransferDuration(fiveMinutes)
.build();

FareAttribute fareAttributeABC = FareAttribute
.of(new FeedScopedId(FEED_ID, "ABC"))
.setCurrencyType("EUR")
.setPrice(ABC_PRICE)
.setTransferDuration(fiveMinutes)
.build();

FareAttribute fareAttributeBCD = FareAttribute
.of(new FeedScopedId(FEED_ID, "BCD"))
.setCurrencyType("EUR")
.setPrice(BCD_PRICE)
.setTransferDuration(fiveMinutes)
.build();

FareAttribute fareAttributeABCD = FareAttribute
.of(new FeedScopedId(FEED_ID, "ABCD"))
.setCurrencyType("EUR")
.setPrice(ABCD_PRICE)
.setTransferDuration(fiveMinutes)
.build();

// Fare rule sets
FareRuleSet ruleSetAB = new FareRuleSet(fareAttributeAB);
ruleSetAB.addContains("A");
ruleSetAB.addContains("B");

FareRuleSet ruleSetBC = new FareRuleSet(fareAttributeBC);
ruleSetBC.addContains("B");
ruleSetBC.addContains("C");

FareRuleSet ruleSetCD = new FareRuleSet(fareAttributeCD);
ruleSetCD.addContains("C");
ruleSetCD.addContains("D");

FareRuleSet ruleSetABC = new FareRuleSet(fareAttributeABC);
ruleSetABC.addContains("A");
ruleSetABC.addContains("B");
ruleSetABC.addContains("C");

FareRuleSet ruleSetBCD = new FareRuleSet(fareAttributeBCD);
ruleSetBCD.addContains("B");
ruleSetBCD.addContains("C");
ruleSetBCD.addContains("D");

FareRuleSet ruleSetABCD = new FareRuleSet(fareAttributeABCD);
ruleSetABCD.addContains("A");
ruleSetABCD.addContains("B");
ruleSetABCD.addContains("C");
ruleSetABCD.addContains("D");

FareRuleSet ruleSetD = new FareRuleSet(fareAttributeD);
ruleSetD.addContains("D");

hslFareService.addFareRules(
FareType.regular,
List.of(ruleSetAB, ruleSetBC, ruleSetCD, ruleSetABC, ruleSetBCD, ruleSetABCD, ruleSetD)
);

// Itineraries within zone A
Itinerary A1_A2 = newItinerary(A1, T11_06).bus(1, T11_06, T11_12, A2).build();

args.add(
Arguments.of(
"Bus ride within zone A",
hslFareService,
A1_A2,
List.of(fareAttributeAB.getId())
)
);

// Itineraries within zone B
Itinerary B1_B2 = newItinerary(B1, T11_06).bus(1, T11_06, T11_12, B2).build();

args.add(
Arguments.of(
"Bus ride within zone B",
hslFareService,
B1_B2,
List.of(fareAttributeAB.getId())
)
);

// Itineraries within zone C
Itinerary C1_C2 = newItinerary(C1, T11_06).bus(1, T11_06, T11_12, C2).build();

args.add(
Arguments.of(
"Bus ride within zone C",
hslFareService,
C1_C2,
List.of(fareAttributeBC.getId())
)
);

// Itineraries within zone D
Itinerary D1_D2 = newItinerary(D1, T11_06).bus(1, T11_06, T11_12, D2).build();

args.add(
Arguments.of("Bus ride within zone D", hslFareService, D1_D2, List.of(fareAttributeD.getId()))
);

// Itineraries between zones A and B
Itinerary A1_B1 = newItinerary(A1, T11_06).bus(1, T11_06, T11_12, B1).build();

args.add(
Arguments.of(
"Bus ride between zones A and B",
hslFareService,
A1_B1,
List.of(fareAttributeAB.getId())
)
);

// Itineraries between zones B and C
Itinerary B1_C1 = newItinerary(B1, T11_06).bus(1, T11_06, T11_12, C1).build();

args.add(
Arguments.of(
"Bus ride between zones B and C",
hslFareService,
B1_C1,
List.of(fareAttributeBC.getId())
)
);

// Itineraries between zones C and D
Itinerary C1_D1 = newItinerary(C1, T11_06).bus(1, T11_06, T11_12, D1).build();

args.add(
Arguments.of(
"Bus ride between zones C and D",
hslFareService,
C1_D1,
List.of(fareAttributeCD.getId())
)
);

// Itineraries between zones A and D
Itinerary A1_D1 = newItinerary(A1, T11_06).bus(1, T11_20, T11_30, D1).build();

args.add(
Arguments.of(
"Bus ride between zones A and D",
hslFareService,
A1_D1,
List.of(fareAttributeABCD.getId())
)
);

// Itineraries between zones A and C
Itinerary A1_C1 = newItinerary(A1, T11_06).bus(1, T11_20, T11_30, C1).build();

args.add(
Arguments.of(
"Bus ride between zones A and C",
hslFareService,
A1_C1,
List.of(fareAttributeABC.getId())
)
);

// Itineraries between zones B and D
Itinerary B1_D1 = newItinerary(B1, T11_06).bus(1, T11_20, T11_30, D1).build();

args.add(
Arguments.of(
"Bus ride between zones B and D",
hslFareService,
B1_D1,
List.of(fareAttributeBCD.getId())
)
);

Itinerary twoTicketsItinerary = newItinerary(A1, T11_20)
.bus(1, T11_20, T11_30, B1)
.bus(1, T11_33, T11_50, C1)
.build();

args.add(
Arguments.of(
"Ride that needs two tickets",
hslFareService,
twoTicketsItinerary,
List.of(fareAttributeAB.getId(), fareAttributeBC.getId())
)
);

return args;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.opentripplanner.ext.fares.model.FareAttribute;
import org.opentripplanner.ext.fares.model.FareRuleSet;
import org.opentripplanner.model.plan.Itinerary;
import org.opentripplanner.model.plan.PlanTestConstants;
import org.opentripplanner.routing.core.FareRuleSet;
import org.opentripplanner.routing.core.FareType;
import org.opentripplanner.routing.core.Money;
import org.opentripplanner.routing.fares.FareService;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.fasterxml.jackson.databind.JsonNode;
import org.opentripplanner.ext.fares.impl.DefaultFareServiceFactory;
import org.opentripplanner.ext.fares.impl.HSLFareServiceFactory;
import org.opentripplanner.ext.fares.impl.HighestFareInFreeTransferWindowFareServiceFactory;
import org.opentripplanner.ext.fares.impl.MultipleFareServiceFactory;
import org.opentripplanner.ext.fares.impl.NoopFareServiceFactory;
Expand Down Expand Up @@ -85,6 +86,7 @@ private static FareServiceFactory createFactory(String type) {
case "san-francisco" -> new SFBayFareServiceFactory();
case "new-york" -> new NycFareServiceFactory();
case "highestFareInFreeTransferWindow" -> new HighestFareInFreeTransferWindowFareServiceFactory();
case "hsl" -> new HSLFareServiceFactory();
default -> throw new IllegalArgumentException(String.format("Unknown fare type: '%s'", type));
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
import org.opentripplanner.ext.fares.model.FareAttribute;
import org.opentripplanner.ext.fares.model.FareLegRule;
import org.opentripplanner.ext.fares.model.FareRule;
import org.opentripplanner.ext.fares.model.FareRuleSet;
import org.opentripplanner.ext.fares.model.FareRulesData;
import org.opentripplanner.model.OtpTransitService;
import org.opentripplanner.routing.core.FareRuleSet;
import org.opentripplanner.routing.core.FareType;
import org.opentripplanner.routing.fares.FareService;
import org.opentripplanner.routing.fares.FareServiceFactory;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@
import java.util.Map;
import java.util.Set;
import org.opentripplanner.ext.fares.model.FareAttribute;
import org.opentripplanner.ext.fares.model.FareRuleSet;
import org.opentripplanner.ext.flex.FlexibleTransitLeg;
import org.opentripplanner.model.plan.Itinerary;
import org.opentripplanner.model.plan.Leg;
import org.opentripplanner.model.plan.ScheduledTransitLeg;
import org.opentripplanner.routing.core.FareComponent;
import org.opentripplanner.routing.core.FareRuleSet;
import org.opentripplanner.routing.core.FareType;
import org.opentripplanner.routing.core.ItineraryFares;
import org.opentripplanner.routing.core.Money;
Expand Down Expand Up @@ -90,6 +90,10 @@ public void addFareRules(FareType fareType, Collection<FareRuleSet> fareRules) {
fareRulesPerType.put(fareType, new ArrayList<>(fareRules));
}

public Map<FareType, Collection<FareRuleSet>> getFareRulesPerType() {
return fareRulesPerType;
}

@Override
public ItineraryFares getCost(Itinerary itinerary) {
var fareLegs = itinerary
Expand Down Expand Up @@ -278,7 +282,7 @@ private FareSearch performSearch(
return r;
}

private FareAndId getBestFareAndId(
protected FareAndId getBestFareAndId(
FareType fareType,
List<Leg> legs,
Collection<FareRuleSet> fareRules
Expand Down Expand Up @@ -357,7 +361,7 @@ private FareAndId getBestFareAndId(
return new FareAndId(bestFare, bestAttribute == null ? null : bestAttribute.getId());
}

private float getFarePrice(FareAttribute fare, FareType type) {
protected float getFarePrice(FareAttribute fare, FareType type) {
switch (type) {
case senior:
if (fare.getSeniorPrice() >= 0) {
Expand Down
Loading