Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LUCENE-8973: XYRectangle2D should work on float space #865

Closed
wants to merge 15 commits into from
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -30,21 +34,46 @@
* @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 */
Expand All @@ -53,17 +82,17 @@ protected boolean queryMatches(byte[] t, ShapeField.DecodedTriangle scratchTrian
// 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 + "]");
}
}
Expand All @@ -75,13 +104,13 @@ public boolean equals(Object o) {

@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;
}

Expand All @@ -95,7 +124,7 @@ public String toString(String field) {
sb.append(this.field);
sb.append(':');
}
sb.append(rectangle2D.toString());
sb.append(rectangle.toString());
return sb.toString();
}
}
163 changes: 151 additions & 12 deletions lucene/sandbox/src/java/org/apache/lucene/geo/XYRectangle2D.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,42 +16,181 @@
*/
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));
this.minX = minX;
this.maxX = maxX;
this.minY = minY;
this.maxY = maxY;
}

@Override
public double getMinX() {
return minX;
}

/** Builds a Rectangle2D from rectangle */
public static XYRectangle2D create(XYRectangle rectangle) {
return new XYRectangle2D(rectangle.minX, rectangle.maxX, rectangle.minY, rectangle.maxY);
@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 boolean crossesDateline() {
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;
}

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);
}
}