Skip to content

Commit

Permalink
Rewrite to minimize changes to DefaultFareServiceImpl. No configurati…
Browse files Browse the repository at this point in the history
…on necessary.
  • Loading branch information
sdjacobs committed Aug 16, 2017
1 parent 4e9968b commit 4f13dd7
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 203 deletions.
Expand Up @@ -146,14 +146,6 @@ class FareAndId {
}
}

/** Allow a FareService to override the default mode of adding fares together for subsets of rides */
interface FareAdditiveStrategy {
/**
* Given sets of rides and associated costs, add the costs together.
*/
float addFares(List<Ride> ride0, List<Ride> ride1, float cost0, float cost1);
}

/**
* This fare service module handles the cases that GTFS handles within a single feed.
* It cannot necessarily handle multi-feed graphs, because a rule-less fare attribute
Expand All @@ -169,8 +161,6 @@ public class DefaultFareServiceImpl implements FareService, Serializable {

private static final Logger LOG = LoggerFactory.getLogger(DefaultFareServiceImpl.class);

private FareAdditiveStrategy fareAdditiveStrategy;

/** For each fare type (regular, student, etc...) the collection of rules that apply. */
protected Map<FareType, Collection<FareRuleSet>> fareRulesPerType;

Expand All @@ -182,10 +172,6 @@ public void addFareRules(FareType fareType, Collection<FareRuleSet> fareRules) {
fareRulesPerType.put(fareType, new ArrayList<>(fareRules));
}

public void setFareAdditiveStrategy(FareAdditiveStrategy fareAdditiveStrategy) {
this.fareAdditiveStrategy = fareAdditiveStrategy;
}

protected List<Ride> createRides(GraphPath path) {
List<Ride> rides = new LinkedList<Ride>();
Ride ride = null;
Expand Down Expand Up @@ -276,14 +262,8 @@ private FareSearch performSearch(FareType fareType, List<Ride> rides,
r.resultTable[j][j + i] = cost;
r.fareIds[j][j + i] = best.fareId;
for (int k = 0; k < i; k++) {
float via;
if (fareAdditiveStrategy == null) {
via = r.resultTable[j][j + k] + r.resultTable[j + k + 1][j + i];
}
else {
via = fareAdditiveStrategy.addFares(rides.subList(j, j + k + 1), rides.subList(j + k + 1, j + i + 1),
r.resultTable[j][j + k], r.resultTable[j + k + 1][j + i]);
}
float via = addFares(rides.subList(j, j + k + 1), rides.subList(j + k + 1, j + i + 1),
r.resultTable[j][j + k], r.resultTable[j + k + 1][j + i]);
if (r.resultTable[j][j + i] > via) {
r.resultTable[j][j + i] = via;
r.endOfComponent[j] = j + i;
Expand All @@ -295,6 +275,10 @@ private FareSearch performSearch(FareType fareType, List<Ride> rides,
return r;
}

protected float addFares(List<Ride> ride0, List<Ride> ride1, float cost0, float cost1) {
return cost0 + cost1;
}

protected float getLowestCost(FareType fareType, List<Ride> rides,
Collection<FareRuleSet> fareRules) {
FareSearch r = performSearch(fareType, rides, fareRules);
Expand Down
Expand Up @@ -13,19 +13,12 @@ the License, or (at your option) any later version.

package org.opentripplanner.routing.impl;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import org.onebusaway.gtfs.model.AgencyAndId;
import org.onebusaway.gtfs.model.Trip;
import org.onebusaway.gtfs.services.GtfsRelationalDao;
import org.opentripplanner.gtfs.GtfsLibrary;
import org.opentripplanner.routing.core.Fare.FareType;
import org.opentripplanner.routing.core.FareRuleSet;
import org.opentripplanner.routing.services.FareService;
Expand All @@ -39,40 +32,27 @@ public class SeattleFareServiceFactory extends DefaultFareServiceFactory {
@SuppressWarnings("unused")
private static final Logger LOG = LoggerFactory.getLogger(SeattleFareServiceFactory.class);

private Multimap<String, String> agenciesByFeed = ArrayListMultimap.create();

@Override
public FareService makeFareService() {

DefaultFareServiceImpl fareService = new DefaultFareServiceImpl();
SeattleFareServiceImpl fareService = new SeattleFareServiceImpl();
fareService.addFareRules(FareType.regular, regularFareRules.values());
fareService.addFareRules(FareType.youth, regularFareRules.values());
fareService.addFareRules(FareType.senior, regularFareRules.values());

fareService.setFareAdditiveStrategy(new SeattleFareAdditiveStrategy(agenciesByFeed));

return fareService;
}

@Override
public void configure(JsonNode config) {
if (config.has("useMaxFareStrategy")) {
Iterator<JsonNode> iter = config.get("useMaxFareStrategy").iterator();
while (iter.hasNext()) {
// feed:agency ie 97:1
AgencyAndId feedAndAgency = GtfsLibrary.convertIdFromString(iter.next().asText());
String feedId = feedAndAgency.getAgencyId();
String agencyId = feedAndAgency.getId();
agenciesByFeed.put(feedId, agencyId);
}
}
// No config for the moment
}

@Override
public void processGtfs(GtfsRelationalDao dao) {
// Add custom extension: trips may have a fare ID specified in KCM GTFS.
// Need to ensure that we are scoped to feed when adding trips to FareRuleSet,
// since fare IDs may not be unique across feeds and trip agency IDs
// since fare IDs may not be unique across feeds and trip agency IDsqq
// may not match fare attribute agency IDs (which are feed IDs).

Map<AgencyAndId, FareRuleSet> feedFareRules = new HashMap<>();
Expand All @@ -96,35 +76,3 @@ public void processGtfs(GtfsRelationalDao dao) {

}
}

class SeattleFareAdditiveStrategy implements FareAdditiveStrategy, Serializable {

private static final long serialVersionUID = 1L;

private Multimap<String, String> agenciesByFeed;

SeattleFareAdditiveStrategy(Multimap<String, String> agenciesByFeed) {
this.agenciesByFeed = agenciesByFeed;
}

@Override
public float addFares(List<Ride> ride0, List<Ride> ride1, float cost0, float cost1) {
String feedId = ride0.get(0).firstStop.getId().getAgencyId();
String agencyId = ride0.get(0).agency;
if (agenciesByFeed.get(feedId).contains(agencyId)) {
for (Ride r : Iterables.concat(ride0, ride1)) {
if (!isCorrectAgency(r, feedId, agencyId)) {
return cost0 + cost1;
}
}
return Math.max(cost0, cost1);
}
return cost0 + cost1;
}

private static boolean isCorrectAgency(Ride r, String feedId, String agencyId) {
String rideFeedId = r.firstStop.getId().getAgencyId();
String rideAgencyId = r.agency;
return feedId.equals(rideFeedId) && agencyId.equals(rideAgencyId);
}
}
Expand Up @@ -13,140 +13,35 @@ the License, or (at your option) any later version.

package org.opentripplanner.routing.impl;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Currency;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Iterables;

import org.opentripplanner.common.model.T2;
import org.opentripplanner.routing.core.Fare;
import org.opentripplanner.routing.core.Fare.FareType;
import org.opentripplanner.routing.core.FareRuleSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;

public class SeattleFareServiceImpl extends DefaultFareServiceImpl {
private static final long serialVersionUID = 1L;

// Agency IDs defined in King Metro Transit GTFS
public static final String KCM_EOS_AGENCY_ID = "EOS";

public static final String KCM_KCM_AGENCY_ID = "KCM";

public static final String KCM_ST_AGENCY_ID = "ST";

public static final String KCM_KMD_AGENCY_ID = "KMD";

// Agency IDs defined in Pierce Transit GTFS
public static final String PT_PT_AGENCY_ID = "3";

public static final String PT_ST_AGENCY_ID = "40";

// Agency IDs defined in Sound Transit GTFS
public static final String ST_ST_AGENCY_ID = "SoundTransit";
private static final long serialVersionUID = 2L;

// Agency IDs defined in Community Transit GTFS
public static final String CT_CT_AGENCY_ID = "29";

public static final int TRANSFER_DURATION_SEC = 7200;

// Fallback in case no rules apply for an agency
private Map<T2<FareType, String>, Float> defaultFares = new HashMap<>();

private static final Logger LOG = LoggerFactory.getLogger(SeattleFareServiceImpl.class);

public SeattleFareServiceImpl(Collection<FareRuleSet> regularFareRules,
Collection<FareRuleSet> youthFareRules, Collection<FareRuleSet> seniorFareRules) {
super();
addFareRules(FareType.regular, regularFareRules);
addFareRules(FareType.youth, youthFareRules);
addFareRules(FareType.senior, seniorFareRules);
}

public void addDefaultFare(FareType fareType, String agencyId, float cost) {
defaultFares.put(new T2<FareType, String>(fareType, agencyId), cost);
}
private static final String KCM_FEED_ID = "1";
private static final String KCM_AGENCY_ID = "1";

@Override
protected boolean populateFare(Fare fare, Currency currency, FareType fareType, List<Ride> rides,
Collection<FareRuleSet> fareRules) {
float lowestCost = getLowestCost(fareType, rides, fareRules);
if(lowestCost != Float.POSITIVE_INFINITY) {
fare.addFare(fareType, getMoney(currency, lowestCost));
return true;
}
return false;
}

@Override
protected float getLowestCost(FareType fareType, List<Ride> rides,
Collection<FareRuleSet> fareRules) {

// Split rides per agency
List<List<Ride>> ridesPerAgency = new ArrayList<List<Ride>>();
String lastAgency = null;
List<Ride> currentRides = null;
for (Ride ride : rides) {
if (ride.agency != lastAgency) {
currentRides = new ArrayList<Ride>();
ridesPerAgency.add(currentRides);
lastAgency = ride.agency;
}
currentRides.add(ride);
}

LOG.debug("=== Rides for fare class {} ===", fareType);
for (List<Ride> ridesForAgency : ridesPerAgency) {
LOG.debug("Ride for agency {} : {}", ridesForAgency.get(0).agency,
Arrays.toString(ridesForAgency.toArray()));
}

float currentCost = 0f;
float totalCost = 0f;
long lastStartSec = 0L;
for (List<Ride> ridesForAgency : ridesPerAgency) {

String agencyId = ridesForAgency.get(0).agency;
long startSec = ridesForAgency.get(0).startTime; // seconds
float costForAgency = super.getLowestCost(fareType, ridesForAgency, fareRules);

if (costForAgency == Float.POSITIVE_INFINITY) {
Float def = defaultFares.get(new T2<FareType, String>(fareType, agencyId));
if (def == null) {
LOG.error("No fares and no fallback for class {}, agency {}, rides {}",
fareType, agencyId, ridesForAgency);
return Float.POSITIVE_INFINITY;
}
costForAgency = def;
}
LOG.debug("Agency {} cost is {}", agencyId, costForAgency);

// Check for transfer
if (startSec < lastStartSec + TRANSFER_DURATION_SEC) {
// Transfer OK
if (costForAgency > currentCost) {
// Add top-up
float deltaCost = costForAgency - currentCost;
totalCost += deltaCost;
// Record max ticket price for current transfer
currentCost = costForAgency;
LOG.debug("Transfer, additional cost is {}, total is {}", deltaCost, totalCost);
} else {
LOG.debug("New ticket cost lower than current {}", currentCost);
protected float addFares(List<Ride> ride0, List<Ride> ride1, float cost0, float cost1) {
String feedId = ride0.get(0).firstStop.getId().getAgencyId();
String agencyId = ride0.get(0).agency;
if (KCM_FEED_ID.equals(feedId) && KCM_AGENCY_ID.equals(agencyId)) {
for (Ride r : Iterables.concat(ride0, ride1)) {
if (!isCorrectAgency(r, feedId, agencyId)) {
return cost0 + cost1;
}
// TODO Record discount
} else {
// New one needed
currentCost = costForAgency;
totalCost += costForAgency;
LOG.debug("New ticket, cost is {}, total is {}", costForAgency, totalCost);
lastStartSec = startSec;
}
return Math.max(cost0, cost1);
}
return cost0 + cost1;
}

return totalCost;
private static boolean isCorrectAgency(Ride r, String feedId, String agencyId) {
String rideFeedId = r.firstStop.getId().getAgencyId();
String rideAgencyId = r.agency;
return feedId.equals(rideFeedId) && agencyId.equals(rideAgencyId);
}

}

0 comments on commit 4f13dd7

Please sign in to comment.