Skip to content

Commit 35342b9

Browse files
author
Landon Reed
authored
Merge branch 'master' into turnRestrictionRoadSplit
2 parents e624c5e + 7e79ccb commit 35342b9

49 files changed

Lines changed: 2061 additions & 612 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.travis.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ after_success:
4343
# Upload the shaded jar. It's named with git describe, so begins with the last tag name, so begins with the letter v.
4444
# On the other hand, the main un-shaded artifact will begin with the project name 'r5'.
4545
- aws s3 cp --recursive --exclude "*" --include "v*.jar" /home/travis/build/conveyal/r5/target s3://r5-builds
46+
# copy Javadoc to S3
47+
# The syntax below sets JAVADOC_TARGET to TRAVIS_TAG if defined otherwise TRAVIS_BRANCH
48+
# If TRAVIS_TAG is defined TRAVIS_BRANCH will be undefined pr travis docs, but travis will run two
49+
# builds: one for the tag and one for the commit to the branch.
50+
- JAVADOC_TARGET=${TRAVIS_TAG:-$TRAVIS_BRANCH}
51+
- if [[ "$TRAVIS_PULL_REQUEST" = false ]]; then mvn javadoc:javadoc; aws s3 cp --cache-control max-age=300 --recursive /home/travis/build/conveyal/r5/target/site/apidocs s3://javadoc.conveyal.com/r5/${TRAVIS_BRANCH}/; fi
4652

4753
sudo: true # sudo: true gets us more memory, which we need, at the expense of slower builds
4854

@@ -55,4 +61,3 @@ cache:
5561

5662
notifications:
5763
slack: conveyal:vqrkN7708qU1OTL9XkbMqQFV
58-

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
The MIT License (MIT)
22

3-
Copyright (c) 2014-2016 Conveyal
3+
Copyright (c) 2014-2017 Conveyal
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ on this particular point [here](http://conveyal.com/blog/2015/05/04/variation-in
99

1010
Please follow the Conveyal Java style guide at https://github.com/conveyal/JavaStyle/
1111

12+
Javadoc for the project is built automatically after every change and published at http://javadoc.conveyal.com/r5/master/
13+
1214
## History
1315

1416
R<sup>5</sup> grew out of several open-source projects. The focus on analytic applications, and the core development team behind R<sup>5</sup>,

pom.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
<groupId>com.conveyal</groupId>
1010
<artifactId>r5</artifactId>
11-
<version>2.2.0-SNAPSHOT</version>
11+
<version>2.5.0-SNAPSHOT</version>
1212
<packaging>jar</packaging>
1313

1414
<licenses>
@@ -72,6 +72,7 @@
7272
<filtering>true</filtering>
7373
<includes>
7474
<include>**/*.properties</include>
75+
<include>worker.sh</include>
7576
<include>debug-plan/**</include>
7677
<include>fares/**</include>
7778
</includes>

src/main/java/com/conveyal/r5/R5Main.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@
1717
*/
1818
public class R5Main {
1919
public static void main (String... args) throws Exception {
20+
System.out.println("__________________\n" +
21+
"___ __ \\__ ____/\n" +
22+
"__ /_/ /_____ \\ \n" +
23+
"_ _, _/ ____/ / \n" +
24+
"/_/ |_| /_____/ \n" +
25+
" ");
26+
2027
// Pull argument 0 off as the sub-command,
2128
// then pass the remaining args (1..n) on to that subcommand.
2229
String command = args[0];
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package com.conveyal.r5.analyst;
2+
3+
/**
4+
* Compute p-values that the two regional analysis results differ due to systematic variation (change in transit network,
5+
* percentile of interest, land use, time window, etc.) rather than due to random variation from the Monte Carlo search
6+
* process. This uses the confidence interval method of bootstrap hypothesis testing described on page 214 of Efron and
7+
* Tibshirani (1993). We perform a two-tailed test, so we are not making an assumptions about whether the change was positive
8+
* or negative; a significant value indicates that there was a change, and that the sign is correct.
9+
*
10+
* This technique works by constructing a confidence interval that has one end at 0 and the other end where it may lie to
11+
* make the confidence interval symmetric (i.e. the same amount of density in both tails). We use the percentile method
12+
* to calculate this confidence interval; while we are aware there are issues with this, the better bootstrap confidence
13+
* interval methods require us to store additional data so we can compute a jackknife estimate of acceleration (Efron
14+
* and Tibshirani 1993, ch. 14).
15+
*
16+
* While it might initially seem that we could use the permutation test described earlier in that chapter, we in fact cannot.
17+
* It attractively promises to compare two distributions, which is exactly what we have, but it promises to compare some
18+
* statistic of those distributions. We're not interested in the difference in means of the sampling distributions, we're
19+
* interested in the the difference of the full sampling distribution. To use the permutation test, we would have to apply
20+
* it one level up the stack, when computing the regional results, and compute two regional runs simultaneously so we could
21+
* permute them when we still had the travel times for all iterations in memory.
22+
*
23+
* References
24+
* Efron, B., & Tibshirani, R. J. (1993). An Introduction to the Bootstrap. Boca Raton, FL: Chapman and Hall/CRC.
25+
*/
26+
public class BootstrapPercentileHypothesisTestGridStatisticComputer extends DualGridStatisticComputer {
27+
@Override
28+
protected double computeValuesForOrigin(int x, int y, int[] aValues, int[] bValues) {
29+
// compute the value
30+
int nBelowZero = 0;
31+
int nZero = 0;
32+
int nAboveZero = 0;
33+
int nTotal = 0;
34+
35+
// get the point estimate of the difference
36+
int pointEstimate = bValues[0] - aValues[0];
37+
if (pointEstimate == 0) return 0; // no difference, not statistically significant
38+
39+
// subtract every value in b from every value in a
40+
// this creates a bootstrapped sampling distribution of the differences, since each bootstrap sample in each analysis
41+
// is independent of all others (we've taken a lot of care to ensure this is the case).
42+
for (int aIdx = 1; aIdx < aValues.length; aIdx++) {
43+
// TODO a and b values are used more than once. This doesn't create bootstrap dependence, correct?
44+
int aVal = aValues[aIdx];
45+
for (int bIdx = 1; bIdx < bValues.length; bIdx++, nTotal++) {
46+
int bVal = bValues[bIdx];
47+
int difference = bVal - aVal;
48+
49+
if (difference > 0) nAboveZero++;
50+
else if (difference < 0) nBelowZero++;
51+
else nZero++;
52+
}
53+
}
54+
55+
double pVal;
56+
57+
// if the point estimate was less than zero, assume the confidence interval is less than zero
58+
// This could be wrong if the accessibility does not lie on the same side of zero as the majority of the density.
59+
// TODO Efron and Tibshirani don't really discuss how to handle that case, and in particular don't discuss two-
60+
// tailed tests at all.
61+
if (pointEstimate < 0) {
62+
// compute the density that lies at or above zero. We have changed the technique slightly from what is
63+
// described in Efron and Tibshirani 1993 to explicitly handle values that are exactly 0 (important because
64+
// we are working with discretized, integer data).
65+
double densityAtOrAboveZero = (double) (nZero + nAboveZero) / nTotal;
66+
// two tailed test, take this much density off the other end as well, and that's the p-value for this test.
67+
pVal = 2 * densityAtOrAboveZero;
68+
} else {
69+
// compute the density that lies at or below zero
70+
double densityAtOrBelowZero = (double) (nBelowZero + nZero) / nTotal;
71+
// two tailed test
72+
pVal = 2 * densityAtOrBelowZero;
73+
}
74+
75+
// fix up case where point est does not lie on same side of 0 as majority of density.
76+
if (pVal > 1) pVal = 1;
77+
78+
// scale probability to 0 - 100,000
79+
// note that the return value can actually be less than 0 if a majority of the density lies on the
80+
// opposite side of zero from the point estimate. Maybe we shouldn't be looking at the point estimate
81+
// NB for historical reasons, the probability grids show the probability of change, not the probability of no
82+
// change (i.e. the p-value). Invert the values (i.e. replace p with alpha)
83+
return (1 - pVal) * 1e5;
84+
}
85+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package com.conveyal.r5.analyst;
2+
3+
import com.amazonaws.services.s3.AmazonS3;
4+
import com.amazonaws.services.s3.AmazonS3Client;
5+
import com.amazonaws.services.s3.model.S3Object;
6+
import com.google.common.io.LittleEndianDataInputStream;
7+
8+
import java.io.FileInputStream;
9+
import java.io.FileOutputStream;
10+
import java.io.IOException;
11+
import java.io.InputStream;
12+
import java.util.zip.GZIPInputStream;
13+
14+
/**
15+
* A DualGridStatisticComputer computes statistics based on two grids.
16+
*/
17+
public abstract class DualGridStatisticComputer {
18+
private static final AmazonS3 s3 = new AmazonS3Client();
19+
/** Version of the access grid format we read */
20+
private static final int ACCESS_GRID_VERSION = 0;
21+
22+
/**
23+
* Calculate the probability at each origin that a random individual sample from regional analysis B is larger than one from regional
24+
* analysis A. We do this empirically and exhaustively by for each origin looping over every possible combination of
25+
* samples and taking a difference, then evaluating the number that yielded results greater than zero.
26+
*
27+
* The regional analysis access grids must be of identical size and zoom level, and a Grid object (the same as is used
28+
* for destination grids) will be returned, with probabilities scaled from 0 to 100,000.
29+
*/
30+
public Grid computeImprovementProbability (String resultBucket, String regionalAnalysisAKey, String regionalAnalysisBKey) throws IOException {
31+
S3Object aGrid = s3.getObject(resultBucket, regionalAnalysisAKey);
32+
S3Object bGrid = s3.getObject(resultBucket, regionalAnalysisBKey);
33+
return computeImprovementProbability(aGrid.getObjectContent(), bGrid.getObjectContent());
34+
}
35+
36+
public Grid computeImprovementProbability(InputStream a, InputStream b) throws IOException {
37+
LittleEndianDataInputStream aIn = new LittleEndianDataInputStream(new GZIPInputStream(a));
38+
LittleEndianDataInputStream bIn = new LittleEndianDataInputStream(new GZIPInputStream(b));
39+
40+
validateHeaderAndVersion(aIn);
41+
validateHeaderAndVersion(bIn);
42+
43+
int aZoom = aIn.readInt();
44+
int aWest = aIn.readInt();
45+
int aNorth = aIn.readInt();
46+
int aWidth = aIn.readInt();
47+
int aHeight = aIn.readInt();
48+
49+
int bZoom = bIn.readInt();
50+
int bWest = bIn.readInt();
51+
int bNorth = bIn.readInt();
52+
int bWidth = bIn.readInt();
53+
int bHeight = bIn.readInt();
54+
55+
if (aZoom != bZoom ||
56+
aWest != bWest ||
57+
aNorth != bNorth ||
58+
aWidth != bWidth ||
59+
aHeight != bHeight) {
60+
throw new IllegalArgumentException("Grid sizes for comparison must be identical!");
61+
}
62+
63+
// number of iterations need not be equal, the computed probability is still valid even if they are not
64+
// as the probability of choosing any particular sample is still uniform within each scenario.
65+
int aIterations = aIn.readInt();
66+
int bIterations = bIn.readInt();
67+
68+
Grid out = new Grid(aZoom, aWidth, aHeight, aNorth, aWest);
69+
70+
// pixels are in row-major order, iterate over y on outside
71+
for (int y = 0; y < aHeight; y++) {
72+
for (int x = 0; x < aWidth; x++) {
73+
int[] aValues = new int[aIterations];
74+
int[] bValues = new int[bIterations];
75+
76+
for (int iteration = 0, val = 0; iteration < aIterations; iteration++) {
77+
aValues[iteration] = (val += aIn.readInt());
78+
}
79+
80+
for (int iteration = 0, val = 0; iteration < bIterations; iteration++) {
81+
bValues[iteration] = (val += bIn.readInt());
82+
}
83+
84+
out.grid[x][y] = computeValuesForOrigin(x, y, aValues, bValues);
85+
}
86+
}
87+
88+
return out;
89+
}
90+
91+
private static void validateHeaderAndVersion(LittleEndianDataInputStream input) throws IOException {
92+
char[] header = new char[8];
93+
for (int i = 0; i < 8; i++) {
94+
header[i] = (char) input.readByte();
95+
}
96+
97+
if (!"ACCESSGR".equals(new String(header))) {
98+
throw new IllegalArgumentException("Input not in access grid format!");
99+
}
100+
101+
int version = input.readInt();
102+
103+
if (version != ACCESS_GRID_VERSION) {
104+
throw new IllegalArgumentException(String.format("Version mismatch of access grids, expected %s, found %s", ACCESS_GRID_VERSION, version));
105+
}
106+
}
107+
108+
/** Given the origin coordinates and the values from the two grids, compute a value for the output grid */
109+
protected abstract double computeValuesForOrigin (int x, int y, int[] aValues, int[] bValues);
110+
111+
public static void main (String... args) throws IOException {
112+
DualGridStatisticComputer comp;
113+
if ("--two-tailed".equals(args[0])) {
114+
comp = new BootstrapPercentileHypothesisTestGridStatisticComputer();
115+
} else {
116+
throw new RuntimeException("Unknown grid statistic computer " + args[0]);
117+
}
118+
119+
FileInputStream a = new FileInputStream(args[1]);
120+
FileInputStream b = new FileInputStream(args[2]);
121+
Grid grid = comp.computeImprovementProbability(a, b);
122+
123+
if (args[3].endsWith(".grid"))
124+
grid.write(new FileOutputStream(args[3]));
125+
else if (args[3].endsWith(".png"))
126+
grid.writePng(new FileOutputStream(args[3]));
127+
else if (args[3].endsWith(".tif"))
128+
grid.writeGeotiff(new FileOutputStream(args[3]));
129+
}
130+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.conveyal.r5.analyst;
2+
3+
import com.amazonaws.services.s3.AmazonS3;
4+
import com.amazonaws.services.s3.AmazonS3Client;
5+
import com.amazonaws.services.s3.model.S3Object;
6+
import com.conveyal.r5.analyst.scenario.GridStatisticComputer;
7+
import com.google.common.io.LittleEndianDataInputStream;
8+
9+
import java.io.IOException;
10+
import java.util.Arrays;
11+
import java.util.zip.GZIPInputStream;
12+
13+
/**
14+
* Access grids are three-dimensional arrays, with the first two dimensions consisting of x and y coordinates of origins
15+
* within the regional analysis, and the third dimension reflects multiple values of the indicator of interest. This could
16+
* be instantaneous accessibility results for each Monte Carlo draw when computing average instantaneous accessibility (i.e.
17+
* Owen-style accessibility), or it could be multiple bootstrap replications of the sampling distribution of accessibility
18+
* given median travel time (see Conway, M. W., Byrd, A. and van Eggermond, M. "A Statistical Approach to Comparing
19+
* Accessibility Results: Including Uncertainty in Public Transport Sketch Planning," paper presented at the 2017 World
20+
* Symposium of Transport and Land Use Research, Brisbane, QLD, Australia, Jul 3-6.)
21+
*
22+
* An ExtractingGridStatisticComputer simply grabs the value at a particular index within each origin.
23+
* When storing bootstrap replications of travel time, we also store the point estimate (using all Monte Carlo draws
24+
* equally weighted) as the first value, so an ExtractingGridStatisticComputer(0) can be used to retrieve the point estimate.
25+
*/
26+
public class ExtractingGridStatisticComputer extends GridStatisticComputer {
27+
public final int index;
28+
29+
/** Initialize with the index to extract */
30+
public ExtractingGridStatisticComputer (int index) {
31+
this.index = index;
32+
}
33+
34+
@Override
35+
protected double computeValueForOrigin(int x, int y, int[] valuesThisOrigin) {
36+
return valuesThisOrigin[index];
37+
}
38+
}

src/main/java/com/conveyal/r5/analyst/Grid.java

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,12 +122,24 @@ public Grid (int zoom, int width, int height, int north, int west) {
122122
this.grid = new double[width][height];
123123
}
124124

125+
/**
126+
* Version of getPixelWeights which returns the weights as relative to the total area of the input geometry (i.e.
127+
* the weight at a pixel is the proportion of the input geometry that falls within that pixel.
128+
*/
129+
public TObjectDoubleMap<int[]> getPixelWeights (Geometry geometry) {
130+
return getPixelWeights(geometry, false);
131+
}
132+
125133
/**
126134
* Get the proportions of an input polygon feature that overlap each grid cell, in the format [x, y] => weight.
127135
* This weight object can then be fed into the incrementFromPixelWeights function to actually burn a polygon into the
128136
* grid.
137+
*
138+
* If relativeToPixels is true, the weights are the proportion of the pixel that is covered. Otherwise they are the
139+
* portion of this polygon which is within the given grid cell. If using incrementPixelWeights, this should be set to
140+
* false.
129141
*/
130-
public TObjectDoubleMap<int[]> getPixelWeights (Geometry geometry) {
142+
public TObjectDoubleMap<int[]> getPixelWeights (Geometry geometry, boolean relativeToPixels) {
131143
// No need to convert to a local coordinate system
132144
// Both the supplied polygon and the web mercator pixel geometries are left in WGS84 geographic coordinates.
133145
// Both are distorted equally along the X axis at a given latitude so the proportion of the geometry within
@@ -151,7 +163,8 @@ public TObjectDoubleMap<int[]> getPixelWeights (Geometry geometry) {
151163

152164
Geometry pixel = getPixelGeometry(x + west, y + north, zoom);
153165
Geometry intersection = pixel.intersection(geometry);
154-
double weight = intersection.getArea() / area;
166+
double denominator = relativeToPixels ? pixel.getArea() : area;
167+
double weight = intersection.getArea() / denominator;
155168
weights.put(new int[] { x, y }, weight);
156169
}
157170
}
@@ -345,20 +358,34 @@ public void writeShapefile (String fileName, String fieldName) {
345358

346359
/* functions below from http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Mathematics */
347360

361+
/** Return the pixel the given longitude falls within */
348362
public static int lonToPixel (double lon, int zoom) {
349363
return (int) ((lon + 180) / 360 * Math.pow(2, zoom) * 256);
350364
}
351365

366+
/** return the west side of the given pixel (assuming an integer pixel; noninteger pixels will return the appropriate location within the pixel) */
352367
public static double pixelToLon (double pixel, int zoom) {
353368
return pixel / (Math.pow(2, zoom) * 256) * 360 - 180;
354369
}
355370

371+
/** Return the longitude of the center of the given pixel */
372+
public static double pixelToCenterLon (int pixel, int zoom) {
373+
return pixelToLon(pixel + 0.5, zoom);
374+
}
375+
376+
/** Return the pixel the given latitude falls within */
356377
public static int latToPixel (double lat, int zoom) {
357378
double latRad = FastMath.toRadians(lat);
358379
return (int) ((1 - log(tan(latRad) + 1 / cos(latRad)) / Math.PI) * Math.pow(2, zoom - 1) * 256);
359380
}
360381

361-
// We're using FastMath here, because the built-in math functions were taking a laarge amount of time in profiling.
382+
/** Return the latitude of the center of the given pixel */
383+
public static double pixelToCenterLat (int pixel, int zoom) {
384+
return pixelToLat(pixel + 0.5, zoom);
385+
}
386+
387+
// We're using FastMath here, because the built-in math functions were taking a large amount of time in profiling.
388+
/** return the north side of the given pixel (assuming an integer pixel; noninteger pixels will return the appropriate location within the pixel) */
362389
public static double pixelToLat (double pixel, int zoom) {
363390
return FastMath.toDegrees(atan(sinh(Math.PI - (pixel / 256d) / Math.pow(2, zoom) * 2 * Math.PI)));
364391
}

0 commit comments

Comments
 (0)