LUCENE-8620: Add CONTAINS support for LatLonShape and XYShape (#872)

This commit is contained in:
Ignacio Vera 2019-12-11 09:16:51 +01:00 committed by iverase
parent dda88f73bb
commit a06a2ea3da
37 changed files with 1289 additions and 162 deletions

View File

@ -11,8 +11,8 @@ API Changes
(Jack Conradson via Adrien Grand)
New Features
---------------------
(No changes)
* LUCENE-8620: Add CONTAINS support for LatLonShape and XYShape. (Ignacio Vera)
Improvements

View File

@ -60,6 +60,33 @@ public interface Component2D {
return relateTriangle(minX, maxX, minY, maxY, aX, aY, bX, bY, cX, cY);
}
/** Used by withinTriangle to check the within relationship between a triangle and the query shape
* (e.g. if the query shape is within the triangle). */
enum WithinRelation {
/** If the shape is a candidate for within. Typically this is return if the query shape is fully inside
* the triangle or if the query shape intersects only edges that do not belong to the original shape. */
CANDIDATE,
/** The query shape intersects an edge that does belong to the original shape or any point of
* the triangle is inside the shape. */
NOTWITHIN,
/** The query shape is disjoint with the triangle. */
DISJOINT
}
/** Compute the within relation of this component2D with a triangle **/
default WithinRelation withinTriangle(double aX, double aY, boolean ab, double bX, double bY, boolean bc, double cX, double cY, boolean ca) {
double minY = StrictMath.min(StrictMath.min(aY, bY), cY);
double minX = StrictMath.min(StrictMath.min(aX, bX), cX);
double maxY = StrictMath.max(StrictMath.max(aY, bY), cY);
double maxX = StrictMath.max(StrictMath.max(aX, bX), cX);
return withinTriangle(minX, maxX, minY, maxY, aX, aY, ab, bX, bY, bc, cX, cY, ca);
}
/** Compute the within relation of this component2D with a triangle **/
WithinRelation withinTriangle(double minX, double maxX, double minY, double maxY,
double aX, double aY, boolean ab, double bX, double bY, boolean bc, double cX, double cY, boolean ca);
/** Compute whether the bounding boxes are disjoint **/
static boolean disjoint(double minX1, double maxX1, double minY1, double maxY1, double minX2, double maxX2, double minY2, double maxY2) {
return (maxY1 < minY2 || minY1 > maxY2 || maxX1 < minX2 || minX1 > maxX2);

View File

@ -122,6 +122,15 @@ final class ComponentTree implements Component2D {
return 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) {
if (left != null || right != null) {
throw new IllegalArgumentException("withinTriangle is not supported for shapes with more than one component");
}
return component.withinTriangle(minX, maxX, minY, maxY, aX, aY, ab, bX, bY, bc, cX, cY, ca);
}
/** Returns relation to the provided rectangle */
@Override
public Relation relate(double minX, double maxX, double minY, double maxY) {

View File

@ -165,6 +165,64 @@ public class Polygon2D implements Component2D {
return relateIndexedTriangle(minX, maxX, minY, maxY, ax, ay, bx, by, cx, cy);
}
@Override
public WithinRelation withinTriangle(double minX, double maxX, double minY, double maxY,
double ax, double ay, boolean ab, double bx, double by, boolean bc, double cx, double cy, boolean ca) {
// short cut, lines and points cannot contain this type of shape
if ((ax == bx && ay == by) || (ax == cx && ay == cy) || (bx == cx && by == cy)) {
return WithinRelation.DISJOINT;
}
if (Component2D.disjoint(this.minX, this.maxX, this.minY, this.maxY, minX, maxX, minY, maxY)) {
return WithinRelation.DISJOINT;
}
// if any of the points is inside the polygon, the polygon cannot be within this indexed
// shape because points belong to the original indexed shape.
if (contains(ax, ay) || contains(bx, by) || contains(cx, cy)) {
return WithinRelation.NOTWITHIN;
}
WithinRelation relation = WithinRelation.DISJOINT;
// if any of the edges intersects an the edge belongs to the shape then it cannot be within.
// if it only intersects edges that do not belong to the shape, then it is a candidate
// we skip edges at the dateline to support shapes crossing it
if (tree.crossesLine(minX, maxX, minY, maxY, ax, ay, bx, by)) {
if (ab == true) {
return WithinRelation.NOTWITHIN;
} else {
relation = WithinRelation.CANDIDATE;
}
}
if (tree.crossesLine(minX, maxX, minY, maxY, bx, by, cx, cy)) {
if (bc == true) {
return WithinRelation.NOTWITHIN;
} else {
relation = WithinRelation.CANDIDATE;
}
}
if (tree.crossesLine(minX, maxX, minY, maxY, cx, cy, ax, ay)) {
if (ca == true) {
return WithinRelation.NOTWITHIN;
} else {
relation = WithinRelation.CANDIDATE;
}
}
// if any of the edges crosses and 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, tree.x1, tree.y1, ax, ay, bx, by, cx, cy) == true) {
return WithinRelation.CANDIDATE;
}
return relation;
}
/** relates an indexed line segment (a "flat triangle") with the polygon */
private Relation relateIndexedLineSegment(double minX, double maxX, double minY, double maxY,
double a2x, double a2y, double b2x, double b2y) {

View File

@ -21,10 +21,13 @@ import java.util.List;
import org.apache.lucene.document.ShapeField.QueryRelation; // javadoc
import org.apache.lucene.document.ShapeField.Triangle;
import org.apache.lucene.geo.GeoUtils;
import org.apache.lucene.geo.Line;
import org.apache.lucene.geo.Polygon;
import org.apache.lucene.geo.Tessellator;
import org.apache.lucene.index.PointValues; // javadoc
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Query;
import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitude;
@ -93,6 +96,12 @@ public class LatLonShape {
/** create a query to find all indexed geo shapes that intersect a defined bounding box **/
public static Query newBoxQuery(String field, QueryRelation queryRelation, double minLatitude, double maxLatitude, double minLongitude, double maxLongitude) {
if (queryRelation == QueryRelation.CONTAINS && minLongitude > maxLongitude) {
BooleanQuery.Builder builder = new BooleanQuery.Builder();
builder.add(newBoxQuery(field, queryRelation, minLatitude, maxLatitude, minLongitude, GeoUtils.MAX_LON_INCL), BooleanClause.Occur.MUST);
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);
}
@ -100,6 +109,13 @@ public class LatLonShape {
* note: does not support dateline crossing
**/
public static Query newLineQuery(String field, QueryRelation queryRelation, Line... lines) {
if (queryRelation == QueryRelation.CONTAINS && lines.length > 1) {
BooleanQuery.Builder builder = new BooleanQuery.Builder();
for (int i =0; i < lines.length; i++) {
builder.add(newLineQuery(field, queryRelation, lines[i]), BooleanClause.Occur.MUST);
}
return builder.build();
}
return new LatLonShapeLineQuery(field, queryRelation, lines);
}
@ -107,6 +123,13 @@ public class LatLonShape {
* note: does not support dateline crossing
**/
public static Query newPolygonQuery(String field, QueryRelation queryRelation, Polygon... polygons) {
if (queryRelation == QueryRelation.CONTAINS && polygons.length > 1) {
BooleanQuery.Builder builder = new BooleanQuery.Builder();
for (int i =0; i < polygons.length; i++) {
builder.add(newPolygonQuery(field, queryRelation, polygons[i]), BooleanClause.Occur.MUST);
}
return builder.build();
}
return new LatLonShapePolygonQuery(field, queryRelation, polygons);
}
}

View File

@ -17,6 +17,7 @@
package org.apache.lucene.document;
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.index.PointValues.Relation;
@ -69,6 +70,16 @@ final class LatLonShapeBoundingBoxQuery extends ShapeQuery {
}
}
@Override
protected Component2D.WithinRelation queryWithin(byte[] t, ShapeField.DecodedTriangle scratchTriangle) {
// decode indexed triangle
ShapeField.decodeTriangle(t, scratchTriangle);
return rectangle2D.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));

View File

@ -74,7 +74,7 @@ final class LatLonShapeLineQuery extends ShapeQuery {
@Override
protected Relation relateRangeBBoxToQuery(int minXOffset, int minYOffset, byte[] minTriangle,
int maxXOffset, int maxYOffset, byte[] maxTriangle) {
int maxXOffset, int maxYOffset, byte[] maxTriangle) {
double minLat = GeoEncodingUtils.decodeLatitude(NumericUtils.sortableBytesToInt(minTriangle, minYOffset));
double minLon = GeoEncodingUtils.decodeLongitude(NumericUtils.sortableBytesToInt(minTriangle, minXOffset));
double maxLat = GeoEncodingUtils.decodeLatitude(NumericUtils.sortableBytesToInt(maxTriangle, maxYOffset));
@ -103,6 +103,20 @@ final class LatLonShapeLineQuery extends ShapeQuery {
}
}
@Override
protected Component2D.WithinRelation queryWithin(byte[] t, ShapeField.DecodedTriangle scratchTriangle) {
ShapeField.decodeTriangle(t, scratchTriangle);
double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle.aY);
double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle.aX);
double blat = GeoEncodingUtils.decodeLatitude(scratchTriangle.bY);
double blon = GeoEncodingUtils.decodeLongitude(scratchTriangle.bX);
double clat = GeoEncodingUtils.decodeLatitude(scratchTriangle.cY);
double clon = GeoEncodingUtils.decodeLongitude(scratchTriangle.cX);
return line2D.withinTriangle(alon, alat, scratchTriangle.ab, blon, blat, scratchTriangle.bc, clon, clat, scratchTriangle.ca);
}
@Override
public String toString(String field) {
final StringBuilder sb = new StringBuilder();

View File

@ -97,6 +97,20 @@ final class LatLonShapePolygonQuery extends ShapeQuery {
}
}
@Override
protected Component2D.WithinRelation queryWithin(byte[] t, ShapeField.DecodedTriangle scratchTriangle) {
ShapeField.decodeTriangle(t, scratchTriangle);
double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle.aY);
double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle.aX);
double blat = GeoEncodingUtils.decodeLatitude(scratchTriangle.bY);
double blon = GeoEncodingUtils.decodeLongitude(scratchTriangle.bX);
double clat = GeoEncodingUtils.decodeLatitude(scratchTriangle.cY);
double clon = GeoEncodingUtils.decodeLongitude(scratchTriangle.cX);
return poly2D.withinTriangle(alon, alat, scratchTriangle.ab, blon, blat, scratchTriangle.bc, clon, clat, scratchTriangle.ca);
}
@Override
public String toString(String field) {
final StringBuilder sb = new StringBuilder();

View File

@ -88,7 +88,7 @@ public final class ShapeField {
/** Query Relation Types **/
public enum QueryRelation {
INTERSECTS, WITHIN, DISJOINT
INTERSECTS, WITHIN, DISJOINT, CONTAINS
}
private static final int MINY_MINX_MAXY_MAXX_Y_X = 0;

View File

@ -20,6 +20,7 @@ import java.io.IOException;
import java.util.Objects;
import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.Component2D;
import org.apache.lucene.index.FieldInfo;
import org.apache.lucene.index.LeafReader;
import org.apache.lucene.index.LeafReaderContext;
@ -61,9 +62,11 @@ abstract class ShapeQuery extends Query {
/** field name */
final String field;
/** query relation
* disjoint: {@code CELL_OUTSIDE_QUERY}
* intersects: {@code CELL_CROSSES_QUERY},
* within: {@code CELL_WITHIN_QUERY} */
* disjoint: {@link QueryRelation#DISJOINT},
* intersects: {@link QueryRelation#INTERSECTS},
* within: {@link QueryRelation#DISJOINT},
* contains: {@link QueryRelation#CONTAINS}
* */
final QueryRelation queryRelation;
protected ShapeQuery(String field, final QueryRelation queryType) {
@ -85,6 +88,9 @@ abstract class ShapeQuery extends Query {
/** returns true if the provided triangle matches the query */
protected abstract boolean queryMatches(byte[] triangle, ShapeField.DecodedTriangle scratchTriangle, ShapeField.QueryRelation queryRelation);
/** Return the within relationship between the query and the indexed shape.*/
protected abstract Component2D.WithinRelation queryWithin(byte[] triangle, ShapeField.DecodedTriangle scratchTriangle);
/** relates a range of triangles (internal node) to the query */
protected Relation relateRangeToQuery(byte[] minTriangle, byte[] maxTriangle, QueryRelation queryRelation) {
// compute bounding box of internal node
@ -132,11 +138,10 @@ abstract class ShapeQuery extends Query {
final Weight weight = this;
final Relation rel = relateRangeToQuery(values.getMinPackedValue(), values.getMaxPackedValue(), queryRelation);
if (rel == Relation.CELL_OUTSIDE_QUERY) {
if (rel == Relation.CELL_OUTSIDE_QUERY || (rel == Relation.CELL_INSIDE_QUERY && queryRelation == QueryRelation.CONTAINS)) {
// no documents match the query
return null;
}
else if (values.getDocCount() == reader.maxDoc() && rel == Relation.CELL_INSIDE_QUERY) {
} else if (values.getDocCount() == reader.maxDoc() && rel == Relation.CELL_INSIDE_QUERY) {
// all documents match the query
return new ScorerSupplier() {
@Override
@ -151,6 +156,7 @@ abstract class ShapeQuery extends Query {
};
} else {
if (queryRelation != QueryRelation.INTERSECTS
&& queryRelation != QueryRelation.CONTAINS
&& hasAnyHits(query, values) == false) {
// First we check if we have any hits so we are fast in the adversarial case where
// the shape does not match any documents and we are in the dense case
@ -226,6 +232,7 @@ abstract class ShapeQuery extends Query {
case INTERSECTS: return getSparseScorer(reader, weight, boost, scoreMode);
case WITHIN:
case DISJOINT: return getDenseScorer(reader, weight, boost, scoreMode);
case CONTAINS: return getContainsDenseScorer(reader, weight, boost, scoreMode);
default: throw new IllegalArgumentException("Unsupported query type :[" + query.getQueryRelation() + "]");
}
}
@ -278,6 +285,17 @@ abstract class ShapeQuery extends Query {
return new ConstantScoreScorer(weight, boost, scoreMode, iterator);
}
private Scorer getContainsDenseScorer(LeafReader reader, Weight weight, final float boost, ScoreMode scoreMode) throws IOException {
final FixedBitSet result = new FixedBitSet(reader.maxDoc());
final long[] cost = new long[]{0};
// Get potential documents.
final FixedBitSet excluded = new FixedBitSet(reader.maxDoc());
values.intersect(getContainsDenseVisitor(query, result, excluded, cost));
result.andNot(excluded);
final DocIdSetIterator iterator = new BitSetIterator(result, cost[0]);
return new ConstantScoreScorer(weight, boost, scoreMode, iterator);
}
@Override
public long cost() {
if (cost == -1) {
@ -389,6 +407,48 @@ abstract class ShapeQuery extends Query {
};
}
/** create a visitor that adds documents that match the query using a dense bitset; used with CONTAINS */
private static IntersectVisitor getContainsDenseVisitor(final ShapeQuery query, final FixedBitSet result, final FixedBitSet excluded, final long[] cost) {
return new IntersectVisitor() {
final ShapeField.DecodedTriangle scratchTriangle = new ShapeField.DecodedTriangle();
@Override
public void visit(int docID) {
excluded.set(docID);
}
@Override
public void visit(int docID, byte[] t) {
Component2D.WithinRelation within = query.queryWithin(t, scratchTriangle);
if (within == Component2D.WithinRelation.CANDIDATE) {
cost[0]++;
result.set(docID);
} else if (within == Component2D.WithinRelation.NOTWITHIN) {
excluded.set(docID);
}
}
@Override
public void visit(DocIdSetIterator iterator, byte[] t) throws IOException {
Component2D.WithinRelation within = query.queryWithin(t, scratchTriangle);
int docID;
while ((docID = iterator.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
if (within == Component2D.WithinRelation.CANDIDATE) {
cost[0]++;
result.set(docID);
} else if (within == Component2D.WithinRelation.NOTWITHIN) {
excluded.set(docID);
}
}
}
@Override
public Relation compare(byte[] minTriangle, byte[] maxTriangle) {
return query.relateRangeToQuery(minTriangle, maxTriangle, query.getQueryRelation());
}
};
}
/** create a visitor that clears documents that do not match the polygon query using a dense bitset; used with WITHIN & DISJOINT */
private static IntersectVisitor getInverseDenseVisitor(final ShapeQuery query, final FixedBitSet result, final long[] cost) {
return new IntersectVisitor() {

View File

@ -25,6 +25,8 @@ import org.apache.lucene.geo.Tessellator;
import org.apache.lucene.index.PointValues; // javadoc
import org.apache.lucene.geo.XYLine;
import org.apache.lucene.geo.XYPolygon;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Query;
import static org.apache.lucene.geo.XYEncodingUtils.encode;
@ -93,11 +95,25 @@ public class XYShape {
/** create a query to find all cartesian shapes that intersect a provided linestring (or array of linestrings) **/
public static Query newLineQuery(String field, QueryRelation queryRelation, XYLine... lines) {
if (queryRelation == QueryRelation.CONTAINS && lines.length > 1) {
BooleanQuery.Builder builder = new BooleanQuery.Builder();
for (int i =0; i < lines.length; i++) {
builder.add(newLineQuery(field, queryRelation, lines[i]), BooleanClause.Occur.MUST);
}
return builder.build();
}
return new XYShapeLineQuery(field, queryRelation, lines);
}
/** create a query to find all cartesian shapes that intersect a provided polygon (or array of polygons) **/
public static Query newPolygonQuery(String field, QueryRelation queryRelation, XYPolygon... polygons) {
if (queryRelation == QueryRelation.CONTAINS && polygons.length > 1) {
BooleanQuery.Builder builder = new BooleanQuery.Builder();
for (int i =0; i < polygons.length; i++) {
builder.add(newPolygonQuery(field, queryRelation, polygons[i]), BooleanClause.Occur.MUST);
}
return builder.build();
}
return new XYShapePolygonQuery(field, queryRelation, polygons);
}
}

View File

@ -17,9 +17,13 @@
package org.apache.lucene.document;
import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.Component2D;
import org.apache.lucene.geo.XYRectangle;
import org.apache.lucene.geo.XYRectangle2D;
import org.apache.lucene.index.PointValues;
import org.apache.lucene.util.NumericUtils;
import static org.apache.lucene.geo.XYEncodingUtils.decode;
/**
* Finds all previously indexed cartesian shapes that intersect the specified bounding box.
@ -30,21 +34,46 @@ import org.apache.lucene.index.PointValues;
* @lucene.experimental
**/
public class XYShapeBoundingBoxQuery extends ShapeQuery {
final XYRectangle2D rectangle2D;
final Component2D rectangle2D;
final private XYRectangle rectangle;
public XYShapeBoundingBoxQuery(String field, QueryRelation queryRelation, double minX, double maxX, double minY, double maxY) {
super(field, queryRelation);
XYRectangle rectangle = new XYRectangle(minX, maxX, minY, maxY);
this.rectangle2D = XYRectangle2D.create(rectangle);
this.rectangle = new XYRectangle(minX, maxX, minY, maxY);
this.rectangle2D = XYRectangle2D.create(this.rectangle);
}
@Override
protected PointValues.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);
double minY = decode(NumericUtils.sortableBytesToInt(minTriangle, minYOffset));
double minX = decode(NumericUtils.sortableBytesToInt(minTriangle, minXOffset));
double maxY = decode(NumericUtils.sortableBytesToInt(maxTriangle, maxYOffset));
double maxX = decode(NumericUtils.sortableBytesToInt(maxTriangle, maxXOffset));
// check internal node against query
PointValues.Relation rel = rectangle2D.relate(minX, maxX, minY, maxY);
// TODO: Check if this really helps
if (queryRelation == QueryRelation.INTERSECTS && rel == PointValues.Relation.CELL_CROSSES_QUERY) {
// for intersects we can restrict the conditions by using the inner box
double innerMaxY = decode(NumericUtils.sortableBytesToInt(maxTriangle, minYOffset));
if (rectangle2D.relate(minX, maxX, minY, innerMaxY) == PointValues.Relation.CELL_INSIDE_QUERY) {
return PointValues.Relation.CELL_INSIDE_QUERY;
}
double innerMaX = decode(NumericUtils.sortableBytesToInt(maxTriangle, minXOffset));
if (rectangle2D.relate(minX, innerMaX, minY, maxY) == PointValues.Relation.CELL_INSIDE_QUERY) {
return PointValues.Relation.CELL_INSIDE_QUERY;
}
double innerMinY = decode(NumericUtils.sortableBytesToInt(minTriangle, maxYOffset));
if (rectangle2D.relate(minX, maxX, innerMinY, maxY) == PointValues.Relation.CELL_INSIDE_QUERY) {
return PointValues.Relation.CELL_INSIDE_QUERY;
}
double innerMinX = decode(NumericUtils.sortableBytesToInt(minTriangle, maxXOffset));
if (rectangle2D.relate(innerMinX, maxX, minY, maxY) == PointValues.Relation.CELL_INSIDE_QUERY) {
return PointValues.Relation.CELL_INSIDE_QUERY;
}
}
return rectangle2D.relateRangeBBox(minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle);
return rel;
}
/** returns true if the query matches the encoded triangle */
@ -53,21 +82,35 @@ public class XYShapeBoundingBoxQuery extends ShapeQuery {
// decode indexed triangle
ShapeField.decodeTriangle(t, scratchTriangle);
int aY = scratchTriangle.aY;
int aX = scratchTriangle.aX;
int bY = scratchTriangle.bY;
int bX = scratchTriangle.bX;
int cY = scratchTriangle.cY;
int cX = scratchTriangle.cX;
double aY = decode(scratchTriangle.aY);
double aX = decode(scratchTriangle.aX);
double bY = decode(scratchTriangle.bY);
double bX = decode(scratchTriangle.bX);
double cY = decode(scratchTriangle.cY);
double cX = decode(scratchTriangle.cX);
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 rectangle2D.relateTriangle(aX, aY, bX, bY, cX, cY) != PointValues.Relation.CELL_OUTSIDE_QUERY;
case WITHIN: return rectangle2D.contains(aX, aY) && rectangle2D.contains(bX, bY) && rectangle2D.contains(cX, cY);
case DISJOINT: return rectangle2D.relateTriangle(aX, aY, bX, bY, cX, cY) == PointValues.Relation.CELL_OUTSIDE_QUERY;
default: throw new IllegalArgumentException("Unsupported query type :[" + queryRelation + "]");
}
}
@Override
protected Component2D.WithinRelation queryWithin(byte[] t, ShapeField.DecodedTriangle scratchTriangle) {
ShapeField.decodeTriangle(t, scratchTriangle);
double aY = decode(scratchTriangle.aY);
double aX = decode(scratchTriangle.aX);
double bY = decode(scratchTriangle.bY);
double bX = decode(scratchTriangle.bX);
double cY = decode(scratchTriangle.cY);
double cX = decode(scratchTriangle.cX);
return rectangle2D.withinTriangle(aX, aY, scratchTriangle.ab, bX, bY, scratchTriangle.bc, cX, cY, scratchTriangle.ca);
}
@Override
public boolean equals(Object o) {
return sameClassAs(o) && equalsTo(getClass().cast(o));
@ -75,13 +118,13 @@ public class XYShapeBoundingBoxQuery extends ShapeQuery {
@Override
protected boolean equalsTo(Object o) {
return super.equalsTo(o) && rectangle2D.equals(((XYShapeBoundingBoxQuery)o).rectangle2D);
return super.equalsTo(o) && rectangle.equals(((XYShapeBoundingBoxQuery)o).rectangle);
}
@Override
public int hashCode() {
int hash = super.hashCode();
hash = 31 * hash + rectangle2D.hashCode();
hash = 31 * hash + rectangle.hashCode();
return hash;
}
@ -95,7 +138,7 @@ public class XYShapeBoundingBoxQuery extends ShapeQuery {
sb.append(this.field);
sb.append(':');
}
sb.append(rectangle2D.toString());
sb.append(rectangle.toString());
return sb.toString();
}
}
}

View File

@ -105,6 +105,20 @@ final class XYShapeLineQuery extends ShapeQuery {
}
}
@Override
protected Component2D.WithinRelation queryWithin(byte[] t, ShapeField.DecodedTriangle scratchTriangle) {
ShapeField.decodeTriangle(t, scratchTriangle);
double alat = decode(scratchTriangle.aY);
double alon = decode(scratchTriangle.aX);
double blat = decode(scratchTriangle.bY);
double blon = decode(scratchTriangle.bX);
double clat = decode(scratchTriangle.cY);
double clon = decode(scratchTriangle.cX);
return line2D.withinTriangle(alon, alat, scratchTriangle.ab, blon, blat, scratchTriangle.bc, clon, clat, scratchTriangle.ca);
}
@Override
public String toString(String field) {
final StringBuilder sb = new StringBuilder();

View File

@ -96,6 +96,20 @@ final class XYShapePolygonQuery extends ShapeQuery {
}
}
@Override
protected Component2D.WithinRelation queryWithin(byte[] t, ShapeField.DecodedTriangle scratchTriangle) {
ShapeField.decodeTriangle(t, scratchTriangle);
double alat = decode(scratchTriangle.aY);
double alon = decode(scratchTriangle.aX);
double blat = decode(scratchTriangle.bY);
double blon = decode(scratchTriangle.bX);
double clat = decode(scratchTriangle.cY);
double clon = decode(scratchTriangle.cX);
return poly2D.withinTriangle(alon, alat, scratchTriangle.ab, blon, blat, scratchTriangle.bc, clon, clat, scratchTriangle.ca);
}
@Override
public String toString(String field) {
final StringBuilder sb = new StringBuilder();

View File

@ -133,6 +133,57 @@ public final class Line2D implements Component2D {
return 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 this type of shape
if ((ax == bx && ay == by) || (ax == cx && ay == cy) || (bx == cx && by == cy)) {
return WithinRelation.DISJOINT;
}
if (Component2D.disjoint(this.minX, this.maxX, this.minY, this.maxY, minX, maxX, minY, maxY)) {
return WithinRelation.DISJOINT;
}
WithinRelation relation = WithinRelation.DISJOINT;
// if any of the edges intersects an the edge belongs to the shape then it cannot be within.
// if it only intersects edges that do not belong to the shape, then it is a candidate
// we skip edges at the dateline to support shapes crossing it
if (tree.crossesLine(minX, maxX, minY, maxY, ax, ay, bx, by)) {
if (ab == true) {
return WithinRelation.NOTWITHIN;
} else {
relation = WithinRelation.CANDIDATE;
}
}
if (tree.crossesLine(minX, maxX, minY, maxY, bx, by, cx, cy)) {
if (bc == true) {
return WithinRelation.NOTWITHIN;
} else {
relation = WithinRelation.CANDIDATE;
}
}
if (tree.crossesLine(minX, maxX, minY, maxY, cx, cy, ax, ay)) {
if (ca == true) {
return WithinRelation.NOTWITHIN;
} else {
relation = WithinRelation.CANDIDATE;
}
}
// if any of the edges crosses and 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, tree.x1, tree.y1, ax, ay, bx, by, cx, cy) == true) {
return WithinRelation.CANDIDATE;
}
return relation;
}
/** create a Line2D edge tree from provided array of Linestrings */
public static Component2D create(Line... lines) {
Component2D components[] = new Component2D[lines.length];

View File

@ -170,6 +170,66 @@ public class Rectangle2D {
return false;
}
/** 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");
}
// 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 **/
public boolean containsTriangle(int ax, int ay, int bx, int by, int cx, int cy) {
if (this.crossesDateline() == true) {

View File

@ -16,42 +16,234 @@
*/
package org.apache.lucene.geo;
import static org.apache.lucene.geo.XYEncodingUtils.decode;
import static org.apache.lucene.geo.XYEncodingUtils.encode;
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.
*
* @lucene.internal
*/
public class XYRectangle2D extends Rectangle2D {
public class XYRectangle2D implements Component2D {
private final double minX;
private final double maxX;
private final double minY;
private final double maxY;
protected XYRectangle2D(double minX, double maxX, double minY, double maxY) {
super(encode(minX), encode(maxX), encode(minY), encode(maxY));
}
/** Builds a Rectangle2D from rectangle */
public static XYRectangle2D create(XYRectangle rectangle) {
return new XYRectangle2D(rectangle.minX, rectangle.maxX, rectangle.minY, rectangle.maxY);
this.minX = minX;
this.maxX = maxX;
this.minY = minY;
this.maxY = maxY;
}
@Override
public boolean crossesDateline() {
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(decode(minX));
sb.append(minX);
sb.append(" TO ");
sb.append(decode(maxX));
sb.append(maxX);
sb.append(" y=");
sb.append(decode(minY));
sb.append(minY);
sb.append(" TO ");
sb.append(decode(maxY));
sb.append(maxY);
sb.append(")");
return sb.toString();
}
}
/** create a component2D from provided array of rectangles */
public static Component2D create(XYRectangle... rectangles) {
XYRectangle2D[] components = new XYRectangle2D[rectangles.length];
for (int i = 0; i < components.length; ++i) {
components[i] = new XYRectangle2D(XYEncodingUtils.decode(XYEncodingUtils.encode(rectangles[i].minX)),
XYEncodingUtils.decode(XYEncodingUtils.encode(rectangles[i].maxX)),
XYEncodingUtils.decode(XYEncodingUtils.encode(rectangles[i].minY)),
XYEncodingUtils.decode(XYEncodingUtils.encode(rectangles[i].maxY)));
}
return ComponentTree.create(components);
}
}

View File

@ -224,6 +224,16 @@ public abstract class BaseLatLonShapeTestCase extends BaseShapeTestCase {
@Override
protected Encoder getEncoder() {
return new Encoder() {
@Override
double decodeX(int encoded) {
return decodeLongitude(encoded);
}
@Override
double decodeY(int encoded) {
return decodeLatitude(encoded);
}
@Override
double quantizeX(double raw) {
return decodeLongitude(encodeLongitude(raw));

View File

@ -24,6 +24,7 @@ import java.util.Set;
import com.carrotsearch.randomizedtesting.generators.RandomPicks;
import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.Component2D;
import org.apache.lucene.geo.GeoUtils;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
@ -60,7 +61,7 @@ public abstract class BaseShapeTestCase extends LuceneTestCase {
protected static final String FIELD_NAME = "shape";
public final Encoder ENCODER;
public final Validator VALIDATOR;
protected static final QueryRelation[] POINT_LINE_RELATIONS = {QueryRelation.INTERSECTS, QueryRelation.DISJOINT};
protected static final QueryRelation[] POINT_LINE_RELATIONS = {QueryRelation.INTERSECTS, QueryRelation.DISJOINT, QueryRelation.CONTAINS};
public BaseShapeTestCase() {
ENCODER = getEncoder();
@ -312,17 +313,25 @@ public abstract class BaseShapeTestCase extends LuceneTestCase {
} else if (shapes[id] == null) {
expected = false;
} else {
// check quantized poly against quantized query
if (qMinLon > qMaxLon && rectCrossesDateline(rect) == false) {
// if the quantization creates a false dateline crossing (because of encodeCeil):
// then do not use encodeCeil
qMinLon = ENCODER.quantizeX(rectMinX(rect));
}
if (qMinLat > qMaxLat) {
qMinLat = ENCODER.quantizeY(rectMaxY(rect));
}
expected = VALIDATOR.setRelation(queryRelation).testBBoxQuery(qMinLat, qMaxLat, qMinLon, qMaxLon, shapes[id]);
if (queryRelation == QueryRelation.CONTAINS && rectCrossesDateline(rect)) {
//For contains we need to call the validator for each section. It is only expected
//if both sides are contained.
expected = VALIDATOR.setRelation(queryRelation).testBBoxQuery(qMinLat, qMaxLat, qMinLon, GeoUtils.MAX_LON_INCL, shapes[id]);
if (expected) {
expected = VALIDATOR.setRelation(queryRelation).testBBoxQuery(qMinLat, qMaxLat, GeoUtils.MIN_LON_INCL, qMaxLon, shapes[id]);
}
} else {
// check quantized poly against quantized query
if (qMinLon > qMaxLon && rectCrossesDateline(rect) == false) {
// if the quantization creates a false dateline crossing (because of encodeCeil):
// then do not use encodeCeil
qMinLon = ENCODER.quantizeX(rectMinX(rect));
}
expected = VALIDATOR.setRelation(queryRelation).testBBoxQuery(qMinLat, qMaxLat, qMinLon, qMaxLon, shapes[id]);
}
}
if (hits.get(docID) != expected) {
@ -541,6 +550,8 @@ public abstract class BaseShapeTestCase extends LuceneTestCase {
protected abstract Validator getValidator();
protected static abstract class Encoder {
abstract double decodeX(int encoded);
abstract double decodeY(int encoded);
abstract double quantizeX(double raw);
abstract double quantizeXCeil(double raw);
abstract double quantizeY(double raw);

View File

@ -124,6 +124,15 @@ public abstract class BaseXYShapeTestCase extends BaseShapeTestCase {
@Override
protected Encoder getEncoder() {
return new Encoder() {
@Override
double decodeX(int encoded) {
return decode(encoded);
}
@Override
double decodeY(int encoded) {
return decode(encoded);
}
@Override
double quantizeX(double raw) {
return decode(encode(raw));

View File

@ -80,24 +80,38 @@ public class TestLatLonLineShapeQueries extends BaseLatLonShapeTestCase {
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;
}
@Override
public boolean testComponentQuery(Component2D component2D, Object shape) {
Line line = (Line) shape;
if (queryRelation == QueryRelation.CONTAINS) {
return testWithinLine(component2D, (Line) shape);
}
for (int i = 0, j = 1; j < line.numPoints(); ++i, ++j) {
double[] qTriangle = encoder.quantizeTriangle(line.getLon(i), line.getLat(i), true, line.getLon(j), line.getLat(j), true, line.getLon(i), line.getLat(i), true);
Relation r = component2D.relateTriangle(qTriangle[1], qTriangle[0], qTriangle[3], qTriangle[2], qTriangle[5], qTriangle[4]);
@ -111,5 +125,19 @@ public class TestLatLonLineShapeQueries extends BaseLatLonShapeTestCase {
}
return queryRelation == QueryRelation.INTERSECTS ? false : true;
}
private boolean testWithinLine(Component2D component2D, Line line) {
Component2D.WithinRelation answer = Component2D.WithinRelation.DISJOINT;
for (int i = 0, j = 1; j < line.numPoints(); ++i, ++j) {
double[] qTriangle = encoder.quantizeTriangle(line.getLon(i), line.getLat(i), true, line.getLon(j), line.getLat(j), true, line.getLon(i), line.getLat(i), true);
Component2D.WithinRelation relation = component2D.withinTriangle(qTriangle[1], qTriangle[0], true, qTriangle[3], qTriangle[2], true, qTriangle[5], qTriangle[4], true);
if (relation == Component2D.WithinRelation.NOTWITHIN) {
return false;
} else if (relation == Component2D.WithinRelation.CANDIDATE) {
answer = Component2D.WithinRelation.CANDIDATE;
}
}
return answer == Component2D.WithinRelation.CANDIDATE;
}
}
}

View File

@ -80,13 +80,15 @@ public class TestLatLonMultiLineShapeQueries extends BaseLatLonShapeTestCase {
boolean b = LINEVALIDATOR.testBBoxQuery(minLat, maxLat, minLon, maxLon, l);
if (b == true && queryRelation == QueryRelation.INTERSECTS) {
return true;
} else if (b == true && queryRelation == QueryRelation.CONTAINS) {
return true;
} else if (b == false && queryRelation == QueryRelation.DISJOINT) {
return false;
} else if (b == false && queryRelation == QueryRelation.WITHIN) {
return false;
}
}
return queryRelation != QueryRelation.INTERSECTS;
return queryRelation != QueryRelation.INTERSECTS && queryRelation != QueryRelation.CONTAINS;
}
@Override
@ -96,13 +98,15 @@ public class TestLatLonMultiLineShapeQueries extends BaseLatLonShapeTestCase {
boolean b = LINEVALIDATOR.testComponentQuery(query, l);
if (b == true && queryRelation == QueryRelation.INTERSECTS) {
return true;
} else if (b == true && queryRelation == QueryRelation.CONTAINS) {
return true;
} else if (b == false && queryRelation == QueryRelation.DISJOINT) {
return false;
} else if (b == false && queryRelation == QueryRelation.WITHIN) {
return false;
}
}
return queryRelation != QueryRelation.INTERSECTS;
return queryRelation != QueryRelation.INTERSECTS && queryRelation != QueryRelation.CONTAINS;
}
}

View File

@ -79,13 +79,15 @@ public class TestLatLonMultiPointShapeQueries extends BaseLatLonShapeTestCase {
boolean b = POINTVALIDATOR.testBBoxQuery(minLat, maxLat, minLon, maxLon, p);
if (b == true && queryRelation == QueryRelation.INTERSECTS) {
return true;
} else if (b == true && queryRelation == QueryRelation.CONTAINS) {
return true;
} else if (b == false && queryRelation == QueryRelation.DISJOINT) {
return false;
} else if (b == false && queryRelation == QueryRelation.WITHIN) {
return false;
}
}
return queryRelation != QueryRelation.INTERSECTS;
return queryRelation != QueryRelation.INTERSECTS && queryRelation != QueryRelation.CONTAINS;
}
@Override
@ -95,13 +97,15 @@ public class TestLatLonMultiPointShapeQueries extends BaseLatLonShapeTestCase {
boolean b = POINTVALIDATOR.testComponentQuery(query, p);
if (b == true && queryRelation == QueryRelation.INTERSECTS) {
return true;
} else if (b == true && queryRelation == QueryRelation.CONTAINS) {
return true;
} else if (b == false && queryRelation == QueryRelation.DISJOINT) {
return false;
} else if (b == false && queryRelation == QueryRelation.WITHIN) {
return false;
}
}
return queryRelation != QueryRelation.INTERSECTS;
return queryRelation != QueryRelation.INTERSECTS && queryRelation != QueryRelation.CONTAINS;
}
}

View File

@ -34,17 +34,26 @@ public class TestLatLonMultiPolygonShapeQueries extends BaseLatLonShapeTestCase
@Override
protected Polygon[] nextShape() {
int n = random().nextInt(4) + 1;
Polygon[] polygons = new Polygon[n];
for (int i =0; i < n; i++) {
int repetitions =0;
while (true) {
// if we can't tessellate; then random polygon generator created a malformed shape
Polygon p = (Polygon) getShapeType().nextShape();
try {
Tessellator.tessellate(p);
polygons[i] = p;
break;
//polygons are disjoint so CONTAINS works. Note that if we intersect
//any shape then contains return false.
if (isDisjoint(polygons, p)) {
polygons[i] = p;
break;
}
repetitions++;
if (repetitions > 50) {
//try again
return nextShape();
}
} catch (IllegalArgumentException e) {
continue;
}
@ -53,6 +62,22 @@ public class TestLatLonMultiPolygonShapeQueries extends BaseLatLonShapeTestCase
return polygons;
}
private boolean isDisjoint(Polygon[] polygons, Polygon check) {
// we use bounding boxes so we do not get intersecting polygons.
for (Polygon polygon : polygons) {
if (polygon != null) {
if (getEncoder().quantizeY(polygon.minLat) > getEncoder().quantizeY(check.maxLat)
|| getEncoder().quantizeY(polygon.maxLat) < getEncoder().quantizeY(check.minLat)
|| getEncoder().quantizeX(polygon.minLon) > getEncoder().quantizeX(check.maxLon)
|| getEncoder().quantizeX(polygon.maxLon) < getEncoder().quantizeX(check.minLon)) {
continue;
}
return false;
}
}
return true;
}
@Override
protected Field[] createIndexableFields(String name, Object o) {
Polygon[] polygons = (Polygon[]) o;
@ -92,13 +117,15 @@ public class TestLatLonMultiPolygonShapeQueries extends BaseLatLonShapeTestCase
boolean b = POLYGONVALIDATOR.testBBoxQuery(minLat, maxLat, minLon, maxLon, p);
if (b == true && queryRelation == QueryRelation.INTERSECTS) {
return true;
} else if (b == true && queryRelation == QueryRelation.CONTAINS) {
return true;
} else if (b == false && queryRelation == QueryRelation.DISJOINT) {
return false;
} else if (b == false && queryRelation == QueryRelation.WITHIN) {
return false;
}
}
return queryRelation != QueryRelation.INTERSECTS;
return queryRelation != QueryRelation.INTERSECTS && queryRelation != QueryRelation.CONTAINS;
}
@Override
@ -108,13 +135,15 @@ public class TestLatLonMultiPolygonShapeQueries extends BaseLatLonShapeTestCase
boolean b = POLYGONVALIDATOR.testComponentQuery(query, p);
if (b == true && queryRelation == QueryRelation.INTERSECTS) {
return true;
} else if (b == true && queryRelation == QueryRelation.CONTAINS) {
return true;
} else if (b == false && queryRelation == QueryRelation.DISJOINT) {
return false;
} else if (b == false && queryRelation == QueryRelation.WITHIN) {
return false;
}
}
return queryRelation != QueryRelation.INTERSECTS;
return queryRelation != QueryRelation.INTERSECTS && queryRelation != QueryRelation.CONTAINS;
}
}

View File

@ -74,6 +74,9 @@ public class TestLatLonPointShapeQueries extends BaseLatLonShapeTestCase {
@Override
public boolean testBBoxQuery(double minLat, double maxLat, double minLon, double maxLon, Object shape) {
if (queryRelation == QueryRelation.CONTAINS) {
return false;
}
Point p = (Point)shape;
double lat = encoder.quantizeY(p.lat);
double lon = encoder.quantizeX(p.lon);
@ -90,6 +93,9 @@ public class TestLatLonPointShapeQueries extends BaseLatLonShapeTestCase {
@Override
public boolean testComponentQuery(Component2D query, Object shape) {
if (queryRelation == QueryRelation.CONTAINS) {
return false;
}
Point p = (Point) shape;
double lat = encoder.quantizeY(p.lat);
double lon = encoder.quantizeX(p.lon);

View File

@ -68,32 +68,46 @@ public class TestLatLonPolygonShapeQueries extends BaseLatLonShapeTestCase {
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));
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;
}
@Override
public boolean testComponentQuery(Component2D query, Object o) {
Polygon shape = (Polygon) o;
if (queryRelation == QueryRelation.CONTAINS) {
return testWithinPolygon(query, (Polygon) shape);
}
List<Tessellator.Triangle> tessellation = Tessellator.tessellate(shape);
for (Tessellator.Triangle t : tessellation) {
double[] qTriangle = encoder.quantizeTriangle(t.getX(0), t.getY(0), t.isEdgefromPolygon(0),
t.getX(1), t.getY(1), t.isEdgefromPolygon(1),
t.getX(2), t.getY(2), t.isEdgefromPolygon(2));
t.getX(1), t.getY(1), t.isEdgefromPolygon(1),
t.getX(2), t.getY(2), t.isEdgefromPolygon(2));
Relation r = query.relateTriangle(qTriangle[1], qTriangle[0], qTriangle[3], qTriangle[2], qTriangle[5], qTriangle[4]);
if (queryRelation == QueryRelation.DISJOINT) {
if (r != Relation.CELL_OUTSIDE_QUERY) return false;
@ -105,6 +119,25 @@ public class TestLatLonPolygonShapeQueries extends BaseLatLonShapeTestCase {
}
return queryRelation == QueryRelation.INTERSECTS ? false : true;
}
private boolean testWithinPolygon(Component2D component2D, Polygon shape) {
List<Tessellator.Triangle> tessellation = Tessellator.tessellate(shape);
Component2D.WithinRelation answer = Component2D.WithinRelation.DISJOINT;
for (Tessellator.Triangle t : tessellation) {
ShapeField.DecodedTriangle qTriangle = encoder.encodeDecodeTriangle(t.getX(0), t.getY(0), t.isEdgefromPolygon(0),
t.getX(1), t.getY(1), t.isEdgefromPolygon(1),
t.getX(2), t.getY(2), t.isEdgefromPolygon(2));
Component2D.WithinRelation relation = component2D.withinTriangle(encoder.decodeX(qTriangle.aX), encoder.decodeY(qTriangle.aY), qTriangle.ab,
encoder.decodeX(qTriangle.bX), encoder.decodeY(qTriangle.bY), qTriangle.bc,
encoder.decodeX(qTriangle.cX), encoder.decodeY(qTriangle.cY), qTriangle.ca);
if (relation == Component2D.WithinRelation.NOTWITHIN) {
return false;
} else if (relation == Component2D.WithinRelation.CANDIDATE) {
answer = Component2D.WithinRelation.CANDIDATE;
}
}
return answer == Component2D.WithinRelation.CANDIDATE;
}
}
@Nightly

View File

@ -151,6 +151,49 @@ public class TestLatLonShape extends LuceneTestCase {
IOUtils.close(reader, dir);
}
public void testBasicContains() throws Exception {
Directory dir = newDirectory();
RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
// add a random polygon document
double[] polyLats = new double[] {-10, -10, 10, 10, -10};
double[] polyLons = new double[] {-10, 10, 10, -10, -10};
Polygon p = new Polygon(polyLats, polyLons);
Document document = new Document();
addPolygonsToDoc(FIELDNAME, document, p);
writer.addDocument(document);
// add a line document
document = new Document();
// add a line string
double lats[] = new double[p.numPoints() - 1];
double lons[] = new double[p.numPoints() - 1];
for (int i = 0; i < lats.length; ++i) {
lats[i] = p.getPolyLat(i);
lons[i] = p.getPolyLon(i);
}
Line l = new Line(lats, lons);
addLineToDoc(FIELDNAME, document, l);
writer.addDocument(document);
////// search /////
// search a Polygon
IndexReader reader = writer.getReader();
writer.close();
IndexSearcher searcher = newSearcher(reader);
polyLats = new double[] {-5, -5, 5, 5, -5};
polyLons = new double[] {-5, 5, 5, -5, -5};
Polygon query = new Polygon(polyLats, polyLons);
Query q = LatLonShape.newPolygonQuery(FIELDNAME, QueryRelation.CONTAINS, query);
assertEquals(1, searcher.count(q));
// search a bounding box
searcher = newSearcher(reader);
q = new LatLonShapeBoundingBoxQuery(FIELDNAME, QueryRelation.CONTAINS,0, 0, 0, 0);
assertEquals(1, searcher.count(q));
IOUtils.close(reader, dir);
}
/** test random polygons with a single hole */
public void testPolygonWithHole() throws Exception {
int numVertices = TestUtil.nextInt(random(), 50, 100);
@ -224,6 +267,134 @@ public class TestLatLonShape extends LuceneTestCase {
IOUtils.close(reader, dir);
}
public void testWithinDateLine() throws Exception {
Directory dir = newDirectory();
RandomIndexWriter w = new RandomIndexWriter(random(), dir);
Document doc = new Document();
Polygon indexPoly1 = new Polygon(
new double[] {-7.5d, 15d, 15d, 0d, -7.5d},
new double[] {-180d, -180d, -176d, -176d, -180d}
);
Polygon indexPoly2 = new Polygon(
new double[] {15d, -7.5d, -15d, -10d, 15d, 15d},
new double[] {180d, 180d, 176d, 174d, 176d, 180d}
);
Field[] fields = LatLonShape.createIndexableFields("test", indexPoly1);
for (Field f : fields) {
doc.add(f);
}
fields = LatLonShape.createIndexableFields("test", indexPoly2);
for (Field f : fields) {
doc.add(f);
}
w.addDocument(doc);
w.forceMerge(1);
///// search //////
IndexReader reader = w.getReader();
w.close();
IndexSearcher searcher = newSearcher(reader);
Polygon[] searchPoly = new Polygon[] {
new Polygon(new double[] {-20d, 20d, 20d, -20d, -20d},
new double[] {-180d, -180d, -170d, -170d, -180d}),
new Polygon(new double[] {20d, -20d, -20d, 20d, 20d},
new double[] {180d, 180d, 170d, 170d, 180d})
};
Query q = LatLonShape.newPolygonQuery("test", QueryRelation.WITHIN, searchPoly);
assertEquals(1, searcher.count(q));
q = LatLonShape.newPolygonQuery("test", QueryRelation.INTERSECTS, searchPoly);
assertEquals(1, searcher.count(q));
q = LatLonShape.newPolygonQuery("test", QueryRelation.DISJOINT, searchPoly);
assertEquals(0, searcher.count(q));
q = LatLonShape.newPolygonQuery("test", QueryRelation.CONTAINS, searchPoly);
assertEquals(0, searcher.count(q));
q = LatLonShape.newBoxQuery("test", QueryRelation.WITHIN, -20, 20, 170, -170);
assertEquals(1, searcher.count(q));
q = LatLonShape.newBoxQuery("test", QueryRelation.INTERSECTS, -20, 20, 170, -170);
assertEquals(1, searcher.count(q));
q = LatLonShape.newBoxQuery("test", QueryRelation.DISJOINT, -20, 20, 170, -170);
assertEquals(0, searcher.count(q));
q = LatLonShape.newBoxQuery("test", QueryRelation.CONTAINS, -20, 20, 170, -170);
assertEquals(0, searcher.count(q));
IOUtils.close(w, reader, dir);
}
public void testContainsDateLine() throws Exception {
Directory dir = newDirectory();
RandomIndexWriter w = new RandomIndexWriter(random(), dir);
Document doc = new Document();
Polygon indexPoly1 = new Polygon(
new double[] {-2d, -2d, 2d, 2d, -2d},
new double[] {178d, 180d, 180d, 178d, 178d}
);
Polygon indexPoly2 = new Polygon(
new double[] {-2d, -2d, 2d, 2d, -2d},
new double[] {-180d, -178d, -178d, -180d, -180d}
);
Field[] fields = LatLonShape.createIndexableFields("test", indexPoly1);
for (Field f : fields) {
doc.add(f);
}
fields = LatLonShape.createIndexableFields("test", indexPoly2);
for (Field f : fields) {
doc.add(f);
}
w.addDocument(doc);
w.forceMerge(1);
///// search //////
IndexReader reader = w.getReader();
w.close();
IndexSearcher searcher = newSearcher(reader);
Polygon[] searchPoly = new Polygon[] {
new Polygon(new double[] {-1d, -1d, 1d, 1d, -1d},
new double[] {179d, 180d, 180d, 179d, 179d}),
new Polygon(new double[] {-1d, -1d, 1d, 1d, -1d},
new double[] {-180d, -179d, -179d, -180d, -180d})
};
Query q;
// Not supported due to encoding
//Query q = LatLonShape.newPolygonQuery("test", QueryRelation.CONTAINS, searchPoly);
//assertEquals(1, searcher.count(q));
q = LatLonShape.newPolygonQuery("test", QueryRelation.INTERSECTS, searchPoly);
assertEquals(1, searcher.count(q));
q = LatLonShape.newPolygonQuery("test", QueryRelation.DISJOINT, searchPoly);
assertEquals(0, searcher.count(q));
q = LatLonShape.newPolygonQuery("test", QueryRelation.WITHIN, searchPoly);
assertEquals(0, searcher.count(q));
q = LatLonShape.newBoxQuery("test", QueryRelation.INTERSECTS, -1, 1, 179, -179);
assertEquals(1, searcher.count(q));
q = LatLonShape.newBoxQuery("test", QueryRelation.WITHIN, -1, 1, 179, -179);
assertEquals(0, searcher.count(q));
q = LatLonShape.newBoxQuery("test", QueryRelation.DISJOINT, -1, 1, 179, -179);
assertEquals(0, searcher.count(q));
IOUtils.close(w, reader, dir);
}
public void testLUCENE8454() throws Exception {
Directory dir = newDirectory();
RandomIndexWriter writer = new RandomIndexWriter(random(), dir);

View File

@ -76,26 +76,16 @@ public class TestXYLineShapeQueries extends BaseXYShapeTestCase {
@Override
public boolean testBBoxQuery(double minY, double maxY, double minX, double maxX, Object shape) {
XYLine line = (XYLine)shape;
XYRectangle2D rectangle2D = XYRectangle2D.create(new XYRectangle(minX, maxX, minY, maxY));
for (int i = 0, j = 1; j < line.numPoints(); ++i, ++j) {
ShapeField.DecodedTriangle decoded = encoder.encodeDecodeTriangle(line.getX(i), line.getY(i), true, line.getX(j), line.getY(j), true, line.getX(i), line.getY(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 (rectangle2D.intersectsTriangle(decoded.aX, decoded.aY, decoded.bX, decoded.bY, decoded.cX, decoded.cY) == true) {
return queryRelation == QueryRelation.INTERSECTS;
}
}
}
return queryRelation != QueryRelation.INTERSECTS;
Component2D rectangle2D = XYRectangle2D.create(new XYRectangle(minX, maxX, minY, maxY));
return testComponentQuery(rectangle2D, shape);
}
@Override
public boolean testComponentQuery(Component2D query, Object shape) {
XYLine line = (XYLine) shape;
if (queryRelation == QueryRelation.CONTAINS) {
return testWithinLine(query, (XYLine) shape);
}
for (int i = 0, j = 1; j < line.numPoints(); ++i, ++j) {
double[] qTriangle = encoder.quantizeTriangle(line.getX(i), line.getY(i), true, line.getX(j), line.getY(j), true, line.getX(i), line.getY(i), true);
Relation r = query.relateTriangle(qTriangle[1], qTriangle[0], qTriangle[3], qTriangle[2], qTriangle[5], qTriangle[4]);
@ -109,5 +99,19 @@ public class TestXYLineShapeQueries extends BaseXYShapeTestCase {
}
return queryRelation == QueryRelation.INTERSECTS ? false : true;
}
private boolean testWithinLine(Component2D tree, XYLine line) {
Component2D.WithinRelation answer = Component2D.WithinRelation.DISJOINT;
for (int i = 0, j = 1; j < line.numPoints(); ++i, ++j) {
double[] qTriangle = encoder.quantizeTriangle(line.getX(i), line.getY(i), true, line.getX(j), line.getY(j), true, line.getX(i), line.getY(i), true);
Component2D.WithinRelation relation = tree.withinTriangle(qTriangle[1], qTriangle[0], true, qTriangle[3], qTriangle[2], true, qTriangle[5], qTriangle[4], true);
if (relation == Component2D.WithinRelation.NOTWITHIN) {
return false;
} else if (relation == Component2D.WithinRelation.CANDIDATE) {
answer = Component2D.WithinRelation.CANDIDATE;
}
}
return answer == Component2D.WithinRelation.CANDIDATE;
}
}
}

View File

@ -22,6 +22,8 @@ import java.util.List;
import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.Component2D;
import org.apache.lucene.geo.XYLine;
import org.apache.lucene.geo.XYRectangle;
import org.apache.lucene.geo.XYRectangle2D;
/** random cartesian bounding box, line, and polygon query tests for random indexed arrays of cartesian {@link XYLine} types */
public class TestXYMultiLineShapeQueries extends BaseXYShapeTestCase {
@ -73,19 +75,9 @@ public class TestXYMultiLineShapeQueries extends BaseXYShapeTestCase {
}
@Override
public boolean testBBoxQuery(double minLat, double maxLat, double minLon, double maxLon, Object shape) {
XYLine[] lines = (XYLine[])shape;
for (XYLine l : lines) {
boolean b = LINEVALIDATOR.testBBoxQuery(minLat, maxLat, minLon, maxLon, l);
if (b == true && queryRelation == ShapeField.QueryRelation.INTERSECTS) {
return true;
} else if (b == false && queryRelation == ShapeField.QueryRelation.DISJOINT) {
return false;
} else if (b == false && queryRelation == ShapeField.QueryRelation.WITHIN) {
return false;
}
}
return queryRelation != ShapeField.QueryRelation.INTERSECTS;
public boolean testBBoxQuery(double minY, double maxY, double minX, double maxX, Object shape) {
Component2D rectangle2D = XYRectangle2D.create(new XYRectangle(minX, maxX, minY, maxY));
return testComponentQuery(rectangle2D, shape);
}
@Override
@ -95,13 +87,15 @@ public class TestXYMultiLineShapeQueries extends BaseXYShapeTestCase {
boolean b = LINEVALIDATOR.testComponentQuery(query, l);
if (b == true && queryRelation == ShapeField.QueryRelation.INTERSECTS) {
return true;
} else if (b == true && queryRelation == QueryRelation.CONTAINS) {
return true;
} else if (b == false && queryRelation == ShapeField.QueryRelation.DISJOINT) {
return false;
} else if (b == false && queryRelation == ShapeField.QueryRelation.WITHIN) {
return false;
}
}
return queryRelation != ShapeField.QueryRelation.INTERSECTS;
return queryRelation != ShapeField.QueryRelation.INTERSECTS && queryRelation != QueryRelation.CONTAINS;
}
}

View File

@ -21,6 +21,8 @@ import java.util.List;
import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.Component2D;
import org.apache.lucene.geo.XYRectangle;
import org.apache.lucene.geo.XYRectangle2D;
/** random cartesian bounding box, line, and polygon query tests for random indexed arrays of {@code x, y} points */
public class TestXYMultiPointShapeQueries extends BaseXYShapeTestCase {
@ -72,19 +74,9 @@ public class TestXYMultiPointShapeQueries extends BaseXYShapeTestCase {
}
@Override
public boolean testBBoxQuery(double minLat, double maxLat, double minLon, double maxLon, Object shape) {
Point[] points = (Point[]) shape;
for (Point p : points) {
boolean b = POINTVALIDATOR.testBBoxQuery(minLat, maxLat, minLon, maxLon, p);
if (b == true && queryRelation == QueryRelation.INTERSECTS) {
return true;
} else if (b == false && queryRelation == QueryRelation.DISJOINT) {
return false;
} else if (b == false && queryRelation == QueryRelation.WITHIN) {
return false;
}
}
return queryRelation != QueryRelation.INTERSECTS;
public boolean testBBoxQuery(double minY, double maxY, double minX, double maxX, Object shape) {
Component2D rectangle2D = XYRectangle2D.create(new XYRectangle(minX, maxX, minY, maxY));
return testComponentQuery(rectangle2D, shape);
}
@Override
@ -94,13 +86,15 @@ public class TestXYMultiPointShapeQueries extends BaseXYShapeTestCase {
boolean b = POINTVALIDATOR.testComponentQuery(query, p);
if (b == true && queryRelation == QueryRelation.INTERSECTS) {
return true;
} else if (b == true && queryRelation == QueryRelation.CONTAINS) {
return true;
} else if (b == false && queryRelation == QueryRelation.DISJOINT) {
return false;
} else if (b == false && queryRelation == QueryRelation.WITHIN) {
return false;
}
}
return queryRelation != QueryRelation.INTERSECTS;
return queryRelation != QueryRelation.INTERSECTS && queryRelation != QueryRelation.CONTAINS;
}
}

View File

@ -23,6 +23,8 @@ import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.Component2D;
import org.apache.lucene.geo.Tessellator;
import org.apache.lucene.geo.XYPolygon;
import org.apache.lucene.geo.XYRectangle;
import org.apache.lucene.geo.XYRectangle2D;
/** random cartesian bounding box, line, and polygon query tests for random indexed arrays of cartesian {@link XYPolygon} types */
public class TestXYMultiPolygonShapeQueries extends BaseXYShapeTestCase {
@ -33,17 +35,26 @@ public class TestXYMultiPolygonShapeQueries extends BaseXYShapeTestCase {
@Override
protected XYPolygon[] nextShape() {
int n = random().nextInt(4) + 1;
XYPolygon[] polygons = new XYPolygon[n];
for (int i =0; i < n; i++) {
int repetitions =0;
while (true) {
// if we can't tessellate; then random polygon generator created a malformed shape
XYPolygon p = (XYPolygon) getShapeType().nextShape();
try {
Tessellator.tessellate(p);
polygons[i] = p;
break;
//polygons are disjoint so CONTAINS works. Note that if we intersect
//any shape then contains return false.
if (isDisjoint(polygons, p, i)) {
polygons[i] = p;
break;
}
repetitions++;
if (repetitions > 50) {
//try again
return nextShape();
}
} catch (IllegalArgumentException e) {
continue;
}
@ -52,6 +63,22 @@ public class TestXYMultiPolygonShapeQueries extends BaseXYShapeTestCase {
return polygons;
}
private boolean isDisjoint(XYPolygon[] polygons, XYPolygon check, int totalPolygons) {
// we use bounding boxes so we do not get polygons with shared points.
for (XYPolygon polygon : polygons) {
if (polygon != null) {
if (getEncoder().quantizeY(polygon.minY) > getEncoder().quantizeY(check.maxY)
|| getEncoder().quantizeY(polygon.maxY) < getEncoder().quantizeY(check.minY)
|| getEncoder().quantizeX(polygon.minX) > getEncoder().quantizeX(check.maxX)
|| getEncoder().quantizeX(polygon.maxX) < getEncoder().quantizeX(check.minX)) {
continue;
}
return false;
}
}
return true;
}
@Override
protected Field[] createIndexableFields(String name, Object o) {
XYPolygon[] polygons = (XYPolygon[]) o;
@ -85,19 +112,9 @@ public class TestXYMultiPolygonShapeQueries extends BaseXYShapeTestCase {
}
@Override
public boolean testBBoxQuery(double minLat, double maxLat, double minLon, double maxLon, Object shape) {
XYPolygon[] polygons = (XYPolygon[])shape;
for (XYPolygon p : polygons) {
boolean b = POLYGONVALIDATOR.testBBoxQuery(minLat, maxLat, minLon, maxLon, p);
if (b == true && queryRelation == QueryRelation.INTERSECTS) {
return true;
} else if (b == false && queryRelation == QueryRelation.DISJOINT) {
return false;
} else if (b == false && queryRelation == QueryRelation.WITHIN) {
return false;
}
}
return queryRelation != QueryRelation.INTERSECTS;
public boolean testBBoxQuery(double minY, double maxY, double minX, double maxX, Object shape) {
Component2D rectangle2D = XYRectangle2D.create(new XYRectangle(minX, maxX, minY, maxY));
return testComponentQuery(rectangle2D, shape);
}
@Override
@ -107,13 +124,15 @@ public class TestXYMultiPolygonShapeQueries extends BaseXYShapeTestCase {
boolean b = POLYGONVALIDATOR.testComponentQuery(query, p);
if (b == true && queryRelation == QueryRelation.INTERSECTS) {
return true;
} else if (b == true && queryRelation == QueryRelation.CONTAINS) {
return true;
} else if (b == false && queryRelation == QueryRelation.DISJOINT) {
return false;
} else if (b == false && queryRelation == QueryRelation.WITHIN) {
return false;
}
}
return queryRelation != QueryRelation.INTERSECTS;
return queryRelation != QueryRelation.INTERSECTS && queryRelation != QueryRelation.CONTAINS;
}
}

View File

@ -21,6 +21,8 @@ import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.Component2D;
import org.apache.lucene.geo.ShapeTestUtil;
import org.apache.lucene.geo.XYLine;
import org.apache.lucene.geo.XYRectangle;
import org.apache.lucene.geo.XYRectangle2D;
import org.apache.lucene.index.PointValues.Relation;
/** random cartesian bounding box, line, and polygon query tests for random generated {@code x, y} points */
@ -73,23 +75,16 @@ public class TestXYPointShapeQueries extends BaseXYShapeTestCase {
}
@Override
public boolean testBBoxQuery(double minLat, double maxLat, double minLon, double maxLon, Object shape) {
Point p = (Point)shape;
double lat = encoder.quantizeY(p.y);
double lon = encoder.quantizeX(p.x);
boolean isDisjoint = lat < minLat || lat > maxLat;
isDisjoint = isDisjoint || ((minLon > maxLon)
? lon < minLon && lon > maxLon
: lon < minLon || lon > maxLon);
if (queryRelation == QueryRelation.DISJOINT) {
return isDisjoint;
}
return isDisjoint == false;
public boolean testBBoxQuery(double minY, double maxY, double minX, double maxX, Object shape) {
Component2D rectangle2D = XYRectangle2D.create(new XYRectangle(minX, maxX, minY, maxY));
return testComponentQuery(rectangle2D, shape);
}
@Override
public boolean testComponentQuery(Component2D query, Object shape) {
if (queryRelation == QueryRelation.CONTAINS) {
return false;
}
Point p = (Point) shape;
double lat = encoder.quantizeY(p.y);
double lon = encoder.quantizeX(p.x);

View File

@ -66,34 +66,21 @@ public class TestXYPolygonShapeQueries extends BaseXYShapeTestCase {
@Override
public boolean testBBoxQuery(double minY, double maxY, double minX, double maxX, Object shape) {
XYPolygon p = (XYPolygon)shape;
XYRectangle2D rectangle2D = XYRectangle2D.create(new XYRectangle(minX, maxX, minY, maxY));
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 (rectangle2D.intersectsTriangle(decoded.aX, decoded.aY, decoded.bX, decoded.bY, decoded.cX, decoded.cY) == true) {
return queryRelation == QueryRelation.INTERSECTS;
}
}
}
return queryRelation != QueryRelation.INTERSECTS;
Component2D rectangle2D = XYRectangle2D.create(new XYRectangle(minX, maxX, minY, maxY));
return testComponentQuery(rectangle2D, shape);
}
@Override
public boolean testComponentQuery(Component2D query, Object o) {
XYPolygon shape = (XYPolygon) o;
if (queryRelation == QueryRelation.CONTAINS) {
return testWithinPolygon(query, (XYPolygon) shape);
}
List<Tessellator.Triangle> tessellation = Tessellator.tessellate(shape);
for (Tessellator.Triangle t : tessellation) {
double[] qTriangle = encoder.quantizeTriangle(t.getX(0), t.getY(0), t.isEdgefromPolygon(0),
t.getX(1), t.getY(1), t.isEdgefromPolygon(1),
t.getX(2), t.getY(2), t.isEdgefromPolygon(2));
t.getX(1), t.getY(1), t.isEdgefromPolygon(1),
t.getX(2), t.getY(2), t.isEdgefromPolygon(2));
Relation r = query.relateTriangle(qTriangle[1], qTriangle[0], qTriangle[3], qTriangle[2], qTriangle[5], qTriangle[4]);
if (queryRelation == QueryRelation.DISJOINT) {
if (r != Relation.CELL_OUTSIDE_QUERY) return false;
@ -105,6 +92,25 @@ public class TestXYPolygonShapeQueries extends BaseXYShapeTestCase {
}
return queryRelation == QueryRelation.INTERSECTS ? false : true;
}
private boolean testWithinPolygon(Component2D tree, XYPolygon shape) {
List<Tessellator.Triangle> tessellation = Tessellator.tessellate(shape);
Component2D.WithinRelation answer = Component2D.WithinRelation.DISJOINT;
for (Tessellator.Triangle t : tessellation) {
ShapeField.DecodedTriangle qTriangle = encoder.encodeDecodeTriangle(t.getX(0), t.getY(0), t.isEdgefromPolygon(0),
t.getX(1), t.getY(1), t.isEdgefromPolygon(1),
t.getX(2), t.getY(2), t.isEdgefromPolygon(2));
Component2D.WithinRelation relation = tree.withinTriangle(encoder.decodeX(qTriangle.aX), encoder.decodeY(qTriangle.aY), qTriangle.ab,
encoder.decodeX(qTriangle.bX), encoder.decodeY(qTriangle.bY), qTriangle.bc,
encoder.decodeX(qTriangle.cX), encoder.decodeY(qTriangle.cY), qTriangle.ca);
if (relation == Component2D.WithinRelation.NOTWITHIN) {
return false;
} else if (relation == Component2D.WithinRelation.CANDIDATE) {
answer = Component2D.WithinRelation.CANDIDATE;
}
}
return answer == Component2D.WithinRelation.CANDIDATE;
}
}
@Nightly

View File

@ -18,8 +18,10 @@ package org.apache.lucene.document;
import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.ShapeTestUtil;
import org.apache.lucene.geo.Tessellator;
import org.apache.lucene.geo.XYLine;
import org.apache.lucene.geo.XYPolygon;
import org.apache.lucene.geo.XYRectangle;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.RandomIndexWriter;
import org.apache.lucene.search.IndexSearcher;
@ -108,4 +110,58 @@ public class TestXYShape extends LuceneTestCase {
IOUtils.close(reader, dir);
}
public void testBoundingBoxQueries() throws Exception {
XYRectangle r1 = ShapeTestUtil.nextBox();
XYRectangle r2 = ShapeTestUtil.nextBox();
XYPolygon p;
//find two boxes so that r1 contains r2
while (true) {
// TODO: Should XYRectangle hold values as float?
if (areBoxDisjoint(r1, r2)) {
p = toPolygon(r2);
try {
Tessellator.tessellate(p);
break;
} catch (Exception e) {
// ignore, try other combination
}
}
r1 = ShapeTestUtil.nextBox();
r2 = ShapeTestUtil.nextBox();
}
Directory dir = newDirectory();
RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
// add the polygon to the index
Document document = new Document();
addPolygonsToDoc(FIELDNAME, document, p);
writer.addDocument(document);
////// search /////
IndexReader reader = writer.getReader();
writer.close();
IndexSearcher searcher = newSearcher(reader);
// Query by itself should match
Query q = newRectQuery(FIELDNAME, r2.minX, r2.maxX, r2.minY, r2.maxY);
assertEquals(1, searcher.count(q));
// r1 contains r2, intersects should match
q = newRectQuery(FIELDNAME, r1.minX, r1.maxX, r1.minY, r1.maxY);
assertEquals(1, searcher.count(q));
// r1 contains r2, WITHIN should match
q = XYShape.newBoxQuery(FIELDNAME, QueryRelation.WITHIN, (float) r1.minX, (float) r1.maxX, (float) r1.minY, (float) r1.maxY);
assertEquals(1, searcher.count(q));
IOUtils.close(reader, dir);
}
private static boolean areBoxDisjoint(XYRectangle r1, XYRectangle r2) {
return ((float) r1.minX <= (float) r2.minX && (float) r1.minY <= (float) r2.minY && (float) r1.maxX >= (float) r2.maxX && (float) r1.maxY >= (float) r2.maxY);
}
private static XYPolygon toPolygon(XYRectangle r) {
return new XYPolygon(new float[]{(float) r.minX, (float) r.maxX, (float) r.maxX, (float) r.minX, (float) r.minX},
new float[]{(float) r.minY, (float) r.minY, (float) r.maxY, (float) r.maxY, (float) r.minY});
}
}

View File

@ -32,7 +32,8 @@ public class TestLine2D extends LuceneTestCase {
int by = GeoEncodingUtils.encodeLatitude(5);
int cx = GeoEncodingUtils.encodeLongitude(5);
int cy = GeoEncodingUtils.encodeLatitude(4);
assertEquals(Relation.CELL_OUTSIDE_QUERY, line2D.relateTriangle(ax, ay, bx, by , cx, cy));;
assertEquals(Relation.CELL_OUTSIDE_QUERY, line2D.relateTriangle(ax, ay, bx, by , cx, cy));
assertEquals(Component2D.WithinRelation.DISJOINT, line2D.withinTriangle(ax, ay, true, bx, by , true, cx, cy, true));
}
public void testTriangleIntersects() {
@ -45,6 +46,7 @@ public class TestLine2D extends LuceneTestCase {
int cx = GeoEncodingUtils.encodeLongitude(0);
int cy = GeoEncodingUtils.encodeLatitude(1);
assertEquals(Relation.CELL_CROSSES_QUERY, line2D.relateTriangle(ax, ay, bx, by , cx, cy));
assertEquals(Component2D.WithinRelation.NOTWITHIN, line2D.withinTriangle(ax, ay, true, bx, by , true, cx, cy, true));
}
public void testTriangleContains() {
@ -57,6 +59,7 @@ public class TestLine2D extends LuceneTestCase {
int cx = GeoEncodingUtils.encodeLongitude(4);
int cy = GeoEncodingUtils.encodeLatitude(30);
assertEquals(Relation.CELL_CROSSES_QUERY, line2D.relateTriangle(ax, ay, bx, by , cx, cy));
assertEquals(Component2D.WithinRelation.CANDIDATE, line2D.withinTriangle(ax, ay, true, bx, by , true, cx, cy, true));
}
public void testRandomTriangles() {
@ -79,6 +82,9 @@ public class TestLine2D extends LuceneTestCase {
Relation r = line2D.relate(tMinX, tMaxX, tMinY, tMaxY);
if (r == Relation.CELL_OUTSIDE_QUERY) {
assertEquals(Relation.CELL_OUTSIDE_QUERY, line2D.relateTriangle(ax, ay, bx, by, cx, cy));
assertEquals(Component2D.WithinRelation.DISJOINT, line2D.withinTriangle(ax, ay, true, bx, by, true, cx, cy, true));
} else if (line2D.relateTriangle(ax, ay, bx, by, cx, cy) == Relation.CELL_INSIDE_QUERY) {
assertNotEquals(Component2D.WithinRelation.CANDIDATE, line2D.withinTriangle(ax, ay, true, bx, by, true, cx, cy, true));
}
}
}

View File

@ -36,6 +36,7 @@ public class TestRectangle2D extends LuceneTestCase {
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));
}
public void testTriangleIntersects() {
@ -49,6 +50,7 @@ public class TestRectangle2D extends LuceneTestCase {
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));
}
public void testTriangleContains() {
@ -62,10 +64,58 @@ public class TestRectangle2D extends LuceneTestCase {
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));
}
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));
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));
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);
for (int i =0; i < 100; i++) {
@ -97,9 +147,13 @@ public class TestRectangle2D extends LuceneTestCase {
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));
}
else if (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));
}
}
}

View File

@ -0,0 +1,88 @@
/*
* 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 org.apache.lucene.index.PointValues;
import org.apache.lucene.util.LuceneTestCase;
public class TestXYRectangle2D extends LuceneTestCase {
public void testTriangleDisjoint() {
XYRectangle rectangle = new XYRectangle(0d, 1d, 0d, 1d);
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(0d, 1d, 0d, 1d);
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() {
XYRectangle rectangle = ShapeTestUtil.nextBox();
Component2D rectangle2D = XYRectangle2D.create(rectangle);
for (int i =0; i < 100; i++) {
float ax = (float) ShapeTestUtil.nextDouble();
float ay = (float) ShapeTestUtil.nextDouble();
float bx = (float) ShapeTestUtil.nextDouble();
float by = (float) ShapeTestUtil.nextDouble();
float cx = (float) ShapeTestUtil.nextDouble();
float cy = (float) ShapeTestUtil.nextDouble();
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));
}
}
}
}