-
Notifications
You must be signed in to change notification settings - Fork 75
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #884 from conveyal/abyrd/nearest-n
Temporal Opportunity Density and Dual Accessibility
- Loading branch information
Showing
13 changed files
with
308 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
90 changes: 90 additions & 0 deletions
90
src/main/java/com/conveyal/analysis/results/TemporalDensityCsvResultWriter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
package com.conveyal.analysis.results; | ||
|
||
import com.conveyal.file.FileStorage; | ||
import com.conveyal.r5.analyst.cluster.RegionalTask; | ||
import com.conveyal.r5.analyst.cluster.RegionalWorkResult; | ||
|
||
import java.io.IOException; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
/** | ||
* This handles collating regional results into CSV files containing temporal opportunity density | ||
* (number of opportunities reached in each one-minute interval, the derivative of step-function accessibility) | ||
* as well as "dual" accessibility (the amount of time needed to reach n opportunities). | ||
*/ | ||
public class TemporalDensityCsvResultWriter extends CsvResultWriter { | ||
|
||
private final int dualThreshold; | ||
|
||
public TemporalDensityCsvResultWriter(RegionalTask task, FileStorage fileStorage) throws IOException { | ||
super(task, fileStorage); | ||
dualThreshold = task.dualAccessibilityThreshold; | ||
} | ||
|
||
@Override | ||
public CsvResultType resultType () { | ||
return CsvResultType.TDENSITY; | ||
} | ||
|
||
@Override | ||
public String[] columnHeaders () { | ||
List<String> headers = new ArrayList<>(); | ||
// The ids of the freeform origin point and destination set | ||
headers.add("originId"); | ||
headers.add("destId"); | ||
headers.add("percentile"); | ||
for (int m = 0; m < 120; m += 1) { | ||
// The opportunity density over travel minute m | ||
headers.add(Integer.toString(m)); | ||
} | ||
// The number of minutes needed to reach d destination opportunities | ||
headers.add("D" + dualThreshold); | ||
return headers.toArray(new String[0]); | ||
} | ||
|
||
@Override | ||
protected void checkDimension (RegionalWorkResult workResult) { | ||
checkDimension( | ||
workResult, "destination pointsets", | ||
workResult.opportunitiesPerMinute.length, task.destinationPointSetKeys.length | ||
); | ||
for (double[][] percentilesForPointset : workResult.opportunitiesPerMinute) { | ||
checkDimension(workResult, "percentiles", percentilesForPointset.length, task.percentiles.length); | ||
for (double[] minutesForPercentile : percentilesForPointset) { | ||
checkDimension(workResult, "minutes", minutesForPercentile.length, 120); | ||
} | ||
} | ||
} | ||
|
||
@Override | ||
public Iterable<String[]> rowValues (RegionalWorkResult workResult) { | ||
List<String[]> rows = new ArrayList<>(); | ||
String originId = task.originPointSet.getId(workResult.taskId); | ||
for (int d = 0; d < task.destinationPointSetKeys.length; d++) { | ||
double[][] percentilesForDestPointset = workResult.opportunitiesPerMinute[d]; | ||
for (int p = 0; p < task.percentiles.length; p++) { | ||
List<String> row = new ArrayList<>(125); | ||
row.add(originId); | ||
row.add(task.destinationPointSetKeys[d]); | ||
row.add(Integer.toString(p)); | ||
// One density value for each of 120 minutes | ||
double[] densitiesPerMinute = percentilesForDestPointset[p]; | ||
for (int m = 0; m < 120; m++) { | ||
row.add(Double.toString(densitiesPerMinute[m])); | ||
} | ||
// One dual accessibility value | ||
int m = 0; | ||
double sum = 0; | ||
while (sum < dualThreshold && m < 120) { | ||
sum += densitiesPerMinute[m]; | ||
m += 1; | ||
} | ||
row.add(Integer.toString(m >= 120 ? -1 : m)); | ||
rows.add(row.toArray(new String[row.size()])); | ||
} | ||
} | ||
return rows; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
95 changes: 95 additions & 0 deletions
95
src/main/java/com/conveyal/r5/analyst/TemporalDensityResult.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
package com.conveyal.r5.analyst; | ||
|
||
import com.conveyal.r5.analyst.cluster.AnalysisWorkerTask; | ||
import com.google.common.base.Preconditions; | ||
|
||
import static com.conveyal.r5.common.Util.notNullOrEmpty; | ||
import static com.conveyal.r5.profile.FastRaptorWorker.UNREACHED; | ||
|
||
/** | ||
* An instance of this is included in a OneOriginResult for reporting how many opportunities are encountered during each | ||
* minute of travel. If we use more than one destination point set they are already constrained to all be aligned with | ||
* the same number of destinations. | ||
* | ||
* The data retained here feed into three different kinds of results: "Dual" accessibility (the number of opportunities | ||
* reached in a given number of minutes of travel time); temporal opportunity density (analogous to a probability density | ||
* function, how many opportunities are encountered during each minute of travel, whose integral is the cumulative | ||
* accessibility curve). | ||
* | ||
* Originally this class was tracking the identity of the N nearest points rather than just binning them by travel time. | ||
* This is more efficient in cases where N is small, and allows retaining the one-second resolution. However currently | ||
* there does not seem to be much demand among users for this level of detail, so it has been removed in the interest | ||
* of simplicity and maintainability. See issue 884 for more comments on implementation trade-offs. | ||
*/ | ||
public class TemporalDensityResult { | ||
|
||
// Internal state fields | ||
|
||
private final PointSet[] destinationPointSets; | ||
private final int nPercentiles; | ||
private final int opportunityThreshold; | ||
|
||
// Externally visible fields for accumulating results | ||
|
||
/** | ||
* The temporal density of opportunities. For each destination set, for each percentile, for each minute of | ||
* travel from 0 to 120, the number of opportunities reached in travel times from i (inclusive) to i+1 (exclusive). | ||
*/ | ||
public final double[][][] opportunitiesPerMinute; | ||
|
||
public TemporalDensityResult(AnalysisWorkerTask task) { | ||
Preconditions.checkArgument( | ||
notNullOrEmpty(task.destinationPointSets), | ||
"Dual accessibility requires at least one destination pointset." | ||
); | ||
this.destinationPointSets = task.destinationPointSets; | ||
this.nPercentiles = task.percentiles.length; | ||
this.opportunityThreshold = task.dualAccessibilityThreshold; | ||
this.opportunitiesPerMinute = new double[destinationPointSets.length][nPercentiles][120]; | ||
} | ||
|
||
public void recordOneTarget (int target, int[] travelTimePercentilesSeconds) { | ||
// Increment histogram bin for the number of minutes of travel by the number of opportunities at the target. | ||
for (int d = 0; d < destinationPointSets.length; d++) { | ||
PointSet dps = destinationPointSets[d]; | ||
for (int p = 0; p < nPercentiles; p++) { | ||
if (travelTimePercentilesSeconds[p] == UNREACHED) { | ||
break; // If any percentile is unreached, all higher ones are also unreached. | ||
} | ||
int m = travelTimePercentilesSeconds[p] / 60; | ||
if (m <= 120) { | ||
opportunitiesPerMinute[d][p][m] += dps.getOpportunityCount(target); | ||
} | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Calculate "dual" accessibility from the accumulated temporal opportunity density array. | ||
* @param n the threshold quantity of opportunities | ||
* @return the minimum whole number of minutes necessary to reach n opportunities, | ||
* for each destination set and percentile of travel time. | ||
*/ | ||
public int[][] minutesToReachOpportunities(int n) { | ||
int[][] result = new int[destinationPointSets.length][nPercentiles]; | ||
for (int d = 0; d < destinationPointSets.length; d++) { | ||
for (int p = 0; p < nPercentiles; p++) { | ||
result[d][p] = -1; | ||
double count = 0; | ||
for (int m = 0; m < 120; m++) { | ||
count += opportunitiesPerMinute[d][p][m]; | ||
if (count >= n) { | ||
result[d][p] = m + 1; | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
return result; | ||
} | ||
|
||
public int[][] minutesToReachOpportunities() { | ||
return minutesToReachOpportunities(opportunityThreshold); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.