LUCENE-9273: Speed up geometry queries by specialising Component2D spatial operations (#1341)

Speed up geometry queries by specialising Component2D spatial operations. Instead of using a generic relate method for all relations, we use specialise methods for each one. In addition, the type of triangle is computed at deserialisation time, therefore we can be more selective when decoding points of a triangle
This commit is contained in:
Ignacio Vera 2020-04-20 19:24:49 +02:00 committed by GitHub
parent 37ad0e552d
commit f914e08b36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 1283 additions and 969 deletions

View File

@ -161,6 +161,11 @@ Optimizations
* LUCENE-7788: fail precommit on unparameterised log messages and examine for wasted work/objects (Erick Erickson)
* LUCENE-9273: Speed up geometry queries by specialising Component2D spatial operations. Instead of using a generic
relate method for all relations, we use specialize methods for each one. In addition, the type of triangle is
computed at deserialization time, therefore we can be more selective when decoding points of a triangle.
(Ignacio Vera)
Bug Fixes
---------------------
* LUCENE-9259: Fix wrong NGramFilterFactory argument name for preserveOriginal option (Paul Pazderski)

View File

@ -20,8 +20,8 @@ import java.util.Arrays;
import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.Component2D;
import org.apache.lucene.geo.GeoUtils;
import org.apache.lucene.geo.Rectangle;
import org.apache.lucene.geo.Tessellator;
import org.apache.lucene.index.PointValues.Relation;
import org.apache.lucene.util.NumericUtils;
@ -32,7 +32,6 @@ import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitude;
import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitudeCeil;
import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude;
import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitudeCeil;
import static org.apache.lucene.geo.GeoUtils.orient;
/**
* Finds all previously indexed geo shapes that intersect the specified bounding box.
@ -59,35 +58,86 @@ final class LatLonShapeBoundingBoxQuery extends ShapeQuery {
return encodedRectangle.relateRangeBBox(minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle);
}
/** returns true if the query matches the encoded triangle */
@Override
protected boolean queryMatches(byte[] t, ShapeField.DecodedTriangle scratchTriangle, QueryRelation queryRelation) {
// decode indexed triangle
protected boolean queryIntersects(byte[] t, ShapeField.DecodedTriangle scratchTriangle) {
ShapeField.decodeTriangle(t, scratchTriangle);
int aY = scratchTriangle.aY;
int aX = scratchTriangle.aX;
int bY = scratchTriangle.bY;
int bX = scratchTriangle.bX;
int cY = scratchTriangle.cY;
int cX = scratchTriangle.cX;
switch (scratchTriangle.type) {
case POINT: {
return encodedRectangle.contains(scratchTriangle.aX, scratchTriangle.aY);
}
case LINE: {
int aY = scratchTriangle.aY;
int aX = scratchTriangle.aX;
int bY = scratchTriangle.bY;
int bX = scratchTriangle.bX;
return encodedRectangle.intersectsLine(aX, aY, bX, bY);
}
case TRIANGLE: {
int aY = scratchTriangle.aY;
int aX = scratchTriangle.aX;
int bY = scratchTriangle.bY;
int bX = scratchTriangle.bX;
int cY = scratchTriangle.cY;
int cX = scratchTriangle.cX;
return encodedRectangle.intersectsTriangle(aX, aY, bX, bY, cX, cY);
}
default: throw new IllegalArgumentException("Unsupported triangle type :[" + scratchTriangle.type + "]");
}
}
switch (queryRelation) {
case INTERSECTS: return encodedRectangle.intersectsTriangle(aX, aY, bX, bY, cX, cY);
case WITHIN: return encodedRectangle.containsTriangle(aX, aY, bX, bY, cX, cY);
case DISJOINT: return encodedRectangle.intersectsTriangle(aX, aY, bX, bY, cX, cY) == false;
default: throw new IllegalArgumentException("Unsupported query type :[" + queryRelation + "]");
@Override
protected boolean queryContains(byte[] t, ShapeField.DecodedTriangle scratchTriangle) {
ShapeField.decodeTriangle(t, scratchTriangle);
switch (scratchTriangle.type) {
case POINT: {
return encodedRectangle.contains(scratchTriangle.aX, scratchTriangle.aY);
}
case LINE: {
int aY = scratchTriangle.aY;
int aX = scratchTriangle.aX;
int bY = scratchTriangle.bY;
int bX = scratchTriangle.bX;
return encodedRectangle.containsLine(aX, aY, bX, bY);
}
case TRIANGLE: {
int aY = scratchTriangle.aY;
int aX = scratchTriangle.aX;
int bY = scratchTriangle.bY;
int bX = scratchTriangle.bX;
int cY = scratchTriangle.cY;
int cX = scratchTriangle.cX;
return encodedRectangle.containsTriangle(aX, aY, bX, bY, cX, cY);
}
default: throw new IllegalArgumentException("Unsupported triangle type :[" + scratchTriangle.type + "]");
}
}
@Override
protected Component2D.WithinRelation queryWithin(byte[] t, ShapeField.DecodedTriangle scratchTriangle) {
if (encodedRectangle.crossesDateline()) {
throw new IllegalArgumentException("withinTriangle is not supported for rectangles crossing the date line");
}
// decode indexed triangle
ShapeField.decodeTriangle(t, scratchTriangle);
return encodedRectangle.withinTriangle(scratchTriangle.aX, scratchTriangle.aY, scratchTriangle.ab,
scratchTriangle.bX, scratchTriangle.bY, scratchTriangle.bc,
scratchTriangle.cX, scratchTriangle.cY, scratchTriangle.ca);
switch (scratchTriangle.type) {
case POINT: {
return Component2D.WithinRelation.DISJOINT;
}
case LINE: {
return encodedRectangle.withinLine(scratchTriangle.aX, scratchTriangle.aY, scratchTriangle.ab,
scratchTriangle.bX, scratchTriangle.bY);
}
case TRIANGLE: {
return encodedRectangle.withinTriangle(scratchTriangle.aX, scratchTriangle.aY, scratchTriangle.ab,
scratchTriangle.bX, scratchTriangle.bY, scratchTriangle.bc,
scratchTriangle.cX, scratchTriangle.cY, scratchTriangle.ca);
}
default: throw new IllegalArgumentException("Unsupported triangle type :[" + scratchTriangle.type + "]");
}
}
@Override
@ -124,6 +174,7 @@ final class LatLonShapeBoundingBoxQuery extends ShapeQuery {
protected final int maxX;
protected final int minY;
protected final int maxY;
private final boolean crossesDateline;
EncodedRectangle(double minLat, double maxLat, double minLon, double maxLon) {
this.bbox = new byte[4 * BYTES];
@ -138,6 +189,7 @@ final class LatLonShapeBoundingBoxQuery extends ShapeQuery {
this.maxY = maxYenc;
if (minLon > maxLon == true) {
this.crossesDateline = true;
// crossing dateline is split into east/west boxes
this.west = new byte[4 * BYTES];
this.minX = minXenc;
@ -145,6 +197,7 @@ final class LatLonShapeBoundingBoxQuery extends ShapeQuery {
encode(MIN_LON_ENCODED, this.maxX, this.minY, this.maxY, this.west);
encode(this.minX, MAX_LON_ENCODED, this.minY, this.maxY, this.bbox);
} else {
this.crossesDateline = false;
// encodeLongitudeCeil may cause minX to be > maxX iff
// the delta between the longitude < the encoding resolution
if (minXenc > maxXenc) {
@ -155,28 +208,31 @@ final class LatLonShapeBoundingBoxQuery extends ShapeQuery {
this.maxX = maxXenc;
encode(this.minX, this.maxX, this.minY, this.maxY, bbox);
}
}
private boolean crossesDateline() {
return minX > maxX;
}
/**
* Checks if the rectangle contains the provided point
**/
boolean queryContainsPoint(int x, int y) {
if (this.crossesDateline() == true) {
return bboxContainsPoint(x, y, MIN_LON_ENCODED, this.maxX, this.minY, this.maxY)
|| bboxContainsPoint(x, y, this.minX, MAX_LON_ENCODED, this.minY, this.maxY);
* encodes a bounding box into the provided byte array
*/
private static void encode(final int minX, final int maxX, final int minY, final int maxY, byte[] b) {
if (b == null) {
b = new byte[4 * BYTES];
}
return bboxContainsPoint(x, y, this.minX, this.maxX, this.minY, this.maxY);
NumericUtils.intToSortableBytes(minY, b, 0);
NumericUtils.intToSortableBytes(minX, b, BYTES);
NumericUtils.intToSortableBytes(maxY, b, 2 * BYTES);
NumericUtils.intToSortableBytes(maxX, b, 3 * BYTES);
}
private boolean crossesDateline() {
return crossesDateline;
}
/**
* compare this to a provided range bounding box
**/
Relation relateRangeBBox(int minXOffset, int minYOffset, byte[] minTriangle,
int maxXOffset, int maxYOffset, byte[] maxTriangle) {
Relation relateRangeBBox(int minXOffset, int minYOffset, byte[] minTriangle,
int maxXOffset, int maxYOffset, byte[] maxTriangle) {
Relation eastRelation = compareBBoxToRangeBBox(this.bbox,
minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle);
if (this.crossesDateline() && eastRelation == Relation.CELL_OUTSIDE_QUERY) {
@ -189,7 +245,7 @@ final class LatLonShapeBoundingBoxQuery extends ShapeQuery {
* intersects this to a provided range bounding box
**/
Relation intersectRangeBBox(int minXOffset, int minYOffset, byte[] minTriangle,
int maxXOffset, int maxYOffset, byte[] maxTriangle) {
int maxXOffset, int maxYOffset, byte[] maxTriangle) {
Relation eastRelation = intersectBBoxWithRangeBBox(this.bbox,
minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle);
if (this.crossesDateline() && eastRelation == Relation.CELL_OUTSIDE_QUERY) {
@ -198,122 +254,6 @@ final class LatLonShapeBoundingBoxQuery extends ShapeQuery {
return eastRelation;
}
/**
* Checks if the rectangle intersects the provided triangle
**/
boolean intersectsTriangle(int aX, int aY, int bX, int bY, int cX, int cY) {
// 1. query contains any triangle points
if (queryContainsPoint(aX, aY) || queryContainsPoint(bX, bY) || queryContainsPoint(cX, cY)) {
return true;
}
// compute bounding box of triangle
int tMinX = StrictMath.min(StrictMath.min(aX, bX), cX);
int tMaxX = StrictMath.max(StrictMath.max(aX, bX), cX);
int tMinY = StrictMath.min(StrictMath.min(aY, bY), cY);
int tMaxY = StrictMath.max(StrictMath.max(aY, bY), cY);
// 2. check bounding boxes are disjoint
if (this.crossesDateline() == true) {
if (boxesAreDisjoint(tMinX, tMaxX, tMinY, tMaxY, MIN_LON_ENCODED, this.maxX, this.minY, this.maxY)
&& boxesAreDisjoint(tMinX, tMaxX, tMinY, tMaxY, this.minX, MAX_LON_ENCODED, this.minY, this.maxY)) {
return false;
}
} else if (tMaxX < minX || tMinX > maxX || tMinY > maxY || tMaxY < minY) {
return false;
}
// 3. check triangle contains any query points
if (Tessellator.pointInTriangle(minX, minY, aX, aY, bX, bY, cX, cY)) {
return true;
} else if (Tessellator.pointInTriangle(maxX, minY, aX, aY, bX, bY, cX, cY)) {
return true;
} else if (Tessellator.pointInTriangle(maxX, maxY, aX, aY, bX, bY, cX, cY)) {
return true;
} else if (Tessellator.pointInTriangle(minX, maxY, aX, aY, bX, bY, cX, cY)) {
return true;
}
// 4. last ditch effort: check crossings
if (queryIntersects(aX, aY, bX, bY, cX, cY)) {
return true;
}
return false;
}
/**
* Returns the Within relation to the provided triangle
*/
Component2D.WithinRelation withinTriangle(int ax, int ay, boolean ab, int bx, int by, boolean bc, int cx, int cy, boolean ca) {
if (this.crossesDateline() == true) {
throw new IllegalArgumentException("withinTriangle is not supported for rectangles crossing the date line");
}
// Short cut, lines and points cannot contain a bbox
if ((ax == bx && ay == by) || (ax == cx && ay == cy) || (bx == cx && by == cy)) {
return Component2D.WithinRelation.DISJOINT;
}
// Compute bounding box of triangle
int tMinX = StrictMath.min(StrictMath.min(ax, bx), cx);
int tMaxX = StrictMath.max(StrictMath.max(ax, bx), cx);
int tMinY = StrictMath.min(StrictMath.min(ay, by), cy);
int tMaxY = StrictMath.max(StrictMath.max(ay, by), cy);
// Bounding boxes disjoint?
if (boxesAreDisjoint(tMinX, tMaxX, tMinY, tMaxY, minX, maxX, minY, maxY)) {
return Component2D.WithinRelation.DISJOINT;
}
// Points belong to the shape so if points are inside the rectangle then it cannot be within.
if (bboxContainsPoint(ax, ay, minX, maxX, minY, maxY) ||
bboxContainsPoint(bx, by, minX, maxX, minY, maxY) ||
bboxContainsPoint(cx, cy, minX, maxX, minY, maxY)) {
return Component2D.WithinRelation.NOTWITHIN;
}
// If any of the edges intersects an edge belonging to the shape then it cannot be within.
Component2D.WithinRelation relation = Component2D.WithinRelation.DISJOINT;
if (edgeIntersectsBox(ax, ay, bx, by, minX, maxX, minY, maxY) == true) {
if (ab == true) {
return Component2D.WithinRelation.NOTWITHIN;
} else {
relation = Component2D.WithinRelation.CANDIDATE;
}
}
if (edgeIntersectsBox(bx, by, cx, cy, minX, maxX, minY, maxY) == true) {
if (bc == true) {
return Component2D.WithinRelation.NOTWITHIN;
} else {
relation = Component2D.WithinRelation.CANDIDATE;
}
}
if (edgeIntersectsBox(cx, cy, ax, ay, minX, maxX, minY, maxY) == true) {
if (ca == true) {
return Component2D.WithinRelation.NOTWITHIN;
} else {
relation = Component2D.WithinRelation.CANDIDATE;
}
}
// If any of the rectangle edges crosses a triangle edge that does not belong to the shape
// then it is a candidate for within
if (relation == Component2D.WithinRelation.CANDIDATE) {
return Component2D.WithinRelation.CANDIDATE;
}
// Check if shape is within the triangle
if (Tessellator.pointInTriangle(minX, minY, ax, ay, bx, by, cx, cy)) {
return Component2D.WithinRelation.CANDIDATE;
}
return relation;
}
/**
* Checks if the rectangle contains the provided triangle
**/
boolean containsTriangle(int ax, int ay, int bx, int by, int cx, int cy) {
if (this.crossesDateline() == true) {
return bboxContainsTriangle(ax, ay, bx, by, cx, cy, MIN_LON_ENCODED, this.maxX, this.minY, this.maxY)
|| bboxContainsTriangle(ax, ay, bx, by, cx, cy, this.minX, MAX_LON_ENCODED, this.minY, this.maxY);
}
return bboxContainsTriangle(ax, ay, bx, by, cx, cy, minX, maxX, minY, maxY);
}
/**
* static utility method to compare a bbox with a range of triangles (just the bbox of the triangle collection)
**/
@ -381,119 +321,208 @@ final class LatLonShapeBoundingBoxQuery extends ShapeQuery {
int minXOffset, int minYOffset, byte[] minTriangle,
int maxXOffset, int maxYOffset, byte[] maxTriangle) {
return Arrays.compareUnsigned(minTriangle, minXOffset, minXOffset + BYTES, bbox, 3 * BYTES, 4 * BYTES) > 0 ||
Arrays.compareUnsigned(maxTriangle, maxXOffset, maxXOffset + BYTES, bbox, BYTES, 2 * BYTES) < 0 ||
Arrays.compareUnsigned(minTriangle, minYOffset, minYOffset + BYTES, bbox, 2 * BYTES, 3 * BYTES) > 0 ||
Arrays.compareUnsigned(maxTriangle, maxYOffset, maxYOffset + BYTES, bbox, 0, BYTES) < 0;
Arrays.compareUnsigned(maxTriangle, maxXOffset, maxXOffset + BYTES, bbox, BYTES, 2 * BYTES) < 0 ||
Arrays.compareUnsigned(minTriangle, minYOffset, minYOffset + BYTES, bbox, 2 * BYTES, 3 * BYTES) > 0 ||
Arrays.compareUnsigned(maxTriangle, maxYOffset, maxYOffset + BYTES, bbox, 0, BYTES) < 0;
}
/**
* encodes a bounding box into the provided byte array
*/
private static void encode(final int minX, final int maxX, final int minY, final int maxY, byte[] b) {
if (b == null) {
b = new byte[4 * BYTES];
}
NumericUtils.intToSortableBytes(minY, b, 0);
NumericUtils.intToSortableBytes(minX, b, BYTES);
NumericUtils.intToSortableBytes(maxY, b, 2 * BYTES);
NumericUtils.intToSortableBytes(maxX, b, 3 * BYTES);
}
/**
* returns true if the query intersects the provided triangle (in encoded space)
*/
private boolean queryIntersects(int ax, int ay, int bx, int by, int cx, int cy) {
// check each edge of the triangle against the query
if (edgeIntersectsQuery(ax, ay, bx, by) ||
edgeIntersectsQuery(bx, by, cx, cy) ||
edgeIntersectsQuery(cx, cy, ax, ay)) {
return true;
}
return false;
}
/**
* returns true if the edge (defined by (ax, ay) (bx, by)) intersects the query
*/
private boolean edgeIntersectsQuery(int ax, int ay, int bx, int by) {
if (this.crossesDateline() == true) {
return edgeIntersectsBox(ax, ay, bx, by, MIN_LON_ENCODED, this.maxX, this.minY, this.maxY)
|| edgeIntersectsBox(ax, ay, bx, by, this.minX, MAX_LON_ENCODED, this.minY, this.maxY);
}
return edgeIntersectsBox(ax, ay, bx, by, this.minX, this.maxX, this.minY, this.maxY);
}
/**
* static utility method to check if a bounding box contains a point
*/
private static boolean bboxContainsPoint(int x, int y, int minX, int maxX, int minY, int maxY) {
return (x < minX || x > maxX || y < minY || y > maxY) == false;
}
/**
* static utility method to check if a bounding box contains a triangle
*/
private static boolean bboxContainsTriangle(int ax, int ay, int bx, int by, int cx, int cy,
int minX, int maxX, int minY, int maxY) {
return bboxContainsPoint(ax, ay, minX, maxX, minY, maxY)
&& bboxContainsPoint(bx, by, minX, maxX, minY, maxY)
&& bboxContainsPoint(cx, cy, minX, maxX, minY, maxY);
}
/**
* returns true if the edge (defined by (ax, ay) (bx, by)) intersects the query
*/
private static boolean edgeIntersectsBox(int ax, int ay, int bx, int by,
int minX, int maxX, int minY, int maxY) {
// shortcut: if edge is a point (occurs w/ Line shapes); simply check bbox w/ point
if (ax == bx && ay == by) {
return Rectangle.containsPoint(ay, ax, minY, maxY, minX, maxX);
}
// 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)) {
return true;
}
// shortcut: check bboxes of edges are disjoint
if (boxesAreDisjoint(Math.min(ax, bx), Math.max(ax, bx), Math.min(ay, by), Math.max(ay, by),
minX, maxX, minY, maxY)) {
* Checks if the rectangle contains the provided point
**/
boolean contains(int x, int y) {
if (y < minY || y > maxY) {
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) {
return true;
if (crossesDateline()) {
return (x > maxX && x < minX) == false;
} else {
return (x > maxX || x < minX) == false;
}
// right
if (orient(ax, ay, bx, by, maxX, maxY) * orient(ax, ay, bx, by, maxX, minY) <= 0 &&
orient(maxX, maxY, maxX, minY, ax, ay) * orient(maxX, maxY, maxX, minY, bx, by) <= 0) {
return true;
}
// bottom
if (orient(ax, ay, bx, by, maxX, minY) * orient(ax, ay, bx, by, minX, minY) <= 0 &&
orient(maxX, minY, minX, minY, ax, ay) * orient(maxX, minY, minX, minY, bx, by) <= 0) {
return true;
}
// left
if (orient(ax, ay, bx, by, minX, minY) * orient(ax, ay, bx, by, minX, maxY) <= 0 &&
orient(minX, minY, minX, maxY, ax, ay) * orient(minX, minY, minX, maxY, bx, by) <= 0) {
return true;
}
return false;
}
/**
* utility method to check if two boxes are disjoint
* Checks if the rectangle intersects the provided LINE
**/
boolean intersectsLine(int aX, int aY, int bX, int bY) {
if (contains(aX, aY) || contains(bX, bY)) {
return true;
}
// check bounding boxes are disjoint
if (StrictMath.max(aY, bY) < minY || StrictMath.min(aY, bY) > maxY) {
return false;
}
if (crossesDateline) { // crosses dateline
if (StrictMath.min(aX, bX) > maxX && StrictMath.max(aX, bX) < minX) {
return false;
}
} else {
if (StrictMath.min(aX, bX) > maxX || StrictMath.max(aX, bX) < minX) {
return false;
}
}
// expensive part
return edgeIntersectsQuery(aX, aY, bX, bY);
}
/**
* Checks if the rectangle intersects the provided triangle
**/
boolean intersectsTriangle(int aX, int aY, int bX, int bY, int cX, int cY) {
// query contains any triangle points
if (contains(aX, aY) || contains(bX, bY) || contains(cX, cY)) {
return true;
}
// check bounding box of triangle
int tMinY = StrictMath.min(StrictMath.min(aY, bY), cY);
int tMaxY = StrictMath.max(StrictMath.max(aY, bY), cY);
// check bounding boxes are disjoint
if (tMaxY < minY || tMinY > maxY) {
return false;
}
int tMinX = StrictMath.min(StrictMath.min(aX, bX), cX);
int tMaxX = StrictMath.max(StrictMath.max(aX, bX), cX);
if (crossesDateline) { // crosses dateline
if (tMinX > maxX && tMaxX < minX) {
return false;
}
} else {
if (tMinX > maxX || tMaxX < minX) {
return false;
}
}
// expensive part
return Component2D.pointInTriangle(tMinX, tMaxX, tMinY, tMaxY, minX, minY, aX, aY, bX, bY, cX, cY) ||
edgeIntersectsQuery(aX, aY, bX, bY) ||
edgeIntersectsQuery(bX, bY, cX, cY) ||
edgeIntersectsQuery(cX, cY, aX, aY);
}
/**
* Checks if the rectangle contains the provided LINE
**/
boolean containsLine(int aX, int aY, int bX, int bY) {
if (aY < minY || bY < minY ||
aY > maxY || bY > maxY ) {
return false;
}
if (crossesDateline) { // crosses dateline
return (aX >= minX && bX >= minX) ||
(aX <= maxX && bX <= maxX);
} else {
return aX >= minX && bX >= minX &&
aX <= maxX && bX <= maxX;
}
}
/**
* Checks if the rectangle contains the provided triangle
**/
boolean containsTriangle(int aX, int aY, int bX, int bY, int cX, int cY) {
if (aY < minY || bY < minY || cY < minY ||
aY > maxY || bY > maxY || cY > maxY) {
return false;
}
if (crossesDateline) { // crosses dateline
return (aX >= minX && bX >= minX && cX >= minX) ||
(aX <= maxX && bX <= maxX && cX <= maxX);
} else {
return aX >= minX && bX >= minX && cX >= minX &&
aX <= maxX && bX <= maxX && cX <= maxX;
}
}
/**
* Returns the Within relation to the provided triangle
*/
private static boolean boxesAreDisjoint(final int aMinX, final int aMaxX, final int aMinY, final int aMaxY,
final int bMinX, final int bMaxX, final int bMinY, final int bMaxY) {
return (aMaxX < bMinX || aMinX > bMaxX || aMaxY < bMinY || aMinY > bMaxY);
Component2D.WithinRelation withinLine(int ax, int ay, boolean ab, int bx, int by) {
if (ab == true && edgeIntersectsBox(ax, ay, bx, by, minX, maxX, minY, maxY) == true) {
return Component2D.WithinRelation.NOTWITHIN;
}
return Component2D.WithinRelation.DISJOINT;
}
/**
* Returns the Within relation to the provided triangle
*/
Component2D.WithinRelation withinTriangle(int aX, int aY, boolean ab, int bX, int bY, boolean bc, int cX, int cY, boolean ca) {
// Points belong to the shape so if points are inside the rectangle then it cannot be within.
if (contains(aX, aY) || contains(bX, bY) || contains(cX, cY)) {
return Component2D.WithinRelation.NOTWITHIN;
}
// Bounding boxes disjoint?
int tMinY = StrictMath.min(StrictMath.min(aY, bY), cY);
int tMaxY = StrictMath.max(StrictMath.max(aY, bY), cY);
// check bounding boxes are disjoint
if (tMaxY < minY || tMinY > maxY) {
return Component2D.WithinRelation.DISJOINT;
}
int tMinX = StrictMath.min(StrictMath.min(aX, bX), cX);
int tMaxX = StrictMath.max(StrictMath.max(aX, bX), cX);
if (crossesDateline) { // crosses dateline
if (tMinX > maxX && tMaxX < minX) {
return Component2D.WithinRelation.DISJOINT;
}
} else {
if (tMinX > maxX || tMaxX < minX) {
return Component2D.WithinRelation.DISJOINT;
}
}
// If any of the edges intersects an edge belonging to the shape then it cannot be within.
Component2D.WithinRelation relation = Component2D.WithinRelation.DISJOINT;
if (edgeIntersectsBox(aX, aY, bX, bY, minX, maxX, minY, maxY) == true) {
if (ab == true) {
return Component2D.WithinRelation.NOTWITHIN;
} else {
relation = Component2D.WithinRelation.CANDIDATE;
}
}
if (edgeIntersectsBox(bX, bY, cX, cY, minX, maxX, minY, maxY) == true) {
if (bc == true) {
return Component2D.WithinRelation.NOTWITHIN;
} else {
relation = Component2D.WithinRelation.CANDIDATE;
}
}
if (edgeIntersectsBox(cX, cY, aX, aY, minX, maxX, minY, maxY) == true) {
if (ca == true) {
return Component2D.WithinRelation.NOTWITHIN;
} else {
relation = Component2D.WithinRelation.CANDIDATE;
}
}
// Check if shape is within the triangle
if (relation == Component2D.WithinRelation.CANDIDATE ||
Component2D.pointInTriangle(tMinX, tMaxX, tMinY, tMaxY, minX, minY, aX, aY, bX, bY, cX, cY)) {
return Component2D.WithinRelation.CANDIDATE;
}
return relation;
}
/**
* returns true if the edge (defined by (aX, aY) (bX, bY)) intersects the query
*/
private boolean edgeIntersectsQuery(int aX, int aY, int bX, int bY) {
if (crossesDateline) {
return edgeIntersectsBox(aX, aY, bX, bY, MIN_LON_ENCODED, this.maxX, this.minY, this.maxY)
|| edgeIntersectsBox(aX, aY, bX, bY, this.minX, MAX_LON_ENCODED, this.minY, this.maxY);
}
return edgeIntersectsBox(aX, aY, bX, bY, this.minX, this.maxX, this.minY, this.maxY);
}
/**
* returns true if the edge (defined by (aX, aY) (bX, bY)) intersects the box
*/
private static boolean edgeIntersectsBox(int aX, int aY, int bX, int bY,
int minX, int maxX, int minY, int maxY) {
if (Math.max(aX, bX) < minX || Math.min(aX, bX) > maxX || Math.min(aY, bY) > maxY || Math.max(aY, bY) < minY) {
return false;
}
return GeoUtils.lineCrossesLineWithBoundary(aX, aY, bX, bY, minX, maxY, maxX, maxY) || // top
GeoUtils.lineCrossesLineWithBoundary(aX, aY, bX, bY, maxX, maxY, maxX, minY) || // bottom
GeoUtils.lineCrossesLineWithBoundary(aX, aY, bX, bY, maxX, minY, minX, minY) || // left
GeoUtils.lineCrossesLineWithBoundary(aX, aY, bX, bY, minX, minY, minX, maxY); // right
}
}
}

View File

@ -69,21 +69,62 @@ final class LatLonShapeQuery extends ShapeQuery {
}
@Override
protected boolean queryMatches(byte[] t, ShapeField.DecodedTriangle scratchTriangle, QueryRelation queryRelation) {
protected boolean queryIntersects(byte[] t, ShapeField.DecodedTriangle scratchTriangle) {
ShapeField.decodeTriangle(t, scratchTriangle);
double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle.aY);
double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle.aX);
double blat = GeoEncodingUtils.decodeLatitude(scratchTriangle.bY);
double blon = GeoEncodingUtils.decodeLongitude(scratchTriangle.bX);
double clat = GeoEncodingUtils.decodeLatitude(scratchTriangle.cY);
double clon = GeoEncodingUtils.decodeLongitude(scratchTriangle.cX);
switch (scratchTriangle.type) {
case POINT: {
double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle.aY);
double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle.aX);
return component2D.contains(alon, alat);
}
case LINE: {
double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle.aY);
double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle.aX);
double blat = GeoEncodingUtils.decodeLatitude(scratchTriangle.bY);
double blon = GeoEncodingUtils.decodeLongitude(scratchTriangle.bX);
return component2D.intersectsLine(alon, alat, blon, blat);
}
case TRIANGLE: {
double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle.aY);
double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle.aX);
double blat = GeoEncodingUtils.decodeLatitude(scratchTriangle.bY);
double blon = GeoEncodingUtils.decodeLongitude(scratchTriangle.bX);
double clat = GeoEncodingUtils.decodeLatitude(scratchTriangle.cY);
double clon = GeoEncodingUtils.decodeLongitude(scratchTriangle.cX);
return component2D.intersectsTriangle(alon, alat, blon, blat, clon, clat);
}
default: throw new IllegalArgumentException("Unsupported triangle type :[" + scratchTriangle.type + "]");
}
}
switch (queryRelation) {
case INTERSECTS: return component2D.relateTriangle(alon, alat, blon, blat, clon, clat) != Relation.CELL_OUTSIDE_QUERY;
case WITHIN: return component2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_INSIDE_QUERY;
case DISJOINT: return component2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_OUTSIDE_QUERY;
default: throw new IllegalArgumentException("Unsupported query type :[" + queryRelation + "]");
@Override
protected boolean queryContains(byte[] t, ShapeField.DecodedTriangle scratchTriangle) {
ShapeField.decodeTriangle(t, scratchTriangle);
switch (scratchTriangle.type) {
case POINT: {
double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle.aY);
double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle.aX);
return component2D.contains(alon, alat);
}
case LINE: {
double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle.aY);
double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle.aX);
double blat = GeoEncodingUtils.decodeLatitude(scratchTriangle.bY);
double blon = GeoEncodingUtils.decodeLongitude(scratchTriangle.bX);
return component2D.containsLine(alon, alat, blon, blat);
}
case TRIANGLE: {
double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle.aY);
double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle.aX);
double blat = GeoEncodingUtils.decodeLatitude(scratchTriangle.bY);
double blon = GeoEncodingUtils.decodeLongitude(scratchTriangle.bX);
double clat = GeoEncodingUtils.decodeLatitude(scratchTriangle.cY);
double clon = GeoEncodingUtils.decodeLongitude(scratchTriangle.cX);
return component2D.containsTriangle(alon, alat, blon, blat, clon, clat);
}
default: throw new IllegalArgumentException("Unsupported triangle type :[" + scratchTriangle.type + "]");
}
}
@ -91,14 +132,30 @@ final class LatLonShapeQuery extends ShapeQuery {
protected Component2D.WithinRelation queryWithin(byte[] t, ShapeField.DecodedTriangle scratchTriangle) {
ShapeField.decodeTriangle(t, scratchTriangle);
double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle.aY);
double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle.aX);
double blat = GeoEncodingUtils.decodeLatitude(scratchTriangle.bY);
double blon = GeoEncodingUtils.decodeLongitude(scratchTriangle.bX);
double clat = GeoEncodingUtils.decodeLatitude(scratchTriangle.cY);
double clon = GeoEncodingUtils.decodeLongitude(scratchTriangle.cX);
return component2D.withinTriangle(alon, alat, scratchTriangle.ab, blon, blat, scratchTriangle.bc, clon, clat, scratchTriangle.ca);
switch (scratchTriangle.type) {
case POINT: {
double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle.aY);
double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle.aX);
return component2D.withinPoint(alon, alat);
}
case LINE: {
double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle.aY);
double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle.aX);
double blat = GeoEncodingUtils.decodeLatitude(scratchTriangle.bY);
double blon = GeoEncodingUtils.decodeLongitude(scratchTriangle.bX);
return component2D.withinLine(alon, alat, scratchTriangle.ab, blon, blat);
}
case TRIANGLE: {
double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle.aY);
double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle.aX);
double blat = GeoEncodingUtils.decodeLatitude(scratchTriangle.bY);
double blon = GeoEncodingUtils.decodeLongitude(scratchTriangle.bX);
double clat = GeoEncodingUtils.decodeLatitude(scratchTriangle.cY);
double clon = GeoEncodingUtils.decodeLongitude(scratchTriangle.cX);
return component2D.withinTriangle(alon, alat, scratchTriangle.ab, blon, blat, scratchTriangle.bc, clon, clat, scratchTriangle.ca);
}
default: throw new IllegalArgumentException("Unsupported triangle type :[" + scratchTriangle.type + "]");
}
}
@Override

View File

@ -343,12 +343,44 @@ public final class ShapeField {
bc = (bits & 1 << 4) == 1 << 4;
ca = (bits & 1 << 5) == 1 << 5;
triangle.setValues(aX, aY, ab, bX, bY, bc, cX, cY, ca);
resolveTriangleType(triangle);
}
private static void resolveTriangleType(DecodedTriangle triangle) {
if (triangle.aX == triangle.bX && triangle.aY == triangle.bY) {
if (triangle.aX == triangle.cX && triangle.aY == triangle.cY) {
triangle.type = DecodedTriangle.TYPE.POINT;
} else {
triangle.bX = triangle.cX;
triangle.bY = triangle.cY;
triangle.cX = triangle.aX;
triangle.cY = triangle.aY;
triangle.type = DecodedTriangle.TYPE.LINE;
}
} else if (triangle.aX == triangle.cX && triangle.aY == triangle.cY) {
triangle.type = DecodedTriangle.TYPE.LINE;
} else if (triangle.bX == triangle.cX && triangle.bY == triangle.cY) {
triangle.cX = triangle.aX;
triangle.cY = triangle.aY;
triangle.type = DecodedTriangle.TYPE.LINE;
} else {
triangle.type = DecodedTriangle.TYPE.TRIANGLE;
}
}
/**
* Represents a encoded triangle using {@link ShapeField#decodeTriangle(byte[], DecodedTriangle)}.
*/
public static class DecodedTriangle {
/** type of triangle */
public enum TYPE {
/** all coordinates are equal */
POINT,
/** first and third coordinates are equal */
LINE,
/** all coordinates are different */
TRIANGLE
}
/** x coordinate, vertex one */
public int aX;
/** y coordinate, vertex one */
@ -367,6 +399,8 @@ public final class ShapeField {
public boolean bc;
/** represent if edge ca belongs to original shape */
public boolean ca;
/** triangle type */
public TYPE type;
/** default xtor */
public DecodedTriangle() {

View File

@ -85,7 +85,20 @@ abstract class ShapeQuery extends Query {
int maxXOffset, int maxYOffset, byte[] maxTriangle);
/** returns true if the provided triangle matches the query */
protected abstract boolean queryMatches(byte[] triangle, ShapeField.DecodedTriangle scratchTriangle, ShapeField.QueryRelation queryRelation);
protected boolean queryMatches(byte[] triangle, ShapeField.DecodedTriangle scratchTriangle, ShapeField.QueryRelation queryRelation) {
switch (queryRelation) {
case INTERSECTS: return queryIntersects(triangle, scratchTriangle);
case WITHIN: return queryContains(triangle, scratchTriangle);
case DISJOINT: return queryIntersects(triangle, scratchTriangle) == false;
default: throw new IllegalArgumentException("Unsupported query type :[" + queryRelation + "]");
}
}
/** returns true if the provided triangle intersects the query */
protected abstract boolean queryIntersects(byte[] triangle, ShapeField.DecodedTriangle scratchTriangle);
/** returns true if the provided triangle is within the query */
protected abstract boolean queryContains(byte[] triangle, ShapeField.DecodedTriangle scratchTriangle);
/** Return the within relationship between the query and the indexed shape.*/
protected abstract Component2D.WithinRelation queryWithin(byte[] triangle, ShapeField.DecodedTriangle scratchTriangle);

View File

@ -50,31 +50,72 @@ final class XYShapeQuery extends ShapeQuery {
protected Relation relateRangeBBoxToQuery(int minXOffset, int minYOffset, byte[] minTriangle,
int maxXOffset, int maxYOffset, byte[] maxTriangle) {
double minLat = XYEncodingUtils.decode(NumericUtils.sortableBytesToInt(minTriangle, minYOffset));
double minLon = XYEncodingUtils.decode(NumericUtils.sortableBytesToInt(minTriangle, minXOffset));
double maxLat = XYEncodingUtils.decode(NumericUtils.sortableBytesToInt(maxTriangle, maxYOffset));
double maxLon = XYEncodingUtils.decode(NumericUtils.sortableBytesToInt(maxTriangle, maxXOffset));
double minY = XYEncodingUtils.decode(NumericUtils.sortableBytesToInt(minTriangle, minYOffset));
double minX = XYEncodingUtils.decode(NumericUtils.sortableBytesToInt(minTriangle, minXOffset));
double maxY = XYEncodingUtils.decode(NumericUtils.sortableBytesToInt(maxTriangle, maxYOffset));
double maxX = XYEncodingUtils.decode(NumericUtils.sortableBytesToInt(maxTriangle, maxXOffset));
// check internal node against query
return component2D.relate(minLon, maxLon, minLat, maxLat);
return component2D.relate(minX, maxX, minY, maxY);
}
@Override
protected boolean queryMatches(byte[] t, ShapeField.DecodedTriangle scratchTriangle, QueryRelation queryRelation) {
protected boolean queryIntersects(byte[] t, ShapeField.DecodedTriangle scratchTriangle) {
ShapeField.decodeTriangle(t, scratchTriangle);
double alat = decode(scratchTriangle.aY);
double alon = decode(scratchTriangle.aX);
double blat = decode(scratchTriangle.bY);
double blon = decode(scratchTriangle.bX);
double clat = decode(scratchTriangle.cY);
double clon = decode(scratchTriangle.cX);
switch (scratchTriangle.type) {
case POINT: {
double y = decode(scratchTriangle.aY);
double x = decode(scratchTriangle.aX);
return component2D.contains(x, y);
}
case LINE: {
double aY = decode(scratchTriangle.aY);
double aX = decode(scratchTriangle.aX);
double bY = decode(scratchTriangle.bY);
double bX = decode(scratchTriangle.bX);
return component2D.intersectsLine(aX, aY, bX, bY);
}
case TRIANGLE: {
double aY = decode(scratchTriangle.aY);
double aX = decode(scratchTriangle.aX);
double bY = decode(scratchTriangle.bY);
double bX = decode(scratchTriangle.bX);
double cY = decode(scratchTriangle.cY);
double cX = decode(scratchTriangle.cX);
return component2D.intersectsTriangle(aX, aY, bX, bY, cX, cY);
}
default: throw new IllegalArgumentException("Unsupported triangle type :[" + scratchTriangle.type + "]");
}
}
switch (queryRelation) {
case INTERSECTS: return component2D.relateTriangle(alon, alat, blon, blat, clon, clat) != Relation.CELL_OUTSIDE_QUERY;
case WITHIN: return component2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_INSIDE_QUERY;
case DISJOINT: return component2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_OUTSIDE_QUERY;
default: throw new IllegalArgumentException("Unsupported query type :[" + queryRelation + "]");
@Override
protected boolean queryContains(byte[] t, ShapeField.DecodedTriangle scratchTriangle) {
ShapeField.decodeTriangle(t, scratchTriangle);
switch (scratchTriangle.type) {
case POINT: {
double y = decode(scratchTriangle.aY);
double x = decode(scratchTriangle.aX);
return component2D.contains(x, y);
}
case LINE: {
double aY = decode(scratchTriangle.aY);
double aX = decode(scratchTriangle.aX);
double bY = decode(scratchTriangle.bY);
double bX = decode(scratchTriangle.bX);
return component2D.containsLine(aX, aY, bX, bY);
}
case TRIANGLE: {
double aY = decode(scratchTriangle.aY);
double aX = decode(scratchTriangle.aX);
double bY = decode(scratchTriangle.bY);
double bX = decode(scratchTriangle.bX);
double cY = decode(scratchTriangle.cY);
double cX = decode(scratchTriangle.cX);
return component2D.containsTriangle(aX, aY, bX, bY, cX, cY);
}
default: throw new IllegalArgumentException("Unsupported triangle type :[" + scratchTriangle.type + "]");
}
}
@ -82,14 +123,30 @@ final class XYShapeQuery extends ShapeQuery {
protected Component2D.WithinRelation queryWithin(byte[] t, ShapeField.DecodedTriangle scratchTriangle) {
ShapeField.decodeTriangle(t, scratchTriangle);
double alat = decode(scratchTriangle.aY);
double alon = decode(scratchTriangle.aX);
double blat = decode(scratchTriangle.bY);
double blon = decode(scratchTriangle.bX);
double clat = decode(scratchTriangle.cY);
double clon = decode(scratchTriangle.cX);
return component2D.withinTriangle(alon, alat, scratchTriangle.ab, blon, blat, scratchTriangle.bc, clon, clat, scratchTriangle.ca);
switch (scratchTriangle.type) {
case POINT: {
double y = decode(scratchTriangle.aY);
double x = decode(scratchTriangle.aX);
return component2D.withinPoint(x, y);
}
case LINE: {
double aY = decode(scratchTriangle.aY);
double aX = decode(scratchTriangle.aX);
double bY = decode(scratchTriangle.bY);
double bX = decode(scratchTriangle.bX);
return component2D.withinLine(aX, aY, scratchTriangle.ab, bX, bY);
}
case TRIANGLE: {
double aY = decode(scratchTriangle.aY);
double aX = decode(scratchTriangle.aX);
double bY = decode(scratchTriangle.bY);
double bX = decode(scratchTriangle.bX);
double cY = decode(scratchTriangle.cY);
double cX = decode(scratchTriangle.cX);
return component2D.withinTriangle(aX, aY, scratchTriangle.ab, bX, bY, scratchTriangle.bc, cX, cY, scratchTriangle.ca);
}
default: throw new IllegalArgumentException("Unsupported triangle type :[" + scratchTriangle.type + "]");
}
}
@Override

View File

@ -53,7 +53,7 @@ class Circle2D implements Component2D {
@Override
public boolean contains(double x, double y) {
return calculator.contains(x, y);
return calculator.contains(x, y);
}
@Override
@ -68,128 +68,96 @@ class Circle2D implements Component2D {
}
@Override
public Relation relateTriangle(double minX, double maxX, double minY, double maxY,
double ax, double ay, double bx, double by, double cx, double cy) {
public boolean intersectsLine(double minX, double maxX, double minY, double maxY,
double aX, double aY, double bX, double bY) {
if (calculator.disjoint(minX, maxX, minY, maxY)) {
return Relation.CELL_OUTSIDE_QUERY;
return false;
}
if (ax == bx && bx == cx && ay == by && by == cy) {
// indexed "triangle" is a point: shortcut by checking contains
return contains(ax, ay) ? 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);
} else if (ax == bx && ay == by) {
// indexed "triangle" is a line segment: shortcut by calling appropriate method
return relateIndexedLineSegment(bx, by, cx, cy);
} else if (bx == cx && by == cy) {
// indexed "triangle" is a line segment: shortcut by calling appropriate method
return relateIndexedLineSegment(cx, cy, ax, ay);
return contains(aX, aY) || contains(bX, bY) ||
calculator.intersectsLine(aX, aY, bX, bY);
}
@Override
public boolean intersectsTriangle(double minX, double maxX, double minY, double maxY,
double aX, double aY, double bX, double bY, double cX, double cY) {
if (calculator.disjoint(minX, maxX, minY, maxY)) {
return false;
}
// indexed "triangle" is a triangle:
return relateIndexedTriangle(minX, maxX, minY, maxY, ax, ay, bx, by, cx, cy);
return contains(aX, aY) || contains(bX, bY) || contains(cX, cY) ||
Component2D.pointInTriangle(minX, maxX, minY, maxY, calculator.geX(), calculator.getY(), aX, aY, bX, bY, cX, cY) ||
calculator.intersectsLine(aX, aY, bX, bY) ||
calculator.intersectsLine(bX, bY, cX, cY) ||
calculator.intersectsLine(cX, cY, aX, aY);
}
@Override
public boolean containsLine(double minX, double maxX, double minY, double maxY,
double aX, double aY, double bX, double bY) {
if (calculator.disjoint(minX, maxX, minY, maxY)) {
return false;
}
return contains(aX, aY) && contains(bX, bY);
}
@Override
public boolean containsTriangle(double minX, double maxX, double minY, double maxY,
double aX, double aY, double bX, double bY, double cX, double cY) {
if (calculator.disjoint(minX, maxX, minY, maxY)) {
return false;
}
return contains(aX, aY) && contains(bX, bY) && contains(cX, cY);
}
@Override
public WithinRelation withinPoint(double x, double y) {
return WithinRelation.DISJOINT;
}
@Override
public WithinRelation withinLine(double minX, double maxX, double minY, double maxY,
double aX, double aY, boolean ab, double bX, double bY) {
if (calculator.disjoint(minX, maxX, minY, maxY)) {
return WithinRelation.DISJOINT;
}
if (ab == true && calculator.intersectsLine(aX, aY, bX, bY)) {
return WithinRelation.NOTWITHIN;
}
return WithinRelation.DISJOINT;
}
@Override
public WithinRelation withinTriangle(double minX, double maxX, double minY, double maxY,
double ax, double ay, boolean ab, double bx, double by, boolean bc, double cx, double cy, boolean ca) {
// short cut, lines and points cannot contain this type of shape
if ((ax == bx && ay == by) || (ax == cx && ay == cy) || (bx == cx && by == cy)) {
return WithinRelation.DISJOINT;
}
double aX, double aY, boolean ab, double bX, double bY, boolean bc, double cX, double cY, boolean ca) {
if (calculator.disjoint(minX, maxX, minY, maxY)) {
return WithinRelation.DISJOINT;
}
// if any of the points is inside the circle then we cannot be within this
// indexed shape
if (contains(ax, ay) || contains(bx, by) || contains(cx, cy)) {
if (contains(aX, aY) || contains(bX, bY) || contains(cX, cY)) {
return WithinRelation.NOTWITHIN;
}
// we only check edges that belong to the original polygon. If we intersect any of them, then
// we are not within.
if (ab == true && calculator.intersectsLine(ax, ay, bx, by)) {
if (ab == true && calculator.intersectsLine(aX, aY, bX, bY)) {
return WithinRelation.NOTWITHIN;
}
if (bc == true && calculator.intersectsLine(bx, by, cx, cy)) {
if (bc == true && calculator.intersectsLine(bX, bY, cX, cY)) {
return WithinRelation.NOTWITHIN;
}
if (ca == true && calculator.intersectsLine(cx, cy, ax, ay)) {
if (ca == true && calculator.intersectsLine(cX, cY, aX, aY)) {
return WithinRelation.NOTWITHIN;
}
// check if center is within the triangle. This is the only check that returns this circle as a candidate but that is ol
// is fine as the center must be inside to be one of the triangles.
if (Component2D.pointInTriangle(minX, maxX, minY, maxY, calculator.geX(), calculator.getY(), ax, ay, bx, by, cx, cy) == true) {
if (Component2D.pointInTriangle(minX, maxX, minY, maxY, calculator.geX(), calculator.getY(), aX, aY, bX, bY, cX, cY) == true) {
return WithinRelation.CANDIDATE;
}
return WithinRelation.DISJOINT;
}
/** 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 (contains(a2x, a2y)) {
++numCorners;
}
if (contains(b2x, b2y)) {
++numCorners;
}
if (numCorners == 2) {
return Relation.CELL_INSIDE_QUERY;
} else if (numCorners == 0) {
if (calculator.intersectsLine(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 minX, double maxX, double minY, double maxY,
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) {
return Relation.CELL_INSIDE_QUERY;
} else if (numCorners == 0) {
if (Component2D.pointInTriangle(minX, maxX, minY, maxY, calculator.geX(), calculator.getY(), ax, ay, bx, by, cx, cy) == true) {
return Relation.CELL_CROSSES_QUERY;
}
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;
}
return Relation.CELL_CROSSES_QUERY;
}
private int numberOfTriangleCorners(double ax, double ay, double bx, double by, double cx, double cy) {
int containsCount = 0;
if (contains(ax, ay)) {
containsCount++;
}
if (contains(bx, by)) {
containsCount++;
}
if (containsCount == 1) {
// if one point is inside and the other outside, we know
// already that the triangle intersect.
return containsCount;
}
if (contains(cx, cy)) {
containsCount++;
}
return containsCount;
}
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 vectorAPX = centerX - aX;
@ -296,9 +264,12 @@ class Circle2D implements Component2D {
@Override
public boolean contains(double x, double y) {
final double diffX = x - this.centerX;
final double diffY = y - this.centerY;
return diffX * diffX + diffY * diffY <= radiusSquared;
if (Component2D.containsPoint(x, y, rectangle.minX, rectangle.maxX, rectangle.minY, rectangle.maxY)) {
final double diffX = x - this.centerX;
final double diffY = y - this.centerY;
return diffX * diffX + diffY * diffY <= radiusSquared;
}
return false;
}
@Override
@ -372,7 +343,17 @@ class Circle2D implements Component2D {
@Override
public boolean contains(double x, double y) {
return SloppyMath.haversinSortKey(y, x, this.centerLat, this.centerLon) <= sortKey;
if (crossesDateline) {
if (Component2D.containsPoint(x, y, rectangle.minLon, GeoUtils.MAX_LON_INCL, rectangle.minLat, rectangle.maxLat) ||
Component2D.containsPoint(x, y, GeoUtils.MIN_LON_INCL, rectangle.maxLon, rectangle.minLat, rectangle.maxLat)) {
return SloppyMath.haversinSortKey(y, x, this.centerLat, this.centerLon) <= sortKey;
}
} else {
if (Component2D.containsPoint(x, y, rectangle.minLon, rectangle.maxLon, rectangle.minLat, rectangle.maxLat)) {
return SloppyMath.haversinSortKey(y, x, this.centerLat, this.centerLon) <= sortKey;
}
}
return false;
}

View File

@ -47,18 +47,21 @@ public interface Component2D {
/** relates this component2D with a bounding box **/
PointValues.Relation relate(double minX, double maxX, double minY, double maxY);
/** relates this component2D with a triangle **/
PointValues.Relation relateTriangle(double minX, double maxX, double minY, double maxY,
double aX, double aY, double bX, double bY, double cX, double cY);
/** return true if this component2D intersects the provided line **/
boolean intersectsLine(double minX, double maxX, double minY, double maxY,
double aX, double aY, double bX, double bY);
/** relates this component2D with a triangle **/
default PointValues.Relation relateTriangle(double aX, double aY, double bX, double bY, double cX, double cY) {
double minY = StrictMath.min(StrictMath.min(aY, bY), cY);
double minX = StrictMath.min(StrictMath.min(aX, bX), cX);
double maxY = StrictMath.max(StrictMath.max(aY, bY), cY);
double maxX = StrictMath.max(StrictMath.max(aX, bX), cX);
return relateTriangle(minX, maxX, minY, maxY, aX, aY, bX, bY, cX, cY);
}
/** return true if this component2D intersects the provided triangle **/
boolean intersectsTriangle(double minX, double maxX, double minY, double maxY,
double aX, double aY, double bX, double bY, double cX, double cY);
/** return true if this component2D contains the provided line **/
boolean containsLine(double minX, double maxX, double minY, double maxY,
double aX, double aY, double bX, double bY);
/** return true if this component2D contains the provided triangle **/
boolean containsTriangle(double minX, double maxX, double minY, double maxY,
double aX, double aY, double bX, double bY, double cX, double cY);
/** Used by withinTriangle to check the within relationship between a triangle and the query shape
* (e.g. if the query shape is within the triangle). */
@ -73,6 +76,63 @@ public interface Component2D {
DISJOINT
}
/** Compute the within relation of this component2D with a point **/
WithinRelation withinPoint(double x, double y);
/** Compute the within relation of this component2D with a line **/
WithinRelation withinLine(double minX, double maxX, double minY, double maxY,
double aX, double aY, boolean ab, double bX, double bY);
/** Compute the within relation of this component2D with a triangle **/
WithinRelation withinTriangle(double minX, double maxX, double minY, double maxY,
double aX, double aY, boolean ab, double bX, double bY, boolean bc, double cX, double cY, boolean ca);
/** return true if this component2D intersects the provided line **/
default boolean intersectsLine(double aX, double aY, double bX, double bY) {
double minY = StrictMath.min(aY, bY);
double minX = StrictMath.min(aX, bX);
double maxY = StrictMath.max(aY, bY);
double maxX = StrictMath.max(aX, bX);
return intersectsLine(minX, maxX, minY, maxY, aX, aY, bX, bY);
}
/** return true if this component2D intersects the provided triangle **/
default boolean intersectsTriangle(double aX, double aY, double bX, double bY, double cX, double cY) {
double minY = StrictMath.min(StrictMath.min(aY, bY), cY);
double minX = StrictMath.min(StrictMath.min(aX, bX), cX);
double maxY = StrictMath.max(StrictMath.max(aY, bY), cY);
double maxX = StrictMath.max(StrictMath.max(aX, bX), cX);
return intersectsTriangle(minX, maxX, minY, maxY, aX, aY, bX, bY, cX, cY);
}
/** return true if this component2D contains the provided line **/
default boolean containsLine(double aX, double aY, double bX, double bY) {
double minY = StrictMath.min(aY, bY);
double minX = StrictMath.min(aX, bX);
double maxY = StrictMath.max(aY, bY);
double maxX = StrictMath.max(aX, bX);
return containsLine(minX, maxX, minY, maxY, aX, aY, bX, bY);
}
/** return true if this component2D contains the provided triangle **/
default boolean containsTriangle(double aX, double aY, double bX, double bY, double cX, double cY) {
double minY = StrictMath.min(StrictMath.min(aY, bY), cY);
double minX = StrictMath.min(StrictMath.min(aX, bX), cX);
double maxY = StrictMath.max(StrictMath.max(aY, bY), cY);
double maxX = StrictMath.max(StrictMath.max(aX, bX), cX);
return containsTriangle(minX, maxX, minY, maxY, aX, aY, bX, bY, cX, cY);
}
/** Compute the within relation of this component2D with a triangle **/
default WithinRelation withinLine(double aX, double aY, boolean ab, double bX, double bY) {
double minY = StrictMath.min(aY, bY);
double minX = StrictMath.min(aX, bX);
double maxY = StrictMath.max(aY, bY);
double maxX = StrictMath.max(aX, bX);
return withinLine(minX, maxX, minY, maxY, aX, aY, ab, bX, bY);
}
/** Compute the within relation of this component2D with a triangle **/
default WithinRelation withinTriangle(double aX, double aY, boolean ab, double bX, double bY, boolean bc, double cX, double cY, boolean ca) {
double minY = StrictMath.min(StrictMath.min(aY, bY), cY);
@ -82,11 +142,6 @@ public interface Component2D {
return withinTriangle(minX, maxX, minY, maxY, aX, aY, ab, bX, bY, bc, cX, cY, ca);
}
/** Compute the within relation of this component2D with a triangle **/
WithinRelation withinTriangle(double minX, double maxX, double minY, double maxY,
double aX, double aY, boolean ab, double bX, double bY, boolean bc, double cX, double cY, boolean ca);
/** Compute whether the bounding boxes are disjoint **/
static boolean disjoint(double minX1, double maxX1, double minY1, double maxY1, double minX2, double maxX2, double minY2, double maxY2) {
return (maxY1 < minY2 || minY1 > maxY2 || maxX1 < minX2 || minX1 > maxX2);
@ -105,8 +160,8 @@ public interface Component2D {
/**
* Compute whether the given x, y point is in a triangle; uses the winding order method */
static boolean pointInTriangle(double minX, double maxX, double minY, double maxY, double x, double y, double aX, double aY, double bX, double bY, double cX, double cY) {
//check the bounding box because if the triangle is degenerated, e.g points and lines, we need to filter out
//coplanar points that are not part of the triangle.
// check the bounding box because if the triangle is degenerated, e.g points and lines, we need to filter out
// coplanar points that are not part of the triangle.
if (x >= minX && x <= maxX && y >= minY && y <= maxY) {
int a = orient(x, y, aX, aY, bX, bY);
int b = orient(x, y, bX, bY, cX, cY);

View File

@ -96,27 +96,104 @@ final class ComponentTree implements Component2D {
}
@Override
public Relation relateTriangle(double minX, double maxX, double minY, double maxY,
double ax, double ay, double bx, double by, double cx, double cy) {
public boolean intersectsLine(double minX, double maxX, double minY, double maxY,
double aX, double aY, double bX, double bY) {
if (minY <= this.maxY && minX <= this.maxX) {
Relation relation = component.relateTriangle(minX, maxX, minY, maxY, ax, ay, bx, by, cx, cy);
if (relation != Relation.CELL_OUTSIDE_QUERY) {
return relation;
if(component.intersectsLine(minX, maxX, minY, maxY, aX, aY, bX, bY)) {
return true;
}
if (left != null) {
relation = left.relateTriangle(minX, maxX, minY, maxY, ax, ay, bx, by, cx, cy);
if (relation != Relation.CELL_OUTSIDE_QUERY) {
return relation;
if (left.intersectsLine(minX, maxX, minY, maxY, aX, aY, bX, bY)) {
return true;
}
}
if (right != null && ((splitX == false && maxY >= this.component.getMinY()) || (splitX && maxX >= this.component.getMinX()))) {
relation = right.relateTriangle(minX, maxX, minY, maxY, ax, ay, bx, by, cx, cy);
if (relation != Relation.CELL_OUTSIDE_QUERY) {
return relation;
if (right.intersectsLine(minX, maxX, minY, maxY, aX, aY, bX, bY)) {
return true;
}
}
}
return Relation.CELL_OUTSIDE_QUERY;
return false;
}
@Override
public boolean intersectsTriangle(double minX, double maxX, double minY, double maxY,
double aX, double aY, double bX, double bY, double cX, double cY) {
if (minY <= this.maxY && minX <= this.maxX) {
if(component.intersectsTriangle(minX, maxX, minY, maxY, aX, aY, bX, bY, cX, cY)) {
return true;
}
if (left != null) {
if (left.intersectsTriangle(minX, maxX, minY, maxY, aX, aY, bX, bY, cX, cY)) {
return true;
}
}
if (right != null && ((splitX == false && maxY >= this.component.getMinY()) || (splitX && maxX >= this.component.getMinX()))) {
if (right.intersectsTriangle(minX, maxX, minY, maxY, aX, aY, bX, bY, cX, cY)) {
return true;
}
}
}
return false;
}
@Override
public boolean containsLine(double minX, double maxX, double minY, double maxY,
double aX, double aY, double bX, double bY) {
if (minY <= this.maxY && minX <= this.maxX) {
if(component.containsLine(minX, maxX, minY, maxY, aX, aY, bX, bY)) {
return true;
}
if (left != null) {
if (left.containsLine(minX, maxX, minY, maxY, aX, aY, bX, bY)) {
return true;
}
}
if (right != null && ((splitX == false && maxY >= this.component.getMinY()) || (splitX && maxX >= this.component.getMinX()))) {
if (right.containsLine(minX, maxX, minY, maxY, aX, aY, bX, bY)) {
return true;
}
}
}
return false;
}
@Override
public boolean containsTriangle(double minX, double maxX, double minY, double maxY,
double aX, double aY, double bX, double bY, double cX, double cY) {
if (minY <= this.maxY && minX <= this.maxX) {
if(component.containsTriangle(minX, maxX, minY, maxY, aX, aY, bX, bY, cX, cY)) {
return true;
}
if (left != null) {
if (left.containsTriangle(minX, maxX, minY, maxY, aX, aY, bX, bY, cX, cY)) {
return true;
}
}
if (right != null && ((splitX == false && maxY >= this.component.getMinY()) || (splitX && maxX >= this.component.getMinX()))) {
if (right.containsTriangle(minX, maxX, minY, maxY, aX, aY, bX, bY, cX, cY)) {
return true;
}
}
}
return false;
}
@Override
public WithinRelation withinPoint(double x, double y) {
if (left != null || right != null) {
throw new IllegalArgumentException("withinPoint is not supported for shapes with more than one component");
}
return component.withinPoint(x, y);
}
@Override
public WithinRelation withinLine(double minX, double maxX, double minY, double maxY,
double aX, double aY, boolean ab, double bX, double bY) {
if (left != null || right != null) {
throw new IllegalArgumentException("withinLine is not supported for shapes with more than one component");
}
return component.withinLine(minX, maxX, minY, maxY, aX, aY, ab, bX, bY);
}
@Override

View File

@ -96,50 +96,52 @@ final class Line2D implements Component2D {
}
@Override
public Relation relateTriangle(double minX, double maxX, double minY, double maxY,
double ax, double ay, double bx, double by, double cx, double cy) {
public boolean intersectsLine(double minX, double maxX, double minY, double maxY,
double aX, double aY, double bX, double bY) {
if (Component2D.disjoint(this.minX, this.maxX, this.minY, this.maxY, minX, maxX, minY, maxY)) {
return Relation.CELL_OUTSIDE_QUERY;
return false;
}
if (ax == bx && bx == cx && ay == by && by == cy) {
// indexed "triangle" is a point: check if point lies on any line segment
if (tree.isPointOnLine(ax, ay)) {
return Relation.CELL_INSIDE_QUERY;
}
} else if (ax == cx && ay == cy) {
// indexed "triangle" is a line:
if (tree.crossesLine(minX, maxX, minY, maxY, ax, ay, bx, by, true)) {
return Relation.CELL_CROSSES_QUERY;
}
return Relation.CELL_OUTSIDE_QUERY;
} else if (ax == bx && ay == by) {
// indexed "triangle" is a line:
if (tree.crossesLine(minX, maxX, minY, maxY, bx, by, cx, cy, true)) {
return Relation.CELL_CROSSES_QUERY;
}
return Relation.CELL_OUTSIDE_QUERY;
} else if (bx == cx && by == cy) {
// indexed "triangle" is a line:
if (tree.crossesLine(minX, maxX, minY, maxY, cx, cy, ax, ay, true)) {
return Relation.CELL_CROSSES_QUERY;
}
return Relation.CELL_OUTSIDE_QUERY;
} else if (Component2D.pointInTriangle(minX, maxX, minY, maxY, tree.x1, tree.y1, ax, ay, bx, by, cx, cy) == true ||
tree.crossesTriangle(minX, maxX, minY, maxY, ax, ay, bx, by, cx, cy, true)) {
// indexed "triangle" is a triangle:
return Relation.CELL_CROSSES_QUERY;
return tree.crossesLine(minX, maxX, minY, maxY, aX, aY, bX, bY, true);
}
@Override
public boolean intersectsTriangle(double minX, double maxX, double minY, double maxY,
double aX, double aY, double bX, double bY, double cX, double cY) {
if (Component2D.disjoint(this.minX, this.maxX, this.minY, this.maxY, minX, maxX, minY, maxY)) {
return false;
}
return Relation.CELL_OUTSIDE_QUERY;
return Component2D.pointInTriangle(minX, maxX, minY, maxY, tree.x1, tree.y1, aX, aY, bX, bY, cX, cY) ||
tree.crossesTriangle(minX, maxX, minY, maxY, aX, aY, bX, bY, cX, cY, true);
}
@Override
public boolean containsLine(double minX, double maxX, double minY, double maxY,
double aX, double aY, double bX, double bY) {
// can be improved?
return false;
}
@Override
public boolean containsTriangle(double minX, double maxX, double minY, double maxY,
double aX, double aY, double bX, double bY, double cX, double cY) {
return false;
}
@Override
public WithinRelation withinPoint(double x, double y) {
return WithinRelation.DISJOINT;
}
@Override
public WithinRelation withinLine(double minX, double maxX, double minY, double maxY,
double aX, double aY, boolean ab, double bX, double bY) {
// can be improved?
return WithinRelation.DISJOINT;
}
@Override
public WithinRelation withinTriangle(double minX, double maxX, double minY, double maxY,
double ax, double ay, boolean ab, double bx, double by, boolean bc, double cx, double cy, boolean ca) {
// short cut, lines and points cannot contain this type of shape
if ((ax == bx && ay == by) || (ax == cx && ay == cy) || (bx == cx && by == cy)) {
return WithinRelation.DISJOINT;
}
double aX, double aY, boolean ab, double bX, double bY, boolean bc, double cX, double cY, boolean ca) {
if (Component2D.disjoint(this.minX, this.maxX, this.minY, this.maxY, minX, maxX, minY, maxY)) {
return WithinRelation.DISJOINT;
}
@ -148,7 +150,7 @@ final class Line2D 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 (tree.crossesLine(minX, maxX, minY, maxY, ax, ay, bx, by, true)) {
if (tree.crossesLine(minX, maxX, minY, maxY, aX, aY, bX, bY, true)) {
if (ab == true) {
return WithinRelation.NOTWITHIN;
} else {
@ -156,14 +158,14 @@ final class Line2D implements Component2D {
}
}
if (tree.crossesLine(minX, maxX, minY, maxY, bx, by, cx, cy, true)) {
if (tree.crossesLine(minX, maxX, minY, maxY, bX, bY, cX, cY, true)) {
if (bc == true) {
return WithinRelation.NOTWITHIN;
} else {
relation = WithinRelation.CANDIDATE;
}
}
if (tree.crossesLine(minX, maxX, minY, maxY, cx, cy, ax, ay, true)) {
if (tree.crossesLine(minX, maxX, minY, maxY, cX, cY, aX, aY, true)) {
if (ca == true) {
return WithinRelation.NOTWITHIN;
} else {
@ -177,7 +179,7 @@ final class Line2D implements Component2D {
}
// Check if shape is within the triangle
if (Component2D.pointInTriangle(minX, maxX, minY, maxY, tree.x1, tree.y1, ax, ay, bx, by, cx, cy) == true) {
if (Component2D.pointInTriangle(minX, maxX, minY, maxY, tree.x1, tree.y1, aX, aY, bX, bY, cX, cY) == true) {
return WithinRelation.CANDIDATE;
}
return relation;

View File

@ -19,6 +19,8 @@ package org.apache.lucene.geo;
import org.apache.lucene.index.PointValues;
import static org.apache.lucene.geo.GeoUtils.orient;
/**
* 2D point implementation containing geo spatial logic.
*/
@ -66,15 +68,40 @@ final class Point2D implements Component2D {
}
@Override
public PointValues.Relation relateTriangle(double minX, double maxX, double minY, double maxY,
double ax, double ay, double bx, double by, double cx, double cy) {
if (ax == bx && bx == cx && ay == by && by == cy) {
return contains(ax, ay) ? PointValues.Relation.CELL_INSIDE_QUERY : PointValues.Relation.CELL_OUTSIDE_QUERY;
}
if (Component2D.pointInTriangle(minX, maxX, minY, maxY, x, y, ax, ay, bx, by, cx, cy)) {
return PointValues.Relation.CELL_CROSSES_QUERY;
}
return PointValues.Relation.CELL_OUTSIDE_QUERY;
public boolean intersectsLine(double minX, double maxX, double minY, double maxY,
double aX, double aY, double bX, double bY) {
return Component2D.containsPoint(x, y, minX, maxX, minY, maxY) &&
orient(aX, aY, bX, bY, x, y) == 0;
}
@Override
public boolean intersectsTriangle(double minX, double maxX, double minY, double maxY,
double aX, double aY, double bX, double bY, double cX, double cY) {
return Component2D.pointInTriangle(minX, maxX, minY, maxY, x, y, aX, aY, bX, bY, cX, cY);
}
@Override
public boolean containsLine(double minX, double maxX, double minY, double maxY,
double aX, double aY, double bX, double bY) {
return false;
}
@Override
public boolean containsTriangle(double minX, double maxX, double minY, double maxY,
double aX, double aY, double bX, double bY, double cX, double cY) {
return false;
}
@Override
public WithinRelation withinPoint(double x, double y) {
return contains(x, y) ? WithinRelation.CANDIDATE : WithinRelation.DISJOINT;
}
@Override
public WithinRelation withinLine(double minX, double maxX, double minY, double maxY,
double aX, double aY, boolean ab, double bX, double bY) {
// can be improved?
return intersectsLine(minX, maxX, minY, maxY, aX, aY, bX, bY) ? WithinRelation.CANDIDATE : WithinRelation.DISJOINT;
}
@Override

View File

@ -84,18 +84,8 @@ final class Polygon2D implements Component2D {
*/
@Override
public boolean contains(double x, double y) {
if (Component2D.containsPoint(x, y, minX, maxX, minY, maxY)) {
return internalContains(x, y);
}
return false;
}
private boolean internalContains(double x, double y) {
if (tree.contains(x, y)) {
if (holes != null && holes.contains(x, y)) {
return false;
}
return true;
if (Component2D.containsPoint(x, y, minX, maxX, minY, maxY) && tree.contains(x, y)) {
return holes == null || holes.contains(x, y) == false;
}
return false;
}
@ -137,52 +127,83 @@ final class Polygon2D implements Component2D {
}
@Override
public Relation relateTriangle(double minX, double maxX, double minY, double maxY,
double ax, double ay, double bx, double by, double cx, double cy) {
public boolean intersectsLine(double minX, double maxX, double minY, double maxY,
double aX, double aY, double bX, double bY) {
if (Component2D.disjoint(this.minX, this.maxX, this.minY, this.maxY, minX, maxX, minY, maxY)) {
return Relation.CELL_OUTSIDE_QUERY;
return false;
}
// check any holes
if (holes != null) {
Relation holeRelation = holes.relateTriangle(minX, maxX, minY, maxY, ax, ay, bx, by, cx, cy);
if (holeRelation == Relation.CELL_CROSSES_QUERY) {
return Relation.CELL_CROSSES_QUERY;
} else if (holeRelation == Relation.CELL_INSIDE_QUERY) {
return Relation.CELL_OUTSIDE_QUERY;
}
if (contains(aX, aY) || contains(bX, bY) ||
tree.crossesLine(minX, maxX, minY, maxY, aX, aY, bX, bY, true)) {
return holes == null || holes.containsLine(minX, maxX, minY, maxY, aX, aY, bX, bY) == false;
}
if (ax == bx && bx == cx && ay == by && by == cy) {
// indexed "triangle" is a point: shortcut by checking contains
return internalContains(ax, ay) ? 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(minX, maxX, minY, maxY, ax, ay, bx, by);
} else if (ax == bx && ay == by) {
// indexed "triangle" is a line segment: shortcut by calling appropriate method
return relateIndexedLineSegment(minX, maxX, minY, maxY, bx, by, cx, cy);
} else if (bx == cx && by == cy) {
// indexed "triangle" is a line segment: shortcut by calling appropriate method
return relateIndexedLineSegment(minX, maxX, minY, maxY, cx, cy, ax, ay);
return false;
}
@Override
public boolean intersectsTriangle(double minX, double maxX, double minY, double maxY,
double aX, double aY, double bX, double bY, double cX, double cY) {
if (Component2D.disjoint(this.minX, this.maxX, this.minY, this.maxY, minX, maxX, minY, maxY)) {
return false;
}
// indexed "triangle" is a triangle:
return relateIndexedTriangle(minX, maxX, minY, maxY, ax, ay, bx, by, cx, cy);
if (contains(aX, aY) || contains(bX, bY) || contains(cX, cY) ||
Component2D.pointInTriangle(minX, maxX, minY, maxY, tree.x1, tree.y1, aX, aY, bX, bY, cX, cY)||
tree.crossesTriangle(minX, maxX, minY, maxY, aX, aY, bX, bY, cX, cY, true)) {
return holes == null || holes.containsTriangle(minX, maxX, minY, maxY, aX, aY, bX, bY, cX, cY) == false;
}
return false;
}
@Override
public boolean containsLine(double minX, double maxX, double minY, double maxY,
double aX, double aY, double bX, double bY) {
if (Component2D.disjoint(this.minX, this.maxX, this.minY, this.maxY, minX, maxX, minY, maxY)) {
return false;
}
if (contains(aX, aY) && contains(bX, bY) &&
tree.crossesLine(minX, maxX, minY, maxY, aX, aY, bX, bY, false) == false) {
return holes == null || holes.intersectsLine(minX, maxX, minY, maxY, aX, aY, bX, bY) == false;
}
return false;
}
@Override
public boolean containsTriangle(double minX, double maxX, double minY, double maxY,
double aX, double aY, double bX, double bY, double cX, double cY) {
if (Component2D.disjoint(this.minX, this.maxX, this.minY, this.maxY, minX, maxX, minY, maxY)) {
return false;
}
if (contains(aX, aY) && contains(bX, bY) && contains(cX, cY) &&
tree.crossesTriangle(minX, maxX, minY, maxY, aX, aY, bX, bY, cX, cY, false) == false) {
return holes == null || holes.intersectsTriangle(minX, maxX, minY, maxY, aX, aY, bX, bY, cX, cY) == false;
}
return false;
}
@Override
public WithinRelation withinPoint(double x, double y) {
return WithinRelation.DISJOINT;
}
@Override
public WithinRelation withinLine(double minX, double maxX, double minY, double maxY,
double aX, double aY, boolean ab, double bX, double bY) {
if (ab == true && Component2D.disjoint(this.minX, this.maxX, this.minY, this.maxY, minX, maxX, minY, maxY) == false &&
tree.crossesLine(minX, maxX, minY, maxY, aX, aY, bX, bY, true)) {
return WithinRelation.NOTWITHIN;
}
return WithinRelation.DISJOINT;
}
@Override
public WithinRelation withinTriangle(double minX, double maxX, double minY, double maxY,
double ax, double ay, boolean ab, double bx, double by, boolean bc, double cx, double cy, boolean ca) {
// short cut, lines and points cannot contain this type of shape
if ((ax == bx && ay == by) || (ax == cx && ay == cy) || (bx == cx && by == cy)) {
return WithinRelation.DISJOINT;
}
double aX, double aY, boolean ab, double bX, double bY, boolean bc, double cX, double cY, boolean ca) {
if (Component2D.disjoint(this.minX, this.maxX, this.minY, this.maxY, minX, maxX, minY, maxY)) {
return WithinRelation.DISJOINT;
}
// if any of the points is inside the polygon, the polygon cannot be within this indexed
// shape because points belong to the original indexed shape.
if (contains(ax, ay) || contains(bx, by) || contains(cx, cy)) {
if (contains(aX, aY) || contains(bX, bY) || contains(cX, cY)) {
return WithinRelation.NOTWITHIN;
}
@ -190,7 +211,7 @@ final class Polygon2D 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 (tree.crossesLine(minX, maxX, minY, maxY, ax, ay, bx, by, true)) {
if (tree.crossesLine(minX, maxX, minY, maxY, aX, aY, bX, bY, true)) {
if (ab == true) {
return WithinRelation.NOTWITHIN;
} else {
@ -198,14 +219,14 @@ final class Polygon2D implements Component2D {
}
}
if (tree.crossesLine(minX, maxX, minY, maxY, bx, by, cx, cy, true)) {
if (tree.crossesLine(minX, maxX, minY, maxY, bX, bY, cX, cY, true)) {
if (bc == true) {
return WithinRelation.NOTWITHIN;
} else {
relation = WithinRelation.CANDIDATE;
}
}
if (tree.crossesLine(minX, maxX, minY, maxY, cx, cy, ax, ay, true)) {
if (tree.crossesLine(minX, maxX, minY, maxY, cX, cY, aX, aY, true)) {
if (ca == true) {
return WithinRelation.NOTWITHIN;
} else {
@ -220,77 +241,12 @@ final class Polygon2D implements Component2D {
}
// Check if shape is within the triangle
if (Component2D.pointInTriangle(minX, maxX, minY, maxY, tree.x1, tree.y1, ax, ay, bx, by, cx, cy) == true) {
if (Component2D.pointInTriangle(minX, maxX, minY, maxY, tree.x1, tree.y1, aX, aY, bX, bY, cX, cY) == true) {
return WithinRelation.CANDIDATE;
}
return relation;
}
/** relates an indexed line segment (a "flat triangle") with the polygon */
private Relation relateIndexedLineSegment(double minX, double maxX, double minY, double maxY,
double a2x, double a2y, double b2x, double b2y) {
// check endpoints of the line segment
int numCorners = 0;
if (contains(a2x, a2y)) {
++numCorners;
}
if (contains(b2x, b2y)) {
++numCorners;
}
if (numCorners == 2) {
if (tree.crossesLine(minX, maxX, minY, maxY, a2x, a2y, b2x, b2y, false)) {
return Relation.CELL_CROSSES_QUERY;
}
return Relation.CELL_INSIDE_QUERY;
} else if (numCorners == 0) {
if (tree.crossesLine(minX, maxX, minY, maxY, a2x, a2y, b2x, b2y, true)) {
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 minX, double maxX, double minY, double maxY,
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) {
if (tree.crossesTriangle(minX, maxX, minY, maxY, ax, ay, bx, by, cx, cy, false)) {
return Relation.CELL_CROSSES_QUERY;
}
return Relation.CELL_INSIDE_QUERY;
} else if (numCorners == 0) {
if (Component2D.pointInTriangle(minX, maxX, minY, maxY, tree.x1, tree.y1, ax, ay, bx, by, cx, cy) == true) {
return Relation.CELL_CROSSES_QUERY;
}
if (tree.crossesTriangle(minX, maxX, minY, maxY, ax, ay, bx, by, cx, cy, true)) {
return Relation.CELL_CROSSES_QUERY;
}
return Relation.CELL_OUTSIDE_QUERY;
}
return Relation.CELL_CROSSES_QUERY;
}
private int numberOfTriangleCorners(double ax, double ay, double bx, double by, double cx, double cy) {
int containsCount = 0;
if (contains(ax, ay)) {
containsCount++;
}
if (contains(bx, by)) {
containsCount++;
}
if (containsCount == 1) {
return containsCount;
}
if (contains(cx, cy)) {
containsCount++;
}
return containsCount;
}
// returns 0, 4, or something in between
private int numberOfCorners(double minX, double maxX, double minY, double maxY) {
int containsCount = 0;

View File

@ -28,7 +28,6 @@ import static org.apache.lucene.geo.GeoEncodingUtils.decodeLatitude;
import static org.apache.lucene.geo.GeoEncodingUtils.decodeLongitude;
import static org.apache.lucene.geo.GeoEncodingUtils.MAX_LON_ENCODED;
import static org.apache.lucene.geo.GeoEncodingUtils.MIN_LON_ENCODED;
import static org.apache.lucene.geo.GeoUtils.orient;
/**
* 2D rectangle implementation containing cartesian spatial logic.
@ -84,54 +83,76 @@ final class Rectangle2D implements Component2D {
}
@Override
public PointValues.Relation relateTriangle(double minX, double maxX, double minY, double maxY,
double ax, double ay, double bx, double by, double cx, double cy) {
public boolean intersectsLine(double minX, double maxX, double minY, double maxY,
double aX, double aY, double bX, double bY) {
if (Component2D.disjoint(this.minX, this.maxX, this.minY, this.maxY, minX, maxX, minY, maxY)) {
return PointValues.Relation.CELL_OUTSIDE_QUERY;
return false;
}
int edgesContain = numberOfCorners(ax, ay, bx, by, cx, cy);
if (edgesContain == 3) {
return PointValues.Relation.CELL_INSIDE_QUERY;
} else if (edgesContain != 0) {
return PointValues.Relation.CELL_CROSSES_QUERY;
} else if (Component2D.pointInTriangle(minX, maxX, minY, maxY, this.minX, this.minY,ax, ay, bx, by, cx, cy)
|| edgesIntersect(ax, ay, bx, by)
|| edgesIntersect(bx, by, cx, cy)
|| edgesIntersect(cx, cy, ax, ay)) {
return PointValues.Relation.CELL_CROSSES_QUERY;
return contains(aX, aY) || contains(bX, bY) || edgesIntersect(aX, aY, bX, bY);
}
@Override
public boolean intersectsTriangle(double minX, double maxX, double minY, double maxY,
double aX, double aY, double bX, double bY, double cX, double cY) {
if (Component2D.disjoint(this.minX, this.maxX, this.minY, this.maxY, minX, maxX, minY, maxY)) {
return false;
}
return PointValues.Relation.CELL_OUTSIDE_QUERY;
return contains(aX, aY) || contains(bX, bY) || contains(cX, cY) ||
Component2D.pointInTriangle(minX, maxX, minY, maxY, this.minX, this.minY,aX, aY, bX, bY, cX, cY) ||
edgesIntersect(aX, aY, bX, bY) ||
edgesIntersect(bX, bY, cX, cY) ||
edgesIntersect(cX, cY, aX, aY);
}
@Override
public boolean containsLine(double minX, double maxX, double minY, double maxY,
double aX, double aY, double bX, double bY) {
return Component2D.within(minX, maxX, minY, maxY, this.minX, this.maxX, this.minY, this.maxY);
}
@Override
public boolean containsTriangle(double minX, double maxX, double minY, double maxY,
double aX, double aY, double bX, double bY, double cX, double cY) {
return Component2D.within(minX, maxX, minY, maxY, this.minX, this.maxX, this.minY, this.maxY);
}
@Override
public WithinRelation withinPoint(double x, double y) {
return WithinRelation.DISJOINT;
}
@Override
public WithinRelation withinLine(double minX, double maxX, double minY, double maxY,
double aX, double aY, boolean ab, double bX, double bY) {
if (ab == true && Component2D.disjoint(this.minX, this.maxX, this.minY, this.maxY, minX, maxX, minY, maxY) ==false &&
edgesIntersect(aX, aY, bX, bY)) {
return WithinRelation.NOTWITHIN;
}
return WithinRelation.DISJOINT;
}
@Override
public WithinRelation withinTriangle(double minX, double maxX, double minY, double maxY,
double ax, double ay, boolean ab, double bx, double by, boolean bc, double cx, double cy, boolean ca) {
// Short cut, lines and points cannot contain a bbox
if ((ax == bx && ay == by) || (ax == cx && ay == cy) || (bx == cx && by == cy)) {
return WithinRelation.DISJOINT;
}
double aX, double aY, boolean ab, double bX, double bY, boolean bc, double cX, double cY, boolean ca) {
// Bounding boxes disjoint?
if (Component2D.disjoint(this.minX, this.maxX, this.minY, this.maxY, minX, maxX, minY, maxY)) {
return WithinRelation.DISJOINT;
}
// Points belong to the shape so if points are inside the rectangle then it cannot be within.
if (contains(ax, ay) || contains(bx, by) || contains(cx, cy)) {
if (contains(aX, aY) || contains(bX, bY) || contains(cX, cY)) {
return WithinRelation.NOTWITHIN;
}
// If any of the edges intersects an edge belonging to the shape then it cannot be within.
WithinRelation relation = WithinRelation.DISJOINT;
if (edgesIntersect(ax, ay, bx, by) == true) {
if (edgesIntersect(aX, aY, bX, bY) == true) {
if (ab == true) {
return WithinRelation.NOTWITHIN;
} else {
relation = WithinRelation.CANDIDATE;
}
}
if (edgesIntersect(bx, by, cx, cy) == true) {
if (edgesIntersect(bX, bY, cX, cY) == true) {
if (bc == true) {
return WithinRelation.NOTWITHIN;
} else {
@ -139,7 +160,7 @@ final class Rectangle2D implements Component2D {
}
}
if (edgesIntersect(cx, cy, ax, ay) == true) {
if (edgesIntersect(cX, cY, aX, aY) == true) {
if (ca == true) {
return WithinRelation.NOTWITHIN;
} else {
@ -152,61 +173,21 @@ final class Rectangle2D implements Component2D {
return WithinRelation.CANDIDATE;
}
// Check if shape is within the triangle
if (Component2D.pointInTriangle(minX, maxX, minY, maxY, this.minX, this.minY, ax, ay, bx, by, cx, cy)) {
if (Component2D.pointInTriangle(minX, maxX, minY, maxY, this.minX, this.minY, aX, aY, bX, bY, cX, cY)) {
return WithinRelation.CANDIDATE;
}
return relation;
}
private boolean edgesIntersect(double ax, double ay, double bx, double by) {
// shortcut: if edge is a point (occurs w/ Line shapes); simply check bbox w/ point
if (ax == bx && ay == by) {
return false;
}
private boolean edgesIntersect(double aX, double aY, double bX, double bY) {
// shortcut: check bboxes of edges are disjoint
if ( Math.max(ax, bx) < minX || Math.min(ax, bx) > maxX || Math.min(ay, by) > maxY || Math.max(ay, by) < minY) {
if (Math.max(aX, bX) < minX || Math.min(aX, bX) > maxX || Math.min(aY, bY) > maxY || Math.max(aY, bY) < minY) {
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) {
return true;
}
// right
if (orient(ax, ay, bx, by, maxX, maxY) * orient(ax, ay, bx, by, maxX, minY) <= 0 &&
orient(maxX, maxY, maxX, minY, ax, ay) * orient(maxX, maxY, maxX, minY, bx, by) <= 0) {
return true;
}
// bottom
if (orient(ax, ay, bx, by, maxX, minY) * orient(ax, ay, bx, by, minX, minY) <= 0 &&
orient(maxX, minY, minX, minY, ax, ay) * orient(maxX, minY, minX, minY, bx, by) <= 0) {
return true;
}
// left
if (orient(ax, ay, bx, by, minX, minY) * orient(ax, ay, bx, by, minX, maxY) <= 0 &&
orient(minX, minY, minX, maxY, ax, ay) * orient(minX, minY, minX, maxY, bx, by) <= 0) {
return true;
}
return false;
}
private int numberOfCorners(double ax, double ay, double bx, double by, double cx, double cy) {
int containsCount = 0;
if (contains(ax, ay)) {
containsCount++;
}
if (contains(bx, by)) {
containsCount++;
}
if (contains(cx, cy)) {
containsCount++;
}
return containsCount;
return GeoUtils.lineCrossesLineWithBoundary(aX, aY, bX, bY, minX, maxY, maxX, maxY) || // top
GeoUtils.lineCrossesLineWithBoundary(aX, aY, bX, bY, maxX, maxY, maxX, minY) || // bottom
GeoUtils.lineCrossesLineWithBoundary(aX, aY, bX, bY, maxX, minY, minX, minY) || // left
GeoUtils.lineCrossesLineWithBoundary(aX, aY, bX, bY, minX, minY, minX, maxY); // right
}
@Override

View File

@ -18,7 +18,6 @@ package org.apache.lucene.document;
import org.apache.lucene.geo.Component2D;
import org.apache.lucene.geo.GeoUtils;
import org.apache.lucene.index.PointValues;
import org.apache.lucene.util.LuceneTestCase;
/** base shape encoding class for testing encoding of tessellated {@link org.apache.lucene.document.XYShape} and
@ -381,9 +380,9 @@ public abstract class BaseShapeEncodingTestCase extends LuceneTestCase {
assertEquals(encoded.aY, latEnc);
assertEquals(encoded.aX, axEnc);
assertEquals(encoded.bY, latEnc);
assertEquals(encoded.bX, axEnc);
assertEquals(encoded.bX, bxEnc);
assertEquals(encoded.cY, latEnc);
assertEquals(encoded.cX, bxEnc);
assertEquals(encoded.cX, axEnc);
ShapeField.encodeTriangle(b, latEnc, bxEnc, true, latEnc, axEnc, true, latEnc, axEnc, true);
ShapeField.decodeTriangle(b, encoded);
assertEquals(encoded.aY, latEnc);
@ -415,9 +414,9 @@ public abstract class BaseShapeEncodingTestCase extends LuceneTestCase {
ShapeField.decodeTriangle(b, encoded);
assertEquals(encoded.aY, ayEnc);
assertEquals(encoded.aX, lonEnc);
assertEquals(encoded.bY, ayEnc);
assertEquals(encoded.bY, byEnc);
assertEquals(encoded.bX, lonEnc);
assertEquals(encoded.cY, byEnc);
assertEquals(encoded.cY, ayEnc);
assertEquals(encoded.cX, lonEnc);
ShapeField.encodeTriangle(b, byEnc, lonEnc, true, ayEnc, lonEnc, true, ayEnc, lonEnc, true);
ShapeField.decodeTriangle(b, encoded);
@ -452,10 +451,10 @@ public abstract class BaseShapeEncodingTestCase extends LuceneTestCase {
ShapeField.decodeTriangle(b, encoded);
assertEquals(encoded.aY, ayEnc);
assertEquals(encoded.aX, axEnc);
assertEquals(encoded.bY, ayEnc);
assertEquals(encoded.bX, axEnc);
assertEquals(encoded.cY, byEnc);
assertEquals(encoded.cX, bxEnc);
assertEquals(encoded.bY, byEnc);
assertEquals(encoded.bX, bxEnc);
assertEquals(encoded.cY, ayEnc);
assertEquals(encoded.cX, axEnc);
ShapeField.encodeTriangle(b, byEnc, bxEnc, true, ayEnc, axEnc, true, ayEnc, axEnc, true);
ShapeField.decodeTriangle(b, encoded);
assertEquals(encoded.aY, ayEnc);
@ -491,57 +490,73 @@ public abstract class BaseShapeEncodingTestCase extends LuceneTestCase {
}
private void verifyEncoding(double ay, double ax, double by, double bx, double cy, double cx) {
int[] original = new int[]{
encodeY(ay),
encodeX(ax),
encodeY(by),
encodeX(bx),
encodeY(cy),
encodeX(cx)};
//quantize the triangle
// encode triangle
int[] original = new int[]{encodeX(ax), encodeY(ay), encodeX(bx), encodeY(by), encodeX(cx), encodeY(cy)};
byte[] b = new byte[7 * ShapeField.BYTES];
ShapeField.encodeTriangle(b, original[0], original[1], true, original[2], original[3], true, original[4], original[5], true);
ShapeField.encodeTriangle(b, original[1], original[0], true, original[3], original[2], true, original[5], original[4], true);
ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle();
ShapeField.decodeTriangle(b, encoded);
double[] encodedQuantize = new double[] {
decodeY(encoded.aY),
decodeX(encoded.aX),
decodeY(encoded.bY),
decodeX(encoded.bX),
decodeY(encoded.cY),
decodeX(encoded.cX)};
int orientation = GeoUtils.orient(original[1], original[0], original[3], original[2], original[5], original[4]);
//quantize original
double[] originalQuantize;
//we need to change the orientation if CW
if (orientation == -1) {
originalQuantize = new double[] {
decodeY(original[4]),
decodeX(original[5]),
decodeY(original[2]),
decodeX(original[3]),
decodeY(original[0]),
decodeX(original[1])};
} else {
originalQuantize = new double[] {
decodeY(original[0]),
decodeX(original[1]),
decodeY(original[2]),
decodeX(original[3]),
decodeY(original[4]),
decodeX(original[5])};
}
// quantize decoded triangle
double[] encodedQuantize = new double[] {decodeX(encoded.aX), decodeY(encoded.aY), decodeX(encoded.bX), decodeY(encoded.bY), decodeX(encoded.cX), decodeY(encoded.cY)};
// quantize original and order it to reflect encoding
double[] originalQuantize = orderTriangle(original[0], original[1], original[2], original[3], original[4], original[5]);
// check spatial property
for (int i =0; i < 100; i ++) {
Component2D polygon2D = createPolygon2D(nextPolygon());
PointValues.Relation originalRelation = polygon2D.relateTriangle(originalQuantize[1], originalQuantize[0], originalQuantize[3], originalQuantize[2], originalQuantize[5], originalQuantize[4]);
PointValues.Relation encodedRelation = polygon2D.relateTriangle(encodedQuantize[1], encodedQuantize[0], encodedQuantize[3], encodedQuantize[2], encodedQuantize[5], encodedQuantize[4]);
assertTrue(originalRelation == encodedRelation);
boolean originalIntersects = false;
boolean encodedIntersects = false;
boolean originalContains = false;
boolean encodedContains = false;
switch (encoded.type) {
case POINT:
originalIntersects = polygon2D.contains(originalQuantize[0], originalQuantize[1]);
encodedIntersects = polygon2D.contains(encodedQuantize[0], encodedQuantize[1]);
originalContains = polygon2D.contains(originalQuantize[0], originalQuantize[1]);
encodedContains = polygon2D.contains(encodedQuantize[0], encodedQuantize[1]);
break;
case LINE:
originalIntersects = polygon2D.intersectsLine(originalQuantize[0], originalQuantize[1], originalQuantize[2], originalQuantize[3]);
encodedIntersects = polygon2D.intersectsLine(encodedQuantize[0], encodedQuantize[1], encodedQuantize[2], encodedQuantize[3]);
originalContains = polygon2D.containsLine(originalQuantize[0], originalQuantize[1], originalQuantize[2], originalQuantize[3]);
encodedContains = polygon2D.containsLine(encodedQuantize[0], encodedQuantize[1], encodedQuantize[2], encodedQuantize[3]);
break;
case TRIANGLE:
originalIntersects = polygon2D.intersectsTriangle(originalQuantize[0], originalQuantize[1], originalQuantize[2], originalQuantize[3], originalQuantize[4], originalQuantize[5]);
encodedIntersects = polygon2D.intersectsTriangle(originalQuantize[0], originalQuantize[1], originalQuantize[2], originalQuantize[3], originalQuantize[4], originalQuantize[5]);
originalContains = polygon2D.containsTriangle(originalQuantize[0], originalQuantize[1], originalQuantize[2], originalQuantize[3], originalQuantize[4], originalQuantize[5]);
encodedContains = polygon2D.containsTriangle(originalQuantize[0], originalQuantize[1], originalQuantize[2], originalQuantize[3], originalQuantize[4], originalQuantize[5]);
break;
}
assertTrue(originalIntersects == encodedIntersects);
assertTrue(originalContains == encodedContains);
}
}
private double[] orderTriangle(int aX, int aY, int bX, int bY, int cX, int cY) {
// quantize original and order it to reflect encoding
int orientation = GeoUtils.orient(aX, aY, bX, bY, cX, cY);
if (orientation == -1) {
// we need to change the orientation if CW for triangles
return new double[]{decodeX(cX), decodeY(cY), decodeX(bX), decodeY(bY), decodeX(aX), decodeY(aY)};
} else if (aX == bX && aY == bY) {
if (aX != cX || aY != cY) { // not a point
if (aX < cX) {
return new double[]{decodeX(aX), decodeY(aY), decodeX(cX), decodeY(cY), decodeX(aX), decodeY(aY)};
} else {
return new double[]{decodeX(cX), decodeY(cY), decodeX(aX), decodeY(aY), decodeX(cX), decodeY(cY)};
}
}
} else if ((aX == cX && aY == cY) || (bX == cX && bY == cY)) {
if (aX < bX) {
return new double[]{decodeX(aX), decodeY(aY), decodeX(bX), decodeY(bY), decodeX(aX), decodeY(aY)};
} else {
return new double[]{decodeX(bX), decodeY(bY), decodeX(aX), decodeY(aY), decodeX(bX), decodeY(bY)};
}
}
return new double[]{decodeX(aX), decodeY(aY), decodeX(bX), decodeY(bY), decodeX(cX), decodeY(cY)};
}
public void testDegeneratedTriangle() {
double ay = 1e-26d;
double ax = 0.0d;

View File

@ -788,17 +788,112 @@ public abstract class BaseShapeTestCase extends LuceneTestCase {
/** validator class used to test query results against "ground truth" */
protected static abstract class Validator {
Encoder encoder;
Validator(Encoder encoder) {
this.encoder = encoder;
}
protected QueryRelation queryRelation = QueryRelation.INTERSECTS;
public abstract boolean testBBoxQuery(double minLat, double maxLat, double minLon, double maxLon, Object shape);
public abstract boolean testComponentQuery(Component2D line2d, Object shape);
public Validator setRelation(QueryRelation relation) {
this.queryRelation = relation;
return this;
}
public boolean testComponentQuery(Component2D query, Field[] fields) {
ShapeField.DecodedTriangle decodedTriangle = new ShapeField.DecodedTriangle();
for (Field field : fields) {
boolean intersects;
boolean contains;
ShapeField.decodeTriangle(field.binaryValue().bytes, decodedTriangle);
switch (decodedTriangle.type) {
case POINT: {
double y = encoder.decodeY(decodedTriangle.aY);
double x = encoder.decodeX(decodedTriangle.aX);
intersects = query.contains(x, y);
contains = intersects;
break;
}
case LINE: {
double aY = encoder.decodeY(decodedTriangle.aY);
double aX = encoder.decodeX(decodedTriangle.aX);
double bY = encoder.decodeY(decodedTriangle.bY);
double bX = encoder.decodeX(decodedTriangle.bX);
intersects = query.intersectsLine(aX, aY, bX, bY);
contains = query.containsLine(aX, aY, bX, bY);
break;
}
case TRIANGLE: {
double aY = encoder.decodeY(decodedTriangle.aY);
double aX = encoder.decodeX(decodedTriangle.aX);
double bY = encoder.decodeY(decodedTriangle.bY);
double bX = encoder.decodeX(decodedTriangle.bX);
double cY = encoder.decodeY(decodedTriangle.cY);
double cX = encoder.decodeX(decodedTriangle.cX);
intersects = query.intersectsTriangle(aX, aY, bX, bY, cX, cY);
contains = query.containsTriangle(aX, aY, bX, bY, cX, cY);
break;
}
default:
throw new IllegalArgumentException("Unsupported triangle type :[" + decodedTriangle.type + "]");
}
assertTrue((contains == intersects) || (contains == false && intersects == true));
if (queryRelation == QueryRelation.DISJOINT && intersects) {
return false;
} else if (queryRelation == QueryRelation.WITHIN && contains == false) {
return false;
} else if (queryRelation == QueryRelation.INTERSECTS && intersects) {
return true;
}
}
return queryRelation == QueryRelation.INTERSECTS ? false : true;
}
protected Component2D.WithinRelation testWithinQuery(Component2D query, Field[] fields) {
Component2D.WithinRelation answer = Component2D.WithinRelation.DISJOINT;
ShapeField.DecodedTriangle decodedTriangle = new ShapeField.DecodedTriangle();
for (Field field : fields) {
ShapeField.decodeTriangle(field.binaryValue().bytes, decodedTriangle);
Component2D.WithinRelation relation;
switch (decodedTriangle.type) {
case POINT: {
double y = encoder.decodeY(decodedTriangle.aY);
double x = encoder.decodeX(decodedTriangle.aX);
relation = query.withinPoint(x, y);
break;
}
case LINE: {
double aY = encoder.decodeY(decodedTriangle.aY);
double aX = encoder.decodeX(decodedTriangle.aX);
double bY = encoder.decodeY(decodedTriangle.bY);
double bX = encoder.decodeX(decodedTriangle.bX);
relation = query.withinLine(aX, aY, decodedTriangle.ab, bX, bY);
break;
}
case TRIANGLE: {
double aY = encoder.decodeY(decodedTriangle.aY);
double aX = encoder.decodeX(decodedTriangle.aX);
double bY = encoder.decodeY(decodedTriangle.bY);
double bX = encoder.decodeX(decodedTriangle.bX);
double cY = encoder.decodeY(decodedTriangle.cY);
double cX = encoder.decodeX(decodedTriangle.cX);
relation = query.withinTriangle(aX, aY, decodedTriangle.ab, bX, bY, decodedTriangle.bc, cX, cY, decodedTriangle.ca);
break;
}
default:
throw new IllegalArgumentException("Unsupported triangle type :[" + decodedTriangle.type + "]");
}
if (relation == Component2D.WithinRelation.NOTWITHIN) {
return relation;
} else if (relation == Component2D.WithinRelation.CANDIDATE) {
answer = Component2D.WithinRelation.CANDIDATE;
}
}
return answer;
}
}
}

View File

@ -24,7 +24,6 @@ import org.apache.lucene.geo.GeoTestUtil;
import org.apache.lucene.geo.LatLonGeometry;
import org.apache.lucene.geo.Line;
import org.apache.lucene.geo.Rectangle;
import org.apache.lucene.index.PointValues.Relation;
/** random bounding box, line, and polygon query tests for random generated {@link Line} types */
@SuppressWarnings("SimpleText")
@ -83,37 +82,12 @@ public class TestLatLonLineShapeQueries extends BaseLatLonShapeTestCase {
}
@Override
public boolean testComponentQuery(Component2D component2D, Object shape) {
public boolean testComponentQuery(Component2D query, Object shape) {
Line line = (Line) shape;
if (queryRelation == QueryRelation.CONTAINS) {
return testWithinLine(component2D, (Line) shape);
return testWithinQuery(query, LatLonShape.createIndexableFields("dummy", line)) == Component2D.WithinRelation.CANDIDATE;
}
for (int i = 0, j = 1; j < line.numPoints(); ++i, ++j) {
double[] qTriangle = encoder.quantizeTriangle(line.getLon(i), line.getLat(i), true, line.getLon(j), line.getLat(j), true, line.getLon(i), line.getLat(i), true);
Relation r = component2D.relateTriangle(qTriangle[1], qTriangle[0], qTriangle[3], qTriangle[2], qTriangle[5], qTriangle[4]);
if (queryRelation == QueryRelation.DISJOINT) {
if (r != Relation.CELL_OUTSIDE_QUERY) return false;
} else if (queryRelation == QueryRelation.WITHIN) {
if (r != Relation.CELL_INSIDE_QUERY) return false;
} else {
if (r != Relation.CELL_OUTSIDE_QUERY) return true;
}
}
return queryRelation == QueryRelation.INTERSECTS ? false : true;
}
private boolean testWithinLine(Component2D component2D, Line line) {
Component2D.WithinRelation answer = Component2D.WithinRelation.DISJOINT;
for (int i = 0, j = 1; j < line.numPoints(); ++i, ++j) {
double[] qTriangle = encoder.quantizeTriangle(line.getLon(i), line.getLat(i), true, line.getLon(j), line.getLat(j), true, line.getLon(i), line.getLat(i), true);
Component2D.WithinRelation relation = component2D.withinTriangle(qTriangle[1], qTriangle[0], true, qTriangle[3], qTriangle[2], true, qTriangle[5], qTriangle[4], true);
if (relation == Component2D.WithinRelation.NOTWITHIN) {
return false;
} else if (relation == Component2D.WithinRelation.CANDIDATE) {
answer = Component2D.WithinRelation.CANDIDATE;
}
}
return answer == Component2D.WithinRelation.CANDIDATE;
return testComponentQuery(query, LatLonShape.createIndexableFields("dummy", line));
}
}
}

View File

@ -21,7 +21,6 @@ import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.Component2D;
import org.apache.lucene.geo.GeoTestUtil;
import org.apache.lucene.geo.Line;
import org.apache.lucene.index.PointValues.Relation;
/** random bounding box, line, and polygon query tests for random generated {@code latitude, longitude} points */
public class TestLatLonPointShapeQueries extends BaseLatLonShapeTestCase {
@ -94,19 +93,10 @@ public class TestLatLonPointShapeQueries extends BaseLatLonShapeTestCase {
@Override
public boolean testComponentQuery(Component2D query, Object shape) {
Point p = (Point) shape;
double lat = encoder.quantizeY(p.lat);
double lon = encoder.quantizeX(p.lon);
if (queryRelation == QueryRelation.CONTAINS) {
return query.withinTriangle(lon, lat, true, lon, lat, true, lon, lat, true) == Component2D.WithinRelation.CANDIDATE;
return testWithinQuery(query, LatLonShape.createIndexableFields("dummy", p.lat, p.lon)) == Component2D.WithinRelation.CANDIDATE;
}
// for consistency w/ the query we test the point as a triangle
Relation r = query.relateTriangle(lon, lat, lon, lat, lon, lat);
if (queryRelation == QueryRelation.WITHIN) {
return r == Relation.CELL_INSIDE_QUERY;
} else if (queryRelation == QueryRelation.DISJOINT) {
return r == Relation.CELL_OUTSIDE_QUERY;
}
return r != Relation.CELL_OUTSIDE_QUERY;
return testComponentQuery(query, LatLonShape.createIndexableFields("dummy", p.lat, p.lon));
}
}
}

View File

@ -16,15 +16,12 @@
*/
package org.apache.lucene.document;
import java.util.List;
import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.Component2D;
import org.apache.lucene.geo.LatLonGeometry;
import org.apache.lucene.geo.Polygon;
import org.apache.lucene.geo.Rectangle;
import org.apache.lucene.geo.Tessellator;
import org.apache.lucene.index.PointValues.Relation;
/** random bounding box, line, and polygon query tests for random indexed {@link Polygon} types */
public class TestLatLonPolygonShapeQueries extends BaseLatLonShapeTestCase {
@ -72,45 +69,15 @@ public class TestLatLonPolygonShapeQueries extends BaseLatLonShapeTestCase {
@Override
public boolean testComponentQuery(Component2D query, Object o) {
Polygon shape = (Polygon) o;
Polygon polygon = (Polygon) o;
if (queryRelation == QueryRelation.CONTAINS) {
return testWithinPolygon(query, shape) == Component2D.WithinRelation.CANDIDATE;
return testWithinQuery(query, LatLonShape.createIndexableFields("dummy", polygon)) == Component2D.WithinRelation.CANDIDATE;
}
List<Tessellator.Triangle> tessellation = Tessellator.tessellate(shape);
for (Tessellator.Triangle t : tessellation) {
double[] qTriangle = encoder.quantizeTriangle(t.getX(0), t.getY(0), t.isEdgefromPolygon(0),
t.getX(1), t.getY(1), t.isEdgefromPolygon(1),
t.getX(2), t.getY(2), t.isEdgefromPolygon(2));
Relation r = query.relateTriangle(qTriangle[1], qTriangle[0], qTriangle[3], qTriangle[2], qTriangle[5], qTriangle[4]);
if (queryRelation == QueryRelation.DISJOINT) {
if (r != Relation.CELL_OUTSIDE_QUERY) return false;
} else if (queryRelation == QueryRelation.WITHIN) {
if (r != Relation.CELL_INSIDE_QUERY) return false;
} else {
if (r != Relation.CELL_OUTSIDE_QUERY) return true;
}
}
return queryRelation == QueryRelation.INTERSECTS ? false : true;
return testComponentQuery(query, LatLonShape.createIndexableFields("dummy", polygon));
}
protected Component2D.WithinRelation testWithinPolygon(Component2D component2D, Polygon shape) {
List<Tessellator.Triangle> tessellation = Tessellator.tessellate(shape);
Component2D.WithinRelation answer = Component2D.WithinRelation.DISJOINT;
for (Tessellator.Triangle t : tessellation) {
ShapeField.DecodedTriangle qTriangle = encoder.encodeDecodeTriangle(t.getX(0), t.getY(0), t.isEdgefromPolygon(0),
t.getX(1), t.getY(1), t.isEdgefromPolygon(1),
t.getX(2), t.getY(2), t.isEdgefromPolygon(2));
Component2D.WithinRelation relation = component2D.withinTriangle(encoder.decodeX(qTriangle.aX), encoder.decodeY(qTriangle.aY), qTriangle.ab,
encoder.decodeX(qTriangle.bX), encoder.decodeY(qTriangle.bY), qTriangle.bc,
encoder.decodeX(qTriangle.cX), encoder.decodeY(qTriangle.cY), qTriangle.ca);
if (relation == Component2D.WithinRelation.NOTWITHIN) {
return relation;
} else if (relation == Component2D.WithinRelation.CANDIDATE) {
answer = Component2D.WithinRelation.CANDIDATE;
}
}
return answer;
protected Component2D.WithinRelation testWithinPolygon(Component2D query, Polygon polygon) {
return testWithinQuery(query, LatLonShape.createIndexableFields("dummy", polygon));
}
}

View File

@ -30,7 +30,6 @@ import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.PointValues;
import org.apache.lucene.index.RandomIndexWriter;
import org.apache.lucene.index.SerialMergeScheduler;
import org.apache.lucene.search.IndexSearcher;
@ -500,66 +499,101 @@ 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});
Component2D polygon2D = LatLonGeometry.create(polygon);
PointValues.Relation rel = polygon2D.relateTriangle(
boolean intersects = polygon2D.intersectsTriangle(
quantizeLon(alon), quantizeLat(blat),
quantizeLon(blon), quantizeLat(blat),
quantizeLon(alon), quantizeLat(alat));
assertEquals(PointValues.Relation.CELL_CROSSES_QUERY, rel);
assertTrue(intersects);
rel = polygon2D.relateTriangle(
intersects = polygon2D.intersectsTriangle(
quantizeLon(alon), quantizeLat(blat),
quantizeLon(alon), quantizeLat(alat),
quantizeLon(blon), quantizeLat(blat));
assertEquals(PointValues.Relation.CELL_CROSSES_QUERY, rel);
assertTrue(intersects);
}
public void testTriangleTouchingEdges() {
Polygon p = new Polygon(new double[] {0, 0, 1, 1, 0}, new double[] {0, 1, 1, 0, 0});
Component2D polygon2D = LatLonGeometry.create(p);
//3 shared points
PointValues.Relation rel = polygon2D.relateTriangle(
boolean containsTriangle = polygon2D.containsTriangle(
quantizeLon(0.5), quantizeLat(0),
quantizeLon(1), quantizeLat(0.5),
quantizeLon(0.5), quantizeLat(1));
assertEquals(PointValues.Relation.CELL_INSIDE_QUERY, rel);
boolean intersectsTriangle = polygon2D.intersectsTriangle(
quantizeLon(0.5), quantizeLat(0),
quantizeLon(1), quantizeLat(0.5),
quantizeLon(0.5), quantizeLat(1));
assertTrue(intersectsTriangle);
assertTrue(containsTriangle);
//2 shared points
rel = polygon2D.relateTriangle(
containsTriangle = polygon2D.containsTriangle(
quantizeLon(0.5), quantizeLat(0),
quantizeLon(1), quantizeLat(0.5),
quantizeLon(0.5), quantizeLat(0.75));
assertEquals(PointValues.Relation.CELL_INSIDE_QUERY, rel);
intersectsTriangle = polygon2D.intersectsTriangle(
quantizeLon(0.5), quantizeLat(0),
quantizeLon(1), quantizeLat(0.5),
quantizeLon(0.5), quantizeLat(0.75));
assertTrue(intersectsTriangle);
assertTrue(containsTriangle);
//1 shared point
rel = polygon2D.relateTriangle(
containsTriangle = polygon2D.containsTriangle(
quantizeLon(0.5), quantizeLat(0.5),
quantizeLon(0.5), quantizeLat(0),
quantizeLon(0.75), quantizeLat(0.75));
assertEquals(PointValues.Relation.CELL_INSIDE_QUERY, rel);
intersectsTriangle = polygon2D.intersectsTriangle(
quantizeLon(0.5), quantizeLat(0),
quantizeLon(1), quantizeLat(0.5),
quantizeLon(0.5), quantizeLat(0.75));
assertTrue(intersectsTriangle);
assertTrue(containsTriangle);
// 1 shared point but out
rel = polygon2D.relateTriangle(
containsTriangle = polygon2D.containsTriangle(
quantizeLon(1), quantizeLat(0.5),
quantizeLon(2), quantizeLat(0),
quantizeLon(2), quantizeLat(2));
assertEquals(PointValues.Relation.CELL_CROSSES_QUERY, rel);
intersectsTriangle = polygon2D.intersectsTriangle(
quantizeLon(1), quantizeLat(0.5),
quantizeLon(2), quantizeLat(0),
quantizeLon(2), quantizeLat(2));
assertTrue(intersectsTriangle);
assertFalse(containsTriangle);
// 1 shared point but crossing
rel = polygon2D.relateTriangle(
containsTriangle = polygon2D.containsTriangle(
quantizeLon(0.5), quantizeLat(0),
quantizeLon(2), quantizeLat(0.5),
quantizeLon(0.5), quantizeLat(1));
assertEquals(PointValues.Relation.CELL_CROSSES_QUERY, rel);
intersectsTriangle = polygon2D.intersectsTriangle(
quantizeLon(0.5), quantizeLat(0),
quantizeLon(2), quantizeLat(0.5),
quantizeLon(0.5), quantizeLat(1));
assertTrue(intersectsTriangle);
assertFalse(containsTriangle);
//share one edge
rel = polygon2D.relateTriangle(
containsTriangle = polygon2D.containsTriangle(
quantizeLon(0), quantizeLat(0),
quantizeLon(0), quantizeLat(1),
quantizeLon(0.5), quantizeLat(0.5));
assertEquals(PointValues.Relation.CELL_INSIDE_QUERY, rel);
intersectsTriangle = polygon2D.intersectsTriangle(
quantizeLon(0), quantizeLat(0),
quantizeLon(0), quantizeLat(1),
quantizeLon(0.5), quantizeLat(0.5));
assertTrue(intersectsTriangle);
assertTrue(containsTriangle);
//share one edge outside
rel = polygon2D.relateTriangle(
containsTriangle = polygon2D.containsTriangle(
quantizeLon(0), quantizeLat(1),
quantizeLon(1.5), quantizeLat(1.5),
quantizeLon(1), quantizeLat(1));
assertEquals(PointValues.Relation.CELL_CROSSES_QUERY, rel);
intersectsTriangle = polygon2D.intersectsTriangle(
quantizeLon(0), quantizeLat(1),
quantizeLon(1.5), quantizeLat(1.5),
quantizeLon(1), quantizeLat(1));
assertTrue(intersectsTriangle);
assertFalse(containsTriangle);
}
public void testLUCENE8736() throws Exception {
@ -618,31 +652,30 @@ public class TestLatLonShape extends LuceneTestCase {
public void testTriangleCrossingPolygonVertices() {
Polygon p = new Polygon(new double[] {0, 0, -5, -10, -5, 0}, new double[] {-1, 1, 5, 0, -5, -1});
Component2D polygon2D = LatLonGeometry.create(p);
PointValues.Relation rel = polygon2D.relateTriangle(
boolean intersectsTriangle = polygon2D.intersectsTriangle(
quantizeLon(-5), quantizeLat(0),
quantizeLon(10), quantizeLat(0),
quantizeLon(-5), quantizeLat(-15));
assertEquals(PointValues.Relation.CELL_CROSSES_QUERY, rel);
assertTrue(intersectsTriangle);
}
public void testLineCrossingPolygonVertices() {
Polygon p = new Polygon(new double[] {0, -1, 0, 1, 0}, new double[] {-1, 0, 1, 0, -1});
Component2D polygon2D = LatLonGeometry.create(p);
PointValues.Relation rel = polygon2D.relateTriangle(
boolean intersectsTriangle = polygon2D.intersectsTriangle(
quantizeLon(-1.5), quantizeLat(0),
quantizeLon(1.5), quantizeLat(0),
quantizeLon(-1.5), quantizeLat(0));
assertEquals(PointValues.Relation.CELL_CROSSES_QUERY, rel);
assertTrue(intersectsTriangle);
}
public void testLineSharedLine() {
Line l = new Line(new double[] {0, 0, 0, 0}, new double[] {-2, -1, 0, 1});
Component2D l2d = LatLonGeometry.create(l);
PointValues.Relation r = l2d.relateTriangle(
boolean intersectsLine = l2d.intersectsLine(
quantizeLon(-5), quantizeLat(0),
quantizeLon(5), quantizeLat(0),
quantizeLon(-5), quantizeLat(0));
assertEquals(PointValues.Relation.CELL_CROSSES_QUERY, r);
quantizeLon(5), quantizeLat(0));
assertTrue(intersectsLine);
}
public void testLUCENE9055() throws Exception {

View File

@ -26,7 +26,6 @@ import org.apache.lucene.geo.ShapeTestUtil;
import org.apache.lucene.geo.XYGeometry;
import org.apache.lucene.geo.XYLine;
import org.apache.lucene.geo.XYRectangle;
import org.apache.lucene.index.PointValues.Relation;
/** random cartesian bounding box, line, and polygon query tests for random generated cartesian {@link XYLine} types */
public class TestXYLineShapeQueries extends BaseXYShapeTestCase {
@ -88,34 +87,9 @@ public class TestXYLineShapeQueries extends BaseXYShapeTestCase {
public boolean testComponentQuery(Component2D query, Object shape) {
XYLine line = (XYLine) shape;
if (queryRelation == QueryRelation.CONTAINS) {
return testWithinLine(query, (XYLine) shape);
return testWithinQuery(query, XYShape.createIndexableFields("dummy", line)) == Component2D.WithinRelation.CANDIDATE;
}
for (int i = 0, j = 1; j < line.numPoints(); ++i, ++j) {
double[] qTriangle = encoder.quantizeTriangle(line.getX(i), line.getY(i), true, line.getX(j), line.getY(j), true, line.getX(i), line.getY(i), true);
Relation r = query.relateTriangle(qTriangle[1], qTriangle[0], qTriangle[3], qTriangle[2], qTriangle[5], qTriangle[4]);
if (queryRelation == QueryRelation.DISJOINT) {
if (r != Relation.CELL_OUTSIDE_QUERY) return false;
} else if (queryRelation == QueryRelation.WITHIN) {
if (r != Relation.CELL_INSIDE_QUERY) return false;
} else {
if (r != Relation.CELL_OUTSIDE_QUERY) return true;
}
}
return queryRelation == QueryRelation.INTERSECTS ? false : true;
}
private boolean testWithinLine(Component2D tree, XYLine line) {
Component2D.WithinRelation answer = Component2D.WithinRelation.DISJOINT;
for (int i = 0, j = 1; j < line.numPoints(); ++i, ++j) {
double[] qTriangle = encoder.quantizeTriangle(line.getX(i), line.getY(i), true, line.getX(j), line.getY(j), true, line.getX(i), line.getY(i), true);
Component2D.WithinRelation relation = tree.withinTriangle(qTriangle[1], qTriangle[0], true, qTriangle[3], qTriangle[2], true, qTriangle[5], qTriangle[4], true);
if (relation == Component2D.WithinRelation.NOTWITHIN) {
return false;
} else if (relation == Component2D.WithinRelation.CANDIDATE) {
answer = Component2D.WithinRelation.CANDIDATE;
}
}
return answer == Component2D.WithinRelation.CANDIDATE;
return testComponentQuery(query, XYShape.createIndexableFields("dummy", line));
}
}
}

View File

@ -26,7 +26,6 @@ import org.apache.lucene.geo.ShapeTestUtil;
import org.apache.lucene.geo.XYGeometry;
import org.apache.lucene.geo.XYLine;
import org.apache.lucene.geo.XYRectangle;
import org.apache.lucene.index.PointValues.Relation;
/** random cartesian bounding box, line, and polygon query tests for random generated {@code x, y} points */
public class TestXYPointShapeQueries extends BaseXYShapeTestCase {
@ -86,20 +85,11 @@ public class TestXYPointShapeQueries extends BaseXYShapeTestCase {
@Override
public boolean testComponentQuery(Component2D query, Object shape) {
Point p = (Point) shape;
double lat = encoder.quantizeY(p.y);
double lon = encoder.quantizeX(p.x);
Point point = (Point) shape;
if (queryRelation == QueryRelation.CONTAINS) {
return query.withinTriangle(lon, lat, true, lon, lat, true, lon, lat, true) == Component2D.WithinRelation.CANDIDATE;
return testWithinQuery(query, XYShape.createIndexableFields("dummy", point.x, point.y)) == Component2D.WithinRelation.CANDIDATE;
}
// for consistency w/ the query we test the point as a triangle
Relation r = query.relateTriangle(lon, lat, lon, lat, lon, lat);
if (queryRelation == QueryRelation.WITHIN) {
return r == Relation.CELL_INSIDE_QUERY;
} else if (queryRelation == QueryRelation.DISJOINT) {
return r == Relation.CELL_OUTSIDE_QUERY;
}
return r != Relation.CELL_OUTSIDE_QUERY;
return testComponentQuery(query, XYShape.createIndexableFields("dummy", point.x, point.y));
}
}
}

View File

@ -16,15 +16,12 @@
*/
package org.apache.lucene.document;
import java.util.List;
import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.Component2D;
import org.apache.lucene.geo.Tessellator;
import org.apache.lucene.geo.XYGeometry;
import org.apache.lucene.geo.XYPolygon;
import org.apache.lucene.geo.XYRectangle;
import org.apache.lucene.index.PointValues.Relation;
/** random cartesian bounding box, line, and polygon query tests for random indexed {@link XYPolygon} types */
public class TestXYPolygonShapeQueries extends BaseXYShapeTestCase {
@ -72,44 +69,11 @@ public class TestXYPolygonShapeQueries extends BaseXYShapeTestCase {
@Override
public boolean testComponentQuery(Component2D query, Object o) {
XYPolygon shape = (XYPolygon) o;
XYPolygon polygon = (XYPolygon) o;
if (queryRelation == QueryRelation.CONTAINS) {
return testWithinPolygon(query, shape);
return testWithinQuery(query, XYShape.createIndexableFields("dummy", polygon)) == Component2D.WithinRelation.CANDIDATE;
}
List<Tessellator.Triangle> tessellation = Tessellator.tessellate(shape);
for (Tessellator.Triangle t : tessellation) {
double[] qTriangle = encoder.quantizeTriangle(t.getX(0), t.getY(0), t.isEdgefromPolygon(0),
t.getX(1), t.getY(1), t.isEdgefromPolygon(1),
t.getX(2), t.getY(2), t.isEdgefromPolygon(2));
Relation r = query.relateTriangle(qTriangle[1], qTriangle[0], qTriangle[3], qTriangle[2], qTriangle[5], qTriangle[4]);
if (queryRelation == QueryRelation.DISJOINT) {
if (r != Relation.CELL_OUTSIDE_QUERY) return false;
} else if (queryRelation == QueryRelation.WITHIN) {
if (r != Relation.CELL_INSIDE_QUERY) return false;
} else {
if (r != Relation.CELL_OUTSIDE_QUERY) return true;
}
}
return queryRelation == QueryRelation.INTERSECTS ? false : true;
}
private boolean testWithinPolygon(Component2D tree, XYPolygon shape) {
List<Tessellator.Triangle> tessellation = Tessellator.tessellate(shape);
Component2D.WithinRelation answer = Component2D.WithinRelation.DISJOINT;
for (Tessellator.Triangle t : tessellation) {
ShapeField.DecodedTriangle qTriangle = encoder.encodeDecodeTriangle(t.getX(0), t.getY(0), t.isEdgefromPolygon(0),
t.getX(1), t.getY(1), t.isEdgefromPolygon(1),
t.getX(2), t.getY(2), t.isEdgefromPolygon(2));
Component2D.WithinRelation relation = tree.withinTriangle(encoder.decodeX(qTriangle.aX), encoder.decodeY(qTriangle.aY), qTriangle.ab,
encoder.decodeX(qTriangle.bX), encoder.decodeY(qTriangle.bY), qTriangle.bc,
encoder.decodeX(qTriangle.cX), encoder.decodeY(qTriangle.cY), qTriangle.ca);
if (relation == Component2D.WithinRelation.NOTWITHIN) {
return false;
} else if (relation == Component2D.WithinRelation.CANDIDATE) {
answer = Component2D.WithinRelation.CANDIDATE;
}
}
return answer == Component2D.WithinRelation.CANDIDATE;
return testComponentQuery(query, XYShape.createIndexableFields("dummy", polygon));
}
}

View File

@ -37,7 +37,10 @@ public class TestCircle2D extends LuceneTestCase {
double by = 5;
double cx = 5;
double cy = 4;
assertEquals(PointValues.Relation.CELL_OUTSIDE_QUERY, circle2D.relateTriangle(ax, ay, bx, by , cx, cy));
assertFalse(circle2D.intersectsTriangle(ax, ay, bx, by , cx, cy));
assertFalse(circle2D.intersectsLine(ax, ay, bx, by));
assertFalse(circle2D.containsTriangle(ax, ay, bx, by , cx, cy));
assertFalse(circle2D.containsLine(ax, ay, bx, by));
assertEquals(Component2D.WithinRelation.DISJOINT, circle2D.withinTriangle(ax, ay, true, bx, by, true, cx, cy, true));
}
@ -56,7 +59,10 @@ public class TestCircle2D extends LuceneTestCase {
double by = 1;
double cx = 0;
double cy = 90;
assertEquals(PointValues.Relation.CELL_CROSSES_QUERY, circle2D.relateTriangle(ax, ay, bx, by , cx, cy));
assertTrue(circle2D.intersectsTriangle(ax, ay, bx, by, cx, cy));
assertTrue(circle2D.intersectsLine(ax, ay, bx, by));
assertFalse(circle2D.containsTriangle(ax, ay, bx, by , cx, cy));
assertFalse(circle2D.containsLine(ax, ay, bx, by));
assertEquals(Component2D.WithinRelation.NOTWITHIN, circle2D.withinTriangle(ax, ay, true, bx, by, true, cx, cy, true));
}
@ -69,7 +75,10 @@ public class TestCircle2D extends LuceneTestCase {
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));
assertTrue(circle2D.intersectsTriangle(ax, ay, bx, by, cx, cy));
assertTrue(circle2D.intersectsLine(ax, ay, bx, by));
assertFalse(circle2D.containsTriangle(ax, ay, bx, by , cx, cy));
assertFalse(circle2D.containsLine(ax, ay, bx, by));
assertEquals(Component2D.WithinRelation.NOTWITHIN, circle2D.withinTriangle(ax, ay, true, bx, by, true, cx, cy, true));
}
@ -88,7 +97,10 @@ public class TestCircle2D extends LuceneTestCase {
double by = 0.5;
double cx = 0.5;
double cy = 0.25;
assertEquals(PointValues.Relation.CELL_INSIDE_QUERY, circle2D.relateTriangle(ax, ay, bx, by , cx, cy));
assertTrue(circle2D.intersectsTriangle(ax, ay, bx, by, cx, cy));
assertTrue(circle2D.intersectsLine(ax, ay, bx, by));
assertTrue(circle2D.containsTriangle(ax, ay, bx, by , cx, cy));
assertTrue(circle2D.containsLine(ax, ay, bx, by));
assertEquals(Component2D.WithinRelation.NOTWITHIN, circle2D.withinTriangle(ax, ay, true, bx, by, true, cx, cy, true));
}
@ -108,7 +120,10 @@ public class TestCircle2D extends LuceneTestCase {
double by = -20;
double cx = 0;
double cy = 20;
assertEquals(PointValues.Relation.CELL_CROSSES_QUERY, circle2D.relateTriangle(ax, ay, bx, by , cx, cy));
assertTrue(circle2D.intersectsTriangle(ax, ay, bx, by, cx, cy));
assertFalse(circle2D.intersectsLine(bx, by, cx, cy));
assertFalse(circle2D.containsTriangle(ax, ay, bx, by , cx, cy));
assertFalse(circle2D.containsLine(bx, by, cx, cy));
assertEquals(Component2D.WithinRelation.CANDIDATE, circle2D.withinTriangle(ax, ay, true, bx, by, true, cx, cy, true));
}
@ -136,10 +151,16 @@ public class TestCircle2D extends LuceneTestCase {
PointValues.Relation r = circle2D.relate(tMinX, tMaxX, tMinY, tMaxY);
if (r == PointValues.Relation.CELL_OUTSIDE_QUERY) {
assertEquals(PointValues.Relation.CELL_OUTSIDE_QUERY, circle2D.relateTriangle(ax, ay, bx, by , cx, cy));
assertFalse(circle2D.intersectsTriangle(ax, ay, bx, by, cx, cy));
assertFalse(circle2D.intersectsLine(ax, ay, bx, by));
assertFalse(circle2D.containsTriangle(ax, ay, bx, by , cx, cy));
assertFalse(circle2D.containsLine(ax, ay, bx, by));
assertEquals(Component2D.WithinRelation.DISJOINT, circle2D.withinTriangle(ax, ay, true, bx, by, true, cx, cy, true));
} else if (r == PointValues.Relation.CELL_INSIDE_QUERY) {
assertEquals(PointValues.Relation.CELL_INSIDE_QUERY, circle2D.relateTriangle(ax, ay, bx, by , cx, cy));
assertTrue(circle2D.intersectsTriangle(ax, ay, bx, by, cx, cy));
assertTrue(circle2D.intersectsLine(ax, ay, bx, by));
assertTrue(circle2D.containsTriangle(ax, ay, bx, by , cx, cy));
assertTrue(circle2D.containsLine(ax, ay, bx, by));
assertNotEquals(Component2D.WithinRelation.CANDIDATE, circle2D.withinTriangle(ax, ay, true, bx, by, true, cx, cy, true));
}
}

View File

@ -32,7 +32,10 @@ public class TestLine2D extends LuceneTestCase {
int by = GeoEncodingUtils.encodeLatitude(5);
int cx = GeoEncodingUtils.encodeLongitude(5);
int cy = GeoEncodingUtils.encodeLatitude(4);
assertEquals(Relation.CELL_OUTSIDE_QUERY, line2D.relateTriangle(ax, ay, bx, by , cx, cy));
assertFalse(line2D.intersectsTriangle(ax, ay, bx, by , cx, cy));
assertFalse(line2D.intersectsLine(ax, ay, bx, by));
assertFalse(line2D.containsTriangle(ax, ay, bx, by , cx, cy));
assertFalse(line2D.containsLine(ax, ay, bx, by));
assertEquals(Component2D.WithinRelation.DISJOINT, line2D.withinTriangle(ax, ay, true, bx, by , true, cx, cy, true));
}
@ -45,7 +48,10 @@ public class TestLine2D extends LuceneTestCase {
int by = GeoEncodingUtils.encodeLatitude(0);
int cx = GeoEncodingUtils.encodeLongitude(0);
int cy = GeoEncodingUtils.encodeLatitude(1);
assertEquals(Relation.CELL_CROSSES_QUERY, line2D.relateTriangle(ax, ay, bx, by , cx, cy));
assertTrue(line2D.intersectsTriangle(ax, ay, bx, by , cx, cy));
assertTrue(line2D.intersectsLine(ax, ay, bx, by));
assertFalse(line2D.containsTriangle(ax, ay, bx, by , cx, cy));
assertFalse(line2D.containsLine(ax, ay, bx, by));
assertEquals(Component2D.WithinRelation.NOTWITHIN, line2D.withinTriangle(ax, ay, true, bx, by , true, cx, cy, true));
}
@ -58,7 +64,10 @@ public class TestLine2D extends LuceneTestCase {
int by = GeoEncodingUtils.encodeLatitude(-10);
int cx = GeoEncodingUtils.encodeLongitude(4);
int cy = GeoEncodingUtils.encodeLatitude(30);
assertEquals(Relation.CELL_CROSSES_QUERY, line2D.relateTriangle(ax, ay, bx, by , cx, cy));
assertTrue(line2D.intersectsTriangle(ax, ay, bx, by , cx, cy));
assertFalse(line2D.intersectsLine(bx, by, cx, cy));
assertFalse(line2D.containsTriangle(ax, ay, bx, by , cx, cy));
assertFalse(line2D.containsLine(bx, by, cx, cy));
assertEquals(Component2D.WithinRelation.CANDIDATE, line2D.withinTriangle(ax, ay, true, bx, by , true, cx, cy, true));
}
@ -81,9 +90,12 @@ public class TestLine2D extends LuceneTestCase {
Relation r = line2D.relate(tMinX, tMaxX, tMinY, tMaxY);
if (r == Relation.CELL_OUTSIDE_QUERY) {
assertEquals(Relation.CELL_OUTSIDE_QUERY, line2D.relateTriangle(ax, ay, bx, by, cx, cy));
assertFalse(line2D.intersectsTriangle(ax, ay, bx, by, cx, cy));
assertFalse(line2D.intersectsLine(ax, ay, bx, by));
assertFalse(line2D.containsTriangle(ax, ay, bx, by , cx, cy));
assertFalse(line2D.containsLine(ax, ay, bx, by));
assertEquals(Component2D.WithinRelation.DISJOINT, line2D.withinTriangle(ax, ay, true, bx, by, true, cx, cy, true));
} else if (line2D.relateTriangle(ax, ay, bx, by, cx, cy) == Relation.CELL_INSIDE_QUERY) {
} else if (line2D.containsTriangle(ax, ay, bx, by, cx, cy)) {
assertNotEquals(Component2D.WithinRelation.CANDIDATE, line2D.withinTriangle(ax, ay, true, bx, by, true, cx, cy, true));
}
}

View File

@ -30,7 +30,10 @@ public class TestPoint2D extends LuceneTestCase {
double by = 5;
double cx = 5;
double cy = 4;
assertEquals(Relation.CELL_OUTSIDE_QUERY, point2D.relateTriangle(ax, ay, bx, by , cx, cy));
assertFalse(point2D.intersectsTriangle(ax, ay, bx, by , cx, cy));
assertFalse(point2D.intersectsLine(ax, ay, bx, by));
assertFalse(point2D.containsTriangle(ax, ay, bx, by , cx, cy));
assertFalse(point2D.containsLine(ax, ay, bx, by));
assertEquals(Component2D.WithinRelation.DISJOINT,
point2D.withinTriangle(ax, ay, random().nextBoolean(), bx, by, random().nextBoolean(), cx, cy, random().nextBoolean()));
}
@ -43,7 +46,10 @@ public class TestPoint2D extends LuceneTestCase {
double by = 0;
double cx = 0;
double cy = 1;
assertEquals(Relation.CELL_CROSSES_QUERY, point2D.relateTriangle(ax, ay, bx, by , cx, cy));
assertTrue(point2D.intersectsTriangle(ax, ay, bx, by , cx, cy));
assertTrue(point2D.intersectsLine(ax, ay, bx, by));
assertFalse(point2D.containsTriangle(ax, ay, bx, by , cx, cy));
assertFalse(point2D.containsLine(ax, ay, bx, by));
assertEquals(Component2D.WithinRelation.CANDIDATE,
point2D.withinTriangle(ax, ay, random().nextBoolean(), bx, by, random().nextBoolean(), cx, cy, random().nextBoolean()));
}
@ -52,13 +58,9 @@ public class TestPoint2D extends LuceneTestCase {
Component2D point2D = Point2D.create(new Point(0, 0));
double ax = 0.0;
double ay = 0.0;
double bx = 0;
double by = 0;
double cx = 0;
double cy = 0;
assertEquals(Relation.CELL_INSIDE_QUERY, point2D.relateTriangle(ax, ay, bx, by , cx, cy));
assertTrue(point2D.contains(ax, ay));
assertEquals(Component2D.WithinRelation.CANDIDATE,
point2D.withinTriangle(ax, ay, random().nextBoolean(), bx, by, random().nextBoolean(), cx, cy, random().nextBoolean()));
point2D.withinTriangle(ax, ay, random().nextBoolean(), ax, ay, random().nextBoolean(), ax, ay, random().nextBoolean()));
}
@ -80,7 +82,10 @@ public class TestPoint2D extends LuceneTestCase {
Relation r = point2D.relate(tMinX, tMaxX, tMinY, tMaxY);
if (r == Relation.CELL_OUTSIDE_QUERY) {
assertEquals(Relation.CELL_OUTSIDE_QUERY, point2D.relateTriangle(ax, ay, bx, by, cx, cy));
assertFalse(point2D.intersectsTriangle(ax, ay, bx, by , cx, cy));
assertFalse(point2D.intersectsLine(ax, ay, bx, by));
assertFalse(point2D.containsTriangle(ax, ay, bx, by , cx, cy));
assertFalse(point2D.containsLine(ax, ay, bx, by));
assertEquals(Component2D.WithinRelation.DISJOINT,
point2D.withinTriangle(ax, ay, random().nextBoolean(), bx, by, random().nextBoolean(), cx, cy, random().nextBoolean()));
}

View File

@ -277,17 +277,25 @@ public class TestPolygon2D extends LuceneTestCase {
public void testIntersectsSameEdge() {
Component2D poly = Polygon2D.create(new Polygon(new double[] { -2, -2, 2, 2, -2 }, new double[] { -2, 2, 2, -2, -2 }));
// line inside edge
assertEquals(Relation.CELL_INSIDE_QUERY, poly.relateTriangle(-1, -1, 1, 1, -1, -1));
assertEquals(Relation.CELL_INSIDE_QUERY, poly.relateTriangle(-2, -2, 2, 2, -2, -2));
assertTrue(poly.containsTriangle(-1, -1, 1, 1, -1, -1));
assertTrue(poly.containsTriangle(-2, -2, 2, 2, -2, -2));
assertTrue(poly.intersectsTriangle(-1, -1, 1, 1, -1, -1));
assertTrue(poly.intersectsTriangle(-2, -2, 2, 2, -2, -2));
// line over edge
assertEquals(Relation.CELL_CROSSES_QUERY, poly.relateTriangle(-4, -4, 4, 4, -4, -4));
assertEquals(Relation.CELL_CROSSES_QUERY, poly.relateTriangle(-2, -2, 4, 4, 4, 4));
assertFalse(poly.containsTriangle(-4, -4, 4, 4, -4, -4));
assertFalse(poly.containsTriangle(-2, -2, 4, 4, 4, 4));
assertTrue(poly.intersectsTriangle(-4, -4, 4, 4, -4, -4));
assertTrue(poly.intersectsTriangle(-2, -2, 4, 4, 4, 4));
// line inside edge
assertEquals(Relation.CELL_CROSSES_QUERY, poly.relateTriangle(-1, -1, 3, 3, 1, 1));
assertEquals(Relation.CELL_CROSSES_QUERY, poly.relateTriangle(-2, -2, 3, 3, 2, 2));
assertFalse(poly.containsTriangle(-1, -1, 3, 3, 1, 1));
assertFalse(poly.containsTriangle(-2, -2, 3, 3, 2, 2));
assertTrue(poly.intersectsTriangle(-1, -1, 3, 3, 1, 1));
assertTrue(poly.intersectsTriangle(-2, -2, 3, 3, 2, 2));
// line over edge
assertEquals(Relation.CELL_CROSSES_QUERY, poly.relateTriangle(-4, -4, 7, 7, 4, 4));
assertEquals(Relation.CELL_CROSSES_QUERY, poly.relateTriangle(-2, -2, 7, 7, 4, 4));
assertFalse(poly.containsTriangle(-4, -4, 7, 7, 4, 4));
assertFalse(poly.containsTriangle(-2, -2, 7, 7, 4, 4));
assertTrue(poly.intersectsTriangle(-4, -4, 7, 7, 4, 4));
assertTrue(poly.intersectsTriangle(-2, -2, 7, 7, 4, 4));
}
/** Tests current impl against original algorithm */
@ -325,7 +333,7 @@ public class TestPolygon2D extends LuceneTestCase {
// if the point is within poly, then triangle should not intersect
if (impl.contains(a[1], a[0]) || impl.contains(b[1], b[0]) || impl.contains(c[1], c[0])) {
assertTrue(impl.relateTriangle(a[1], a[0], b[1], b[0], c[1], c[0]) != Relation.CELL_OUTSIDE_QUERY);
assertTrue(impl.intersectsTriangle(a[1], a[0], b[1], b[0], c[1], c[0]));
}
}
}
@ -334,7 +342,7 @@ public class TestPolygon2D extends LuceneTestCase {
public void testRelateTriangleContainsPolygon() {
Polygon polygon = new Polygon(new double[]{0, 0, 1, 1, 0}, new double[]{0, 1, 1, 0, 0});
Component2D impl = Polygon2D.create(polygon);
assertEquals(Relation.CELL_CROSSES_QUERY, impl.relateTriangle(-10 , -1, 2, -1, 10, 10));
assertTrue(impl.intersectsTriangle(-10 , -1, 2, -1, 10, 10));
}
// test
@ -353,7 +361,7 @@ public class TestPolygon2D extends LuceneTestCase {
double[] b = new double[] {polygon.getPolyLat(j - 1), polygon.getPolyLon(j - 1)};
// occassionally test pancake triangles
double[] c = random().nextBoolean() ? new double[] {polygon.getPolyLat(j), polygon.getPolyLon(j)} : new double[] {a[0], a[1]};
assertTrue(impl.relateTriangle(a[0], a[1], b[0], b[1], c[0], c[1]) != Relation.CELL_OUTSIDE_QUERY);
assertTrue(impl.intersectsTriangle(a[0], a[1], b[0], b[1], c[0], c[1]));
}
}
}
@ -361,13 +369,13 @@ public class TestPolygon2D extends LuceneTestCase {
public void testLineCrossingPolygonPoints() {
Polygon p = new Polygon(new double[] {0, -1, 0, 1, 0}, new double[] {-1, 0, 1, 0, -1});
Component2D polygon2D = Polygon2D.create(p);
Relation rel = polygon2D.relateTriangle(GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(-1.5)),
boolean intersects = polygon2D.intersectsTriangle(GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(-1.5)),
GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(0)),
GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(1.5)),
GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(0)),
GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(-1.5)),
GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(0)));
assertEquals(Relation.CELL_CROSSES_QUERY, rel);
assertTrue(intersects);
}
public void testRandomLineCrossingPolygon() {
@ -376,14 +384,14 @@ public class TestPolygon2D extends LuceneTestCase {
for (int i=0; i < 1000; i ++) {
double longitude = GeoTestUtil.nextLongitude();
double latitude = GeoTestUtil.nextLatitude();
Relation rel = polygon2D.relateTriangle(
boolean intersects = polygon2D.intersectsTriangle(
GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(-longitude)),
GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(-latitude)),
GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(longitude)),
GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(latitude)),
GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(-longitude)),
GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(-latitude)));
assertNotEquals(Relation.CELL_OUTSIDE_QUERY, rel);
assertTrue(intersects);
}
}
}

View File

@ -34,60 +34,45 @@ public class TestRectangle2D extends LuceneTestCase {
float by = 5f;
float cx = 5f;
float cy = 4f;
assertEquals(PointValues.Relation.CELL_OUTSIDE_QUERY, rectangle2D.relateTriangle(ax, ay, bx, by , cx, cy));
assertFalse(rectangle2D.intersectsTriangle(ax, ay, bx, by , cx, cy));
assertFalse(rectangle2D.intersectsLine(ax, ay, bx, by));
assertFalse(rectangle2D.containsTriangle(ax, ay, bx, by , cx, cy));
assertFalse(rectangle2D.containsLine(ax, ay, bx, by));
assertEquals(Component2D.WithinRelation.DISJOINT,
rectangle2D.withinTriangle(ax, ay, random().nextBoolean(), bx, by, random().nextBoolean(), cx, cy, random().nextBoolean()));
}
public void testTriangleIntersects() {
XYRectangle rectangle = new XYRectangle(0f, 1f, 0f, 1f);
Component2D rectangle2D = Rectangle2D.create(rectangle);
Component2D rectangle2D = Rectangle2D.create(rectangle);
float ax = 0.5f;
float ay = 0.5f;
float bx = 2f;
float by = 2f;
float cx = 0.5f;
float cy = 2f;
assertEquals(PointValues.Relation.CELL_CROSSES_QUERY, rectangle2D.relateTriangle(ax, ay, bx, by , cx, cy));
assertTrue(rectangle2D.intersectsTriangle(ax, ay, bx, by , cx, cy));
assertTrue(rectangle2D.intersectsLine(ax, ay, bx, by));
assertFalse(rectangle2D.containsTriangle(ax, ay, bx, by , cx, cy));
assertFalse(rectangle2D.containsLine(ax, ay, bx, by));
assertEquals(Component2D.WithinRelation.NOTWITHIN,
rectangle2D.withinTriangle(ax, ay, true, bx, by, true, cx, cy, true));
}
public void testTriangleContains() {
XYRectangle rectangle = new XYRectangle(0, 1, 0, 1);
Component2D rectangle2D = Rectangle2D.create(rectangle);
Component2D rectangle2D = Rectangle2D.create(rectangle);
float ax = 0.25f;
float ay = 0.25f;
float bx = 0.5f;
float by = 0.5f;
float cx = 0.5f;
float cy = 0.25f;
assertEquals(PointValues.Relation.CELL_INSIDE_QUERY, rectangle2D.relateTriangle(ax, ay, bx, by , cx, cy));
}
public void testTriangleContainsEdgeCase() {
Rectangle rectangle = new Rectangle(0, 1, 0, 1);
Component2D rectangle2D = Rectangle2D.create(rectangle);
double ax = 0.0;
double ay = 0.0;
double bx = 0.0;
double by = 0.5;
double cx = 0.5;
double cy = 0.25;
assertEquals(PointValues.Relation.CELL_INSIDE_QUERY, rectangle2D.relateTriangle(ax, ay, bx, by , cx, cy));
assertEquals(Component2D.WithinRelation.NOTWITHIN, rectangle2D.withinTriangle(ax, ay, true, bx, by, true, cx, cy, true));
}
public void testTriangleWithinCrossingDateLine() {
Rectangle rectangle = new Rectangle(0, 2, 179, -179);
Component2D rectangle2D = Rectangle2D.create(rectangle);
double ax = 169;
double ay = -10;
double bx = 180;
double by = -10;
double cx = 180;
double cy = 30;
assertEquals(PointValues.Relation.CELL_CROSSES_QUERY, rectangle2D.relateTriangle(ax, ay, bx, by , cx, cy));
expectThrows(IllegalArgumentException.class, () -> {
rectangle2D.withinTriangle(ax, ay, true, bx, by, true, cx, cy, true);
});
assertTrue(rectangle2D.intersectsTriangle(ax, ay, bx, by , cx, cy));
assertTrue(rectangle2D.intersectsLine(ax, ay, bx, by));
assertTrue(rectangle2D.containsTriangle(ax, ay, bx, by , cx, cy));
assertTrue(rectangle2D.containsLine(ax, ay, bx, by));
assertEquals(Component2D.WithinRelation.NOTWITHIN, rectangle2D.withinTriangle(ax, ay, true, bx, by , true, cx, cy, true));
}
public void testRandomTriangles() {
@ -110,10 +95,17 @@ public class TestRectangle2D extends LuceneTestCase {
PointValues.Relation r = rectangle2D.relate(tMinX, tMaxX, tMinY, tMaxY);
if (r == PointValues.Relation.CELL_OUTSIDE_QUERY) {
assertEquals(PointValues.Relation.CELL_OUTSIDE_QUERY, rectangle2D.relateTriangle(ax, ay, bx, by , cx, cy));
assertFalse(rectangle2D.intersectsTriangle(ax, ay, bx, by, cx, cy));
assertFalse(rectangle2D.intersectsLine(ax, ay, bx, by));
assertFalse(rectangle2D.containsTriangle(ax, ay, bx, by , cx, cy));
assertFalse(rectangle2D.containsLine(ax, ay, bx, by));
assertEquals(Component2D.WithinRelation.DISJOINT, rectangle2D.withinTriangle(ax, ay, true, bx, by, true, cx, cy, true));
}
else if (r == PointValues.Relation.CELL_INSIDE_QUERY) {
assertEquals(PointValues.Relation.CELL_INSIDE_QUERY, rectangle2D.relateTriangle(ax, ay, bx, by , cx, cy));
assertTrue(rectangle2D.intersectsTriangle(ax, ay, bx, by, cx, cy));
assertTrue(rectangle2D.intersectsLine(ax, ay, bx, by));
assertTrue(rectangle2D.containsTriangle(ax, ay, bx, by , cx, cy));
assertTrue(rectangle2D.containsLine(ax, ay, bx, by));
}
}
}