Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
50 changes: 25 additions & 25 deletions java-r5rcore/src/org/ipea/r5r/Fares/FarePerTransfer.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,36 @@
import com.fasterxml.jackson.annotation.JsonIgnore;

public class FarePerTransfer {
private String firstLeg;
private String secondLeg;
private String alightLeg;
private String boardLeg;
private float fare;
@JsonIgnore
private int integerFare;

public int getFirstLegFullIntegerFare() {
return firstLegFullIntegerFare;
public int getAlightLegFullIntegerFare() {
return alightLegFullIntegerFare;
}

public void setFirstLegFullIntegerFare(int firstLegFullIntegerFare) {
this.firstLegFullIntegerFare = firstLegFullIntegerFare;
public void setAlightLegFullIntegerFare(int alightLegFullIntegerFare) {
this.alightLegFullIntegerFare = alightLegFullIntegerFare;
}

public int getSecondLegFullIntegerFare() {
return secondLegFullIntegerFare;
public int getBoardLegFullIntegerFare() {
return boardLegFullIntegerFare;
}

public void setSecondLegFullIntegerFare(int secondLegFullIntegerFare) {
this.secondLegFullIntegerFare = secondLegFullIntegerFare;
public void setBoardLegFullIntegerFare(int boardLegFullIntegerFare) {
this.boardLegFullIntegerFare = boardLegFullIntegerFare;
}

@JsonIgnore
private int firstLegFullIntegerFare;
private int alightLegFullIntegerFare;
@JsonIgnore
private int secondLegFullIntegerFare;
private int boardLegFullIntegerFare;

public FarePerTransfer(String firstLeg, String secondLeg, float fare) {
this.firstLeg = firstLeg;
this.secondLeg = secondLeg;
public FarePerTransfer(String alightLeg, String boardLeg, float fare) {
this.alightLeg = alightLeg;
this.boardLeg = boardLeg;
this.fare = fare;
this.integerFare = Math.round(fare * 100.0f);
}
Expand All @@ -41,20 +41,20 @@ public FarePerTransfer() {
this("BUS", "BUS", 0.0f);
}

public String getFirstLeg() {
return firstLeg;
public String getAlightLeg() {
return alightLeg;
}

public void setFirstLeg(String firstLeg) {
this.firstLeg = firstLeg;
public void setAlightLeg(String alightLeg) {
this.alightLeg = alightLeg;
}

public String getSecondLeg() {
return secondLeg;
public String getBoardLeg() {
return boardLeg;
}

public void setSecondLeg(String secondLeg) {
this.secondLeg = secondLeg;
public void setBoardLeg(String boardLeg) {
this.boardLeg = boardLeg;
}

public float getFare() {
Expand All @@ -73,8 +73,8 @@ public void setFare(float fare) {
@Override
public String toString() {
return "FarePerTransfer{" +
"firstLeg='" + firstLeg + '\'' +
", secondLeg='" + secondLeg + '\'' +
"alightLeg='" + alightLeg + '\'' +
", boardLeg='" + boardLeg + '\'' +
", fare=" + fare +
'}';
}
Expand Down
4 changes: 2 additions & 2 deletions java-r5rcore/src/org/ipea/r5r/Fares/FareStructureBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ private void populateFareStructure(String type, float fare) {
String[] legs = transfer.split("&");

FarePerTransfer newTransfer = new FarePerTransfer();
newTransfer.setFirstLeg(legs[0]);
newTransfer.setSecondLeg(legs[1]);
newTransfer.setAlightLeg(legs[0]);
newTransfer.setBoardLeg(legs[1]);
newTransfer.setFare(fare);

faresPerTransfer.add(newTransfer);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import com.conveyal.r5.analyst.fare.FareBounds;
import com.conveyal.r5.analyst.fare.InRoutingFareCalculator;
import com.conveyal.r5.analyst.fare.TransferAllowance;
import com.conveyal.r5.profile.McRaptorSuboptimalPathProfileRouter;
import com.conveyal.r5.transit.RouteInfo;
import com.conveyal.r5.transit.TransitLayer;
Expand Down Expand Up @@ -65,11 +64,11 @@ private void loadFareInformation() {
this.faresPerTransfer = new FarePerTransfer[nTypes][nTypes];

for (FarePerTransfer transfer : fareStructure.getFaresPerTransfer()) {
int firstTypeIndex = indexTransportType.get(transfer.getFirstLeg());
int secondTypeIndex = indexTransportType.get(transfer.getSecondLeg());
int firstTypeIndex = indexTransportType.get(transfer.getAlightLeg());
int secondTypeIndex = indexTransportType.get(transfer.getBoardLeg());

transfer.setFirstLegFullIntegerFare(fareStructure.getFaresPerType().get(firstTypeIndex).getIntegerFare());
transfer.setSecondLegFullIntegerFare(fareStructure.getFaresPerType().get(secondTypeIndex).getIntegerFare());
transfer.setAlightLegFullIntegerFare(fareStructure.getFaresPerType().get(firstTypeIndex).getIntegerFare());
transfer.setBoardLegFullIntegerFare(fareStructure.getFaresPerType().get(secondTypeIndex).getIntegerFare());

faresPerTransfer[firstTypeIndex][secondTypeIndex] = transfer;
}
Expand Down Expand Up @@ -129,13 +128,13 @@ public FareBounds calculateFare(McRaptorSuboptimalPathProfileRouter.McRaptorStat
currentBoardTime = boardTimes.get(ride);

// get info on each leg
FarePerRoute firstLegType = faresPerRoute[previousPatternIndex];
FarePerRoute secondLegType = faresPerRoute[currentPatternIndex];
lastFareType = secondLegType.getTypeIndex();
FarePerRoute alightLegType = faresPerRoute[previousPatternIndex];
FarePerRoute boardLegType = faresPerRoute[currentPatternIndex];
lastFareType = boardLegType.getTypeIndex();

// check if transfer is in same type with unlimited transfers
if (firstLegType.getTypeIndex() == secondLegType.getTypeIndex()) {
FarePerType typeData = getTypeByIndex(firstLegType.getTypeIndex());
if (alightLegType.getTypeIndex() == boardLegType.getTypeIndex()) {
FarePerType typeData = getTypeByIndex(alightLegType.getTypeIndex());

// unlimited transfers mean the fare is $ 0.00, and transfer allowance is not spent
if (typeData.isUnlimitedTransfers()) {
Expand Down Expand Up @@ -195,7 +194,7 @@ public FareBounds calculateFare(McRaptorSuboptimalPathProfileRouter.McRaptorStat
int maxAllowanceValue = 0;
for (FarePerTransfer transfer : faresPerTransfer[faresPerRoute[currentPatternIndex].getTypeIndex()]) {
if (transfer != null) {
int fullTransferFare = transfer.getFirstLegFullIntegerFare() + transfer.getSecondLegFullIntegerFare();
int fullTransferFare = transfer.getAlightLegFullIntegerFare() + transfer.getBoardLegFullIntegerFare();

int allowance = fullTransferFare - transfer.getIntegerFare();
maxAllowanceValue = Math.max(allowance, maxAllowanceValue);
Expand Down Expand Up @@ -223,47 +222,47 @@ private int getFullFareForRoute(int patternIndex) {
}

private IntegratedFare getIntegrationFare(int firstPattern, int secondPattern, int transferTime) {
FarePerRoute firstLeg = faresPerRoute[firstPattern];
FarePerRoute secondLeg = faresPerRoute[secondPattern];
FarePerRoute alightLeg = faresPerRoute[firstPattern];
FarePerRoute boardLeg = faresPerRoute[secondPattern];

FarePerTransfer transferFare = getTransferByIndex(firstLeg.getTypeIndex(), secondLeg.getTypeIndex());
FarePerTransfer transferFare = getTransferByIndex(alightLeg.getTypeIndex(), boardLeg.getTypeIndex());
if (transferFare == null) {
// there is no record in transfers table, return full fare of second route
int fareSecondLeg = getFullFareForRoute(secondPattern);
return new IntegratedFare(fareSecondLeg, false);
int fareboardLeg = getFullFareForRoute(secondPattern);
return new IntegratedFare(fareboardLeg, false);
}

// discounted transfer found
// check if transfer is allowed (transfer between same route ids)
if (!isTransferAllowed(firstLeg, secondLeg)) {
if (!isTransferAllowed(alightLeg, boardLeg)) {
// transfer is not allowed, so return full fare for second leg
int fareSecondLeg = getFullFareForRoute(secondPattern);
return new IntegratedFare(fareSecondLeg, false);
int fareboardLeg = getFullFareForRoute(secondPattern);
return new IntegratedFare(fareboardLeg, false);
}

// transfer is allowed
// check if transfer time is within limits
if (transferTime > this.fareStructure.getTransferTimeAllowanceSeconds()) {
// transfer time expired, return full fare for second leg
int fareSecondLeg = getFullFareForRoute(secondPattern);
return new IntegratedFare(fareSecondLeg, false);
int fareboardLeg = getFullFareForRoute(secondPattern);
return new IntegratedFare(fareboardLeg, false);
}

// all restrictions clear: transfer is allowed
// discount the fare already considered in first leg
int fareFirstLeg = getFullFareForRoute(firstPattern);
return new IntegratedFare(transferFare.getIntegerFare() - fareFirstLeg, true);
int farealightLeg = getFullFareForRoute(firstPattern);
return new IntegratedFare(transferFare.getIntegerFare() - farealightLeg, true);
}

private boolean isTransferAllowed(FarePerRoute firstLeg, FarePerRoute secondLeg) {
private boolean isTransferAllowed(FarePerRoute alightLeg, FarePerRoute boardLeg) {
// if transfer is between routes in the same type, the condition 'allow-same-route-transfer' also applies
if (firstLeg.getTypeIndex() == secondLeg.getTypeIndex()) {
if (alightLeg.getTypeIndex() == boardLeg.getTypeIndex()) {
// if type allows transfers between same route, return true
FarePerType typeData = getTypeByIndex(firstLeg.getTypeIndex());
FarePerType typeData = getTypeByIndex(alightLeg.getTypeIndex());
if (typeData.isAllowSameRouteTransfer()) return true;

// if transfers between same route are not allowed, then check if route ids are different
return !firstLeg.getRouteId().equals(secondLeg.getRouteId());
return !alightLeg.getRouteId().equals(boardLeg.getRouteId());
} else {
// transfer is between routes in different types, so transfer is allowed
return true;
Expand Down
12 changes: 6 additions & 6 deletions r-package/R/fare_structure.R
Original file line number Diff line number Diff line change
Expand Up @@ -299,8 +299,8 @@ read_fare_structure <- function(file_path, encoding = "UTF-8") {
fare_structure$fares_per_transfer <- data.table::fread(
file = tmpfile("fares_per_transfer.csv"),
select = c(
first_leg = "character",
second_leg = "character",
alight_leg = "character",
board_leg = "character",
fare = "numeric"
),
encoding = encoding
Expand Down Expand Up @@ -403,19 +403,19 @@ assert_fare_structure <- function(fare_structure) {
checkmate::expect_data_frame(fare_structure$fares_per_transfer)
if (length(names(fare_structure$fares_per_transfer)) > 0) {
checkmate::expect_character(
fare_structure$fares_per_transfer$first_leg,
fare_structure$fares_per_transfer$alight_leg,
any.missing = FALSE
)
checkmate::assert_names(
fare_structure$fares_per_transfer$first_leg,
fare_structure$fares_per_transfer$alight_leg,
subset.of = unique(fare_structure$fares_per_type$type)
)
checkmate::expect_character(
fare_structure$fares_per_transfer$second_leg,
fare_structure$fares_per_transfer$board_leg,
any.missing = FALSE
)
checkmate::assert_names(
fare_structure$fares_per_transfer$second_leg,
fare_structure$fares_per_transfer$board_leg,
subset.of = unique(fare_structure$fares_per_type$type)
)
checkmate::expect_numeric(
Expand Down
Binary file modified r-package/inst/extdata/poa/fares/fares_poa.zip
Binary file not shown.
Binary file modified r-package/inst/jar/r5r.jar
Binary file not shown.
12 changes: 6 additions & 6 deletions r-package/tests/testthat/test-assert_fare_structure.R
Original file line number Diff line number Diff line change
Expand Up @@ -151,21 +151,21 @@ test_that("fares_per_transfer is right", {
expect_true(assert_fare_structure(struc_copy))

struc_copy <- copied_element("fares_per_transfer")
struc_copy$fares_per_transfer[, first_leg := as.factor(first_leg)]
struc_copy$fares_per_transfer[, alight_leg := as.factor(alight_leg)]
expect_error(assert_fare_structure(struc_copy))
struc_copy <- copied_element("fares_per_transfer")
struc_copy$fares_per_transfer[1, first_leg := NA]
struc_copy$fares_per_transfer[1, alight_leg := NA]
expect_error(assert_fare_structure(struc_copy))
struc_copy$fares_per_transfer[1, first_leg := "oie"]
struc_copy$fares_per_transfer[1, alight_leg := "oie"]
expect_error(assert_fare_structure(struc_copy))

struc_copy <- copied_element("fares_per_transfer")
struc_copy$fares_per_transfer[, second_leg := as.factor(second_leg)]
struc_copy$fares_per_transfer[, board_leg := as.factor(board_leg)]
expect_error(assert_fare_structure(struc_copy))
struc_copy <- copied_element("fares_per_transfer")
struc_copy$fares_per_transfer[1, second_leg := NA]
struc_copy$fares_per_transfer[1, board_leg := NA]
expect_error(assert_fare_structure(struc_copy))
struc_copy$fares_per_transfer[1, second_leg := "oie"]
struc_copy$fares_per_transfer[1, board_leg := "oie"]
expect_error(assert_fare_structure(struc_copy))

struc_copy <- copied_element("fares_per_transfer")
Expand Down
4 changes: 2 additions & 2 deletions r-package/tests/testthat/test-read_fare_structure.R
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ test_that("outputs a list with correct elements", {
expect_type(struc$fares_per_type$fare, "double")

expect_s3_class(struc$fares_per_transfer, "data.table")
expect_type(struc$fares_per_transfer$first_leg, "character")
expect_type(struc$fares_per_transfer$second_leg, "character")
expect_type(struc$fares_per_transfer$alight_leg, "character")
expect_type(struc$fares_per_transfer$board_leg, "character")
expect_type(struc$fares_per_transfer$fare, "double")

expect_s3_class(struc$fares_per_route, "data.table")
Expand Down
20 changes: 10 additions & 10 deletions r-package/tests/testthat/test-setup_fare_structure.R
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ test_that("outputs a list with correct elements", {
expect_type(struc$fares_per_type$fare, "double")

expect_s3_class(struc$fares_per_transfer, "data.table")
expect_type(struc$fares_per_transfer$first_leg, "character")
expect_type(struc$fares_per_transfer$second_leg, "character")
expect_type(struc$fares_per_transfer$alight_leg, "character")
expect_type(struc$fares_per_transfer$board_leg, "character")
expect_type(struc$fares_per_transfer$fare, "double")

expect_s3_class(struc$fares_per_route, "data.table")
Expand All @@ -113,32 +113,32 @@ test_that("uses the parameter 'by' to fill the structure", {
struc <- tester(by = "AGENCY_ID")
expect_true(all(gtfs$agency$agency_id %in% struc$fares_per_type$type))
expect_true(
all(gtfs$agency$agency_id %in% struc$fares_per_transfer$first_leg)
all(gtfs$agency$agency_id %in% struc$fares_per_transfer$alight_leg)
)
expect_true(
all(gtfs$agency$agency_id %in% struc$fares_per_transfer$second_leg)
all(gtfs$agency$agency_id %in% struc$fares_per_transfer$board_leg)
)

struc <- tester(by = "AGENCY_NAME")
expect_true(all(gtfs$agency$agency_name %in% struc$fares_per_type$type))
expect_true(
all(gtfs$agency$agency_name %in% struc$fares_per_transfer$first_leg)
all(gtfs$agency$agency_name %in% struc$fares_per_transfer$alight_leg)
)
expect_true(
all(gtfs$agency$agency_name %in% struc$fares_per_transfer$second_leg)
all(gtfs$agency$agency_name %in% struc$fares_per_transfer$board_leg)
)

gtfs_modes <- gtfs$routes$route_type
gtfs_modes <- ifelse(gtfs_modes == 3, "BUS", "RAIL")
struc <- tester(by = "MODE")
expect_true(all(gtfs_modes %in% struc$fares_per_type$type))
expect_true(all(gtfs_modes %in% struc$fares_per_transfer$first_leg))
expect_true(all(gtfs_modes %in% struc$fares_per_transfer$second_leg))
expect_true(all(gtfs_modes %in% struc$fares_per_transfer$alight_leg))
expect_true(all(gtfs_modes %in% struc$fares_per_transfer$board_leg))

struc <- tester(by = "GENERIC")
expect_true(struc$fares_per_type$type == "GENERIC")
expect_true(struc$fares_per_transfer$first_leg == "GENERIC")
expect_true(struc$fares_per_transfer$second_leg == "GENERIC")
expect_true(struc$fares_per_transfer$alight_leg == "GENERIC")
expect_true(struc$fares_per_transfer$board_leg == "GENERIC")
})

test_that("debug info is correctly set", {
Expand Down
12 changes: 6 additions & 6 deletions r-package/vignettes/fare_structure.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ fare_structure$fares_per_type

The fare rules for transfer are stored in the `fares_per_transfer` data.frame,
which is shown below. Each row contains the fare prices for transfers between the
modes specified in `first_leg` and `second_leg` columns.
modes specified in `alight_leg` and `board_leg` columns.

```{r}
fare_structure$fares_per_transfer
Expand All @@ -248,18 +248,18 @@ Porto Alegre.

```{r}
# conditional update fare value
fare_structure$fares_per_transfer[first_leg == "BUS" & second_leg == "BUS", fare := 7.2]
fare_structure$fares_per_transfer[alight_leg == "BUS" & board_leg == "BUS", fare := 7.2]
```

- Transfers between "BUS" and "RAIL" (in any direction) cost 8.37, once the 10% discount is applied. Let's make a final update in the data.frame to account for that.

```{r}
# conditional update fare value
fare_structure$fares_per_transfer[first_leg != second_leg, fare := 8.37]
fare_structure$fares_per_transfer[alight_leg != board_leg, fare := 8.37]

# use fcase instead ?
fare_structure$fares_per_transfer[, fare := fcase(first_leg == "BUS" & second_leg == "BUS", 7.2,
first_leg != second_leg, 8.37)]
fare_structure$fares_per_transfer[, fare := fcase(alight_leg == "BUS" & board_leg == "BUS", 7.2,
alight_leg != board_leg, 8.37)]

```

Expand All @@ -271,7 +271,7 @@ will count to the global `max_discounted_transfers` allowance.

```{r}
# remove row
fare_structure$fares_per_transfer <- fare_structure$fares_per_transfer[!(first_leg == "RAIL" & second_leg == "RAIL")]
fare_structure$fares_per_transfer <- fare_structure$fares_per_transfer[!(alight_leg == "RAIL" & board_leg == "RAIL")]

```

Expand Down
4 changes: 2 additions & 2 deletions r-package/vignettes/pareto_frontier.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,8 @@ fare_structure$fares_per_type[, fare := fcase(type == "BUS", 4.80,
type == "RAIL", 4.50)]

# update the cost of tranfers
fare_structure$fares_per_transfer[, fare := fcase(first_leg == "BUS" & second_leg == "BUS", 7.2,
first_leg != second_leg, 8.37)]
fare_structure$fares_per_transfer[, fare := fcase(alight_leg == "BUS" & board_leg == "BUS", 7.2,
alight_leg != board_leg, 8.37)]

# update transfer_time_allowance to 60 minutes
fare_structure$transfer_time_allowance <- 60
Expand Down
Loading