Skip to content

Commit

Permalink
Merge pull request #351 from HSLdevcom/DT-4417
Browse files Browse the repository at this point in the history
DT-4417: Changes to ticket zones
  • Loading branch information
vesameskanen authored Feb 26, 2021
2 parents f27b666 + a33ae45 commit 5a84980
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 82 deletions.
30 changes: 30 additions & 0 deletions src/main/java/org/opentripplanner/common/model/P3.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.opentripplanner.common.model;

/**
* An ordered objects of three (the same type)
*
* @param <E>
*/
public class P3<E> extends T3<E, E, E> {

private static final long serialVersionUID = 1L;

public static <E> P3<E> create(E first, E second, E third) {
return new P3<E>(first, second, third);
}

public P3(E first, E second, E third) {
super(first, second, third);
}

public P3(E[] entries) {
super(entries[0], entries[1], entries[2]);
if (entries.length != 3) {
throw new IllegalArgumentException("This only takes arrays of 3 arguments");
}
}

public String toString() {
return "P3(" + first + ", " + second + ", " + third + ")";
}
}
59 changes: 59 additions & 0 deletions src/main/java/org/opentripplanner/common/model/T3.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package org.opentripplanner.common.model;

import java.io.Serializable;

/**
* An ordered object of three (potentially different types)
*/
public class T3<E1, E2, E3> implements Serializable {
private static final long serialVersionUID = 1L;

public final E1 first;

public final E2 second;

public final E3 third;

public T3(E1 first, E2 second, E3 third) {
this.first = first;
this.second = second;
this.third = third;
}

@Override
public int hashCode() {
return (first != null ? first.hashCode() : 0) + (second != null ? second.hashCode() : 0) + (third != null ? third.hashCode() : 0);
}

@Override
public boolean equals(Object object) {
if (!(object instanceof T3)) return false;

T3 other = (T3) object;

if (first == null) {
if (other.first != null) return false;
} else {
if (!first.equals(other.first)) return false;
}

if (second == null) {
if (other.second != null) return false;
} else {
if (!second.equals(other.second)) return false;
}

if (third == null) {
if (other.third != null) return false;
} else {
if (!third.equals(other.third)) return false;
}

return true;
}

@Override
public String toString() {
return "T3(" + first + ", " + second + ", " + third + ")";
}
}
26 changes: 19 additions & 7 deletions src/main/java/org/opentripplanner/routing/core/FareRuleSet.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.opentripplanner.model.FeedScopedId;
import org.opentripplanner.model.FareAttribute;
import org.opentripplanner.common.model.P2;
import org.opentripplanner.common.model.P3;

public class FareRuleSet implements Serializable {

Expand All @@ -16,26 +17,29 @@ public class FareRuleSet implements Serializable {
private Set<FeedScopedId> routes;
private Set<P2<String>> originDestinations;
private Set<String> contains;
private Set<P3<String>> routeOriginDestinations;

public Set<String> getContains() {
return contains;
}

private FareAttribute fareAttribute;
private Set<FeedScopedId> trips;

public FareRuleSet(FareAttribute fareAttribute) {
this.fareAttribute = fareAttribute;
routes = new HashSet<FeedScopedId>();
originDestinations= new HashSet<P2<String>>();
contains = new HashSet<String>();
trips = new HashSet<FeedScopedId>();
routeOriginDestinations= new HashSet<P3<String>>();
}

public void setAgency(String agency) {
// TODO With new GTFS lib, read value from fareAttribute directly?
this.agency = agency;
}

public String getAgency() {
return agency;
}
Expand All @@ -51,11 +55,11 @@ public Set<P2<String>> getOriginDestinations() {
public void addContains(String containsId) {
contains.add(containsId);
}

public void addRoute(FeedScopedId route) {
routes.add(route);
}

public Set<FeedScopedId> getRoutes() {
return routes;
}
Expand All @@ -71,11 +75,19 @@ public boolean hasAgencyDefined() {
public void addTrip(FeedScopedId trip) {
trips.add(trip);
}

public Set<FeedScopedId> getTrips() {
return trips;
}


public void addRouteOriginDestination(String route, String origin, String destination) {
routeOriginDestinations.add(new P3<String>(route, origin, destination));
}

public Set<P3<String>> getRouteOriginDestinations() {
return routeOriginDestinations;
}

public boolean matches(Set<String> agencies, String startZone, String endZone, Set<String> zonesVisited,
Set<FeedScopedId> routesVisited, Set<FeedScopedId> tripsVisited) {
//check for matching agency
Expand Down Expand Up @@ -111,7 +123,7 @@ public boolean matches(Set<String> agencies, String startZone, String endZone, S
return false;
}
}

//check for matching trips
if (trips.size() != 0) {
if (!trips.containsAll(tripsVisited)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,15 +78,22 @@ protected void fillFareRules(String agencyId, Collection<FareAttribute> fareAttr
if (contains != null) {
fareRule.addContains(contains);
}

String origin = rule.getOriginId();
String destination = rule.getDestinationId();
if (origin != null || destination != null) {
fareRule.addOriginDestination(origin, destination);
}
Route route = rule.getRoute();

if (route != null) {
FeedScopedId routeId = route.getId();
fareRule.addRoute(routeId);
if (origin != null && destination != null) {
fareRule.addRouteOriginDestination(routeId.toString(), origin, destination);
} else {
fareRule.addRoute(routeId);
}
} else {
if (origin != null || destination != null) {
fareRule.addOriginDestination(origin, destination);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,17 +100,25 @@ protected FareAndId getBestFareAndId(FareType fareType, List<Ride> rides, Collec
long startTime = rides.get(0).startTime;
long lastRideStartTime = startTime;

float specialRouteFare = Float.POSITIVE_INFINITY;
FareAttribute specialFareAttribute = null;

for (Ride ride : rides) {
lastRideStartTime = ride.startTime;

/* HSL specific logig: all exception routes start and end from the defined zone set,
/* HSL specific logic: all exception routes start and end from the defined zone set,
but visit temporarily (maybe 1 stop only) an 'external' zone */
float bestSpecialFare = Float.POSITIVE_INFINITY;
Set<String> ruleZones = null;
for (FareRuleSet ruleSet : fareRules) {
if(ruleSet.getRoutes().contains(ride.route) &&
String routeOriginDestination = String.valueOf(ride.route) + ", " + String.valueOf(ride.startZone) + ", " + String.valueOf(ride.endZone);
boolean isSpecialRoute = false;
if(!ruleSet.getRouteOriginDestinations().isEmpty() && ruleSet.getRouteOriginDestinations().toString().indexOf(routeOriginDestination) != -1) {
isSpecialRoute = true;
}
if(isSpecialRoute || (ruleSet.getRoutes().contains(ride.route) &&
ruleSet.getContains().contains(ride.startZone) &&
ruleSet.getContains().contains(ride.endZone)) {
ruleSet.getContains().contains(ride.endZone))) {
// check validity of this special rule and that it is the cheapest applicable one
FareAttribute attribute = ruleSet.getFareAttribute();
if (!attribute.isTransferDurationSet() ||
Expand All @@ -119,6 +127,10 @@ but visit temporarily (maybe 1 stop only) an 'external' zone */
if (newFare < bestSpecialFare) {
bestSpecialFare = newFare;
ruleZones = ruleSet.getContains();
if(isSpecialRoute) {
specialRouteFare = bestSpecialFare;
specialFareAttribute = attribute;
}
}
}
}
Expand All @@ -137,29 +149,34 @@ but visit temporarily (maybe 1 stop only) an 'external' zone */
float bestFare = Float.POSITIVE_INFINITY;
long tripTime = lastRideStartTime - startTime;

// find the best fare that matches this set of rides
for (FareRuleSet ruleSet : fareRules) {
/* another HSL specific change: We do not set rules for every possible zone combination,
but for the largest zone set allowed for a certain ticket type.
This way we need only a few rules instead of hundreds of rules. Good for speed!
*/
if (ruleSet.getContains().containsAll(zones)) { // contains, not equals !!
FareAttribute attribute = ruleSet.getFareAttribute();
// transfers are evaluated at boarding time
if (attribute.isTransferDurationSet()) {
if(tripTime > attribute.getTransferDuration()) {
LOG.debug("transfer time exceeded; {} > {} in fare {}", tripTime, attribute.getTransferDuration(), attribute.getId());
continue;
} else {
LOG.debug("transfer time OK; {} < {} in fare {}", tripTime, attribute.getTransferDuration(), attribute.getId());
if(zones.size() > 0) {
// find the best fare that matches this set of rides
for (FareRuleSet ruleSet : fareRules) {
/* another HSL specific change: We do not set rules for every possible zone combination,
but for the largest zone set allowed for a certain ticket type.
This way we need only a few rules instead of hundreds of rules. Good for speed!
*/
if (ruleSet.getContains().containsAll(zones)) { // contains, not equals !!
FareAttribute attribute = ruleSet.getFareAttribute();
// transfers are evaluated at boarding time
if (attribute.isTransferDurationSet()) {
if(tripTime > attribute.getTransferDuration()) {
LOG.debug("transfer time exceeded; {} > {} in fare {}", tripTime, attribute.getTransferDuration(), attribute.getId());
continue;
} else {
LOG.debug("transfer time OK; {} < {} in fare {}", tripTime, attribute.getTransferDuration(), attribute.getId());
}
}
float newFare = getFarePrice(attribute, fareType);
if (newFare < bestFare) {
bestAttribute = attribute;
bestFare = newFare;
}
}
float newFare = getFarePrice(attribute, fareType);
if (newFare < bestFare) {
bestAttribute = attribute;
bestFare = newFare;
}
}
} else if (specialRouteFare != Float.POSITIVE_INFINITY && specialFareAttribute != null) {
bestFare = specialRouteFare;
bestAttribute = specialFareAttribute;
}
LOG.debug("HSL {} best for {}", bestAttribute, rides);
return new FareAndId(bestFare, bestAttribute == null ? null : bestAttribute.getId());
Expand Down
52 changes: 4 additions & 48 deletions src/test/java/org/opentripplanner/routing/algorithm/TestFares.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
public class TestFares extends TestCase {

private AStar aStar = new AStar();

public void testBasic() throws Exception {

Graph gg = new Graph();
Expand All @@ -51,7 +51,7 @@ public void testBasic() throws Exception {
path = spt.getPath(gg.getVertex(feedId + ":Mountain View Caltrain"), true);

FareService fareService = gg.getService(FareService.class);

Fare cost = fareService.getCost(path);
assertEquals(cost.getFare(FareType.regular), new Money(new WrappedCurrency("USD"), 425));
}
Expand Down Expand Up @@ -86,9 +86,9 @@ public void testPortland() throws Exception {
path = spt.getPath(gg.getVertex(feedId + ":1252"), true);
assertNotNull(path);
cost = fareService.getCost(path);

//assertEquals(cost.getFare(FareType.regular), new Money(new WrappedCurrency("USD"), 460));

// complex trip
options.maxTransfers = 5;
startTime = TestUtils.dateInSeconds("America/Los_Angeles", 2009, 11, 1, 14, 0, 0);
Expand All @@ -104,50 +104,6 @@ public void testPortland() throws Exception {
// thread on gtfs-changes.
// assertEquals(cost.getFare(FareType.regular), new Money(new WrappedCurrency("USD"), 430));
}


public void testKCM() throws Exception {

Graph gg = new Graph();
GtfsContext context = GtfsLibrary.readGtfs(new File(ConstantsForTests.KCM_GTFS));

PatternHopFactory factory = new PatternHopFactory(context);
factory.setFareServiceFactory(new SeattleFareServiceFactory());

factory.run(gg);
gg.putService(
CalendarServiceData.class,
createCalendarServiceData(context.getOtpTransitService())
);
RoutingRequest options = new RoutingRequest();
String feedId = gg.getFeedIds().iterator().next();

String vertex0 = feedId + ":2010";
String vertex1 = feedId + ":2140";
ShortestPathTree spt;
GraphPath path = null;

FareService fareService = gg.getService(FareService.class);

long offPeakStartTime = TestUtils.dateInSeconds("America/Los_Angeles", 2016, 5, 24, 5, 0, 0);
options.dateTime = offPeakStartTime;
options.setRoutingContext(gg, vertex0, vertex1);
spt = aStar.getShortestPathTree(options);
path = spt.getPath(gg.getVertex(vertex1), true);

Fare costOffPeak = fareService.getCost(path);
assertEquals(costOffPeak.getFare(FareType.regular), new Money(new WrappedCurrency("USD"), 250));

long onPeakStartTime = TestUtils.dateInSeconds("America/Los_Angeles", 2016, 5, 24, 8, 0, 0);
options.dateTime = onPeakStartTime;
options.setRoutingContext(gg, vertex0, vertex1);
spt = aStar.getShortestPathTree(options);
path = spt.getPath(gg.getVertex(vertex1), true);

Fare costOnPeak = fareService.getCost(path);
assertEquals(costOnPeak.getFare(FareType.regular), new Money(new WrappedCurrency("USD"), 275));

}

public void testFareComponent() throws Exception {
Graph gg = new Graph();
Expand Down

0 comments on commit 5a84980

Please sign in to comment.