Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
package org.opentripplanner.routing.fares.impl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Currency;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.opentripplanner.model.FeedScopedId;
import org.opentripplanner.model.Route;
import org.opentripplanner.model.plan.Itinerary;
import org.opentripplanner.model.plan.Leg;
import org.opentripplanner.routing.core.Fare;
import org.opentripplanner.routing.core.Fare.FareType;
import org.opentripplanner.routing.core.WrappedCurrency;
import org.opentripplanner.routing.fares.FareService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
enum NycFareState {
INIT,
SUBWAY_PRE_TRANSFER,
SUBWAY_PRE_TRANSFER_WALKED,
SUBWAY_POST_TRANSFER,
SIR_PRE_TRANSFER,
SIR_POST_TRANSFER_FROM_SUBWAY,
SIR_POST_TRANSFER_FROM_BUS,
EXPENSIVE_EXPRESS_BUS,
BUS_PRE_TRANSFER, CANARSIE,
}
enum NycRideClassifier {
SUBWAY,
SIR,
LOCAL_BUS,
EXPRESS_BUS,
EXPENSIVE_EXPRESS_BUS,
WALK
}
/**
* This handles the New York City MTA's baroque fare rules for subways and buses
* with the following limitations:
* (1) the two hour limit on transfers is not enforced
* (2) the b61/b62 special case is not handled
* (3) MNR, LIRR, and LI Bus are not supported -- only subways and buses
*
* I have not yet tested this on NY data since we switched to OTP2 (Raptor). It may need to be fixed.
* The only thing I've changed is how we produce rides from PathLegs instead of AStar states.
* The actual fare calculation logic remains exactly the same except for one thing: thanks to
* switching to typesafe enums, I fixed one bug where we were adding the enum value instead of the
* fare to the total cost.
*/
public class NycFareServiceImpl implements FareService {
private static final Logger LOG = LoggerFactory.getLogger(NycFareServiceImpl.class);
private static final long serialVersionUID = 1L;
private static final float ORDINARY_FARE = 2.75f;
private static final float EXPRESS_FARE = 6.50f;
private static final float EXPENSIVE_EXPRESS_FARE = 7.50f; // BxM4C only
private static final List SIR_PAID_STOPS = makeMtaStopList("S31", "S30");
private static final List SUBWAY_FREE_TRANSFER_STOPS = makeMtaStopList(
"R11", "B08", "629");
private static final List SIR_BONUS_STOPS = makeMtaStopList("140", "420",
"419", "418", "M22", "M23", "R27", "R26");
private static final List SIR_BONUS_ROUTES = makeMtaStopList("M5", "M20",
"M15-SBS");
private static final List CANARSIE = makeMtaStopList("L29", "303345");
// List of NYC agencies to set fares for
private static final List AGENCIES = Arrays.asList("MTABC", "MTA NYCT");
public NycFareServiceImpl() { }
@Override
public Fare getCost(Itinerary itinerary) {
// Use custom ride-categorizing method instead of the usual mapper from default fare service.
List rides = createRides(itinerary);
// There are no rides, so there's no fare.
if (rides.size() == 0) {
return null;
}
NycFareState state = NycFareState.INIT;
boolean lexFreeTransfer = false;
boolean canarsieFreeTransfer = false;
boolean siLocalBus = false;
boolean sirBonusTransfer = false;
float totalFare = 0;
for (Ride ride : rides) {
FeedScopedId firstStopId = null;
FeedScopedId lastStopId = null;
if (ride.firstStop != null) {
firstStopId = ride.firstStop.getId();
lastStopId = ride.lastStop.getId();
}
switch (state) {
case INIT:
lexFreeTransfer = siLocalBus = canarsieFreeTransfer = false;
if (ride.classifier.equals(NycRideClassifier.WALK)) {
// walking keeps you in init
} else if (ride.classifier.equals(NycRideClassifier.SUBWAY)) {
state = NycFareState.SUBWAY_PRE_TRANSFER;
totalFare += ORDINARY_FARE;
if (SUBWAY_FREE_TRANSFER_STOPS.contains(ride.lastStop.getId())) {
lexFreeTransfer = true;
}
if (CANARSIE.contains(ride.lastStop.getId())) {
canarsieFreeTransfer = true;
}
} else if (ride.classifier.equals(NycRideClassifier.SIR)) {
state = NycFareState.SIR_PRE_TRANSFER;
if (SIR_PAID_STOPS.contains(firstStopId)
|| SIR_PAID_STOPS.contains(lastStopId)) {
totalFare += ORDINARY_FARE;
}
} else if (ride.classifier.equals(NycRideClassifier.LOCAL_BUS)) {
state = NycFareState.BUS_PRE_TRANSFER;
totalFare += ORDINARY_FARE;
if (CANARSIE.contains(ride.lastStop.getId())) {
canarsieFreeTransfer = true;
}
siLocalBus = ride.route.getId().startsWith("S");
} else if (ride.classifier.equals(NycRideClassifier.EXPRESS_BUS)) {
state = NycFareState.BUS_PRE_TRANSFER;
totalFare += EXPRESS_FARE;
} else if (ride.classifier.equals(NycRideClassifier.EXPENSIVE_EXPRESS_BUS)) {
state = NycFareState.EXPENSIVE_EXPRESS_BUS;
totalFare += EXPENSIVE_EXPRESS_FARE;
}
break;
case SUBWAY_PRE_TRANSFER_WALKED:
if (ride.classifier.equals(NycRideClassifier.SUBWAY)) {
// subway-to-subway transfers are verbotten except at
// lex and 59/63
if (!(lexFreeTransfer && SUBWAY_FREE_TRANSFER_STOPS
.contains(ride.firstStop.getId()))) {
totalFare += ORDINARY_FARE;
}
lexFreeTransfer = canarsieFreeTransfer = false;
if (SUBWAY_FREE_TRANSFER_STOPS.contains(ride.lastStop.getId())) {
lexFreeTransfer = true;
}
if (CANARSIE.contains(ride.lastStop.getId())) {
canarsieFreeTransfer = true;
}
}
/* FALL THROUGH */
case SUBWAY_PRE_TRANSFER:
// it will always be possible to transfer from the first subway
// trip to anywhere,
// since no sequence of subway trips takes greater than two
// hours (if only just)
if (ride.classifier.equals(NycRideClassifier.WALK)) {
state = NycFareState.SUBWAY_PRE_TRANSFER_WALKED;
} else if (ride.classifier.equals(NycRideClassifier.SIR)) {
state = NycFareState.SIR_POST_TRANSFER_FROM_SUBWAY;
} else if (ride.classifier.equals(NycRideClassifier.LOCAL_BUS)) {
if (CANARSIE.contains(ride.firstStop.getId())
&& canarsieFreeTransfer) {
state = NycFareState.BUS_PRE_TRANSFER;
} else {
state = NycFareState.INIT;
}
} else if (ride.classifier.equals(NycRideClassifier.EXPRESS_BUS)) {
// need to pay the upgrade cost
totalFare += EXPRESS_FARE - ORDINARY_FARE;
} else if (ride.classifier.equals(NycRideClassifier.EXPENSIVE_EXPRESS_BUS)) {
totalFare += EXPENSIVE_EXPRESS_FARE; // no transfers to the
// BxMM4C
}
break;
case BUS_PRE_TRANSFER:
if (ride.classifier.equals(NycRideClassifier.SUBWAY)) {
if (CANARSIE.contains(ride.firstStop.getId())
&& canarsieFreeTransfer) {
state = NycFareState.SUBWAY_PRE_TRANSFER;
} else {
state = NycFareState.INIT;
}
} else if (ride.classifier.equals(NycRideClassifier.SIR)) {
if (siLocalBus) {
// SI local bus to SIR, so it is as if we started on the
// SIR (except that when we enter the bus or subway system we need to do
// so at certain places)
sirBonusTransfer = true;
state = NycFareState.SIR_PRE_TRANSFER;
} else {
//transfers exhausted
state = NycFareState.INIT;
}
} else if (ride.classifier.equals(NycRideClassifier.LOCAL_BUS)) {
state = NycFareState.INIT;
} else if (ride.classifier.equals(NycRideClassifier.EXPRESS_BUS)) {
// need to pay the upgrade cost
totalFare += EXPRESS_FARE - ORDINARY_FARE;
state = NycFareState.INIT;
} else if (ride.classifier.equals(NycRideClassifier.EXPENSIVE_EXPRESS_BUS)) {
totalFare += EXPENSIVE_EXPRESS_FARE;
// no transfers to the BxMM4C
}
break;
case SIR_PRE_TRANSFER:
if (ride.classifier.equals(NycRideClassifier.SUBWAY)) {
if (sirBonusTransfer && !SIR_BONUS_STOPS.contains(ride.firstStop.getId())) {
//we were relying on the bonus transfer to be in the "pre-transfer state",
//but the bonus transfer does not apply here
totalFare += ORDINARY_FARE;
}
if (CANARSIE.contains(ride.lastStop.getId())) {
canarsieFreeTransfer = true;
}
state = NycFareState.SUBWAY_POST_TRANSFER;
} else if (ride.classifier.equals(NycRideClassifier.SIR)) {
/* should not happen, and unhandled */
LOG.warn("Should not transfer from SIR to SIR");
} else if (ride.classifier.equals(NycRideClassifier.LOCAL_BUS)) {
if (!SIR_BONUS_ROUTES.contains(ride.route)) {
totalFare += ORDINARY_FARE;
}
state = NycFareState.BUS_PRE_TRANSFER;
} else if (ride.classifier.equals(NycRideClassifier.EXPRESS_BUS)) {
totalFare += EXPRESS_FARE;
state = NycFareState.BUS_PRE_TRANSFER;
} else if (ride.classifier.equals(NycRideClassifier.EXPENSIVE_EXPRESS_BUS)) {
totalFare += EXPENSIVE_EXPRESS_FARE;
state = NycFareState.BUS_PRE_TRANSFER;
}
break;
case SIR_POST_TRANSFER_FROM_SUBWAY:
if (ride.classifier.equals(NycRideClassifier.SUBWAY)) {
/* should not happen */
totalFare += ORDINARY_FARE;
state = NycFareState.SUBWAY_PRE_TRANSFER;
} else if (ride.classifier.equals(NycRideClassifier.SIR)) {
/* should not happen, and unhandled */
LOG.warn("Should not transfer from SIR to SIR");
} else if (ride.classifier.equals(NycRideClassifier.LOCAL_BUS)) {
if (!ride.route.getId().startsWith("S")) {
totalFare += ORDINARY_FARE;
state = NycFareState.BUS_PRE_TRANSFER;
} else {
state = NycFareState.INIT;
}
} else if (ride.classifier.equals(NycRideClassifier.EXPRESS_BUS)) {
// need to pay the full cost
totalFare += EXPRESS_FARE;
state = NycFareState.INIT;
} else if (ride.classifier.equals(NycRideClassifier.EXPENSIVE_EXPRESS_BUS)) {
/* should not happen */
// no transfers to the BxMM4C
totalFare += EXPENSIVE_EXPRESS_FARE;
state = NycFareState.BUS_PRE_TRANSFER;
}
break;
case SUBWAY_POST_TRANSFER:
if (ride.classifier.equals(NycRideClassifier.WALK)) {
if (!canarsieFreeTransfer) {
/* note: if we end up walking to another subway after alighting
* at Canarsie, we will mistakenly not be charged, but nobody
* would ever do this */
state = NycFareState.INIT;
}
} else if (ride.classifier.equals(NycRideClassifier.SIR)) {
totalFare += ORDINARY_FARE;
state = NycFareState.SIR_PRE_TRANSFER;
} else if (ride.classifier.equals(NycRideClassifier.LOCAL_BUS)) {
if (!(CANARSIE.contains(ride.firstStop.getId())
&& canarsieFreeTransfer)) {
totalFare += ORDINARY_FARE;
}
state = NycFareState.INIT;
} else if (ride.classifier.equals(NycRideClassifier.SUBWAY)) {
//walking transfer
totalFare += ORDINARY_FARE;
state = NycFareState.SUBWAY_PRE_TRANSFER;
} else if (ride.classifier.equals(NycRideClassifier.EXPRESS_BUS)) {
totalFare += EXPRESS_FARE;
state = NycFareState.BUS_PRE_TRANSFER;
} else if (ride.classifier.equals(NycRideClassifier.EXPENSIVE_EXPRESS_BUS)) {
totalFare += EXPENSIVE_EXPRESS_FARE;
state = NycFareState.BUS_PRE_TRANSFER;
}
}
}
Currency currency = Currency.getInstance("USD");
Fare fare = new Fare();
fare.addFare(FareType.regular, new WrappedCurrency(currency),
(int) Math.round(totalFare
* Math.pow(10, currency.getDefaultFractionDigits())));
return fare;
}
private static List createRides(Itinerary itinerary) {
return itinerary.legs.stream()
.map(leg -> mapToRide(itinerary, leg))
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
private static Ride mapToRide(Itinerary itinerary, Leg leg) {
// It seems like we should do something more sophisticated than just ignore
// agency IDs we don't recognize.
if (!AGENCIES.contains(leg.getAgency().getId().getFeedId())) {
return null;
} else if (isTransferLeg(leg, itinerary)) {
Ride ride = new Ride();
ride.classifier = NycRideClassifier.WALK;
return ride;
} else if (leg.isTransitLeg()) {
Ride ride = RideMapper.rideForTransitPathLeg(leg);
Route route = leg.getRoute();
int routeType = route.getGtfsType();
// Note the old implementation directly used the ints as classifiers here.
if (routeType == 1) {
ride.classifier = NycRideClassifier.SUBWAY;
} else if (routeType == 2) {
// All rail is Staten Island Railway? This won't work for LIRR and MNRR.
ride.classifier = NycRideClassifier.SIR;
} else if (routeType == 3) {
ride.classifier = NycRideClassifier.LOCAL_BUS;
}
String shortName = route.getShortName();
if (shortName == null ) {
ride.classifier = NycRideClassifier.SUBWAY;
} else if (shortName.equals("BxM4C")) {
ride.classifier = NycRideClassifier.EXPENSIVE_EXPRESS_BUS;
} else if (shortName.startsWith("X")
|| shortName.startsWith("BxM")
|| shortName.startsWith("QM")
|| shortName.startsWith("BM")) {
ride.classifier = NycRideClassifier.EXPRESS_BUS;
}
return ride;
}
return null;
}
private static boolean isTransferLeg(Leg leg, Itinerary itinerary) {
return !itinerary.firstLeg().equals(leg) && !itinerary.lastLeg().equals(leg) && leg.isWalkingLeg();
}
private static List makeMtaStopList(String... stops) {
ArrayList out = new ArrayList();
for (String stop : stops) {
out.add(new FeedScopedId("MTA NYCT", stop));
out.add(new FeedScopedId("MTA NYCT", stop + "N"));
out.add(new FeedScopedId("MTA NYCT", stop + "S"));
}
return out;
}
}