mirror of https://github.com/apache/lucene.git
LUCENE-9225: Rectangle extends LatLonGeometry so it can be used in a geometry collection (#1258)
This commit is contained in:
parent
c313365c5f
commit
286d22717b
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -45,7 +45,7 @@ public final class XYRectangle extends XYGeometry {
|
|||
|
||||
@Override
|
||||
protected Component2D toComponent2D() {
|
||||
return XYRectangle2D.create(this);
|
||||
return Rectangle2D.create(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue