diff --git a/x-pack/plugin/vector-tile/src/main/java/org/elasticsearch/xpack/vectortile/feature/SimpleFeatureFactory.java b/x-pack/plugin/vector-tile/src/main/java/org/elasticsearch/xpack/vectortile/feature/SimpleFeatureFactory.java index 7a6a1cf4cc5b6..53abad9ec1435 100644 --- a/x-pack/plugin/vector-tile/src/main/java/org/elasticsearch/xpack/vectortile/feature/SimpleFeatureFactory.java +++ b/x-pack/plugin/vector-tile/src/main/java/org/elasticsearch/xpack/vectortile/feature/SimpleFeatureFactory.java @@ -21,14 +21,15 @@ public class SimpleFeatureFactory { private final int extent; - private final Rectangle rectangle; - private final double pointXScale, pointYScale; + private final double pointXScale, pointYScale, pointXTranslate, pointYTranslate; public SimpleFeatureFactory(int z, int x, int y, int extent) { this.extent = extent; - rectangle = FeatureFactoryUtils.getTileBounds(z, x, y); + final Rectangle rectangle = FeatureFactoryUtils.getTileBounds(z, x, y); pointXScale = (double) extent / (rectangle.getMaxLon() - rectangle.getMinLon()); - pointYScale = -(double) extent / (rectangle.getMaxLat() - rectangle.getMinLat()); + pointYScale = (double) -extent / (rectangle.getMaxLat() - rectangle.getMinLat()); + pointXTranslate = -pointXScale * rectangle.getMinX(); + pointYTranslate = -pointYScale * rectangle.getMinY(); } public void point(VectorTile.Tile.Feature.Builder featureBuilder, double lon, double lat) { @@ -62,10 +63,10 @@ public void box(VectorTile.Tile.Feature.Builder featureBuilder, double minLon, d } private int lat(double lat) { - return (int) Math.round(pointYScale * (FeatureFactoryUtils.latToSphericalMercator(lat) - rectangle.getMinY())) + extent; + return (int) Math.round(pointYScale * FeatureFactoryUtils.latToSphericalMercator(lat) + pointYTranslate) + extent; } private int lon(double lon) { - return (int) Math.round(pointXScale * (FeatureFactoryUtils.lonToSphericalMercator(lon) - rectangle.getMinX())); + return (int) Math.round(pointXScale * FeatureFactoryUtils.lonToSphericalMercator(lon) + pointXTranslate); } } diff --git a/x-pack/plugin/vector-tile/src/test/java/org/elasticsearch/xpack/vectortile/feature/FeatureFactoryTests.java b/x-pack/plugin/vector-tile/src/test/java/org/elasticsearch/xpack/vectortile/feature/FeatureFactoryTests.java index c486168c4b472..7076bb8f6ec6d 100644 --- a/x-pack/plugin/vector-tile/src/test/java/org/elasticsearch/xpack/vectortile/feature/FeatureFactoryTests.java +++ b/x-pack/plugin/vector-tile/src/test/java/org/elasticsearch/xpack/vectortile/feature/FeatureFactoryTests.java @@ -17,6 +17,7 @@ import org.elasticsearch.test.ESTestCase; import org.hamcrest.Matchers; +import java.util.Arrays; import java.util.List; public class FeatureFactoryTests extends ESTestCase { @@ -26,6 +27,8 @@ public void testPoint() { int x = randomIntBetween(0, (1 << z) - 1); int y = randomIntBetween(0, (1 << z) - 1); int extent = randomIntBetween(1 << 8, 1 << 14); + // check if we might have numerical error due to floating point arithmetic + assumeFalse("", hasNumericalError(z, x, y, extent)); Rectangle rectangle = GeoTileUtils.toBoundingBox(x, y, z); SimpleFeatureFactory builder = new SimpleFeatureFactory(z, x, y, extent); FeatureFactory factory = new FeatureFactory(z, x, y, extent); @@ -44,6 +47,34 @@ public void testPoint() { } } + public void testIssue74341() { + int z = 1; + int x = 0; + int y = 0; + int extent = 1730; + // this is the typical case we need to guard from. + assertThat(hasNumericalError(z, x, y, extent), Matchers.equalTo(true)); + double lon = -171.0; + double lat = 0.9999999403953552; + SimpleFeatureFactory builder = new SimpleFeatureFactory(z, x, y, extent); + FeatureFactory factory = new FeatureFactory(z, x, y, extent); + VectorTile.Tile.Feature.Builder featureBuilder = VectorTile.Tile.Feature.newBuilder(); + builder.point(featureBuilder, lon, lat); + byte[] b1 = featureBuilder.build().toByteArray(); + Point point = new Point(lon, lat); + List features = factory.getFeatures(point, new UserDataIgnoreConverter()); + assertThat(features.size(), Matchers.equalTo(1)); + byte[] b2 = features.get(0).toByteArray(); + assertThat(Arrays.equals(b1, b2), Matchers.equalTo(false)); + } + + private boolean hasNumericalError(int z, int x, int y, int extent) { + final Rectangle rectangle = FeatureFactoryUtils.getTileBounds(z, x, y); + final double xDiff = rectangle.getMaxLon() - rectangle.getMinLon(); + final double yDiff = rectangle.getMaxLat() - rectangle.getMinLat(); + return (double) -extent / yDiff != -1d / (yDiff / (double) extent) || (double) extent / xDiff != 1d / (xDiff / (double) extent); + } + public void testRectangle() { int z = randomIntBetween(1, 10); int x = randomIntBetween(0, (1 << z) - 1);