diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt index c13cf6abea5..9ac57115c83 100644 --- a/lucene/CHANGES.txt +++ b/lucene/CHANGES.txt @@ -115,6 +115,8 @@ Bug Fixes so an query for UNORDERED(foo, foo) would match a document containing 'foo' only once. (Alan Woodward) +* LUCENE-9250: Add support for Circle2d#intersectsLine around the dateline. (Ignacio Vera) + Other --------------------- diff --git a/lucene/core/src/java/org/apache/lucene/geo/Circle2D.java b/lucene/core/src/java/org/apache/lucene/geo/Circle2D.java index 18bc587010f..91690f17787 100644 --- a/lucene/core/src/java/org/apache/lucene/geo/Circle2D.java +++ b/lucene/core/src/java/org/apache/lucene/geo/Circle2D.java @@ -112,7 +112,7 @@ class Circle2D implements Component2D { // if any of the edges intersects an the edge belongs to the shape then it cannot be within. // if it only intersects edges that do not belong to the shape, then it is a candidate // we skip edges at the dateline to support shapes crossing it - if (intersectsLine(ax, ay, bx, by)) { + if (calculator.intersectsLine(ax, ay, bx, by)) { if (ab == true) { return WithinRelation.NOTWITHIN; } else { @@ -120,14 +120,14 @@ class Circle2D implements Component2D { } } - if (intersectsLine(bx, by, cx, cy)) { + if (calculator.intersectsLine(bx, by, cx, cy)) { if (bc == true) { return WithinRelation.NOTWITHIN; } else { relation = WithinRelation.CANDIDATE; } } - if (intersectsLine(cx, cy, ax, ay)) { + if (calculator.intersectsLine(cx, cy, ax, ay)) { if (ca == true) { return WithinRelation.NOTWITHIN; } else { @@ -162,7 +162,7 @@ class Circle2D implements Component2D { if (numCorners == 2) { return Relation.CELL_INSIDE_QUERY; } else if (numCorners == 0) { - if (intersectsLine(a2x, a2y, b2x, b2y)) { + if (calculator.intersectsLine(a2x, a2y, b2x, b2y)) { return Relation.CELL_CROSSES_QUERY; } return Relation.CELL_OUTSIDE_QUERY; @@ -181,9 +181,9 @@ class Circle2D implements Component2D { if (Component2D.pointInTriangle(minX, maxX, minY, maxY, calculator.geX(), calculator.getY(), ax, ay, bx, by, cx, cy) == true) { return Relation.CELL_CROSSES_QUERY; } - if (intersectsLine(ax, ay, bx, by) || - intersectsLine(bx, by, cx, cy) || - intersectsLine(cx, cy, ax, ay)) { + if (calculator.intersectsLine(ax, ay, bx, by) || + calculator.intersectsLine(bx, by, cx, cy) || + calculator.intersectsLine(cx, cy, ax, ay)) { return Relation.CELL_CROSSES_QUERY; } return Relation.CELL_OUTSIDE_QUERY; @@ -210,14 +210,16 @@ class Circle2D implements Component2D { return containsCount; } - // This methods in a new helper class XYUtil? - private boolean intersectsLine(double aX, double aY, double bX, double bY) { + private static boolean intersectsLine(double centerX, double centerY, double aX, double aY, double bX, double bY, DistanceCalculator calculator) { //Algorithm based on this thread : https://stackoverflow.com/questions/3120357/get-closest-point-to-a-line - final double[] vectorAP = new double[] {calculator.geX() - aX, calculator.getY() - aY}; - final double[] vectorAB = new double[] {bX - aX, bY - aY}; + final double vectorAPX = centerX - aX; + final double vectorAPY = centerY - aY; - final double magnitudeAB = vectorAB[0] * vectorAB[0] + vectorAB[1] * vectorAB[1]; - final double dotProduct = vectorAP[0] * vectorAB[0] + vectorAP[1] * vectorAB[1]; + final double vectorABX = bX - aX; + final double vectorABY = bY - aY; + + final double magnitudeAB = vectorABX * vectorABX + vectorABY * vectorABY; + final double dotProduct = vectorAPX * vectorABX + vectorAPY * vectorABY; final double distance = dotProduct / magnitudeAB; @@ -225,8 +227,8 @@ class Circle2D implements Component2D { return false; } - final double pX = aX + vectorAB[0] * distance; - final double pY = aY + vectorAB[1] * distance; + final double pX = aX + vectorABX * distance; + final double pY = aY + vectorABY * distance; final double minX = StrictMath.min(aX, bX); final double minY = StrictMath.min(aY, bY); @@ -234,31 +236,34 @@ class Circle2D implements Component2D { final double maxY = StrictMath.max(aY, bY); if (pX >= minX && pX <= maxX && pY >= minY && pY <= maxY) { - return contains(pX, pY); + return calculator.contains(pX, pY); } return false; } private interface DistanceCalculator { - Relation relate(double minX, double maxX, double minY, double maxY); - + /** check if the point is within a distance */ boolean contains(double x, double y); - + /** check if the line is within a distance */ + boolean intersectsLine(double aX, double aY, double bX, double bY); + /** Relates this calculator to the provided bounding box */ + Relation relate(double minX, double maxX, double minY, double maxY); + /** check if the bounding box is disjoint with this calculator bounding box */ boolean disjoint(double minX, double maxX, double minY, double maxY); - + /** check if the bounding box is contains this calculator bounding box */ boolean within(double minX, double maxX, double minY, double maxY); - + /** get min X of this calculator */ double getMinX(); - + /** get max X of this calculator */ double getMaxX(); - + /** get min Y of this calculator */ double getMinY(); - + /** get max Y of this calculator */ double getMaxY(); - + /** get center X */ double geX(); - + /** get center Y */ double getY(); } @@ -321,6 +326,11 @@ class Circle2D implements Component2D { return diffX * diffX + diffY * diffY <= radiusSquared; } + @Override + public boolean intersectsLine(double aX, double aY, double bX, double bY) { + return Circle2D.intersectsLine(centerX, centerY, aX, aY, bX, bY, this); + } + @Override public boolean disjoint(double minX, double maxX, double minY, double maxY) { return Component2D.disjoint(this.minX, this.maxX, this.minY, this.maxY, minX, maxX, minY, maxY); @@ -390,6 +400,19 @@ class Circle2D implements Component2D { return SloppyMath.haversinSortKey(y, x, this.centerLat, this.centerLon) <= sortKey; } + + @Override + public boolean intersectsLine(double aX, double aY, double bX, double bY) { + if (Circle2D.intersectsLine(centerLon, centerLat, aX, aY, bX, bY, this)) { + return true; + } + if (crossesDateline) { + double newCenterLon = (centerLon > 0) ? centerLon - 360 : centerLon + 360; + return Circle2D.intersectsLine(newCenterLon, centerLat, aX, aY, bX, bY, this); + } + return false; + } + @Override public boolean disjoint(double minX, double maxX, double minY, double maxY) { if (crossesDateline) { diff --git a/lucene/core/src/test/org/apache/lucene/geo/TestCircle2D.java b/lucene/core/src/test/org/apache/lucene/geo/TestCircle2D.java index 3d58c8ddaba..ce172847bd5 100644 --- a/lucene/core/src/test/org/apache/lucene/geo/TestCircle2D.java +++ b/lucene/core/src/test/org/apache/lucene/geo/TestCircle2D.java @@ -60,6 +60,19 @@ public class TestCircle2D extends LuceneTestCase { assertEquals(Component2D.WithinRelation.NOTWITHIN, circle2D.withinTriangle(ax, ay, true, bx, by, true, cx, cy, true)); } + public void testTriangleDateLineIntersects() { + Component2D circle2D = LatLonGeometry.create(new Circle(0, 179, 222400)); + double ax = -179; + double ay = 1; + double bx = -179; + double by = -1; + double cx = -178; + double cy = 0; + // we just touch the edge from the dateline + assertEquals(PointValues.Relation.CELL_CROSSES_QUERY, circle2D.relateTriangle(ax, ay, bx, by , cx, cy)); + assertEquals(Component2D.WithinRelation.NOTWITHIN, circle2D.withinTriangle(ax, ay, true, bx, by, true, cx, cy, true)); + } + public void testTriangleContains() { Component2D circle2D; if (random().nextBoolean()) {