-
Notifications
You must be signed in to change notification settings - Fork 75
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Temporal Opportunity Density and Dual Accessibility #884
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
5b36910
initial implementation of closest n destinations
abyrd b0a0a8e
record opportunities per minute (discrete derivative of accessibility)
abyrd 655d0e2
provide dual accessibility, sorted opportunities
abyrd 6a03c8a
allow enabling and recording new results
abyrd 4051ddb
remove tracking of closest N opportunities
abyrd e8944ef
make class and field naming more consistent
abyrd e688365
consistent naming, comments, fix csv writer
abyrd 7513337
check for freeform origins before creating CSV
abyrd 9b8cf0e
Merge branch 'dev' into abyrd/nearest-n
abyrd 98b741a
update javadoc
abyrd File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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). | ||||||||||||||||||
Comment on lines
+14
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps part of this comment was unintentionally deleted? Otherwise,
Suggested change
|
||||||||||||||||||
* | ||||||||||||||||||
* 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.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Dual" accessibility will be recorded as -1 if
n
is not specified, or if the time needed exceeds 120 minutes.