LUCENE-9225: Rectangle extends LatLonGeometry so it can be used in a geometry collection (#1258)

This commit is contained in:
Ignacio Vera 2020-03-03 07:36:44 +01:00 committed by GitHub
parent c313365c5f
commit 286d22717b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 710 additions and 886 deletions

View File

@ -235,6 +235,8 @@ Other
* LUCENE-9068: FuzzyQuery builds its Automaton up-front (Alan Woodward, Mike Drob)
* LUCENE-9225: Rectangle extends LatLonGeometry so it can be used in a geometry collection. (Ignacio Vera)
======================= Lucene 8.4.1 =======================
Bug Fixes

View File

@ -27,6 +27,7 @@ import org.apache.lucene.geo.LatLonGeometry;
import org.apache.lucene.geo.Line;
import org.apache.lucene.geo.Point;
import org.apache.lucene.geo.Polygon;
import org.apache.lucene.geo.Rectangle;
import org.apache.lucene.geo.Tessellator;
import org.apache.lucene.index.PointValues; // javadoc
import org.apache.lucene.search.BooleanClause;
@ -105,7 +106,8 @@ public class LatLonShape {
builder.add(newBoxQuery(field, queryRelation, minLatitude, maxLatitude, GeoUtils.MIN_LON_INCL, maxLongitude), BooleanClause.Occur.MUST);
return builder.build();
}
return new LatLonShapeBoundingBoxQuery(field, queryRelation, minLatitude, maxLatitude, minLongitude, maxLongitude);
Rectangle rectangle = new Rectangle(minLatitude, maxLatitude, minLongitude, maxLongitude);
return new LatLonShapeBoundingBoxQuery(field, queryRelation, rectangle);
}
/** create a query to find all indexed geo shapes that intersect a provided linestring (or array of linestrings)
@ -140,14 +142,35 @@ public class LatLonShape {
/** create a query to find all indexed geo shapes that intersect a provided geometry (or array of geometries).
**/
public static Query newGeometryQuery(String field, QueryRelation queryRelation, LatLonGeometry... latLonGeometries) {
if (queryRelation == QueryRelation.CONTAINS && latLonGeometries.length > 1) {
BooleanQuery.Builder builder = new BooleanQuery.Builder();
for (int i = 0; i < latLonGeometries.length; i++) {
builder.add(newGeometryQuery(field, queryRelation, latLonGeometries[i]), BooleanClause.Occur.MUST);
if (latLonGeometries.length == 1) {
LatLonGeometry geometry = latLonGeometries[0];
if (geometry instanceof Rectangle) {
Rectangle rect = (Rectangle) geometry;
return newBoxQuery(field, queryRelation, rect.minLat, rect.maxLat, rect.minLon, rect.maxLon);
} else {
return new LatLonShapeQuery(field, queryRelation, latLonGeometries);
}
} else {
if (queryRelation == QueryRelation.CONTAINS) {
return makeContainsGeometryQuery(field, latLonGeometries);
} else {
return new LatLonShapeQuery(field, queryRelation, latLonGeometries);
}
return builder.build();
}
return new LatLonShapeQuery(field, queryRelation, latLonGeometries);
}
private static Query makeContainsGeometryQuery(String field, LatLonGeometry... latLonGeometries) {
BooleanQuery.Builder builder = new BooleanQuery.Builder();
for (LatLonGeometry geometry : latLonGeometries) {
if (geometry instanceof Rectangle) {
// this handles rectangles across the dateline
Rectangle rect = (Rectangle) geometry;
builder.add(newBoxQuery(field, QueryRelation.CONTAINS, rect.minLat, rect.maxLat, rect.minLon, rect.maxLon), BooleanClause.Occur.MUST);
} else {
builder.add(new LatLonShapeQuery(field, QueryRelation.CONTAINS, geometry), BooleanClause.Occur.MUST);
}
}
return builder.build();
}
}

View File

@ -16,11 +16,23 @@
*/
package org.apache.lucene.document;
import java.util.Arrays;
import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.Component2D;
import org.apache.lucene.geo.Rectangle;
import org.apache.lucene.geo.Rectangle2D;
import org.apache.lucene.geo.Tessellator;
import org.apache.lucene.index.PointValues.Relation;
import org.apache.lucene.util.NumericUtils;
import static java.lang.Integer.BYTES;
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.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.
@ -29,22 +41,22 @@ import org.apache.lucene.index.PointValues.Relation;
* {@link org.apache.lucene.document.LatLonShape#createIndexableFields} added per document.
**/
final class LatLonShapeBoundingBoxQuery extends ShapeQuery {
final Rectangle rectangle;
final Rectangle2D rectangle2D;
private final Rectangle rectangle;
private final EncodedRectangle encodedRectangle;
public LatLonShapeBoundingBoxQuery(String field, QueryRelation queryRelation, double minLat, double maxLat, double minLon, double maxLon) {
LatLonShapeBoundingBoxQuery(String field, QueryRelation queryRelation, Rectangle rectangle) {
super(field, queryRelation);
this.rectangle = new Rectangle(minLat, maxLat, minLon, maxLon);
this.rectangle2D = Rectangle2D.create(this.rectangle);
this.rectangle = rectangle;
this.encodedRectangle = new EncodedRectangle(rectangle.minLat, rectangle.maxLat, rectangle.minLon, rectangle.maxLon);
}
@Override
protected Relation relateRangeBBoxToQuery(int minXOffset, int minYOffset, byte[] minTriangle,
int maxXOffset, int maxYOffset, byte[] maxTriangle) {
if (queryRelation == QueryRelation.INTERSECTS || queryRelation == QueryRelation.DISJOINT) {
return rectangle2D.intersectRangeBBox(minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle);
return encodedRectangle.intersectRangeBBox(minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle);
}
return rectangle2D.relateRangeBBox(minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle);
return encodedRectangle.relateRangeBBox(minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle);
}
/** returns true if the query matches the encoded triangle */
@ -61,9 +73,9 @@ final class LatLonShapeBoundingBoxQuery extends ShapeQuery {
int cX = scratchTriangle.cX;
switch (queryRelation) {
case INTERSECTS: return rectangle2D.intersectsTriangle(aX, aY, bX, bY, cX, cY);
case WITHIN: return rectangle2D.containsTriangle(aX, aY, bX, bY, cX, cY);
case DISJOINT: return rectangle2D.intersectsTriangle(aX, aY, bX, bY, cX, cY) == false;
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 + "]");
}
}
@ -73,16 +85,11 @@ final class LatLonShapeBoundingBoxQuery extends ShapeQuery {
// decode indexed triangle
ShapeField.decodeTriangle(t, scratchTriangle);
return rectangle2D.withinTriangle(scratchTriangle.aX, scratchTriangle.aY, scratchTriangle.ab,
return encodedRectangle.withinTriangle(scratchTriangle.aX, scratchTriangle.aY, scratchTriangle.ab,
scratchTriangle.bX, scratchTriangle.bY, scratchTriangle.bc,
scratchTriangle.cX, scratchTriangle.cY, scratchTriangle.ca);
}
@Override
public boolean equals(Object o) {
return sameClassAs(o) && equalsTo(getClass().cast(o));
}
@Override
protected boolean equalsTo(Object o) {
return super.equalsTo(o) && rectangle.equals(((LatLonShapeBoundingBoxQuery)o).rectangle);
@ -108,4 +115,385 @@ final class LatLonShapeBoundingBoxQuery extends ShapeQuery {
sb.append(rectangle.toString());
return sb.toString();
}
/** Holds spatial logic for a bounding box that works in the encoded space */
private static class EncodedRectangle {
protected final byte[] bbox;
private final byte[] west;
protected final int minX;
protected final int maxX;
protected final int minY;
protected final int maxY;
EncodedRectangle(double minLat, double maxLat, double minLon, double maxLon) {
this.bbox = new byte[4 * BYTES];
int minXenc = encodeLongitudeCeil(minLon);
int maxXenc = encodeLongitude(maxLon);
int minYenc = encodeLatitudeCeil(minLat);
int maxYenc = encodeLatitude(maxLat);
if (minYenc > maxYenc) {
minYenc = maxYenc;
}
this.minY = minYenc;
this.maxY = maxYenc;
if (minLon > maxLon == true) {
// crossing dateline is split into east/west boxes
this.west = new byte[4 * BYTES];
this.minX = minXenc;
this.maxX = maxXenc;
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 {
// encodeLongitudeCeil may cause minX to be > maxX iff
// the delta between the longitude < the encoding resolution
if (minXenc > maxXenc) {
minXenc = maxXenc;
}
this.west = null;
this.minX = minXenc;
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);
}
return bboxContainsPoint(x, y, this.minX, this.maxX, this.minY, this.maxY);
}
/**
* compare this to a provided range bounding box
**/
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) {
return compareBBoxToRangeBBox(this.west, minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle);
}
return eastRelation;
}
/**
* intersects this to a provided range bounding box
**/
Relation intersectRangeBBox(int minXOffset, int minYOffset, byte[] minTriangle,
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) {
return intersectBBoxWithRangeBBox(this.west, minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle);
}
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)
**/
private static Relation compareBBoxToRangeBBox(final byte[] bbox,
int minXOffset, int minYOffset, byte[] minTriangle,
int maxXOffset, int maxYOffset, byte[] maxTriangle) {
// check bounding box (DISJOINT)
if (disjoint(bbox, minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle)) {
return Relation.CELL_OUTSIDE_QUERY;
}
if (Arrays.compareUnsigned(minTriangle, minXOffset, minXOffset + BYTES, bbox, BYTES, 2 * BYTES) >= 0 &&
Arrays.compareUnsigned(maxTriangle, maxXOffset, maxXOffset + BYTES, bbox, 3 * BYTES, 4 * BYTES) <= 0 &&
Arrays.compareUnsigned(minTriangle, minYOffset, minYOffset + BYTES, bbox, 0, BYTES) >= 0 &&
Arrays.compareUnsigned(maxTriangle, maxYOffset, maxYOffset + BYTES, bbox, 2 * BYTES, 3 * BYTES) <= 0) {
return Relation.CELL_INSIDE_QUERY;
}
return Relation.CELL_CROSSES_QUERY;
}
/**
* static utility method to compare a bbox with a range of triangles (just the bbox of the triangle collection)
* for intersection
**/
private static Relation intersectBBoxWithRangeBBox(final byte[] bbox,
int minXOffset, int minYOffset, byte[] minTriangle,
int maxXOffset, int maxYOffset, byte[] maxTriangle) {
// check bounding box (DISJOINT)
if (disjoint(bbox, minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle)) {
return Relation.CELL_OUTSIDE_QUERY;
}
if (Arrays.compareUnsigned(minTriangle, minXOffset, minXOffset + BYTES, bbox, BYTES, 2 * BYTES) >= 0 &&
Arrays.compareUnsigned(minTriangle, minYOffset, minYOffset + BYTES, bbox, 0, BYTES) >= 0) {
if (Arrays.compareUnsigned(maxTriangle, minXOffset, minXOffset + BYTES, bbox, 3 * BYTES, 4 * BYTES) <= 0 &&
Arrays.compareUnsigned(maxTriangle, maxYOffset, maxYOffset + BYTES, bbox, 2 * BYTES, 3 * BYTES) <= 0) {
return Relation.CELL_INSIDE_QUERY;
}
if (Arrays.compareUnsigned(maxTriangle, maxXOffset, maxXOffset + BYTES, bbox, 3 * BYTES, 4 * BYTES) <= 0 &&
Arrays.compareUnsigned(maxTriangle, minYOffset, minYOffset + BYTES, bbox, 2 * BYTES, 3 * BYTES) <= 0) {
return Relation.CELL_INSIDE_QUERY;
}
}
if (Arrays.compareUnsigned(maxTriangle, maxXOffset, maxXOffset + BYTES, bbox, 3 * BYTES, 4 * BYTES) <= 0 &&
Arrays.compareUnsigned(maxTriangle, maxYOffset, maxYOffset + BYTES, bbox, 2 * BYTES, 3 * BYTES) <= 0) {
if (Arrays.compareUnsigned(minTriangle, minXOffset, minXOffset + BYTES, bbox, BYTES, 2 * BYTES) >= 0 &&
Arrays.compareUnsigned(minTriangle, maxYOffset, maxYOffset + BYTES, bbox, 0, BYTES) >= 0) {
return Relation.CELL_INSIDE_QUERY;
}
if (Arrays.compareUnsigned(minTriangle, maxXOffset, maxXOffset + BYTES, bbox, BYTES, 2 * BYTES) >= 0 &&
Arrays.compareUnsigned(minTriangle, minYOffset, minYOffset + BYTES, bbox, 0, BYTES) >= 0) {
return Relation.CELL_INSIDE_QUERY;
}
}
return Relation.CELL_CROSSES_QUERY;
}
/**
* static utility method to check a bbox is disjoint with a range of triangles
**/
private static boolean disjoint(final byte[] bbox,
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;
}
/**
* 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)) {
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;
}
/**
* utility method to check if two boxes are disjoint
*/
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);
}
}
}

View File

@ -40,7 +40,7 @@ final class LatLonShapeQuery extends ShapeQuery {
/**
* Creates a query that matches all indexed shapes to the provided array of {@link LatLonGeometry}
*/
LatLonShapeQuery(String field, QueryRelation queryRelation, LatLonGeometry[] geometries) {
LatLonShapeQuery(String field, QueryRelation queryRelation, LatLonGeometry... geometries) {
super(field, queryRelation);
if (queryRelation == QueryRelation.WITHIN) {
for (LatLonGeometry geometry : geometries) {

View File

@ -33,7 +33,7 @@ import static org.apache.lucene.util.SloppyMath.asin;
import static org.apache.lucene.util.SloppyMath.cos;
/** Represents a lat/lon rectangle. */
public class Rectangle {
public class Rectangle extends LatLonGeometry {
/** maximum longitude value (in degrees) */
public final double minLat;
/** minimum longitude value (in degrees) */
@ -60,6 +60,11 @@ public class Rectangle {
// NOTE: cannot assert maxLon >= minLon since this rect could cross the dateline
}
@Override
protected Component2D toComponent2D() {
return Rectangle2D.create(this);
}
@Override
public String toString() {
StringBuilder b = new StringBuilder();

View File

@ -14,364 +14,158 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.geo;
import java.util.Arrays;
import java.util.Objects;
import org.apache.lucene.index.PointValues.Relation;
import org.apache.lucene.util.NumericUtils;
import org.apache.lucene.index.PointValues;
import static java.lang.Integer.BYTES;
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.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.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 geo spatial logic.
*
* @lucene.internal
* 2D rectangle implementation containing cartesian spatial logic.
*/
public class Rectangle2D {
protected final byte[] bbox;
private final byte[] west;
protected final int minX;
protected final int maxX;
protected final int minY;
protected final int maxY;
final class Rectangle2D implements Component2D {
protected Rectangle2D(double minLat, double maxLat, double minLon, double maxLon) {
this.bbox = new byte[4 * BYTES];
int minXenc = encodeLongitudeCeil(minLon);
int maxXenc = encodeLongitude(maxLon);
int minYenc = encodeLatitudeCeil(minLat);
int maxYenc = encodeLatitude(maxLat);
if (minYenc > maxYenc) {
minYenc = maxYenc;
}
this.minY = minYenc;
this.maxY = maxYenc;
private final double minX;
private final double maxX;
private final double minY;
private final double maxY;
if (minLon > maxLon == true) {
// crossing dateline is split into east/west boxes
this.west = new byte[4 * BYTES];
this.minX = minXenc;
this.maxX = maxXenc;
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 {
// encodeLongitudeCeil may cause minX to be > maxX iff
// the delta between the longitude < the encoding resolution
if (minXenc > maxXenc) {
minXenc = maxXenc;
}
this.west = null;
this.minX = minXenc;
this.maxX = maxXenc;
encode(this.minX, this.maxX, this.minY, this.maxY, bbox);
}
private Rectangle2D(double minX, double maxX, double minY, double maxY) {
this.minX = minX;
this.maxX = maxX;
this.minY = minY;
this.maxY = maxY;
}
protected Rectangle2D(int minX, int maxX, int minY, int maxY) {
this.bbox = new byte[4 * BYTES];
this.west = null;
this.minX = minX;
this.maxX = maxX;
this.minY = minY;
this.maxY = maxY;
encode(this.minX, this.maxX, this.minY, this.maxY, bbox);
@Override
public double getMinX() {
return minX;
}
/** Builds a Rectangle2D from rectangle */
public static Rectangle2D create(Rectangle rectangle) {
return new Rectangle2D(rectangle.minLat, rectangle.maxLat, rectangle.minLon, rectangle.maxLon);
@Override
public double getMaxX() {
return maxX;
}
public boolean crossesDateline() {
return minX > maxX;
@Override
public double getMinY() {
return minY;
}
/** Checks if the rectangle contains the provided point **/
public 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);
}
return bboxContainsPoint(x, y, this.minX, this.maxX, this.minY, this.maxY);
@Override
public double getMaxY() {
return maxY;
}
/** compare this to a provided range bounding box **/
public 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) {
return compareBBoxToRangeBBox(this.west, minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle);
}
return eastRelation;
@Override
public boolean contains(double x, double y) {
return Component2D.containsPoint(x, y, this.minX, this.maxX, this.minY, this.maxY);
}
/** intersects this to a provided range bounding box **/
public Relation intersectRangeBBox(int minXOffset, int minYOffset, byte[] minTriangle,
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) {
return intersectBBoxWithRangeBBox(this.west, minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle);
@Override
public PointValues.Relation relate(double minX, double maxX, double minY, double maxY) {
if (Component2D.disjoint(this.minX, this.maxX, this.minY, this.maxY, minX, maxX, minY, maxY)) {
return PointValues.Relation.CELL_OUTSIDE_QUERY;
}
return eastRelation;
if (Component2D.within(minX, maxX, minY, maxY, this.minX, this.maxX, this.minY, this.maxY)) {
return PointValues.Relation.CELL_INSIDE_QUERY;
}
return PointValues.Relation.CELL_CROSSES_QUERY;
}
/** Checks if the rectangle intersects the provided triangle **/
public 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;
}
@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) {
// 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;
if (Component2D.disjoint(this.minX, this.maxX, this.minY, this.maxY, minX, maxX, minY, maxY)) {
return PointValues.Relation.CELL_OUTSIDE_QUERY;
}
// 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;
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;
}
// 4. last ditch effort: check crossings
if (queryIntersects(aX, aY, bX, bY, cX, cY)) {
return true;
}
return false;
return PointValues.Relation.CELL_OUTSIDE_QUERY;
}
/** Returns the Within relation to the provided triangle */
public 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");
}
@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 Component2D.WithinRelation.DISJOINT;
return 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;
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 (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 (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.
Component2D.WithinRelation relation = Component2D.WithinRelation.DISJOINT;
if (edgeIntersectsBox(ax, ay, bx, by, minX, maxX, minY, maxY) == true) {
WithinRelation relation = WithinRelation.DISJOINT;
if (edgesIntersect(ax, ay, bx, by) == true) {
if (ab == true) {
return Component2D.WithinRelation.NOTWITHIN;
return WithinRelation.NOTWITHIN;
} else {
relation = Component2D.WithinRelation.CANDIDATE;
relation = WithinRelation.CANDIDATE;
}
}
if (edgeIntersectsBox(bx, by, cx, cy, minX, maxX, minY, maxY) == true) {
if (edgesIntersect(bx, by, cx, cy) == true) {
if (bc == true) {
return Component2D.WithinRelation.NOTWITHIN;
return WithinRelation.NOTWITHIN;
} else {
relation = Component2D.WithinRelation.CANDIDATE;
relation = WithinRelation.CANDIDATE;
}
}
if (edgeIntersectsBox(cx, cy, ax, ay, minX, maxX, minY, maxY) == true) {
if (edgesIntersect(cx, cy, ax, ay) == true) {
if (ca == true) {
return Component2D.WithinRelation.NOTWITHIN;
return WithinRelation.NOTWITHIN;
} else {
relation = Component2D.WithinRelation.CANDIDATE;
relation = 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;
if (relation == WithinRelation.CANDIDATE) {
return WithinRelation.CANDIDATE;
}
// Check if shape is within the triangle
if (Tessellator.pointInTriangle(minX, minY, ax, ay, bx, by, cx, cy)) {
return Component2D.WithinRelation.CANDIDATE;
if (Component2D.pointInTriangle(minX, maxX, minY, maxY, this.minX, this.minY, ax, ay, bx, by, cx, cy)) {
return WithinRelation.CANDIDATE;
}
return relation;
}
/** Checks if the rectangle contains the provided triangle **/
public 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)
**/
private static Relation compareBBoxToRangeBBox(final byte[] bbox,
int minXOffset, int minYOffset, byte[] minTriangle,
int maxXOffset, int maxYOffset, byte[] maxTriangle) {
// check bounding box (DISJOINT)
if (disjoint(bbox, minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle)) {
return Relation.CELL_OUTSIDE_QUERY;
}
if (Arrays.compareUnsigned(minTriangle, minXOffset, minXOffset + BYTES, bbox, BYTES, 2 * BYTES) >= 0 &&
Arrays.compareUnsigned(maxTriangle, maxXOffset, maxXOffset + BYTES, bbox, 3 * BYTES, 4 * BYTES) <= 0 &&
Arrays.compareUnsigned(minTriangle, minYOffset, minYOffset + BYTES, bbox, 0, BYTES) >= 0 &&
Arrays.compareUnsigned(maxTriangle, maxYOffset, maxYOffset + BYTES, bbox, 2 * BYTES, 3 * BYTES) <= 0) {
return Relation.CELL_INSIDE_QUERY;
}
return Relation.CELL_CROSSES_QUERY;
}
/**
* static utility method to compare a bbox with a range of triangles (just the bbox of the triangle collection)
* for intersection
**/
private static Relation intersectBBoxWithRangeBBox(final byte[] bbox,
int minXOffset, int minYOffset, byte[] minTriangle,
int maxXOffset, int maxYOffset, byte[] maxTriangle) {
// check bounding box (DISJOINT)
if (disjoint(bbox, minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle)) {
return Relation.CELL_OUTSIDE_QUERY;
}
if (Arrays.compareUnsigned(minTriangle, minXOffset, minXOffset + BYTES, bbox, BYTES, 2 * BYTES) >= 0 &&
Arrays.compareUnsigned(minTriangle, minYOffset, minYOffset + BYTES, bbox, 0, BYTES) >= 0 ) {
if (Arrays.compareUnsigned(maxTriangle, minXOffset, minXOffset + BYTES, bbox, 3 * BYTES, 4 * BYTES) <= 0 &&
Arrays.compareUnsigned(maxTriangle, maxYOffset, maxYOffset + BYTES, bbox, 2 * BYTES, 3 * BYTES) <= 0) {
return Relation.CELL_INSIDE_QUERY;
}
if (Arrays.compareUnsigned(maxTriangle, maxXOffset, maxXOffset + BYTES, bbox, 3 * BYTES, 4 * BYTES) <= 0 &&
Arrays.compareUnsigned(maxTriangle, minYOffset, minYOffset + BYTES, bbox, 2 * BYTES, 3 * BYTES) <= 0) {
return Relation.CELL_INSIDE_QUERY;
}
}
if (Arrays.compareUnsigned(maxTriangle, maxXOffset, maxXOffset + BYTES, bbox, 3 * BYTES, 4 * BYTES) <= 0 &&
Arrays.compareUnsigned(maxTriangle, maxYOffset, maxYOffset + BYTES, bbox, 2 * BYTES, 3 * BYTES) <= 0 ) {
if (Arrays.compareUnsigned(minTriangle, minXOffset, minXOffset + BYTES, bbox, BYTES, 2 * BYTES) >= 0 &&
Arrays.compareUnsigned(minTriangle, maxYOffset, maxYOffset + BYTES, bbox, 0, BYTES) >= 0) {
return Relation.CELL_INSIDE_QUERY;
}
if (Arrays.compareUnsigned(minTriangle, maxXOffset, maxXOffset + BYTES, bbox, BYTES, 2 * BYTES) >= 0 &&
Arrays.compareUnsigned(minTriangle, minYOffset, minYOffset + BYTES, bbox, 0, BYTES) >= 0) {
return Relation.CELL_INSIDE_QUERY;
}
}
return Relation.CELL_CROSSES_QUERY;
}
/**
* static utility method to check a bbox is disjoint with a range of triangles
**/
private static boolean disjoint(final byte[] bbox,
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;
}
/**
* 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) {
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 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;
return false;
}
// 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)) {
if ( Math.max(ax, bx) < minX || Math.min(ax, bx) > maxX || Math.min(ay, by) > maxY || Math.max(ay, by) < minY) {
return false;
}
@ -401,10 +195,18 @@ public class Rectangle2D {
return false;
}
/** utility method to check if two boxes are disjoint */
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);
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;
}
@Override
@ -415,16 +217,63 @@ public class Rectangle2D {
return minX == that.minX &&
maxX == that.maxX &&
minY == that.minY &&
maxY == that.maxY &&
Arrays.equals(bbox, that.bbox) &&
Arrays.equals(west, that.west);
maxY == that.maxY;
}
@Override
public int hashCode() {
int result = Objects.hash(minX, maxX, minY, maxY);
result = 31 * result + Arrays.hashCode(bbox);
result = 31 * result + Arrays.hashCode(west);
return result;
}
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("XYRectangle(x=");
sb.append(minX);
sb.append(" TO ");
sb.append(maxX);
sb.append(" y=");
sb.append(minY);
sb.append(" TO ");
sb.append(maxY);
sb.append(")");
return sb.toString();
}
/** create a component2D from the provided XY rectangle */
static Component2D create(XYRectangle rectangle) {
return new Rectangle2D(rectangle.minX, rectangle.maxX, rectangle.minY, rectangle.maxY);
}
private static double MIN_LON_INCL_QUANTIZE = decodeLongitude(MIN_LON_ENCODED);
private static double MAX_LON_INCL_QUANTIZE = decodeLongitude(MAX_LON_ENCODED);
/** create a component2D from the provided LatLon rectangle */
static Component2D create(Rectangle rectangle) {
// need to quantize!
double qMinLat = decodeLatitude(encodeLatitudeCeil(rectangle.minLat));
double qMaxLat = decodeLatitude(encodeLatitude(rectangle.maxLat));
double qMinLon = decodeLongitude(encodeLongitudeCeil(rectangle.minLon));
double qMaxLon = decodeLongitude(encodeLongitude(rectangle.maxLon));
if (qMinLat > qMaxLat) {
// encodeLatitudeCeil may cause minY to be > maxY iff
// the delta between the longitude < the encoding resolution
qMinLat = qMaxLat;
}
if (rectangle.minLon > rectangle.maxLon) {
// for rectangles that cross the dateline we need to create two components
Component2D[] components = new Component2D[2];
components[0] = new Rectangle2D(MIN_LON_INCL_QUANTIZE, qMaxLon, qMinLat, qMaxLat);
components[1] = new Rectangle2D(qMinLon, MAX_LON_INCL_QUANTIZE, qMinLat, qMaxLat);
return ComponentTree.create(components);
} else {
// encodeLongitudeCeil may cause minX to be > maxX iff
// the delta between the longitude < the encoding resolution
if (qMinLon > qMaxLon) {
qMinLon = qMaxLon;
}
return new Rectangle2D(qMinLon, qMaxLon, qMinLat, qMaxLat);
}
}
}

View File

@ -45,7 +45,7 @@ public final class XYRectangle extends XYGeometry {
@Override
protected Component2D toComponent2D() {
return XYRectangle2D.create(this);
return Rectangle2D.create(this);
}
@Override

View File

@ -1,240 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.geo;
import java.util.Objects;
import org.apache.lucene.index.PointValues;
import static org.apache.lucene.geo.GeoUtils.orient;
/**
* 2D rectangle implementation containing cartesian spatial logic.
*/
final class XYRectangle2D implements Component2D {
private final double minX;
private final double maxX;
private final double minY;
private final double maxY;
private XYRectangle2D(double minX, double maxX, double minY, double maxY) {
this.minX = minX;
this.maxX = maxX;
this.minY = minY;
this.maxY = maxY;
}
@Override
public double getMinX() {
return minX;
}
@Override
public double getMaxX() {
return maxX;
}
@Override
public double getMinY() {
return minY;
}
@Override
public double getMaxY() {
return maxY;
}
@Override
public boolean contains(double x, double y) {
return Component2D.containsPoint(x, y, this.minX, this.maxX, this.minY, this.maxY);
}
@Override
public PointValues.Relation relate(double minX, double maxX, double minY, double maxY) {
if (Component2D.disjoint(this.minX, this.maxX, this.minY, this.maxY, minX, maxX, minY, maxY)) {
return PointValues.Relation.CELL_OUTSIDE_QUERY;
}
if (Component2D.within(minX, maxX, minY, maxY, this.minX, this.maxX, this.minY, this.maxY)) {
return PointValues.Relation.CELL_INSIDE_QUERY;
}
return PointValues.Relation.CELL_CROSSES_QUERY;
}
@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 (Component2D.disjoint(this.minX, this.maxX, this.minY, this.maxY, minX, maxX, minY, maxY)) {
return PointValues.Relation.CELL_OUTSIDE_QUERY;
}
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 PointValues.Relation.CELL_OUTSIDE_QUERY;
}
@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;
}
// 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)) {
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 (ab == true) {
return WithinRelation.NOTWITHIN;
} else {
relation = WithinRelation.CANDIDATE;
}
}
if (edgesIntersect(bx, by, cx, cy) == true) {
if (bc == true) {
return WithinRelation.NOTWITHIN;
} else {
relation = WithinRelation.CANDIDATE;
}
}
if (edgesIntersect(cx, cy, ax, ay) == true) {
if (ca == true) {
return WithinRelation.NOTWITHIN;
} else {
relation = 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 == WithinRelation.CANDIDATE) {
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)) {
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;
}
// 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) {
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;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof XYRectangle2D)) return false;
XYRectangle2D that = (XYRectangle2D) o;
return minX == that.minX &&
maxX == that.maxX &&
minY == that.minY &&
maxY == that.maxY;
}
@Override
public int hashCode() {
int result = Objects.hash(minX, maxX, minY, maxY);
return result;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("XYRectangle(x=");
sb.append(minX);
sb.append(" TO ");
sb.append(maxX);
sb.append(" y=");
sb.append(minY);
sb.append(" TO ");
sb.append(maxY);
sb.append(")");
return sb.toString();
}
/** create a component2D from the provided XY rectangle */
static Component2D create(XYRectangle rectangle) {
return new XYRectangle2D(rectangle.minX, rectangle.maxX, rectangle.minY, rectangle.maxY);
}
}

View File

@ -26,8 +26,13 @@ import org.apache.lucene.geo.LatLonGeometry;
import org.apache.lucene.geo.Line;
import org.apache.lucene.geo.Polygon;
import org.apache.lucene.geo.Rectangle;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.RandomIndexWriter;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.QueryUtils;
import org.apache.lucene.store.Directory;
import org.apache.lucene.util.IOUtils;
import org.apache.lucene.util.TestUtil;
import org.apache.lucene.geo.Circle;
@ -199,6 +204,46 @@ public abstract class BaseLatLonShapeTestCase extends BaseShapeTestCase {
}
}
public void testBoundingBoxQueriesEquivalence() throws Exception {
int numShapes = atLeast(20);
Directory dir = newDirectory();
RandomIndexWriter w = new RandomIndexWriter(random(), dir);
for (int i =0; i < numShapes; i++) {
indexRandomShapes(w.w, nextShape());
}
if (random().nextBoolean()) {
w.forceMerge(1);
}
///// search //////
IndexReader reader = w.getReader();
w.close();
IndexSearcher searcher = newSearcher(reader);
Rectangle box = GeoTestUtil.nextBox();
Query q1 = LatLonShape.newBoxQuery(FIELD_NAME, QueryRelation.INTERSECTS, box.minLat, box.maxLat, box.minLon, box.maxLon);
Query q2 = new LatLonShapeQuery(FIELD_NAME, QueryRelation.INTERSECTS, box);
assertEquals(searcher.count(q1), searcher.count(q2));
q1 = LatLonShape.newBoxQuery(FIELD_NAME, QueryRelation.WITHIN, box.minLat, box.maxLat, box.minLon, box.maxLon);
q2 = new LatLonShapeQuery(FIELD_NAME, QueryRelation.WITHIN, box);
assertEquals(searcher.count(q1), searcher.count(q2));
q1 = LatLonShape.newBoxQuery(FIELD_NAME, QueryRelation.CONTAINS, box.minLat, box.maxLat, box.minLon, box.maxLon);
if (box.crossesDateline()) {
q2 = LatLonShape.newGeometryQuery(FIELD_NAME, QueryRelation.CONTAINS, box);
} else {
q2 = new LatLonShapeQuery(FIELD_NAME, QueryRelation.CONTAINS, box);
}
assertEquals(searcher.count(q1), searcher.count(q2));
q1 = LatLonShape.newBoxQuery(FIELD_NAME, QueryRelation.DISJOINT, box.minLat, box.maxLat, box.minLon, box.maxLon);
q2 = new LatLonShapeQuery(FIELD_NAME, QueryRelation.DISJOINT, box);
assertEquals(searcher.count(q1), searcher.count(q2));
IOUtils.close(w, reader, dir);
}
/** factory method to create a new polygon query */
protected Query newPolygonQuery(String field, QueryRelation queryRelation, Polygon... polygons) {
return LatLonShape.newPolygonQuery(field, queryRelation, polygons);

View File

@ -21,9 +21,9 @@ import com.carrotsearch.randomizedtesting.generators.RandomNumbers;
import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.Component2D;
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.geo.Rectangle2D;
import org.apache.lucene.index.PointValues.Relation;
/** random bounding box, line, and polygon query tests for random generated {@link Line} types */
@ -78,32 +78,8 @@ public class TestLatLonLineShapeQueries extends BaseLatLonShapeTestCase {
@Override
public boolean testBBoxQuery(double minLat, double maxLat, double minLon, double maxLon, Object shape) {
Line line = (Line)shape;
Rectangle2D rectangle2D = Rectangle2D.create(new Rectangle(minLat, maxLat, minLon, maxLon));
Component2D.WithinRelation withinRelation = Component2D.WithinRelation.DISJOINT;
for (int i = 0, j = 1; j < line.numPoints(); ++i, ++j) {
ShapeField.DecodedTriangle decoded = encoder.encodeDecodeTriangle(line.getLon(i), line.getLat(i), true, line.getLon(j), line.getLat(j), true, line.getLon(i), line.getLat(i), true);
if (queryRelation == QueryRelation.WITHIN) {
if (rectangle2D.containsTriangle(decoded.aX, decoded.aY, decoded.bX, decoded.bY, decoded.cX, decoded.cY) == false) {
return false;
}
} else if (queryRelation == QueryRelation.CONTAINS) {
Component2D.WithinRelation relation = rectangle2D.withinTriangle(decoded.aX, decoded.aY, decoded.ab, decoded.bX, decoded.bY, decoded.bc, decoded.cX, decoded.cY, decoded.ca);
if (relation == Component2D.WithinRelation.NOTWITHIN) {
return false;
} else if (relation == Component2D.WithinRelation.CANDIDATE) {
withinRelation = Component2D.WithinRelation.CANDIDATE;
}
} else {
if (rectangle2D.intersectsTriangle(decoded.aX, decoded.aY, decoded.bX, decoded.bY, decoded.cX, decoded.cY) == true) {
return queryRelation == QueryRelation.INTERSECTS;
}
}
}
if (queryRelation == QueryRelation.CONTAINS) {
return withinRelation == Component2D.WithinRelation.CANDIDATE;
}
return queryRelation != QueryRelation.INTERSECTS;
Component2D rectangle2D = LatLonGeometry.create(new Rectangle(minLat, maxLat, minLon, maxLon));
return testComponentQuery(rectangle2D, shape);
}
@Override

View File

@ -20,9 +20,9 @@ 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.Rectangle2D;
import org.apache.lucene.geo.Tessellator;
import org.apache.lucene.index.PointValues.Relation;
@ -66,35 +66,8 @@ public class TestLatLonPolygonShapeQueries extends BaseLatLonShapeTestCase {
@Override
public boolean testBBoxQuery(double minLat, double maxLat, double minLon, double maxLon, Object shape) {
Polygon p = (Polygon)shape;
Rectangle2D rectangle2D = Rectangle2D.create(new Rectangle(minLat, maxLat, minLon, maxLon));
Component2D.WithinRelation withinRelation = Component2D.WithinRelation.DISJOINT;
List<Tessellator.Triangle> tessellation = Tessellator.tessellate(p);
for (Tessellator.Triangle t : tessellation) {
ShapeField.DecodedTriangle decoded = 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));
if (queryRelation == QueryRelation.WITHIN) {
if (rectangle2D.containsTriangle(decoded.aX, decoded.aY, decoded.bX, decoded.bY, decoded.cX, decoded.cY) == false) {
return false;
}
} else if (queryRelation == QueryRelation.CONTAINS) {
Component2D.WithinRelation relation = rectangle2D.withinTriangle(decoded.aX, decoded.aY, decoded.ab, decoded.bX, decoded.bY, decoded.bc, decoded.cX, decoded.cY, decoded.ca);
if (relation == Component2D.WithinRelation.NOTWITHIN) {
return false;
} else if (relation == Component2D.WithinRelation.CANDIDATE) {
withinRelation = Component2D.WithinRelation.CANDIDATE;
}
} else {
if (rectangle2D.intersectsTriangle(decoded.aX, decoded.aY, decoded.bX, decoded.bY, decoded.cX, decoded.cY) == true) {
return queryRelation == QueryRelation.INTERSECTS;
}
}
}
if (queryRelation == QueryRelation.CONTAINS) {
return withinRelation == Component2D.WithinRelation.CANDIDATE;
}
return queryRelation != QueryRelation.INTERSECTS;
Component2D rectangle2D = LatLonGeometry.create(new Rectangle(minLat, maxLat, minLon, maxLon));
return testComponentQuery(rectangle2D, shape);
}
@Override

View File

@ -25,8 +25,6 @@ import org.apache.lucene.geo.GeoTestUtil;
import org.apache.lucene.geo.LatLonGeometry;
import org.apache.lucene.geo.Line;
import org.apache.lucene.geo.Polygon;
import org.apache.lucene.geo.Rectangle;
import org.apache.lucene.geo.Rectangle2D;
import org.apache.lucene.geo.Tessellator;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
@ -190,7 +188,7 @@ public class TestLatLonShape extends LuceneTestCase {
// search a bounding box
searcher = newSearcher(reader);
q = new LatLonShapeBoundingBoxQuery(FIELDNAME, QueryRelation.CONTAINS,0, 0, 0, 0);
q = LatLonShape.newBoxQuery(FIELDNAME, QueryRelation.CONTAINS,0, 0, 0, 0);
assertEquals(1, searcher.count(q));
IOUtils.close(reader, dir);
}
@ -403,20 +401,6 @@ public class TestLatLonShape extends LuceneTestCase {
Polygon poly = new Polygon(new double[] {-1.490648725633769E-132d, 90d, 90d, -1.490648725633769E-132d},
new double[] {0d, 0d, 180d, 0d});
Rectangle rectangle = new Rectangle(-29.46555603761226d, 0.0d, 8.381903171539307E-8d, 0.9999999403953552d);
Rectangle2D rectangle2D = Rectangle2D.create(rectangle);
Tessellator.Triangle t = Tessellator.tessellate(poly).get(0);
byte[] encoded = new byte[7 * ShapeField.BYTES];
ShapeField.encodeTriangle(encoded, encodeLatitude(t.getY(0)), encodeLongitude(t.getX(0)), t.isEdgefromPolygon(0),
encodeLatitude(t.getY(1)), encodeLongitude(t.getX(1)), t.isEdgefromPolygon(1),
encodeLatitude(t.getY(2)), encodeLongitude(t.getX(2)), t.isEdgefromPolygon(2));
ShapeField.DecodedTriangle decoded = new ShapeField.DecodedTriangle();
ShapeField.decodeTriangle(encoded, decoded);
int expected =rectangle2D.intersectsTriangle(decoded.aX, decoded.aY, decoded.bX, decoded.bY, decoded.cX, decoded.cY) ? 0 : 1;
Document document = new Document();
addPolygonsToDoc(FIELDNAME, document, poly);
writer.addDocument(document);
@ -428,7 +412,7 @@ public class TestLatLonShape extends LuceneTestCase {
// search a bbox in the hole
Query q = LatLonShape.newBoxQuery(FIELDNAME, QueryRelation.DISJOINT,-29.46555603761226d, 0.0d, 8.381903171539307E-8d, 0.9999999403953552d);
assertEquals(expected, searcher.count(q));
assertEquals(1, searcher.count(q));
IOUtils.close(reader, dir);
}

View File

@ -17,194 +17,104 @@
package org.apache.lucene.geo;
import java.util.Random;
import org.apache.lucene.index.PointValues;
import org.apache.lucene.util.LuceneTestCase;
import org.apache.lucene.util.NumericUtils;
import static java.lang.Integer.BYTES;
public class TestRectangle2D extends LuceneTestCase {
public void testTriangleDisjoint() {
Rectangle rectangle = new Rectangle(0, 1, 0, 1);
Rectangle2D rectangle2D = Rectangle2D.create(rectangle);
int ax = GeoEncodingUtils.encodeLongitude(4);
int ay = GeoEncodingUtils.encodeLatitude(4);
int bx = GeoEncodingUtils.encodeLongitude(5);
int by = GeoEncodingUtils.encodeLatitude(5);
int cx = GeoEncodingUtils.encodeLongitude(5);
int cy = GeoEncodingUtils.encodeLatitude(4);
assertFalse(rectangle2D.intersectsTriangle(ax, ay, bx, by , cx, cy));
assertFalse(rectangle2D.containsTriangle(ax, ay, bx, by , cx, cy));
assertEquals(Component2D.WithinRelation.DISJOINT, rectangle2D.withinTriangle(ax, ay, true, bx, by , true, cx, cy, true));
XYRectangle rectangle = new XYRectangle(0f, 1f, 0f, 1f);
Component2D rectangle2D = Rectangle2D.create(rectangle);
float ax = 4f;
float ay = 4f;
float bx = 5f;
float by = 5f;
float cx = 5f;
float cy = 4f;
assertEquals(PointValues.Relation.CELL_OUTSIDE_QUERY, rectangle2D.relateTriangle(ax, ay, bx, by , cx, cy));
}
public void testTriangleIntersects() {
Rectangle rectangle = new Rectangle(0, 1, 0, 1);
Rectangle2D rectangle2D = Rectangle2D.create(rectangle);
int ax = GeoEncodingUtils.encodeLongitude(0.5);
int ay = GeoEncodingUtils.encodeLatitude(0.5);
int bx = GeoEncodingUtils.encodeLongitude(2);
int by = GeoEncodingUtils.encodeLatitude(2);
int cx = GeoEncodingUtils.encodeLongitude(0.5);
int cy = GeoEncodingUtils.encodeLatitude(2);
assertTrue(rectangle2D.intersectsTriangle(ax, ay, bx, by , cx, cy));
assertFalse(rectangle2D.containsTriangle(ax, ay, bx, by , cx, cy));
assertEquals(Component2D.WithinRelation.NOTWITHIN, rectangle2D.withinTriangle(ax, ay, true, bx, by, true, cx, cy, true));
XYRectangle rectangle = new XYRectangle(0f, 1f, 0f, 1f);
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));
}
public void testTriangleContains() {
Rectangle rectangle = new Rectangle(0, 1, 0, 1);
Rectangle2D rectangle2D = Rectangle2D.create(rectangle);
int ax = GeoEncodingUtils.encodeLongitude(0.25);
int ay = GeoEncodingUtils.encodeLatitude(0.25);
int bx = GeoEncodingUtils.encodeLongitude(0.5);
int by = GeoEncodingUtils.encodeLatitude(0.5);
int cx = GeoEncodingUtils.encodeLongitude(0.5);
int cy = GeoEncodingUtils.encodeLatitude(0.25);
assertTrue(rectangle2D.intersectsTriangle(ax, ay, bx, by , cx, cy));
assertTrue(rectangle2D.containsTriangle(ax, ay, bx, by , cx, cy));
assertEquals(Component2D.WithinRelation.NOTWITHIN, rectangle2D.withinTriangle(ax, ay, true, bx, by, true, cx, cy, true));
XYRectangle rectangle = new XYRectangle(0, 1, 0, 1);
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);
Rectangle2D rectangle2D = Rectangle2D.create(rectangle);
int ax = GeoEncodingUtils.encodeLongitude(0.0);
int ay = GeoEncodingUtils.encodeLatitude(0.0);
int bx = GeoEncodingUtils.encodeLongitude(0.0);
int by = GeoEncodingUtils.encodeLatitude(0.5);
int cx = GeoEncodingUtils.encodeLongitude(0.5);
int cy = GeoEncodingUtils.encodeLatitude(0.25);
assertTrue(rectangle2D.intersectsTriangle(ax, ay, bx, by , cx, cy));
assertTrue(rectangle2D.containsTriangle(ax, ay, bx, by , cx, cy));
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 testTriangleWithin() {
Rectangle rectangle = new Rectangle(0, 1, 0, 1);
Rectangle2D rectangle2D = Rectangle2D.create(rectangle);
int ax = GeoEncodingUtils.encodeLongitude(-10);
int ay = GeoEncodingUtils.encodeLatitude(-10);
int bx = GeoEncodingUtils.encodeLongitude(10);
int by = GeoEncodingUtils.encodeLatitude(-10);
int cx = GeoEncodingUtils.encodeLongitude(10);
int cy = GeoEncodingUtils.encodeLatitude(20);
assertTrue(rectangle2D.intersectsTriangle(ax, ay, bx, by , cx, cy));
assertFalse(rectangle2D.containsTriangle(ax, ay, bx, by , cx, cy));
assertEquals(Component2D.WithinRelation.CANDIDATE, rectangle2D.withinTriangle(ax, ay, true, bx, by, true, cx, cy, true));
}
public void testTriangleWithinCrossingDateLine() {
Rectangle rectangle = new Rectangle(0, 2, 179, -179);
Rectangle2D rectangle2D = Rectangle2D.create(rectangle);
int ax = GeoEncodingUtils.encodeLongitude(169);
int ay = GeoEncodingUtils.encodeLatitude(-10);
int bx = GeoEncodingUtils.encodeLongitude(180);
int by = GeoEncodingUtils.encodeLatitude(-10);
int cx = GeoEncodingUtils.encodeLongitude(180);
int cy = GeoEncodingUtils.encodeLatitude(30);
assertTrue(rectangle2D.intersectsTriangle(ax, ay, bx, by , cx, cy));
assertFalse(rectangle2D.containsTriangle(ax, ay, bx, by , cx, cy));
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);
});
}
public void testRandomTriangles() {
Rectangle rectangle = GeoTestUtil.nextBox();
while(rectangle.crossesDateline()) {
rectangle = GeoTestUtil.nextBox();
}
Rectangle2D rectangle2D = Rectangle2D.create(rectangle);
Random random = random();
XYRectangle rectangle = ShapeTestUtil.nextBox(random);
Component2D rectangle2D = Rectangle2D.create(rectangle);
for (int i =0; i < 100; i++) {
int ax = GeoEncodingUtils.encodeLongitude(GeoTestUtil.nextLongitude());
int ay = GeoEncodingUtils.encodeLatitude(GeoTestUtil.nextLatitude());
int bx = GeoEncodingUtils.encodeLongitude(GeoTestUtil.nextLongitude());
int by = GeoEncodingUtils.encodeLatitude(GeoTestUtil.nextLatitude());
int cx = GeoEncodingUtils.encodeLongitude(GeoTestUtil.nextLongitude());
int cy = GeoEncodingUtils.encodeLatitude(GeoTestUtil.nextLatitude());
float ax = ShapeTestUtil.nextFloat(random);
float ay = ShapeTestUtil.nextFloat(random);
float bx = ShapeTestUtil.nextFloat(random);
float by = ShapeTestUtil.nextFloat(random);
float cx = ShapeTestUtil.nextFloat(random);
float cy = ShapeTestUtil.nextFloat(random);
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);
float tMinX = StrictMath.min(StrictMath.min(ax, bx), cx);
float tMaxX = StrictMath.max(StrictMath.max(ax, bx), cx);
float tMinY = StrictMath.min(StrictMath.min(ay, by), cy);
float tMaxY = StrictMath.max(StrictMath.max(ay, by), cy);
byte[] triangle = new byte[4 * BYTES];
NumericUtils.intToSortableBytes(tMinY, triangle, 0);
NumericUtils.intToSortableBytes(tMinX, triangle, BYTES);
NumericUtils.intToSortableBytes(tMaxY, triangle, 2 * BYTES);
NumericUtils.intToSortableBytes(tMaxX, triangle, 3 * BYTES);
PointValues.Relation r;
if (random().nextBoolean()) {
r = rectangle2D.relateRangeBBox(BYTES, 0, triangle, 3 * BYTES, 2 * BYTES, triangle);
} else {
r = rectangle2D.intersectRangeBBox(BYTES, 0, triangle, 3 * BYTES, 2 * BYTES, triangle);
}
PointValues.Relation r = rectangle2D.relate(tMinX, tMaxX, tMinY, tMaxY);
if (r == PointValues.Relation.CELL_OUTSIDE_QUERY) {
assertFalse(rectangle2D.intersectsTriangle(ax, ay, bx, by , cx, cy));
assertFalse(rectangle2D.containsTriangle(ax, ay, bx, by , cx, cy));
assertEquals(Component2D.WithinRelation.DISJOINT, rectangle2D.withinTriangle(ax, ay, true, bx, by, true, cx, cy, true));
} else if (rectangle2D.containsTriangle(ax, ay, bx, by , cx, cy)) {
assertTrue(rectangle2D.intersectsTriangle(ax, ay, bx, by , cx, cy));
assertTrue(rectangle2D.withinTriangle(ax, ay, true, bx, by, true, cx, cy, true) != Component2D.WithinRelation.CANDIDATE);
} else if (rectangle2D.withinTriangle(ax, ay, true, bx, by , true, cx, cy, true) == Component2D.WithinRelation.CANDIDATE) {
assertTrue(rectangle2D.intersectsTriangle(ax, ay, bx, by , cx, cy));
assertFalse(rectangle2D.containsTriangle(ax, ay, bx, by , cx, cy));
assertEquals(PointValues.Relation.CELL_OUTSIDE_QUERY, rectangle2D.relateTriangle(ax, ay, bx, by , cx, cy));
}
else if (r == PointValues.Relation.CELL_INSIDE_QUERY) {
assertEquals(PointValues.Relation.CELL_INSIDE_QUERY, rectangle2D.relateTriangle(ax, ay, bx, by , cx, cy));
}
}
}
public void testIntersectOptimization() {
byte[] minTriangle = box(0, 0, 10, 5);
byte[] maxTriangle = box(20, 10, 30, 15);
Rectangle2D rectangle2D = Rectangle2D.create(new Rectangle(-0.1, 30.1, -0.1, 15.1));
assertEquals(PointValues.Relation.CELL_INSIDE_QUERY,
rectangle2D.intersectRangeBBox(BYTES, 0, minTriangle, 3 * BYTES, 2 * BYTES, maxTriangle));
assertEquals(PointValues.Relation.CELL_INSIDE_QUERY,
rectangle2D.relateRangeBBox(BYTES, 0, minTriangle, 3 * BYTES, 2 * BYTES, maxTriangle));
rectangle2D = Rectangle2D.create(new Rectangle(-0.1, 30.1, -0.1, 10.1));
assertEquals(PointValues.Relation.CELL_INSIDE_QUERY,
rectangle2D.intersectRangeBBox(BYTES, 0, minTriangle, 3 * BYTES, 2 * BYTES, maxTriangle));
assertEquals(PointValues.Relation.CELL_CROSSES_QUERY,
rectangle2D.relateRangeBBox(BYTES, 0, minTriangle, 3 * BYTES, 2 * BYTES, maxTriangle));
rectangle2D = Rectangle2D.create(new Rectangle(-0.1, 30.1, 4.9, 15.1));
assertEquals(PointValues.Relation.CELL_INSIDE_QUERY,
rectangle2D.intersectRangeBBox(BYTES, 0, minTriangle, 3 * BYTES, 2 * BYTES, maxTriangle));
assertEquals(PointValues.Relation.CELL_CROSSES_QUERY,
rectangle2D.relateRangeBBox(BYTES, 0, minTriangle, 3 * BYTES, 2 * BYTES, maxTriangle));
rectangle2D = Rectangle2D.create(new Rectangle(-0.1, 20.1, -0.1, 15.1));
assertEquals(PointValues.Relation.CELL_INSIDE_QUERY,
rectangle2D.intersectRangeBBox(BYTES, 0, minTriangle, 3 * BYTES, 2 * BYTES, maxTriangle));
assertEquals(PointValues.Relation.CELL_CROSSES_QUERY,
rectangle2D.relateRangeBBox(BYTES, 0, minTriangle, 3 * BYTES, 2 * BYTES, maxTriangle));
rectangle2D = Rectangle2D.create(new Rectangle(9.9, 30.1, -0.1, 15.1));
assertEquals(PointValues.Relation.CELL_INSIDE_QUERY,
rectangle2D.intersectRangeBBox(BYTES, 0, minTriangle, 3 * BYTES, 2 * BYTES, maxTriangle));
assertEquals(PointValues.Relation.CELL_CROSSES_QUERY,
rectangle2D.relateRangeBBox(BYTES, 0, minTriangle, 3 * BYTES, 2 * BYTES, maxTriangle));
rectangle2D = Rectangle2D.create(new Rectangle(5, 25, 3, 13));
assertEquals(PointValues.Relation.CELL_CROSSES_QUERY,
rectangle2D.intersectRangeBBox(BYTES, 0, minTriangle, 3 * BYTES, 2 * BYTES, maxTriangle));
assertEquals(PointValues.Relation.CELL_CROSSES_QUERY,
rectangle2D.relateRangeBBox(BYTES, 0, minTriangle, 3 * BYTES, 2 * BYTES, maxTriangle));
}
private byte[] box(int minY, int minX, int maxY, int maxX) {
byte[] bytes = new byte[4 * BYTES];
NumericUtils.intToSortableBytes(GeoEncodingUtils.encodeLatitude(minY), bytes, 0); // min y
NumericUtils.intToSortableBytes(GeoEncodingUtils.encodeLongitude(minX), bytes, BYTES); // min x
NumericUtils.intToSortableBytes(GeoEncodingUtils.encodeLatitude(maxY), bytes, 2 * BYTES); // max y
NumericUtils.intToSortableBytes(GeoEncodingUtils.encodeLongitude(maxX), bytes, 3 * BYTES); // max x
return bytes;
}
}
}

View File

@ -1,91 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.geo;
import java.util.Random;
import org.apache.lucene.index.PointValues;
import org.apache.lucene.util.LuceneTestCase;
public class TestXYRectangle2D extends LuceneTestCase {
public void testTriangleDisjoint() {
XYRectangle rectangle = new XYRectangle(0f, 1f, 0f, 1f);
Component2D rectangle2D = XYRectangle2D.create(rectangle);
float ax = 4f;
float ay = 4f;
float bx = 5f;
float by = 5f;
float cx = 5f;
float cy = 4f;
assertEquals(PointValues.Relation.CELL_OUTSIDE_QUERY, rectangle2D.relateTriangle(ax, ay, bx, by , cx, cy));
}
public void testTriangleIntersects() {
XYRectangle rectangle = new XYRectangle(0f, 1f, 0f, 1f);
Component2D rectangle2D = XYRectangle2D.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));
}
public void testTriangleContains() {
XYRectangle rectangle = new XYRectangle(0, 1, 0, 1);
Component2D rectangle2D = XYRectangle2D.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 testRandomTriangles() {
Random random = random();
XYRectangle rectangle = ShapeTestUtil.nextBox(random);
Component2D rectangle2D = XYRectangle2D.create(rectangle);
for (int i =0; i < 100; i++) {
float ax = ShapeTestUtil.nextFloat(random);
float ay = ShapeTestUtil.nextFloat(random);
float bx = ShapeTestUtil.nextFloat(random);
float by = ShapeTestUtil.nextFloat(random);
float cx = ShapeTestUtil.nextFloat(random);
float cy = ShapeTestUtil.nextFloat(random);
float tMinX = StrictMath.min(StrictMath.min(ax, bx), cx);
float tMaxX = StrictMath.max(StrictMath.max(ax, bx), cx);
float tMinY = StrictMath.min(StrictMath.min(ay, by), cy);
float tMaxY = StrictMath.max(StrictMath.max(ay, by), cy);
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));
}
else if (r == PointValues.Relation.CELL_INSIDE_QUERY) {
assertEquals(PointValues.Relation.CELL_INSIDE_QUERY, rectangle2D.relateTriangle(ax, ay, bx, by , cx, cy));
}
}
}
}