/*
 * 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 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;

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;

Finds all previously indexed geo shapes that intersect the specified bounding box.

The field must be indexed using LatLonShape.createIndexableFields added per document.

/** * 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 ShapeQuery { private final Rectangle rectangle; private final EncodedRectangle encodedRectangle; LatLonShapeBoundingBoxQuery(String field, QueryRelation queryRelation, Rectangle rectangle) { super(field, queryRelation); this.rectangle = rectangle; this.encodedRectangle = new EncodedRectangle(rectangle.minLat, rectangle.maxLat, rectangle.minLon, rectangle.maxLon); } @Override protected Relation relateRangeBBoxToQuery(int minXOffset, int minYOffset, byte[] minTriangle, int maxXOffset, int maxYOffset, byte[] maxTriangle) { if (queryRelation == QueryRelation.INTERSECTS || queryRelation == QueryRelation.DISJOINT) { return encodedRectangle.intersectRangeBBox(minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle); } return encodedRectangle.relateRangeBBox(minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle); } @Override protected boolean queryIntersects(byte[] t, ShapeField.DecodedTriangle scratchTriangle) { ShapeField.decodeTriangle(t, 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 boolean queryContains(byte[] t, ShapeField.DecodedTriangle scratchTriangle) { ShapeField.decodeTriangle(t, 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 Component2D.WithinRelation queryWithin(byte[] t, ShapeField.DecodedTriangle scratchTriangle) { if (encodedRectangle.crossesDateline()) { throw new IllegalArgumentException("withinTriangle is not supported for rectangles crossing the date line"); } // decode indexed triangle ShapeField.decodeTriangle(t, scratchTriangle); switch (scratchTriangle.type) { case POINT: { return 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
/** 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]; int minXenc = encodeLongitudeCeil(minLon); int maxXenc = encodeLongitude(maxLon); int minYenc = encodeLatitudeCeil(minLat); int maxYenc = encodeLatitude(maxLat); if (minYenc > maxYenc) { minYenc = maxYenc; } this.minY = minYenc; this.maxY = maxYenc; if (minLon > maxLon == true) { this.crossesDateline = true; // crossing dateline is split into east/west boxes this.west = new byte[4 * BYTES]; this.minX = minXenc; this.maxX = maxXenc; encode(MIN_LON_ENCODED, this.maxX, this.minY, this.maxY, this.west); encode(this.minX, MAX_LON_ENCODED, this.minY, this.maxY, this.bbox); } else { this.crossesDateline = false; // encodeLongitudeCeil may cause minX to be > maxX iff // the delta between the longitude < the encoding resolution if (minXenc > maxXenc) { minXenc = maxXenc; } this.west = null; this.minX = minXenc; this.maxX = maxXenc; encode(this.minX, this.maxX, this.minY, this.maxY, bbox); } }
encodes a bounding box into the provided byte array
/** * 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
/** * 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
/** * 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)
/** * 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
/** * 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
/** * 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
/** * 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
/** * 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
/** * 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
/** * 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
/** * 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
/** * 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
/** * 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
/** * 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
/** * 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 } } }