blob: 5434958dbabbc6bd58bcf3f8fb7442be06f96252 [file] [log] [blame]
/*
* 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.document;
import static java.lang.Integer.BYTES;
import static org.apache.lucene.geo.GeoEncodingUtils.MAX_LON_ENCODED;
import static org.apache.lucene.geo.GeoEncodingUtils.MIN_LON_ENCODED;
import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitude;
import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitudeCeil;
import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude;
import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitudeCeil;
import java.util.function.Function;
import java.util.function.Predicate;
import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.Component2D;
import org.apache.lucene.geo.GeoUtils;
import org.apache.lucene.geo.Rectangle;
import org.apache.lucene.index.PointValues.Relation;
import org.apache.lucene.util.FutureArrays;
import org.apache.lucene.util.NumericUtils;
/**
* Finds all previously indexed geo shapes that intersect the specified bounding box.
*
* <p>The field must be indexed using {@link
* org.apache.lucene.document.LatLonShape#createIndexableFields} added per document.
*/
final class LatLonShapeBoundingBoxQuery extends SpatialQuery {
private final Rectangle rectangle;
LatLonShapeBoundingBoxQuery(String field, QueryRelation queryRelation, Rectangle rectangle) {
super(field, queryRelation);
this.rectangle = rectangle;
}
@Override
protected SpatialVisitor getSpatialVisitor() {
final EncodedRectangle encodedRectangle =
new EncodedRectangle(
rectangle.minLat, rectangle.maxLat, rectangle.minLon, rectangle.maxLon);
return new SpatialVisitor() {
@Override
protected Relation relate(byte[] minTriangle, byte[] maxTriangle) {
if (queryRelation == QueryRelation.INTERSECTS || queryRelation == QueryRelation.DISJOINT) {
return encodedRectangle.intersectRangeBBox(
ShapeField.BYTES,
0,
minTriangle,
3 * ShapeField.BYTES,
2 * ShapeField.BYTES,
maxTriangle);
}
return encodedRectangle.relateRangeBBox(
ShapeField.BYTES,
0,
minTriangle,
3 * ShapeField.BYTES,
2 * ShapeField.BYTES,
maxTriangle);
}
@Override
protected Predicate<byte[]> intersects() {
final ShapeField.DecodedTriangle scratchTriangle = new ShapeField.DecodedTriangle();
return triangle -> {
ShapeField.decodeTriangle(triangle, scratchTriangle);
switch (scratchTriangle.type) {
case POINT:
{
return encodedRectangle.contains(scratchTriangle.aX, scratchTriangle.aY);
}
case LINE:
{
int aY = scratchTriangle.aY;
int aX = scratchTriangle.aX;
int bY = scratchTriangle.bY;
int bX = scratchTriangle.bX;
return encodedRectangle.intersectsLine(aX, aY, bX, bY);
}
case TRIANGLE:
{
int aY = scratchTriangle.aY;
int aX = scratchTriangle.aX;
int bY = scratchTriangle.bY;
int bX = scratchTriangle.bX;
int cY = scratchTriangle.cY;
int cX = scratchTriangle.cX;
return encodedRectangle.intersectsTriangle(aX, aY, bX, bY, cX, cY);
}
default:
throw new IllegalArgumentException(
"Unsupported triangle type :[" + scratchTriangle.type + "]");
}
};
}
@Override
protected Predicate<byte[]> within() {
final ShapeField.DecodedTriangle scratchTriangle = new ShapeField.DecodedTriangle();
return triangle -> {
ShapeField.decodeTriangle(triangle, scratchTriangle);
switch (scratchTriangle.type) {
case POINT:
{
return encodedRectangle.contains(scratchTriangle.aX, scratchTriangle.aY);
}
case LINE:
{
int aY = scratchTriangle.aY;
int aX = scratchTriangle.aX;
int bY = scratchTriangle.bY;
int bX = scratchTriangle.bX;
return encodedRectangle.containsLine(aX, aY, bX, bY);
}
case TRIANGLE:
{
int aY = scratchTriangle.aY;
int aX = scratchTriangle.aX;
int bY = scratchTriangle.bY;
int bX = scratchTriangle.bX;
int cY = scratchTriangle.cY;
int cX = scratchTriangle.cX;
return encodedRectangle.containsTriangle(aX, aY, bX, bY, cX, cY);
}
default:
throw new IllegalArgumentException(
"Unsupported triangle type :[" + scratchTriangle.type + "]");
}
};
}
@Override
protected Function<byte[], Component2D.WithinRelation> contains() {
if (encodedRectangle.crossesDateline()) {
throw new IllegalArgumentException(
"withinTriangle is not supported for rectangles crossing the date line");
}
final ShapeField.DecodedTriangle scratchTriangle = new ShapeField.DecodedTriangle();
return triangle -> {
// decode indexed triangle
ShapeField.decodeTriangle(triangle, scratchTriangle);
switch (scratchTriangle.type) {
case POINT:
{
return encodedRectangle.contains(scratchTriangle.aX, scratchTriangle.aY)
? Component2D.WithinRelation.NOTWITHIN
: Component2D.WithinRelation.DISJOINT;
}
case LINE:
{
return encodedRectangle.withinLine(
scratchTriangle.aX,
scratchTriangle.aY,
scratchTriangle.ab,
scratchTriangle.bX,
scratchTriangle.bY);
}
case TRIANGLE:
{
return encodedRectangle.withinTriangle(
scratchTriangle.aX,
scratchTriangle.aY,
scratchTriangle.ab,
scratchTriangle.bX,
scratchTriangle.bY,
scratchTriangle.bc,
scratchTriangle.cX,
scratchTriangle.cY,
scratchTriangle.ca);
}
default:
throw new IllegalArgumentException(
"Unsupported triangle type :[" + scratchTriangle.type + "]");
}
};
}
};
}
@Override
protected boolean equalsTo(Object o) {
return super.equalsTo(o) && rectangle.equals(((LatLonShapeBoundingBoxQuery) o).rectangle);
}
@Override
public int hashCode() {
int hash = super.hashCode();
hash = 31 * hash + rectangle.hashCode();
return hash;
}
@Override
public String toString(String field) {
final StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName());
sb.append(':');
if (this.field.equals(field) == false) {
sb.append(" field=");
sb.append(this.field);
sb.append(':');
}
sb.append(rectangle.toString());
return sb.toString();
}
/** Holds spatial logic for a bounding box that works in the encoded space */
private static class EncodedRectangle {
protected final byte[] bbox;
private final byte[] west;
protected final int minX;
protected final int maxX;
protected final int minY;
protected final int maxY;
private final boolean crossesDateline;
EncodedRectangle(double minLat, double maxLat, double minLon, double maxLon) {
this.bbox = new byte[4 * BYTES];
if (minLon == 180.0 && minLon > maxLon) {
minLon = -180;
}
this.minX = encodeLongitudeCeil(minLon);
this.maxX = encodeLongitude(maxLon);
this.minY = encodeLatitudeCeil(minLat);
this.maxY = encodeLatitude(maxLat);
this.crossesDateline = minLon > maxLon;
if (this.crossesDateline) {
// crossing dateline is split into east/west boxes
this.west = new byte[4 * BYTES];
encode(MIN_LON_ENCODED, this.maxX, this.minY, this.maxY, this.west);
encode(this.minX, MAX_LON_ENCODED, this.minY, this.maxY, this.bbox);
} else {
this.west = null;
encode(this.minX, this.maxX, this.minY, this.maxY, bbox);
}
}
/** encodes a bounding box into the provided byte array */
private static void encode(
final int minX, final int maxX, final int minY, final int maxY, byte[] b) {
if (b == null) {
b = new byte[4 * BYTES];
}
NumericUtils.intToSortableBytes(minY, b, 0);
NumericUtils.intToSortableBytes(minX, b, BYTES);
NumericUtils.intToSortableBytes(maxY, b, 2 * BYTES);
NumericUtils.intToSortableBytes(maxX, b, 3 * BYTES);
}
private boolean crossesDateline() {
return crossesDateline;
}
/** compare this to a provided range bounding box */
Relation relateRangeBBox(
int minXOffset,
int minYOffset,
byte[] minTriangle,
int maxXOffset,
int maxYOffset,
byte[] maxTriangle) {
Relation eastRelation =
compareBBoxToRangeBBox(
this.bbox, minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle);
if (this.crossesDateline() && eastRelation == Relation.CELL_OUTSIDE_QUERY) {
return compareBBoxToRangeBBox(
this.west, minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle);
}
return eastRelation;
}
/** intersects this to a provided range bounding box */
Relation intersectRangeBBox(
int minXOffset,
int minYOffset,
byte[] minTriangle,
int maxXOffset,
int maxYOffset,
byte[] maxTriangle) {
Relation eastRelation =
intersectBBoxWithRangeBBox(
this.bbox, minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle);
if (this.crossesDateline() && eastRelation == Relation.CELL_OUTSIDE_QUERY) {
return intersectBBoxWithRangeBBox(
this.west, minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle);
}
return eastRelation;
}
/**
* static utility method to compare a bbox with a range of triangles (just the bbox of the
* triangle collection)
*/
private static Relation compareBBoxToRangeBBox(
final byte[] bbox,
int minXOffset,
int minYOffset,
byte[] minTriangle,
int maxXOffset,
int maxYOffset,
byte[] maxTriangle) {
// check bounding box (DISJOINT)
if (disjoint(
bbox, minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle)) {
return Relation.CELL_OUTSIDE_QUERY;
}
if (FutureArrays.compareUnsigned(
minTriangle, minXOffset, minXOffset + BYTES, bbox, BYTES, 2 * BYTES)
>= 0
&& FutureArrays.compareUnsigned(
maxTriangle, maxXOffset, maxXOffset + BYTES, bbox, 3 * BYTES, 4 * BYTES)
<= 0
&& FutureArrays.compareUnsigned(minTriangle, minYOffset, minYOffset + BYTES, bbox, 0, BYTES)
>= 0
&& FutureArrays.compareUnsigned(
maxTriangle, maxYOffset, maxYOffset + BYTES, bbox, 2 * BYTES, 3 * BYTES)
<= 0) {
return Relation.CELL_INSIDE_QUERY;
}
return Relation.CELL_CROSSES_QUERY;
}
/**
* static utility method to compare a bbox with a range of triangles (just the bbox of the
* triangle collection) for intersection
*/
private static Relation intersectBBoxWithRangeBBox(
final byte[] bbox,
int minXOffset,
int minYOffset,
byte[] minTriangle,
int maxXOffset,
int maxYOffset,
byte[] maxTriangle) {
// check bounding box (DISJOINT)
if (disjoint(
bbox, minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle)) {
return Relation.CELL_OUTSIDE_QUERY;
}
if (FutureArrays.compareUnsigned(
minTriangle, minXOffset, minXOffset + BYTES, bbox, BYTES, 2 * BYTES)
>= 0
&& FutureArrays.compareUnsigned(minTriangle, minYOffset, minYOffset + BYTES, bbox, 0, BYTES)
>= 0) {
if (FutureArrays.compareUnsigned(
maxTriangle, minXOffset, minXOffset + BYTES, bbox, 3 * BYTES, 4 * BYTES)
<= 0
&& FutureArrays.compareUnsigned(
maxTriangle, maxYOffset, maxYOffset + BYTES, bbox, 2 * BYTES, 3 * BYTES)
<= 0) {
return Relation.CELL_INSIDE_QUERY;
}
if (FutureArrays.compareUnsigned(
maxTriangle, maxXOffset, maxXOffset + BYTES, bbox, 3 * BYTES, 4 * BYTES)
<= 0
&& FutureArrays.compareUnsigned(
maxTriangle, minYOffset, minYOffset + BYTES, bbox, 2 * BYTES, 3 * BYTES)
<= 0) {
return Relation.CELL_INSIDE_QUERY;
}
}
if (FutureArrays.compareUnsigned(
maxTriangle, maxXOffset, maxXOffset + BYTES, bbox, 3 * BYTES, 4 * BYTES)
<= 0
&& FutureArrays.compareUnsigned(
maxTriangle, maxYOffset, maxYOffset + BYTES, bbox, 2 * BYTES, 3 * BYTES)
<= 0) {
if (FutureArrays.compareUnsigned(
minTriangle, minXOffset, minXOffset + BYTES, bbox, BYTES, 2 * BYTES)
>= 0
&& FutureArrays.compareUnsigned(minTriangle, maxYOffset, maxYOffset + BYTES, bbox, 0, BYTES)
>= 0) {
return Relation.CELL_INSIDE_QUERY;
}
if (FutureArrays.compareUnsigned(
minTriangle, maxXOffset, maxXOffset + BYTES, bbox, BYTES, 2 * BYTES)
>= 0
&& FutureArrays.compareUnsigned(minTriangle, minYOffset, minYOffset + BYTES, bbox, 0, BYTES)
>= 0) {
return Relation.CELL_INSIDE_QUERY;
}
}
return Relation.CELL_CROSSES_QUERY;
}
/** static utility method to check a bbox is disjoint with a range of triangles */
private static boolean disjoint(
final byte[] bbox,
int minXOffset,
int minYOffset,
byte[] minTriangle,
int maxXOffset,
int maxYOffset,
byte[] maxTriangle) {
return FutureArrays.compareUnsigned(
minTriangle, minXOffset, minXOffset + BYTES, bbox, 3 * BYTES, 4 * BYTES)
> 0
|| FutureArrays.compareUnsigned(
maxTriangle, maxXOffset, maxXOffset + BYTES, bbox, BYTES, 2 * BYTES)
< 0
|| FutureArrays.compareUnsigned(
minTriangle, minYOffset, minYOffset + BYTES, bbox, 2 * BYTES, 3 * BYTES)
> 0
|| FutureArrays.compareUnsigned(maxTriangle, maxYOffset, maxYOffset + BYTES, bbox, 0, BYTES)
< 0;
}
/** Checks if the rectangle contains the provided point */
boolean contains(int x, int y) {
if (y < minY || y > maxY) {
return false;
}
if (crossesDateline()) {
return (x > maxX && x < minX) == false;
} else {
return (x > maxX || x < minX) == false;
}
}
/** Checks if the rectangle intersects the provided LINE */
boolean intersectsLine(int aX, int aY, int bX, int bY) {
if (contains(aX, aY) || contains(bX, bY)) {
return true;
}
// check bounding boxes are disjoint
if (StrictMath.max(aY, bY) < minY || StrictMath.min(aY, bY) > maxY) {
return false;
}
if (crossesDateline) { // crosses dateline
if (StrictMath.min(aX, bX) > maxX && StrictMath.max(aX, bX) < minX) {
return false;
}
} else {
if (StrictMath.min(aX, bX) > maxX || StrictMath.max(aX, bX) < minX) {
return false;
}
}
// expensive part
return edgeIntersectsQuery(aX, aY, bX, bY);
}
/** Checks if the rectangle intersects the provided triangle */
boolean intersectsTriangle(int aX, int aY, int bX, int bY, int cX, int cY) {
// query contains any triangle points
if (contains(aX, aY) || contains(bX, bY) || contains(cX, cY)) {
return true;
}
// check bounding box of triangle
int tMinY = StrictMath.min(StrictMath.min(aY, bY), cY);
int tMaxY = StrictMath.max(StrictMath.max(aY, bY), cY);
// check bounding boxes are disjoint
if (tMaxY < minY || tMinY > maxY) {
return false;
}
int tMinX = StrictMath.min(StrictMath.min(aX, bX), cX);
int tMaxX = StrictMath.max(StrictMath.max(aX, bX), cX);
if (crossesDateline) { // crosses dateline
if (tMinX > maxX && tMaxX < minX) {
return false;
}
} else {
if (tMinX > maxX || tMaxX < minX) {
return false;
}
}
// expensive part
return Component2D.pointInTriangle(
tMinX, tMaxX, tMinY, tMaxY, minX, minY, aX, aY, bX, bY, cX, cY)
|| edgeIntersectsQuery(aX, aY, bX, bY)
|| edgeIntersectsQuery(bX, bY, cX, cY)
|| edgeIntersectsQuery(cX, cY, aX, aY);
}
/** Checks if the rectangle contains the provided LINE */
boolean containsLine(int aX, int aY, int bX, int bY) {
if (aY < minY || bY < minY || aY > maxY || bY > maxY) {
return false;
}
if (crossesDateline) { // crosses dateline
return (aX >= minX && bX >= minX) || (aX <= maxX && bX <= maxX);
} else {
return aX >= minX && bX >= minX && aX <= maxX && bX <= maxX;
}
}
/** Checks if the rectangle contains the provided triangle */
boolean containsTriangle(int aX, int aY, int bX, int bY, int cX, int cY) {
if (aY < minY || bY < minY || cY < minY || aY > maxY || bY > maxY || cY > maxY) {
return false;
}
if (crossesDateline) { // crosses dateline
return (aX >= minX && bX >= minX && cX >= minX) || (aX <= maxX && bX <= maxX && cX <= maxX);
} else {
return aX >= minX && bX >= minX && cX >= minX && aX <= maxX && bX <= maxX && cX <= maxX;
}
}
/** Returns the Within relation to the provided triangle */
Component2D.WithinRelation withinLine(int ax, int ay, boolean ab, int bx, int by) {
if (ab == true && edgeIntersectsBox(ax, ay, bx, by, minX, maxX, minY, maxY) == true) {
return Component2D.WithinRelation.NOTWITHIN;
}
return Component2D.WithinRelation.DISJOINT;
}
/** Returns the Within relation to the provided triangle */
Component2D.WithinRelation withinTriangle(
int aX, int aY, boolean ab, int bX, int bY, boolean bc, int cX, int cY, boolean ca) {
// Points belong to the shape so if points are inside the rectangle then it cannot be within.
if (contains(aX, aY) || contains(bX, bY) || contains(cX, cY)) {
return Component2D.WithinRelation.NOTWITHIN;
}
// Bounding boxes disjoint?
int tMinY = StrictMath.min(StrictMath.min(aY, bY), cY);
int tMaxY = StrictMath.max(StrictMath.max(aY, bY), cY);
// check bounding boxes are disjoint
if (tMaxY < minY || tMinY > maxY) {
return Component2D.WithinRelation.DISJOINT;
}
int tMinX = StrictMath.min(StrictMath.min(aX, bX), cX);
int tMaxX = StrictMath.max(StrictMath.max(aX, bX), cX);
if (crossesDateline) { // crosses dateline
if (tMinX > maxX && tMaxX < minX) {
return Component2D.WithinRelation.DISJOINT;
}
} else {
if (tMinX > maxX || tMaxX < minX) {
return Component2D.WithinRelation.DISJOINT;
}
}
// If any of the edges intersects an edge belonging to the shape then it cannot be within.
Component2D.WithinRelation relation = Component2D.WithinRelation.DISJOINT;
if (edgeIntersectsBox(aX, aY, bX, bY, minX, maxX, minY, maxY) == true) {
if (ab == true) {
return Component2D.WithinRelation.NOTWITHIN;
} else {
relation = Component2D.WithinRelation.CANDIDATE;
}
}
if (edgeIntersectsBox(bX, bY, cX, cY, minX, maxX, minY, maxY) == true) {
if (bc == true) {
return Component2D.WithinRelation.NOTWITHIN;
} else {
relation = Component2D.WithinRelation.CANDIDATE;
}
}
if (edgeIntersectsBox(cX, cY, aX, aY, minX, maxX, minY, maxY) == true) {
if (ca == true) {
return Component2D.WithinRelation.NOTWITHIN;
} else {
relation = Component2D.WithinRelation.CANDIDATE;
}
}
// Check if shape is within the triangle
if (relation == Component2D.WithinRelation.CANDIDATE
|| Component2D.pointInTriangle(
tMinX, tMaxX, tMinY, tMaxY, minX, minY, aX, aY, bX, bY, cX, cY)) {
return Component2D.WithinRelation.CANDIDATE;
}
return relation;
}
/** returns true if the edge (defined by (aX, aY) (bX, bY)) intersects the query */
private boolean edgeIntersectsQuery(int aX, int aY, int bX, int bY) {
if (crossesDateline) {
return edgeIntersectsBox(aX, aY, bX, bY, MIN_LON_ENCODED, this.maxX, this.minY, this.maxY)
|| edgeIntersectsBox(aX, aY, bX, bY, this.minX, MAX_LON_ENCODED, this.minY, this.maxY);
}
return edgeIntersectsBox(aX, aY, bX, bY, this.minX, this.maxX, this.minY, this.maxY);
}
/** returns true if the edge (defined by (aX, aY) (bX, bY)) intersects the box */
private static boolean edgeIntersectsBox(
int aX, int aY, int bX, int bY, int minX, int maxX, int minY, int maxY) {
if (Math.max(aX, bX) < minX
|| Math.min(aX, bX) > maxX
|| Math.min(aY, bY) > maxY
|| Math.max(aY, bY) < minY) {
return false;
}
return GeoUtils.lineCrossesLineWithBoundary(aX, aY, bX, bY, minX, maxY, maxX, maxY)
|| // top
GeoUtils.lineCrossesLineWithBoundary(aX, aY, bX, bY, maxX, maxY, maxX, minY)
|| // bottom
GeoUtils.lineCrossesLineWithBoundary(aX, aY, bX, bY, maxX, minY, minX, minY)
|| // left
GeoUtils.lineCrossesLineWithBoundary(aX, aY, bX, bY, minX, minY, minX, maxY); // right
}
}
}