diff --git a/src/main/java/com/conveyal/r5/analyst/FreeFormPointSet.java b/src/main/java/com/conveyal/r5/analyst/FreeFormPointSet.java index 4e9527be5..d0deaed13 100644 --- a/src/main/java/com/conveyal/r5/analyst/FreeFormPointSet.java +++ b/src/main/java/com/conveyal/r5/analyst/FreeFormPointSet.java @@ -251,8 +251,7 @@ public Envelope getWgsEnvelope () { public WebMercatorExtents getWebMercatorExtents () { final int DEFAULT_ZOOM = 9; Envelope wgsEnvelope = this.getWgsEnvelope(); - WebMercatorExtents webMercatorExtents = WebMercatorExtents.forWgsEnvelope(wgsEnvelope, DEFAULT_ZOOM); - return webMercatorExtents; + return WebMercatorExtents.forBufferedWgsEnvelope(wgsEnvelope, DEFAULT_ZOOM); } /** Construct a freeform point set containing one opportunity at each specified geographic coordinate. */ diff --git a/src/main/java/com/conveyal/r5/analyst/Grid.java b/src/main/java/com/conveyal/r5/analyst/Grid.java index a47021b0f..d3d113ff1 100644 --- a/src/main/java/com/conveyal/r5/analyst/Grid.java +++ b/src/main/java/com/conveyal/r5/analyst/Grid.java @@ -123,7 +123,7 @@ public Grid (WebMercatorExtents extents) { * @param wgsEnvelope Envelope of grid, in absolute WGS84 lat/lon coordinates */ public Grid (int zoom, Envelope wgsEnvelope) { - this(WebMercatorExtents.forWgsEnvelope(wgsEnvelope, zoom)); + this(WebMercatorExtents.forBufferedWgsEnvelope(wgsEnvelope, zoom)); } public static class PixelWeight { @@ -618,7 +618,7 @@ public static List fromCsv(InputStreamProvider csvInputStreamProvider, reader.close(); checkWgsEnvelopeSize(envelope, "CSV points"); - WebMercatorExtents extents = WebMercatorExtents.forWgsEnvelope(envelope, zoom); + WebMercatorExtents extents = WebMercatorExtents.forBufferedWgsEnvelope(envelope, zoom); checkPixelCount(extents, numericColumns.size()); if (progressListener != null) { @@ -690,7 +690,7 @@ public static List fromShapefile (File shapefile, int zoom, ProgressListen ShapefileReader reader = new ShapefileReader(shapefile); Envelope envelope = reader.wgs84Bounds(); checkWgsEnvelopeSize(envelope, "Shapefile"); - WebMercatorExtents extents = WebMercatorExtents.forWgsEnvelope(envelope, zoom); + WebMercatorExtents extents = WebMercatorExtents.forBufferedWgsEnvelope(envelope, zoom); List numericAttributes = reader.numericAttributes(); Set uniqueNumericAttributes = new HashSet<>(numericAttributes); if (uniqueNumericAttributes.size() != numericAttributes.size()) { @@ -772,7 +772,7 @@ public double getOpportunityCount (int i) { public static Grid fromFreeForm (FreeFormPointSet freeForm, int zoom) { // TODO make and us a strongly typed WgsEnvelope class here and in various places Envelope wgsEnvelope = freeForm.getWgsEnvelope(); - WebMercatorExtents webMercatorExtents = WebMercatorExtents.forWgsEnvelope(wgsEnvelope, zoom); + WebMercatorExtents webMercatorExtents = WebMercatorExtents.forBufferedWgsEnvelope(wgsEnvelope, zoom); Grid grid = new Grid(webMercatorExtents); grid.name = freeForm.name; for (int f = 0; f < freeForm.featureCount(); f++) { diff --git a/src/main/java/com/conveyal/r5/analyst/WebMercatorExtents.java b/src/main/java/com/conveyal/r5/analyst/WebMercatorExtents.java index cff116c00..d332d32f6 100644 --- a/src/main/java/com/conveyal/r5/analyst/WebMercatorExtents.java +++ b/src/main/java/com/conveyal/r5/analyst/WebMercatorExtents.java @@ -184,6 +184,18 @@ public static WebMercatorExtents forTrimmedWgsEnvelope (Envelope wgsEnvelope, in return WebMercatorExtents.forWgsEnvelope(wgsEnvelope, zoom); } + /** + * The opposite of forTrimmedWgsEnvelope: makes the envelope a tiny bit bigger before constructing the extents. + * This helps deal with numerical imprecision where we want to be sure all points within a supplied envelope will + * fall inside cells of the resulting web Mercator grid. + */ + public static WebMercatorExtents forBufferedWgsEnvelope (Envelope wgsEnvelope, int zoom) { + // Expand a protective copy of the envelope slightly. + wgsEnvelope = wgsEnvelope.copy(); + wgsEnvelope.expandBy(WGS_EPSILON); + return WebMercatorExtents.forWgsEnvelope(wgsEnvelope, zoom); + } + /** * Produces a new Envelope in WGS84 coordinates that tightly encloses the pixels of this WebMercatorExtents. * The edges of that Envelope will run exactly down the borders between neighboring web Mercator pixels.