mirror of https://github.com/apache/lucene.git
LUCENE-8736: Fix LatLonShapePolygonQuery and Polygon2D.contains to correctly include points that fall on the boundary
This commit is contained in:
parent
a67a941e19
commit
55c241d87f
|
@ -51,6 +51,11 @@ New Features
|
|||
|
||||
Bug fixes
|
||||
|
||||
* LUCENE-8736: LatLonShapePolygonQuery returns incorrect WITHIN results
|
||||
with shared boundaries. Point in Polygon now correctly includes boundary
|
||||
points. Box and Polygon relations with triangles have also been improved to
|
||||
correctly include boundary points. (Nick Knize)
|
||||
|
||||
* LUCENE-8712: Polygon2D does not detect crossings through segment edges.
|
||||
(Ignacio Vera)
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import org.apache.lucene.index.PointValues.Relation;
|
|||
import org.apache.lucene.util.ArrayUtil;
|
||||
|
||||
import static org.apache.lucene.geo.GeoUtils.lineCrossesLine;
|
||||
import static org.apache.lucene.geo.GeoUtils.lineCrossesLineWithBoundary;
|
||||
import static org.apache.lucene.geo.GeoUtils.orient;
|
||||
|
||||
/**
|
||||
|
@ -76,11 +77,9 @@ public abstract class EdgeTree {
|
|||
/** Returns relation to the provided triangle */
|
||||
public Relation relateTriangle(double ax, double ay, double bx, double by, double cx, double cy) {
|
||||
// compute bounding box of triangle
|
||||
double minLat = StrictMath.min(StrictMath.min(ay, by), cy);
|
||||
double minLon = StrictMath.min(StrictMath.min(ax, bx), cx);
|
||||
double maxLat = StrictMath.max(StrictMath.max(ay, by), cy);
|
||||
double maxLon = StrictMath.max(StrictMath.max(ax, bx), cx);
|
||||
if (minLat <= maxY && minLon <= maxX) {
|
||||
double triMinLat = StrictMath.min(StrictMath.min(ay, by), cy);
|
||||
double triMinLon = StrictMath.min(StrictMath.min(ax, bx), cx);
|
||||
if (triMinLat <= maxY && triMinLon <= maxX) {
|
||||
Relation relation = internalComponentRelateTriangle(ax, ay, bx, by, cx, cy);
|
||||
if (relation != Relation.CELL_OUTSIDE_QUERY) {
|
||||
return relation;
|
||||
|
@ -91,7 +90,9 @@ public abstract class EdgeTree {
|
|||
return relation;
|
||||
}
|
||||
}
|
||||
if (right != null && ((splitX == false && maxLat >= this.minLat) || (splitX && maxLon >= this.minLon))) {
|
||||
double triMaxLat = StrictMath.max(StrictMath.max(ay, by), cy);
|
||||
double triMaxLon = StrictMath.max(StrictMath.max(ax, bx), cx);
|
||||
if (right != null && ((splitX == false && triMaxLat >= this.minLat) || (splitX && triMaxLon >= this.minLon))) {
|
||||
relation = right.relateTriangle(ax, ay, bx, by, cx, cy);
|
||||
if (relation != Relation.CELL_OUTSIDE_QUERY) {
|
||||
return relation;
|
||||
|
@ -236,62 +237,47 @@ public abstract class EdgeTree {
|
|||
|
||||
/** Returns true if the triangle crosses any edge in this edge subtree */
|
||||
boolean crossesTriangle(double ax, double ay, double bx, double by, double cx, double cy) {
|
||||
// compute bounding box of triangle
|
||||
double minLat = StrictMath.min(StrictMath.min(ay, by), cy);
|
||||
double minLon = StrictMath.min(StrictMath.min(ax, bx), cx);
|
||||
double maxLat = StrictMath.max(StrictMath.max(ay, by), cy);
|
||||
double maxLon = StrictMath.max(StrictMath.max(ax, bx), cx);
|
||||
|
||||
if (minLat <= max) {
|
||||
// compute min lat of triangle bounding box
|
||||
double triMinLat = StrictMath.min(StrictMath.min(ay, by), cy);
|
||||
if (triMinLat <= max) {
|
||||
double dy = lat1;
|
||||
double ey = lat2;
|
||||
double dx = lon1;
|
||||
double ex = lon2;
|
||||
|
||||
// compute remaining bounding box of triangle
|
||||
double triMinLon = StrictMath.min(StrictMath.min(ax, bx), cx);
|
||||
double triMaxLat = StrictMath.max(StrictMath.max(ay, by), cy);
|
||||
double triMaxLon = StrictMath.max(StrictMath.max(ax, bx), cx);
|
||||
|
||||
// optimization: see if the rectangle is outside of the "bounding box" of the polyline at all
|
||||
// if not, don't waste our time trying more complicated stuff
|
||||
boolean outside = (dy < minLat && ey < minLat) ||
|
||||
(dy > maxLat && ey > maxLat) ||
|
||||
(dx < minLon && ex < minLon) ||
|
||||
(dx > maxLon && ex > maxLon);
|
||||
boolean outside = (dy < triMinLat && ey < triMinLat) ||
|
||||
(dy > triMaxLat && ey > triMaxLat) ||
|
||||
(dx < triMinLon && ex < triMinLon) ||
|
||||
(dx > triMaxLon && ex > triMaxLon);
|
||||
|
||||
if (dateline == false && outside == false) {
|
||||
// does triangle's first edge intersect polyline?
|
||||
// ax, ay -> bx, by
|
||||
if (lineCrossesLine(ax, ay, bx, by, dx, dy, ex, ey)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// does triangle's second edge intersect polyline?
|
||||
// bx, by -> cx, cy
|
||||
if (lineCrossesLine(bx, by, cx, cy, dx, dy, ex, ey)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// does triangle's third edge intersect polyline?
|
||||
// cx, cy -> ax, ay
|
||||
if (lineCrossesLine(cx, cy, ax, ay, dx, dy, ex, ey)) {
|
||||
if (outside == false) {
|
||||
if (lineCrossesLine(dx, dy, ex, ey, ax, ay, bx, by) ||
|
||||
lineCrossesLine(dx, dy, ex, ey, bx, by, cx, cy) ||
|
||||
lineCrossesLine(dx, dy, ex, ey, cx, cy, ax, ay)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (left != null) {
|
||||
if (left.crossesTriangle(ax, ay, bx, by, cx, cy)) {
|
||||
if (left != null && left.crossesTriangle(ax, ay, bx, by, cx, cy)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (right != null && maxLat >= low) {
|
||||
if (right.crossesTriangle(ax, ay, bx, by, cx, cy)) {
|
||||
if (right != null && triMaxLat >= low && right.crossesTriangle(ax, ay, bx, by, cx, cy)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Returns true if the box crosses any edge in this edge subtree */
|
||||
boolean crosses(double minLat, double maxLat, double minLon, double maxLon) {
|
||||
boolean crossesBox(double minLat, double maxLat, double minLon, double maxLon, boolean includeBoundary) {
|
||||
// we just have to cross one edge to answer the question, so we descend the tree and return when we do.
|
||||
if (minLat <= max) {
|
||||
// we compute line intersections of every polygon edge with every box line.
|
||||
|
@ -304,59 +290,73 @@ public abstract class EdgeTree {
|
|||
double cx = lon1;
|
||||
double dx = lon2;
|
||||
|
||||
// optimization: see if either end of the line segment is contained by the rectangle
|
||||
if (Rectangle.containsPoint(cy, cx, minLat, maxLat, minLon, maxLon) ||
|
||||
Rectangle.containsPoint(dy, dx, minLat, maxLat, minLon, maxLon)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// optimization: see if the rectangle is outside of the "bounding box" of the polyline at all
|
||||
// if not, don't waste our time trying more complicated stuff
|
||||
boolean outside = (cy < minLat && dy < minLat) ||
|
||||
(cy > maxLat && dy > maxLat) ||
|
||||
(cx < minLon && dx < minLon) ||
|
||||
(cx > maxLon && dx > maxLon);
|
||||
// optimization: see if either end of the line segment is contained by the rectangle
|
||||
if (Rectangle.containsPoint(cy, cx, minLat, maxLat, minLon, maxLon)
|
||||
|| Rectangle.containsPoint(dy, dx, minLat, maxLat, minLon, maxLon)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (outside == false) {
|
||||
// does box's top edge intersect polyline?
|
||||
// ax = minLon, bx = maxLon, ay = maxLat, by = maxLat
|
||||
if (orient(cx, cy, dx, dy, minLon, maxLat) * orient(cx, cy, dx, dy, maxLon, maxLat) <= 0 &&
|
||||
orient(minLon, maxLat, maxLon, maxLat, cx, cy) * orient(minLon, maxLat, maxLon, maxLat, dx, dy) <= 0) {
|
||||
if (includeBoundary == true &&
|
||||
lineCrossesLineWithBoundary(cx, cy, dx, dy, minLon, minLat, maxLon, minLat) ||
|
||||
lineCrossesLineWithBoundary(cx, cy, dx, dy, maxLon, minLat, maxLon, maxLat) ||
|
||||
lineCrossesLineWithBoundary(cx, cy, dx, dy, maxLon, maxLat, maxLon, minLat) ||
|
||||
lineCrossesLineWithBoundary(cx, cy, dx, dy, minLon, maxLat, minLon, minLat)) {
|
||||
// include boundaries: ensures box edges that terminate on the polygon are included
|
||||
return true;
|
||||
}
|
||||
|
||||
// does box's right edge intersect polyline?
|
||||
// ax = maxLon, bx = maxLon, ay = maxLat, by = minLat
|
||||
if (orient(cx, cy, dx, dy, maxLon, maxLat) * orient(cx, cy, dx, dy, maxLon, minLat) <= 0 &&
|
||||
orient(maxLon, maxLat, maxLon, minLat, cx, cy) * orient(maxLon, maxLat, maxLon, minLat, dx, dy) <= 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// does box's bottom edge intersect polyline?
|
||||
// ax = maxLon, bx = minLon, ay = minLat, by = minLat
|
||||
if (orient(cx, cy, dx, dy, maxLon, minLat) * orient(cx, cy, dx, dy, minLon, minLat) <= 0 &&
|
||||
orient(maxLon, minLat, minLon, minLat, cx, cy) * orient(maxLon, minLat, minLon, minLat, dx, dy) <= 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// does box's left edge intersect polyline?
|
||||
// ax = minLon, bx = minLon, ay = minLat, by = maxLat
|
||||
if (orient(cx, cy, dx, dy, minLon, minLat) * orient(cx, cy, dx, dy, minLon, maxLat) <= 0 &&
|
||||
orient(minLon, minLat, minLon, maxLat, cx, cy) * orient(minLon, minLat, minLon, maxLat, dx, dy) <= 0) {
|
||||
} else if (lineCrossesLine(cx, cy, dx, dy, minLon, minLat, maxLon, minLat) ||
|
||||
lineCrossesLine(cx, cy, dx, dy, maxLon, minLat, maxLon, maxLat) ||
|
||||
lineCrossesLine(cx, cy, dx, dy, maxLon, maxLat, maxLon, minLat) ||
|
||||
lineCrossesLine(cx, cy, dx, dy, minLon, maxLat, minLon, minLat)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (left != null) {
|
||||
if (left.crosses(minLat, maxLat, minLon, maxLon)) {
|
||||
if (left != null && left.crossesBox(minLat, maxLat, minLon, maxLon, includeBoundary)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (right != null && maxLat >= low) {
|
||||
if (right.crosses(minLat, maxLat, minLon, maxLon)) {
|
||||
if (right != null && maxLat >= low && right.crossesBox(minLat, maxLat, minLon, maxLon, includeBoundary)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Returns true if the line crosses any edge in this edge subtree */
|
||||
boolean crossesLine(double a2x, double a2y, double b2x, double b2y) {
|
||||
double minY = StrictMath.min(a2y, b2y);
|
||||
double maxY = StrictMath.max(a2y, b2y);
|
||||
if (minY <= max) {
|
||||
double a1x = lon1;
|
||||
double a1y = lat1;
|
||||
double b1x = lon2;
|
||||
double b1y = lat2;
|
||||
|
||||
double minX = StrictMath.min(a2x, b2x);
|
||||
double maxX = StrictMath.max(a2x, b2x);
|
||||
|
||||
boolean outside = (a1y < minY && b1y < minY) ||
|
||||
(a1y > maxY && b1y > maxY) ||
|
||||
(a1x < minX && b1x < minX) ||
|
||||
(a1x > maxX && b1x > maxX);
|
||||
if (outside == false && lineCrossesLineWithBoundary(a1x, a1y, b1x, b1y, a2x, a2y, b2x, b2y)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (left != null && left.crossesLine(a2x, a2y, b2x, b2y)) {
|
||||
return true;
|
||||
}
|
||||
if (right != null && maxY >= low && right.crossesLine(a2x, a2y, b2x, b2y)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -196,11 +196,21 @@ public final class GeoUtils {
|
|||
|
||||
/** uses orient method to compute whether two line segments cross */
|
||||
public static boolean lineCrossesLine(double a1x, double a1y, double b1x, double b1y, double a2x, double a2y, double b2x, double b2y) {
|
||||
// shortcut: either "line" is actually a point
|
||||
if ((a1x == b1x && a1y == b1y) || (a2x == b2x && a2y == b2y)) {
|
||||
if (orient(a2x, a2y, b2x, b2y, a1x, a1y) * orient(a2x, a2y, b2x, b2y, b1x, b1y) < 0 &&
|
||||
orient(a1x, a1y, b1x, b1y, a2x, a2y) * orient(a1x, a1y, b1x, b1y, b2x, b2y) < 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** uses orient method to compute whether two line segments cross; boundaries included - returning true for
|
||||
* lines that terminate on each other.
|
||||
*
|
||||
* e.g., (plus sign) + == true, and (capital 't') T == true
|
||||
*
|
||||
* Use {@link #lineCrossesLine} to exclude lines that terminate on each other from the truth table
|
||||
**/
|
||||
public static boolean lineCrossesLineWithBoundary(double a1x, double a1y, double b1x, double b1y, double a2x, double a2y, double b2x, double b2y) {
|
||||
if (orient(a2x, a2y, b2x, b2y, a1x, a1y) * orient(a2x, a2y, b2x, b2y, b1x, b1y) <= 0 &&
|
||||
orient(a1x, a1y, b1x, b1y, a2x, a2y) * orient(a1x, a1y, b1x, b1y, b2x, b2y) <= 0) {
|
||||
return true;
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
*/
|
||||
package org.apache.lucene.geo;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.apache.lucene.index.PointValues.Relation;
|
||||
|
||||
/**
|
||||
|
@ -32,6 +34,7 @@ public final class Polygon2D extends EdgeTree {
|
|||
// and pull up max values for both dimensions to each parent node (regardless of split).
|
||||
/** tree of holes, or null */
|
||||
private final Polygon2D holes;
|
||||
private final AtomicBoolean containsBoundary = new AtomicBoolean(false);
|
||||
|
||||
private Polygon2D(Polygon polygon, Polygon2D holes) {
|
||||
super(polygon.minLat, polygon.maxLat, polygon.minLon, polygon.maxLon, polygon.getPolyLats(), polygon.getPolyLons());
|
||||
|
@ -69,7 +72,8 @@ public final class Polygon2D extends EdgeTree {
|
|||
if (latitude < minLat || latitude > maxLat || longitude < minLon || longitude > maxLon) {
|
||||
return false;
|
||||
}
|
||||
if (contains(tree, latitude, longitude)) {
|
||||
containsBoundary.set(false);
|
||||
if (contains(tree, latitude, longitude, containsBoundary)) {
|
||||
if (holes != null && holes.contains(latitude, longitude)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -92,7 +96,7 @@ public final class Polygon2D extends EdgeTree {
|
|||
// check each corner: if < 4 && > 0 are present, its cheaper than crossesSlowly
|
||||
int numCorners = numberOfCorners(minLat, maxLat, minLon, maxLon);
|
||||
if (numCorners == 4) {
|
||||
if (tree.crosses(minLat, maxLat, minLon, maxLon)) {
|
||||
if (tree.crossesBox(minLat, maxLat, minLon, maxLon, false)) {
|
||||
return Relation.CELL_CROSSES_QUERY;
|
||||
}
|
||||
return Relation.CELL_INSIDE_QUERY;
|
||||
|
@ -100,7 +104,7 @@ public final class Polygon2D extends EdgeTree {
|
|||
if (minLat >= tree.lat1 && maxLat <= tree.lat1 && minLon >= tree.lon2 && maxLon <= tree.lon2) {
|
||||
return Relation.CELL_CROSSES_QUERY;
|
||||
}
|
||||
if (tree.crosses(minLat, maxLat, minLon, maxLon)) {
|
||||
if (tree.crossesBox(minLat, maxLat, minLon, maxLon, false)) {
|
||||
return Relation.CELL_CROSSES_QUERY;
|
||||
}
|
||||
return Relation.CELL_OUTSIDE_QUERY;
|
||||
|
@ -119,6 +123,48 @@ public final class Polygon2D extends EdgeTree {
|
|||
return Relation.CELL_OUTSIDE_QUERY;
|
||||
}
|
||||
}
|
||||
if (ax == bx && bx == cx && ay == by && by == cy) {
|
||||
// indexed "triangle" is a point:
|
||||
if (Rectangle.containsPoint(ay, ax, minLat, maxLat, minLon, maxLon) == false) {
|
||||
return Relation.CELL_OUTSIDE_QUERY;
|
||||
}
|
||||
// shortcut by checking contains
|
||||
return contains(ay, ax) ? Relation.CELL_INSIDE_QUERY : Relation.CELL_OUTSIDE_QUERY;
|
||||
} else if (ax == cx && ay == cy) {
|
||||
// indexed "triangle" is a line segment: shortcut by calling appropriate method
|
||||
return relateIndexedLineSegment(ax, ay, bx, by);
|
||||
}
|
||||
// indexed "triangle" is a triangle:
|
||||
return relateIndexedTriangle(ax, ay, bx, by, cx, cy);
|
||||
}
|
||||
|
||||
/** relates an indexed line segment (a "flat triangle") with the polygon */
|
||||
private Relation relateIndexedLineSegment(double a2x, double a2y, double b2x, double b2y) {
|
||||
// check endpoints of the line segment
|
||||
int numCorners = 0;
|
||||
if (componentContains(a2y, a2x)) {
|
||||
++numCorners;
|
||||
}
|
||||
if (componentContains(b2y, b2x)) {
|
||||
++numCorners;
|
||||
}
|
||||
|
||||
if (numCorners == 2) {
|
||||
if (tree.crossesLine(a2x, a2y, b2x, b2y)) {
|
||||
return Relation.CELL_CROSSES_QUERY;
|
||||
}
|
||||
return Relation.CELL_INSIDE_QUERY;
|
||||
} else if (numCorners == 0) {
|
||||
if (tree.crossesLine(a2x, a2y, b2x, b2y)) {
|
||||
return Relation.CELL_CROSSES_QUERY;
|
||||
}
|
||||
return Relation.CELL_OUTSIDE_QUERY;
|
||||
}
|
||||
return Relation.CELL_CROSSES_QUERY;
|
||||
}
|
||||
|
||||
/** relates an indexed triangle with the polygon */
|
||||
private Relation relateIndexedTriangle(double ax, double ay, double bx, double by, double cx, double cy) {
|
||||
// check each corner: if < 3 && > 0 are present, its cheaper than crossesSlowly
|
||||
int numCorners = numberOfTriangleCorners(ax, ay, bx, by, cx, cy);
|
||||
if (numCorners == 3) {
|
||||
|
@ -224,22 +270,27 @@ public final class Polygon2D extends EdgeTree {
|
|||
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
// CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
private static boolean contains(Edge tree, double latitude, double longitude) {
|
||||
// crossings algorithm is an odd-even algorithm, so we descend the tree xor'ing results along our path
|
||||
private static boolean contains(Edge edge, double lat, double lon, AtomicBoolean isOnEdge) {
|
||||
boolean res = false;
|
||||
if (latitude <= tree.max) {
|
||||
if (tree.lat1 > latitude != tree.lat2 > latitude) {
|
||||
if (longitude < (tree.lon1 - tree.lon2) * (latitude - tree.lat2) / (tree.lat1 - tree.lat2) + tree.lon2) {
|
||||
res = true;
|
||||
if (isOnEdge.get() == false && lat <= edge.max) {
|
||||
if (lat == edge.lat1 && lat == edge.lat2 ||
|
||||
(lat <= edge.lat1 && lat >= edge.lat2) != (lat >= edge.lat1 && lat <= edge.lat2)) {
|
||||
if (GeoUtils.orient(edge.lon1, edge.lat1, edge.lon2, edge.lat2, lon, lat) == 0) {
|
||||
// if its on the boundary return true
|
||||
isOnEdge.set(true);
|
||||
return true;
|
||||
} else if (edge.lat1 > lat != edge.lat2 > lat) {
|
||||
res = lon < (edge.lon2 - edge.lon1) * (lat - edge.lat1) / (edge.lat2 - edge.lat1) + edge.lon1;
|
||||
}
|
||||
}
|
||||
if (tree.left != null) {
|
||||
res ^= contains(tree.left, latitude, longitude);
|
||||
if (edge.left != null) {
|
||||
res ^= contains(edge.left, lat, lon, isOnEdge);
|
||||
}
|
||||
if (tree.right != null && latitude >= tree.low) {
|
||||
res ^= contains(tree.right, latitude, longitude);
|
||||
|
||||
if (edge.right != null && lat >= edge.low) {
|
||||
res ^= contains(edge.right, lat, lon, isOnEdge);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
return isOnEdge.get() || res;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -255,18 +255,18 @@ public class TestPolygon2D extends LuceneTestCase {
|
|||
public void testEdgeInsideness() {
|
||||
Polygon2D poly = Polygon2D.create(new Polygon(new double[] { -2, -2, 2, 2, -2 }, new double[] { -2, 2, 2, -2, -2 }));
|
||||
assertTrue(poly.contains(-2, -2)); // bottom left corner: true
|
||||
assertFalse(poly.contains(-2, 2)); // bottom right corner: false
|
||||
assertFalse(poly.contains(2, -2)); // top left corner: false
|
||||
assertFalse(poly.contains(2, 2)); // top right corner: false
|
||||
assertTrue(poly.contains(-2, 2)); // bottom right corner: true
|
||||
assertTrue(poly.contains(2, -2)); // top left corner: true
|
||||
assertTrue(poly.contains(2, 2)); // top right corner: true
|
||||
assertTrue(poly.contains(-2, -1)); // bottom side: true
|
||||
assertTrue(poly.contains(-2, 0)); // bottom side: true
|
||||
assertTrue(poly.contains(-2, 1)); // bottom side: true
|
||||
assertFalse(poly.contains(2, -1)); // top side: false
|
||||
assertFalse(poly.contains(2, 0)); // top side: false
|
||||
assertFalse(poly.contains(2, 1)); // top side: false
|
||||
assertFalse(poly.contains(-1, 2)); // right side: false
|
||||
assertFalse(poly.contains(0, 2)); // right side: false
|
||||
assertFalse(poly.contains(1, 2)); // right side: false
|
||||
assertTrue(poly.contains(2, -1)); // top side: true
|
||||
assertTrue(poly.contains(2, 0)); // top side: true
|
||||
assertTrue(poly.contains(2, 1)); // top side: true
|
||||
assertTrue(poly.contains(-1, 2)); // right side: true
|
||||
assertTrue(poly.contains(0, 2)); // right side: true
|
||||
assertTrue(poly.contains(1, 2)); // right side: true
|
||||
assertTrue(poly.contains(-1, -2)); // left side: true
|
||||
assertTrue(poly.contains(0, -2)); // left side: true
|
||||
assertTrue(poly.contains(1, -2)); // left side: true
|
||||
|
|
|
@ -18,6 +18,8 @@ package org.apache.lucene.geo;
|
|||
|
||||
import org.apache.lucene.index.PointValues.Relation;
|
||||
|
||||
import static org.apache.lucene.geo.GeoUtils.orient;
|
||||
|
||||
/**
|
||||
* 2D line implementation represented as a balanced interval tree of edges.
|
||||
* <p>
|
||||
|
@ -42,22 +44,51 @@ public final class Line2D extends EdgeTree {
|
|||
|
||||
@Override
|
||||
protected Relation componentRelate(double minLat, double maxLat, double minLon, double maxLon) {
|
||||
if (tree.crosses(minLat, maxLat, minLon, maxLon)) {
|
||||
if (tree.crossesBox(minLat, maxLat, minLon, maxLon, true)) {
|
||||
return Relation.CELL_CROSSES_QUERY;
|
||||
}
|
||||
|
||||
return Relation.CELL_OUTSIDE_QUERY;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Relation componentRelateTriangle(double ax, double ay, double bx, double by, double cx, double cy) {
|
||||
if (tree.crossesTriangle(ax, ay, bx, by, cx, cy)) {
|
||||
if (ax == bx && bx == cx && ay == by && by == cy) {
|
||||
// indexed "triangle" is a point: check if point lies on any line segment
|
||||
if (isPointOnLine(tree, ax, ay)) {
|
||||
return Relation.CELL_INSIDE_QUERY;
|
||||
}
|
||||
} else if (ax == cx && ay == cy) {
|
||||
// indexed "triangle" is a line:
|
||||
if (tree.crossesLine(ax, ay, bx, by)) {
|
||||
return Relation.CELL_CROSSES_QUERY;
|
||||
}
|
||||
//check if line is inside triangle
|
||||
if (pointInTriangle(tree.lon1, tree.lat1, ax, ay, bx, by, cx, cy)) {
|
||||
return Relation.CELL_OUTSIDE_QUERY;
|
||||
} else if (pointInTriangle(tree.lon1, tree.lat1, ax, ay, bx, by, cx, cy) == true ||
|
||||
tree.crossesTriangle(ax, ay, bx, by, cx, cy)) {
|
||||
// indexed "triangle" is a triangle:
|
||||
return Relation.CELL_CROSSES_QUERY;
|
||||
}
|
||||
return Relation.CELL_OUTSIDE_QUERY;
|
||||
}
|
||||
|
||||
/** returns true if the provided x, y point lies on the line */
|
||||
private boolean isPointOnLine(Edge tree, double x, double y) {
|
||||
if (y <= tree.max) {
|
||||
double minY = StrictMath.min(tree.lat1, tree.lat2);
|
||||
double maxY = StrictMath.max(tree.lat1, tree.lat2);
|
||||
double minX = StrictMath.min(tree.lon1, tree.lon2);
|
||||
double maxX = StrictMath.max(tree.lon1, tree.lon2);
|
||||
if (Rectangle.containsPoint(y, x, minY, maxY, minX, maxX) &&
|
||||
orient(tree.lon1, tree.lat1, tree.lon2, tree.lat2, x, y) == 0) {
|
||||
return true;
|
||||
}
|
||||
if (tree.left != null && isPointOnLine(tree.left, x, y)) {
|
||||
return true;
|
||||
}
|
||||
if (tree.right != null && maxY >= tree.low && isPointOnLine(tree.right, x, y)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -233,8 +233,8 @@ public class Rectangle2D {
|
|||
}
|
||||
|
||||
// shortcut: check if either of the end points fall inside the box
|
||||
if (bboxContainsPoint(ax, ay, minX, maxX, minY, maxY)
|
||||
|| bboxContainsPoint(bx, by, minX, maxX, minY, maxY)) {
|
||||
if (bboxContainsPoint(ax, ay, minX, maxX, minY, maxY) ||
|
||||
bboxContainsPoint(bx, by, minX, maxX, minY, maxY)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -244,11 +244,6 @@ public class Rectangle2D {
|
|||
return false;
|
||||
}
|
||||
|
||||
// shortcut: edge is a point
|
||||
if (ax == bx && ay == by) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// top
|
||||
if (orient(ax, ay, bx, by, minX, maxY) * orient(ax, ay, bx, by, maxX, maxY) <= 0 &&
|
||||
orient(minX, maxY, maxX, maxY, ax, ay) * orient(minX, maxY, maxX, maxY, bx, by) <= 0) {
|
||||
|
|
|
@ -18,9 +18,9 @@ package org.apache.lucene.document;
|
|||
|
||||
import com.carrotsearch.randomizedtesting.generators.RandomNumbers;
|
||||
import org.apache.lucene.document.LatLonShape.QueryRelation;
|
||||
import org.apache.lucene.geo.GeoEncodingUtils;
|
||||
import org.apache.lucene.geo.GeoTestUtil;
|
||||
import org.apache.lucene.geo.Line;
|
||||
import org.apache.lucene.geo.Line2D;
|
||||
import org.apache.lucene.geo.Polygon;
|
||||
import org.apache.lucene.geo.Polygon2D;
|
||||
import org.apache.lucene.geo.Rectangle;
|
||||
|
@ -41,12 +41,25 @@ import org.apache.lucene.util.LuceneTestCase;
|
|||
import org.apache.lucene.util.TestUtil;
|
||||
import org.junit.Ignore;
|
||||
|
||||
import static org.apache.lucene.geo.GeoEncodingUtils.decodeLatitude;
|
||||
import static org.apache.lucene.geo.GeoEncodingUtils.decodeLongitude;
|
||||
import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitude;
|
||||
import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude;
|
||||
|
||||
/** Test case for indexing polygons and querying by bounding box */
|
||||
public class TestLatLonShape extends LuceneTestCase {
|
||||
protected static String FIELDNAME = "field";
|
||||
|
||||
/** quantizes a latitude value to be consistent with index encoding */
|
||||
protected static double quantizeLat(double rawLat) {
|
||||
return decodeLatitude(encodeLatitude(rawLat));
|
||||
}
|
||||
|
||||
/** quantizes a longitude value to be consistent with index encoding */
|
||||
protected static double quantizeLon(double rawLon) {
|
||||
return decodeLongitude(encodeLongitude(rawLon));
|
||||
}
|
||||
|
||||
protected void addPolygonsToDoc(String field, Document doc, Polygon polygon) {
|
||||
Field[] fields = LatLonShape.createIndexableFields(field, polygon);
|
||||
for (Field f : fields) {
|
||||
|
@ -283,14 +296,8 @@ public class TestLatLonShape extends LuceneTestCase {
|
|||
new double[] {180d, 180d, 176d, 174d, 176d, 180d}
|
||||
);
|
||||
|
||||
Field[] fields = LatLonShape.createIndexableFields("test", indexPoly1);
|
||||
for (Field f : fields) {
|
||||
doc.add(f);
|
||||
}
|
||||
fields = LatLonShape.createIndexableFields("test", indexPoly2);
|
||||
for (Field f : fields) {
|
||||
doc.add(f);
|
||||
}
|
||||
addPolygonsToDoc(FIELDNAME, doc, indexPoly1);
|
||||
addPolygonsToDoc(FIELDNAME, doc, indexPoly2);
|
||||
w.addDocument(doc);
|
||||
w.forceMerge(1);
|
||||
|
||||
|
@ -306,22 +313,22 @@ public class TestLatLonShape extends LuceneTestCase {
|
|||
new double[] {180d, 180d, 170d, 170d, 180d})
|
||||
};
|
||||
|
||||
Query q = LatLonShape.newPolygonQuery("test", QueryRelation.WITHIN, searchPoly);
|
||||
Query q = LatLonShape.newPolygonQuery(FIELDNAME, QueryRelation.WITHIN, searchPoly);
|
||||
assertEquals(1, searcher.count(q));
|
||||
|
||||
q = LatLonShape.newPolygonQuery("test", QueryRelation.INTERSECTS, searchPoly);
|
||||
q = LatLonShape.newPolygonQuery(FIELDNAME, QueryRelation.INTERSECTS, searchPoly);
|
||||
assertEquals(1, searcher.count(q));
|
||||
|
||||
q = LatLonShape.newPolygonQuery("test", QueryRelation.DISJOINT, searchPoly);
|
||||
q = LatLonShape.newPolygonQuery(FIELDNAME, QueryRelation.DISJOINT, searchPoly);
|
||||
assertEquals(0, searcher.count(q));
|
||||
|
||||
q = LatLonShape.newBoxQuery("test", QueryRelation.WITHIN, -20, 20, 170, -170);
|
||||
q = LatLonShape.newBoxQuery(FIELDNAME, QueryRelation.WITHIN, -20, 20, 170, -170);
|
||||
assertEquals(1, searcher.count(q));
|
||||
|
||||
q = LatLonShape.newBoxQuery("test", QueryRelation.INTERSECTS, -20, 20, 170, -170);
|
||||
q = LatLonShape.newBoxQuery(FIELDNAME, QueryRelation.INTERSECTS, -20, 20, 170, -170);
|
||||
assertEquals(1, searcher.count(q));
|
||||
|
||||
q = LatLonShape.newBoxQuery("test", QueryRelation.DISJOINT, -20, 20, 170, -170);
|
||||
q = LatLonShape.newBoxQuery(FIELDNAME, QueryRelation.DISJOINT, -20, 20, 170, -170);
|
||||
assertEquals(0, searcher.count(q));
|
||||
|
||||
IOUtils.close(w, reader, dir);
|
||||
|
@ -335,21 +342,17 @@ public class TestLatLonShape extends LuceneTestCase {
|
|||
Polygon polygon = new Polygon(new double[] {-14.448264200949083, 0, 0, -14.448264200949083, -14.448264200949083},
|
||||
new double[] {0.9999999403953552, 0.9999999403953552, 124.50086371762484, 124.50086371762484, 0.9999999403953552});
|
||||
Polygon2D polygon2D = Polygon2D.create(polygon);
|
||||
PointValues.Relation rel = polygon2D.relateTriangle(GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(alon)),
|
||||
GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(blat)),
|
||||
GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(blon)),
|
||||
GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(blat)),
|
||||
GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(alon)),
|
||||
GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(alat)));
|
||||
PointValues.Relation rel = polygon2D.relateTriangle(
|
||||
quantizeLon(alon), quantizeLat(blat),
|
||||
quantizeLon(blon), quantizeLat(blat),
|
||||
quantizeLon(alon), quantizeLat(alat));
|
||||
|
||||
assertEquals(PointValues.Relation.CELL_CROSSES_QUERY, rel);
|
||||
|
||||
rel = polygon2D.relateTriangle(GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(alon)),
|
||||
GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(blat)),
|
||||
GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(alon)),
|
||||
GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(alat)),
|
||||
GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(blon)),
|
||||
GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(blat)));
|
||||
rel = polygon2D.relateTriangle(
|
||||
quantizeLon(alon), quantizeLat(blat),
|
||||
quantizeLon(alon), quantizeLat(alat),
|
||||
quantizeLon(blon), quantizeLat(blat));
|
||||
|
||||
assertEquals(PointValues.Relation.CELL_CROSSES_QUERY, rel);
|
||||
}
|
||||
|
@ -358,62 +361,129 @@ public class TestLatLonShape extends LuceneTestCase {
|
|||
Polygon p = new Polygon(new double[] {0, 0, 1, 1, 0}, new double[] {0, 1, 1, 0, 0});
|
||||
Polygon2D polygon2D = Polygon2D.create(p);
|
||||
//3 shared points
|
||||
PointValues.Relation rel = polygon2D.relateTriangle(GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(0.5)),
|
||||
GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(0)),
|
||||
GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(1)),
|
||||
GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(0.5)),
|
||||
GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(0.5)),
|
||||
GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(1)));
|
||||
assertEquals(PointValues.Relation.CELL_CROSSES_QUERY, rel);
|
||||
PointValues.Relation rel = polygon2D.relateTriangle(
|
||||
quantizeLon(0.5), quantizeLat(0),
|
||||
quantizeLon(1), quantizeLat(0.5),
|
||||
quantizeLon(0.5), quantizeLat(1));
|
||||
assertEquals(PointValues.Relation.CELL_INSIDE_QUERY, rel);
|
||||
//2 shared points
|
||||
rel = polygon2D.relateTriangle(GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(0.5)),
|
||||
GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(0)),
|
||||
GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(1)),
|
||||
GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(0.5)),
|
||||
GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(0.5)),
|
||||
GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(0.75)));
|
||||
assertEquals(PointValues.Relation.CELL_CROSSES_QUERY, rel);
|
||||
rel = polygon2D.relateTriangle(
|
||||
quantizeLon(0.5), quantizeLat(0),
|
||||
quantizeLon(1), quantizeLat(0.5),
|
||||
quantizeLon(0.5), quantizeLat(0.75));
|
||||
assertEquals(PointValues.Relation.CELL_INSIDE_QUERY, rel);
|
||||
//1 shared point
|
||||
rel = polygon2D.relateTriangle(
|
||||
GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(0.5)),
|
||||
GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(0.5)),
|
||||
GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(0.5)),
|
||||
GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(0)),
|
||||
GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(0.75)),
|
||||
GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(0.75)));
|
||||
assertEquals(PointValues.Relation.CELL_CROSSES_QUERY, rel);
|
||||
quantizeLon(0.5), quantizeLat(0.5),
|
||||
quantizeLon(0.5), quantizeLat(0),
|
||||
quantizeLon(0.75), quantizeLat(0.75));
|
||||
assertEquals(PointValues.Relation.CELL_INSIDE_QUERY, rel);
|
||||
// 1 shared point but out
|
||||
rel = polygon2D.relateTriangle(GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(1)),
|
||||
GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(0.5)),
|
||||
GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(2)),
|
||||
GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(0)),
|
||||
GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(2)),
|
||||
GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(2)));
|
||||
rel = polygon2D.relateTriangle(
|
||||
quantizeLon(1), quantizeLat(0.5),
|
||||
quantizeLon(2), quantizeLat(0),
|
||||
quantizeLon(2), quantizeLat(2));
|
||||
assertEquals(PointValues.Relation.CELL_CROSSES_QUERY, rel);
|
||||
// 1 shared point but crossing
|
||||
rel = polygon2D.relateTriangle(GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(0.5)),
|
||||
GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(0)),
|
||||
GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(2)),
|
||||
GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(0.5)),
|
||||
GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(0.5)),
|
||||
GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(1)));
|
||||
rel = polygon2D.relateTriangle(
|
||||
quantizeLon(0.5), quantizeLat(0),
|
||||
quantizeLon(2), quantizeLat(0.5),
|
||||
quantizeLon(0.5), quantizeLat(1));
|
||||
assertEquals(PointValues.Relation.CELL_CROSSES_QUERY, rel);
|
||||
//share one edge
|
||||
rel = polygon2D.relateTriangle(GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(0)),
|
||||
GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(0)),
|
||||
GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(0)),
|
||||
GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(1)),
|
||||
GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(0.5)),
|
||||
GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(0.5)));
|
||||
assertEquals(PointValues.Relation.CELL_CROSSES_QUERY, rel);
|
||||
rel = polygon2D.relateTriangle(
|
||||
quantizeLon(0), quantizeLat(0),
|
||||
quantizeLon(0), quantizeLat(1),
|
||||
quantizeLon(0.5), quantizeLat(0.5));
|
||||
assertEquals(PointValues.Relation.CELL_INSIDE_QUERY, rel);
|
||||
//share one edge outside
|
||||
rel = polygon2D.relateTriangle(GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(0)),
|
||||
GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(1)),
|
||||
GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(1.5)),
|
||||
GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(1.5)),
|
||||
GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(1)),
|
||||
GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(1)));
|
||||
rel = polygon2D.relateTriangle(
|
||||
quantizeLon(0), quantizeLat(1),
|
||||
quantizeLon(1.5), quantizeLat(1.5),
|
||||
quantizeLon(1), quantizeLat(1));
|
||||
assertEquals(PointValues.Relation.CELL_CROSSES_QUERY, rel);
|
||||
}
|
||||
|
||||
public void testLUCENE8736() throws Exception {
|
||||
Directory dir = newDirectory();
|
||||
RandomIndexWriter w = new RandomIndexWriter(random(), dir);
|
||||
|
||||
// test polygons:
|
||||
Polygon indexPoly1 = new Polygon(
|
||||
new double[] {4d, 4d, 3d, 3d, 4d},
|
||||
new double[] {3d, 4d, 4d, 3d, 3d}
|
||||
);
|
||||
|
||||
Polygon indexPoly2 = new Polygon(
|
||||
new double[] {2d, 2d, 1d, 1d, 2d},
|
||||
new double[] {6d, 7d, 7d, 6d, 6d}
|
||||
);
|
||||
|
||||
Polygon indexPoly3 = new Polygon(
|
||||
new double[] {1d, 1d, 0d, 0d, 1d},
|
||||
new double[] {3d, 4d, 4d, 3d, 3d}
|
||||
);
|
||||
|
||||
Polygon indexPoly4 = new Polygon(
|
||||
new double[] {2d, 2d, 1d, 1d, 2d},
|
||||
new double[] {0d, 1d, 1d, 0d, 0d}
|
||||
);
|
||||
|
||||
// index polygons:
|
||||
Document doc;
|
||||
addPolygonsToDoc(FIELDNAME, doc = new Document(), indexPoly1);
|
||||
w.addDocument(doc);
|
||||
addPolygonsToDoc(FIELDNAME, doc = new Document(), indexPoly2);
|
||||
w.addDocument(doc);
|
||||
addPolygonsToDoc(FIELDNAME, doc = new Document(), indexPoly3);
|
||||
w.addDocument(doc);
|
||||
addPolygonsToDoc(FIELDNAME, doc = new Document(), indexPoly4);
|
||||
w.addDocument(doc);
|
||||
w.forceMerge(1);
|
||||
|
||||
///// search //////
|
||||
IndexReader reader = w.getReader();
|
||||
w.close();
|
||||
IndexSearcher searcher = newSearcher(reader);
|
||||
|
||||
Polygon[] searchPoly = new Polygon[] {
|
||||
new Polygon(new double[] {4d, 4d, 0d, 0d, 4d},
|
||||
new double[] {0d, 7d, 7d, 0d, 0d})
|
||||
};
|
||||
|
||||
Query q = LatLonShape.newPolygonQuery(FIELDNAME, QueryRelation.WITHIN, searchPoly);
|
||||
assertEquals(4, searcher.count(q));
|
||||
|
||||
IOUtils.close(w, reader, dir);
|
||||
}
|
||||
|
||||
public void testTriangleCrossingPolygonVertices() {
|
||||
Polygon p = new Polygon(new double[] {0, 0, -5, -10, -5, 0}, new double[] {-1, 1, 5, 0, -5, -1});
|
||||
Polygon2D polygon2D = Polygon2D.create(p);
|
||||
PointValues.Relation rel = polygon2D.relateTriangle(
|
||||
quantizeLon(-5), quantizeLat(0),
|
||||
quantizeLon(10), quantizeLat(0),
|
||||
quantizeLon(-5), quantizeLat(-15));
|
||||
assertEquals(PointValues.Relation.CELL_CROSSES_QUERY, rel);
|
||||
}
|
||||
|
||||
public void testLineCrossingPolygonVertices() {
|
||||
Polygon p = new Polygon(new double[] {0, -1, 0, 1, 0}, new double[] {-1, 0, 1, 0, -1});
|
||||
Polygon2D polygon2D = Polygon2D.create(p);
|
||||
PointValues.Relation rel = polygon2D.relateTriangle(
|
||||
quantizeLon(-1.5), quantizeLat(0),
|
||||
quantizeLon(1.5), quantizeLat(0),
|
||||
quantizeLon(-1.5), quantizeLat(0));
|
||||
assertEquals(PointValues.Relation.CELL_CROSSES_QUERY, rel);
|
||||
}
|
||||
|
||||
public void testLineSharedLine() {
|
||||
Line l = new Line(new double[] {0, 0, 0, 0}, new double[] {-2, -1, 0, 1});
|
||||
Line2D l2d = Line2D.create(l);
|
||||
PointValues.Relation r = l2d.relateTriangle(
|
||||
quantizeLon(-5), quantizeLat(0),
|
||||
quantizeLon(5), quantizeLat(0),
|
||||
quantizeLon(-5), quantizeLat(0));
|
||||
assertEquals(PointValues.Relation.CELL_CROSSES_QUERY, r);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -695,11 +695,18 @@ public class GeoTestUtil {
|
|||
double vertx[] = polyLons;
|
||||
double testy = latitude;
|
||||
double testx = longitude;
|
||||
for (i = 0, j = nvert-1; i < nvert; j = i++) {
|
||||
if ( ((verty[i]>testy) != (verty[j]>testy)) &&
|
||||
(testx < (vertx[j]-vertx[i]) * (testy-verty[i]) / (verty[j]-verty[i]) + vertx[i]) )
|
||||
for (i = 0, j = 1; j < nvert; ++i, ++j) {
|
||||
if (testy == verty[j] && testy == verty[i] ||
|
||||
((testy <= verty[j] && testy >= verty[i]) != (testy >= verty[j] && testy <= verty[i]))) {
|
||||
if (GeoUtils.orient(vertx[i], verty[i], vertx[j], verty[j], testx, testy) == 0) {
|
||||
// return true if point is on boundary
|
||||
return true;
|
||||
} else if ( ((verty[i] > testy) != (verty[j] > testy)) &&
|
||||
(testx < (vertx[j]-vertx[i]) * (testy-verty[i]) / (verty[j]-verty[i]) + vertx[i]) ) {
|
||||
c = !c;
|
||||
}
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue