[GEO] Fork Lucene's LatLonShape Classes to local lucene package (#36794)
Lucene 7.6 uses a smaller encoding for LatLonShape. This commit forks the LatLonShape classes to Elasticsearch's local lucene package. These classes will be removed on the release of Lucene 7.6.
This commit is contained in:
parent
a8387592db
commit
20b58f0b0f
|
@ -147,3 +147,15 @@ org.apache.logging.log4j.Logger#error(java.lang.Object)
|
|||
org.apache.logging.log4j.Logger#error(java.lang.Object, java.lang.Throwable)
|
||||
org.apache.logging.log4j.Logger#fatal(java.lang.Object)
|
||||
org.apache.logging.log4j.Logger#fatal(java.lang.Object, java.lang.Throwable)
|
||||
|
||||
# Remove once Lucene 7.7 is integrated
|
||||
@defaultMessage Use org.apache.lucene.document.XLatLonShape classes instead
|
||||
org.apache.lucene.document.LatLonShape
|
||||
org.apache.lucene.document.LatLonShapeBoundingBoxQuery
|
||||
org.apache.lucene.document.LatLonShapeLineQuery
|
||||
org.apache.lucene.document.LatLonShapePolygonQuery
|
||||
org.apache.lucene.document.LatLonShapeQuery
|
||||
|
||||
org.apache.lucene.geo.Rectangle2D @ use @org.apache.lucene.geo.XRectangle2D instead
|
||||
|
||||
org.apache.lucene.geo.Tessellator @ use @org.apache.lucene.geo.XTessellator instead
|
|
@ -0,0 +1,373 @@
|
|||
/*
|
||||
* 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.geo.GeoUtils;
|
||||
import org.apache.lucene.geo.Line;
|
||||
import org.apache.lucene.geo.Polygon;
|
||||
import org.apache.lucene.geo.XTessellator;
|
||||
import org.apache.lucene.geo.XTessellator.Triangle;
|
||||
import org.apache.lucene.index.PointValues;
|
||||
import org.apache.lucene.search.Query;
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
import org.apache.lucene.util.NumericUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitude;
|
||||
import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude;
|
||||
|
||||
/**
|
||||
* An indexed shape utility class.
|
||||
* <p>
|
||||
* {@link Polygon}'s are decomposed into a triangular mesh using the {@link XTessellator} utility class
|
||||
* Each {@link Triangle} is encoded and indexed as a multi-value field.
|
||||
* <p>
|
||||
* Finding all shapes that intersect a range (e.g., bounding box) at search time is efficient.
|
||||
* <p>
|
||||
* This class defines static factory methods for common operations:
|
||||
* <ul>
|
||||
* <li>{@link #createIndexableFields(String, Polygon)} for matching polygons that intersect a bounding box.
|
||||
* <li>{@link #newBoxQuery newBoxQuery()} for matching polygons that intersect a bounding box.
|
||||
* </ul>
|
||||
|
||||
* <b>WARNING</b>: Like {@link LatLonPoint}, vertex values are indexed with some loss of precision from the
|
||||
* original {@code double} values (4.190951585769653E-8 for the latitude component
|
||||
* and 8.381903171539307E-8 for longitude).
|
||||
* @see PointValues
|
||||
* @see LatLonDocValuesField
|
||||
*
|
||||
* @lucene.experimental
|
||||
*/
|
||||
public class XLatLonShape {
|
||||
public static final int BYTES = LatLonPoint.BYTES;
|
||||
|
||||
protected static final FieldType TYPE = new FieldType();
|
||||
static {
|
||||
TYPE.setDimensions(7, 4, BYTES);
|
||||
TYPE.freeze();
|
||||
}
|
||||
|
||||
// no instance:
|
||||
private XLatLonShape() {
|
||||
}
|
||||
|
||||
/** create indexable fields for polygon geometry */
|
||||
public static Field[] createIndexableFields(String fieldName, Polygon polygon) {
|
||||
// the lionshare of the indexing is done by the tessellator
|
||||
List<Triangle> tessellation = XTessellator.tessellate(polygon);
|
||||
List<LatLonTriangle> fields = new ArrayList<>();
|
||||
for (Triangle t : tessellation) {
|
||||
fields.add(new LatLonTriangle(fieldName, t));
|
||||
}
|
||||
return fields.toArray(new Field[fields.size()]);
|
||||
}
|
||||
|
||||
/** create indexable fields for line geometry */
|
||||
public static Field[] createIndexableFields(String fieldName, Line line) {
|
||||
int numPoints = line.numPoints();
|
||||
Field[] fields = new Field[numPoints - 1];
|
||||
// create "flat" triangles
|
||||
for (int i = 0, j = 1; j < numPoints; ++i, ++j) {
|
||||
fields[i] = new LatLonTriangle(fieldName, line.getLat(i), line.getLon(i), line.getLat(j), line.getLon(j),
|
||||
line.getLat(i), line.getLon(i));
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
/** create indexable fields for point geometry */
|
||||
public static Field[] createIndexableFields(String fieldName, double lat, double lon) {
|
||||
return new Field[] {new LatLonTriangle(fieldName, lat, lon, lat, lon, lat, lon)};
|
||||
}
|
||||
|
||||
/** create a query to find all polygons that intersect a defined bounding box
|
||||
**/
|
||||
public static Query newBoxQuery(String field, QueryRelation queryRelation,
|
||||
double minLatitude, double maxLatitude, double minLongitude, double maxLongitude) {
|
||||
return new XLatLonShapeBoundingBoxQuery(field, queryRelation, minLatitude, maxLatitude, minLongitude, maxLongitude);
|
||||
}
|
||||
|
||||
/** create a query to find all polygons that intersect a provided linestring (or array of linestrings)
|
||||
* note: does not support dateline crossing
|
||||
**/
|
||||
public static Query newLineQuery(String field, QueryRelation queryRelation, Line... lines) {
|
||||
return new XLatLonShapeLineQuery(field, queryRelation, lines);
|
||||
}
|
||||
|
||||
/** create a query to find all polygons that intersect a provided polygon (or array of polygons)
|
||||
* note: does not support dateline crossing
|
||||
**/
|
||||
public static Query newPolygonQuery(String field, QueryRelation queryRelation, Polygon... polygons) {
|
||||
return new XLatLonShapePolygonQuery(field, queryRelation, polygons);
|
||||
}
|
||||
|
||||
/** polygons are decomposed into tessellated triangles using {@link XTessellator}
|
||||
* these triangles are encoded and inserted as separate indexed POINT fields
|
||||
*/
|
||||
private static class LatLonTriangle extends Field {
|
||||
|
||||
LatLonTriangle(String name, double aLat, double aLon, double bLat, double bLon, double cLat, double cLon) {
|
||||
super(name, TYPE);
|
||||
setTriangleValue(encodeLongitude(aLon), encodeLatitude(aLat), encodeLongitude(bLon), encodeLatitude(bLat),
|
||||
encodeLongitude(cLon), encodeLatitude(cLat));
|
||||
}
|
||||
|
||||
LatLonTriangle(String name, Triangle t) {
|
||||
super(name, TYPE);
|
||||
setTriangleValue(t.getEncodedX(0), t.getEncodedY(0), t.getEncodedX(1), t.getEncodedY(1),
|
||||
t.getEncodedX(2), t.getEncodedY(2));
|
||||
}
|
||||
|
||||
|
||||
public void setTriangleValue(int aX, int aY, int bX, int bY, int cX, int cY) {
|
||||
final byte[] bytes;
|
||||
|
||||
if (fieldsData == null) {
|
||||
bytes = new byte[7 * BYTES];
|
||||
fieldsData = new BytesRef(bytes);
|
||||
} else {
|
||||
bytes = ((BytesRef) fieldsData).bytes;
|
||||
}
|
||||
encodeTriangle(bytes, aY, aX, bY, bX, cY, cX);
|
||||
}
|
||||
}
|
||||
|
||||
/** Query Relation Types **/
|
||||
public enum QueryRelation {
|
||||
INTERSECTS, WITHIN, DISJOINT
|
||||
}
|
||||
|
||||
private static final int MINY_MINX_MAXY_MAXX_Y_X = 0;
|
||||
private static final int MINY_MINX_Y_X_MAXY_MAXX = 1;
|
||||
private static final int MAXY_MINX_Y_X_MINY_MAXX = 2;
|
||||
private static final int MAXY_MINX_MINY_MAXX_Y_X = 3;
|
||||
private static final int Y_MINX_MINY_X_MAXY_MAXX = 4;
|
||||
private static final int Y_MINX_MINY_MAXX_MAXY_X = 5;
|
||||
private static final int MAXY_MINX_MINY_X_Y_MAXX = 6;
|
||||
private static final int MINY_MINX_Y_MAXX_MAXY_X = 7;
|
||||
|
||||
/**
|
||||
* A triangle is encoded using 6 points and an extra point with encoded information in three bits of how to reconstruct it.
|
||||
* Triangles are encoded with CCW orientation and might be rotated to limit the number of possible reconstructions to 2^3.
|
||||
* Reconstruction always happens from west to east.
|
||||
*/
|
||||
public static void encodeTriangle(byte[] bytes, int aLat, int aLon, int bLat, int bLon, int cLat, int cLon) {
|
||||
assert bytes.length == 7 * BYTES;
|
||||
int aX;
|
||||
int bX;
|
||||
int cX;
|
||||
int aY;
|
||||
int bY;
|
||||
int cY;
|
||||
//change orientation if CW
|
||||
if (GeoUtils.orient(aLon, aLat, bLon, bLat, cLon, cLat) == -1) {
|
||||
aX = cLon;
|
||||
bX = bLon;
|
||||
cX = aLon;
|
||||
aY = cLat;
|
||||
bY = bLat;
|
||||
cY = aLat;
|
||||
} else {
|
||||
aX = aLon;
|
||||
bX = bLon;
|
||||
cX = cLon;
|
||||
aY = aLat;
|
||||
bY = bLat;
|
||||
cY = cLat;
|
||||
}
|
||||
//rotate edges and place minX at the beginning
|
||||
if (bX < aX || cX < aX) {
|
||||
if (bX < cX) {
|
||||
int tempX = aX;
|
||||
int tempY = aY;
|
||||
aX = bX;
|
||||
aY = bY;
|
||||
bX = cX;
|
||||
bY = cY;
|
||||
cX = tempX;
|
||||
cY = tempY;
|
||||
} else if (cX < aX) {
|
||||
int tempX = aX;
|
||||
int tempY = aY;
|
||||
aX = cX;
|
||||
aY = cY;
|
||||
cX = bX;
|
||||
cY = bY;
|
||||
bX = tempX;
|
||||
bY = tempY;
|
||||
}
|
||||
} else if (aX == bX && aX == cX) {
|
||||
//degenerated case, all points with same longitude
|
||||
//we need to prevent that aX is in the middle (not part of the MBS)
|
||||
if (bY < aY || cY < aY) {
|
||||
if (bY < cY) {
|
||||
int tempX = aX;
|
||||
int tempY = aY;
|
||||
aX = bX;
|
||||
aY = bY;
|
||||
bX = cX;
|
||||
bY = cY;
|
||||
cX = tempX;
|
||||
cY = tempY;
|
||||
} else if (cY < aY) {
|
||||
int tempX = aX;
|
||||
int tempY = aY;
|
||||
aX = cX;
|
||||
aY = cY;
|
||||
cX = bX;
|
||||
cY = bY;
|
||||
bX = tempX;
|
||||
bY = tempY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int minX = aX;
|
||||
int minY = StrictMath.min(aY, StrictMath.min(bY, cY));
|
||||
int maxX = StrictMath.max(aX, StrictMath.max(bX, cX));
|
||||
int maxY = StrictMath.max(aY, StrictMath.max(bY, cY));
|
||||
|
||||
int bits, x, y;
|
||||
if (minY == aY) {
|
||||
if (maxY == bY && maxX == bX) {
|
||||
y = cY;
|
||||
x = cX;
|
||||
bits = MINY_MINX_MAXY_MAXX_Y_X;
|
||||
} else if (maxY == cY && maxX == cX) {
|
||||
y = bY;
|
||||
x = bX;
|
||||
bits = MINY_MINX_Y_X_MAXY_MAXX;
|
||||
} else {
|
||||
y = bY;
|
||||
x = cX;
|
||||
bits = MINY_MINX_Y_MAXX_MAXY_X;
|
||||
}
|
||||
} else if (maxY == aY) {
|
||||
if (minY == bY && maxX == bX) {
|
||||
y = cY;
|
||||
x = cX;
|
||||
bits = MAXY_MINX_MINY_MAXX_Y_X;
|
||||
} else if (minY == cY && maxX == cX) {
|
||||
y = bY;
|
||||
x = bX;
|
||||
bits = MAXY_MINX_Y_X_MINY_MAXX;
|
||||
} else {
|
||||
y = cY;
|
||||
x = bX;
|
||||
bits = MAXY_MINX_MINY_X_Y_MAXX;
|
||||
}
|
||||
} else if (maxX == bX && minY == bY) {
|
||||
y = aY;
|
||||
x = cX;
|
||||
bits = Y_MINX_MINY_MAXX_MAXY_X;
|
||||
} else if (maxX == cX && maxY == cY) {
|
||||
y = aY;
|
||||
x = bX;
|
||||
bits = Y_MINX_MINY_X_MAXY_MAXX;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Could not encode the provided triangle");
|
||||
}
|
||||
NumericUtils.intToSortableBytes(minY, bytes, 0);
|
||||
NumericUtils.intToSortableBytes(minX, bytes, BYTES);
|
||||
NumericUtils.intToSortableBytes(maxY, bytes, 2 * BYTES);
|
||||
NumericUtils.intToSortableBytes(maxX, bytes, 3 * BYTES);
|
||||
NumericUtils.intToSortableBytes(y, bytes, 4 * BYTES);
|
||||
NumericUtils.intToSortableBytes(x, bytes, 5 * BYTES);
|
||||
NumericUtils.intToSortableBytes(bits, bytes, 6 * BYTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a triangle encoded by {@link XLatLonShape#encodeTriangle(byte[], int, int, int, int, int, int)}.
|
||||
*/
|
||||
public static void decodeTriangle(byte[] t, int[] triangle) {
|
||||
assert triangle.length == 6;
|
||||
int bits = NumericUtils.sortableBytesToInt(t, 6 * XLatLonShape.BYTES);
|
||||
//extract the first three bits
|
||||
int tCode = (((1 << 3) - 1) & (bits >> 0));
|
||||
switch (tCode) {
|
||||
case MINY_MINX_MAXY_MAXX_Y_X:
|
||||
triangle[0] = NumericUtils.sortableBytesToInt(t, 0 * XLatLonShape.BYTES);
|
||||
triangle[1] = NumericUtils.sortableBytesToInt(t, 1 * XLatLonShape.BYTES);
|
||||
triangle[2] = NumericUtils.sortableBytesToInt(t, 2 * XLatLonShape.BYTES);
|
||||
triangle[3] = NumericUtils.sortableBytesToInt(t, 3 * XLatLonShape.BYTES);
|
||||
triangle[4] = NumericUtils.sortableBytesToInt(t, 4 * XLatLonShape.BYTES);
|
||||
triangle[5] = NumericUtils.sortableBytesToInt(t, 5 * XLatLonShape.BYTES);
|
||||
break;
|
||||
case MINY_MINX_Y_X_MAXY_MAXX:
|
||||
triangle[0] = NumericUtils.sortableBytesToInt(t, 0 * XLatLonShape.BYTES);
|
||||
triangle[1] = NumericUtils.sortableBytesToInt(t, 1 * XLatLonShape.BYTES);
|
||||
triangle[2] = NumericUtils.sortableBytesToInt(t, 4 * XLatLonShape.BYTES);
|
||||
triangle[3] = NumericUtils.sortableBytesToInt(t, 5 * XLatLonShape.BYTES);
|
||||
triangle[4] = NumericUtils.sortableBytesToInt(t, 2 * XLatLonShape.BYTES);
|
||||
triangle[5] = NumericUtils.sortableBytesToInt(t, 3 * XLatLonShape.BYTES);
|
||||
break;
|
||||
case MAXY_MINX_Y_X_MINY_MAXX:
|
||||
triangle[0] = NumericUtils.sortableBytesToInt(t, 2 * XLatLonShape.BYTES);
|
||||
triangle[1] = NumericUtils.sortableBytesToInt(t, 1 * XLatLonShape.BYTES);
|
||||
triangle[2] = NumericUtils.sortableBytesToInt(t, 4 * XLatLonShape.BYTES);
|
||||
triangle[3] = NumericUtils.sortableBytesToInt(t, 5 * XLatLonShape.BYTES);
|
||||
triangle[4] = NumericUtils.sortableBytesToInt(t, 0 * XLatLonShape.BYTES);
|
||||
triangle[5] = NumericUtils.sortableBytesToInt(t, 3 * XLatLonShape.BYTES);
|
||||
break;
|
||||
case MAXY_MINX_MINY_MAXX_Y_X:
|
||||
triangle[0] = NumericUtils.sortableBytesToInt(t, 2 * XLatLonShape.BYTES);
|
||||
triangle[1] = NumericUtils.sortableBytesToInt(t, 1 * XLatLonShape.BYTES);
|
||||
triangle[2] = NumericUtils.sortableBytesToInt(t, 0 * XLatLonShape.BYTES);
|
||||
triangle[3] = NumericUtils.sortableBytesToInt(t, 3 * XLatLonShape.BYTES);
|
||||
triangle[4] = NumericUtils.sortableBytesToInt(t, 4 * XLatLonShape.BYTES);
|
||||
triangle[5] = NumericUtils.sortableBytesToInt(t, 5 * XLatLonShape.BYTES);
|
||||
break;
|
||||
case Y_MINX_MINY_X_MAXY_MAXX:
|
||||
triangle[0] = NumericUtils.sortableBytesToInt(t, 4 * XLatLonShape.BYTES);
|
||||
triangle[1] = NumericUtils.sortableBytesToInt(t, 1 * XLatLonShape.BYTES);
|
||||
triangle[2] = NumericUtils.sortableBytesToInt(t, 0 * XLatLonShape.BYTES);
|
||||
triangle[3] = NumericUtils.sortableBytesToInt(t, 5 * XLatLonShape.BYTES);
|
||||
triangle[4] = NumericUtils.sortableBytesToInt(t, 2 * XLatLonShape.BYTES);
|
||||
triangle[5] = NumericUtils.sortableBytesToInt(t, 3 * XLatLonShape.BYTES);
|
||||
break;
|
||||
case Y_MINX_MINY_MAXX_MAXY_X:
|
||||
triangle[0] = NumericUtils.sortableBytesToInt(t, 4 * XLatLonShape.BYTES);
|
||||
triangle[1] = NumericUtils.sortableBytesToInt(t, 1 * XLatLonShape.BYTES);
|
||||
triangle[2] = NumericUtils.sortableBytesToInt(t, 0 * XLatLonShape.BYTES);
|
||||
triangle[3] = NumericUtils.sortableBytesToInt(t, 3 * XLatLonShape.BYTES);
|
||||
triangle[4] = NumericUtils.sortableBytesToInt(t, 2 * XLatLonShape.BYTES);
|
||||
triangle[5] = NumericUtils.sortableBytesToInt(t, 5 * XLatLonShape.BYTES);
|
||||
break;
|
||||
case MAXY_MINX_MINY_X_Y_MAXX:
|
||||
triangle[0] = NumericUtils.sortableBytesToInt(t, 2 * XLatLonShape.BYTES);
|
||||
triangle[1] = NumericUtils.sortableBytesToInt(t, 1 * XLatLonShape.BYTES);
|
||||
triangle[2] = NumericUtils.sortableBytesToInt(t, 0 * XLatLonShape.BYTES);
|
||||
triangle[3] = NumericUtils.sortableBytesToInt(t, 5 * XLatLonShape.BYTES);
|
||||
triangle[4] = NumericUtils.sortableBytesToInt(t, 4 * XLatLonShape.BYTES);
|
||||
triangle[5] = NumericUtils.sortableBytesToInt(t, 3 * XLatLonShape.BYTES);
|
||||
break;
|
||||
case MINY_MINX_Y_MAXX_MAXY_X:
|
||||
triangle[0] = NumericUtils.sortableBytesToInt(t, 0 * XLatLonShape.BYTES);
|
||||
triangle[1] = NumericUtils.sortableBytesToInt(t, 1 * XLatLonShape.BYTES);
|
||||
triangle[2] = NumericUtils.sortableBytesToInt(t, 4 * XLatLonShape.BYTES);
|
||||
triangle[3] = NumericUtils.sortableBytesToInt(t, 3 * XLatLonShape.BYTES);
|
||||
triangle[4] = NumericUtils.sortableBytesToInt(t, 2 * XLatLonShape.BYTES);
|
||||
triangle[5] = NumericUtils.sortableBytesToInt(t, 5 * XLatLonShape.BYTES);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Could not decode the provided triangle");
|
||||
}
|
||||
//Points of the decoded triangle must be co-planar or CCW oriented
|
||||
assert GeoUtils.orient(triangle[1], triangle[0], triangle[3], triangle[2], triangle[5], triangle[4]) >= 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* 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.geo.Rectangle;
|
||||
import org.apache.lucene.geo.XRectangle2D;
|
||||
import org.apache.lucene.index.PointValues.Relation;
|
||||
|
||||
/**
|
||||
* Finds all previously indexed shapes that intersect the specified bounding box.
|
||||
*
|
||||
* <p>The field must be indexed using
|
||||
* {@link XLatLonShape#createIndexableFields} added per document.
|
||||
*
|
||||
* @lucene.experimental
|
||||
**/
|
||||
final class XLatLonShapeBoundingBoxQuery extends XLatLonShapeQuery {
|
||||
final XRectangle2D rectangle2D;
|
||||
|
||||
XLatLonShapeBoundingBoxQuery(String field, XLatLonShape.QueryRelation queryRelation,
|
||||
double minLat, double maxLat, double minLon, double maxLon) {
|
||||
super(field, queryRelation);
|
||||
Rectangle rectangle = new Rectangle(minLat, maxLat, minLon, maxLon);
|
||||
this.rectangle2D = XRectangle2D.create(rectangle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Relation relateRangeBBoxToQuery(int minXOffset, int minYOffset, byte[] minTriangle,
|
||||
int maxXOffset, int maxYOffset, byte[] maxTriangle) {
|
||||
return rectangle2D.relateRangeBBox(minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle);
|
||||
}
|
||||
|
||||
/** returns true if the query matches the encoded triangle */
|
||||
@Override
|
||||
protected boolean queryMatches(byte[] t, int[] scratchTriangle) {
|
||||
// decode indexed triangle
|
||||
XLatLonShape.decodeTriangle(t, scratchTriangle);
|
||||
|
||||
int aY = scratchTriangle[0];
|
||||
int aX = scratchTriangle[1];
|
||||
int bY = scratchTriangle[2];
|
||||
int bX = scratchTriangle[3];
|
||||
int cY = scratchTriangle[4];
|
||||
int cX = scratchTriangle[5];
|
||||
|
||||
if (queryRelation == XLatLonShape.QueryRelation.WITHIN) {
|
||||
return rectangle2D.containsTriangle(aX, aY, bX, bY, cX, cY);
|
||||
}
|
||||
return rectangle2D.intersectsTriangle(aX, aY, bX, bY, cX, cY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return sameClassAs(o) && equalsTo(getClass().cast(o));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean equalsTo(Object o) {
|
||||
return super.equalsTo(o) && rectangle2D.equals(((XLatLonShapeBoundingBoxQuery)o).rectangle2D);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = super.hashCode();
|
||||
hash = 31 * hash + rectangle2D.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(rectangle2D.toString());
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* 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.XLatLonShape.QueryRelation;
|
||||
import org.apache.lucene.geo.GeoEncodingUtils;
|
||||
import org.apache.lucene.geo.Line;
|
||||
import org.apache.lucene.geo.Line2D;
|
||||
import org.apache.lucene.index.PointValues.Relation;
|
||||
import org.apache.lucene.util.NumericUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Finds all previously indexed shapes that intersect the specified arbitrary {@code Line}.
|
||||
* <p>
|
||||
* Note:
|
||||
* <ul>
|
||||
* <li>{@code QueryRelation.WITHIN} queries are not yet supported</li>
|
||||
* <li>Dateline crossing is not yet supported</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* todo:
|
||||
* <ul>
|
||||
* <li>Add distance support for buffered queries</li>
|
||||
* </ul>
|
||||
* <p>The field must be indexed using
|
||||
* {@link XLatLonShape#createIndexableFields} added per document.
|
||||
*
|
||||
* @lucene.experimental
|
||||
**/
|
||||
final class XLatLonShapeLineQuery extends XLatLonShapeQuery {
|
||||
final Line[] lines;
|
||||
private final Line2D line2D;
|
||||
|
||||
XLatLonShapeLineQuery(String field, QueryRelation queryRelation, Line... lines) {
|
||||
super(field, queryRelation);
|
||||
/** line queries do not support within relations, only intersects and disjoint */
|
||||
if (queryRelation == QueryRelation.WITHIN) {
|
||||
throw new IllegalArgumentException("LatLonShapeLineQuery does not support " + QueryRelation.WITHIN + " queries");
|
||||
}
|
||||
|
||||
if (lines == null) {
|
||||
throw new IllegalArgumentException("lines must not be null");
|
||||
}
|
||||
if (lines.length == 0) {
|
||||
throw new IllegalArgumentException("lines must not be empty");
|
||||
}
|
||||
for (int i = 0; i < lines.length; ++i) {
|
||||
if (lines[i] == null) {
|
||||
throw new IllegalArgumentException("line[" + i + "] must not be null");
|
||||
} else if (lines[i].minLon > lines[i].maxLon) {
|
||||
throw new IllegalArgumentException("LatLonShapeLineQuery does not currently support querying across dateline.");
|
||||
}
|
||||
}
|
||||
this.lines = lines.clone();
|
||||
this.line2D = Line2D.create(lines);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Relation relateRangeBBoxToQuery(int minXOffset, int minYOffset, byte[] minTriangle,
|
||||
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));
|
||||
double maxLon = GeoEncodingUtils.decodeLongitude(NumericUtils.sortableBytesToInt(maxTriangle, maxXOffset));
|
||||
|
||||
// check internal node against query
|
||||
return line2D.relate(minLat, maxLat, minLon, maxLon);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean queryMatches(byte[] t, int[] scratchTriangle) {
|
||||
XLatLonShape.decodeTriangle(t, scratchTriangle);
|
||||
|
||||
double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle[0]);
|
||||
double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle[1]);
|
||||
double blat = GeoEncodingUtils.decodeLatitude(scratchTriangle[2]);
|
||||
double blon = GeoEncodingUtils.decodeLongitude(scratchTriangle[3]);
|
||||
double clat = GeoEncodingUtils.decodeLatitude(scratchTriangle[4]);
|
||||
double clon = GeoEncodingUtils.decodeLongitude(scratchTriangle[5]);
|
||||
|
||||
if (queryRelation == XLatLonShape.QueryRelation.WITHIN) {
|
||||
return line2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_INSIDE_QUERY;
|
||||
}
|
||||
// INTERSECTS
|
||||
return line2D.relateTriangle(alon, alat, blon, blat, clon, clat) != Relation.CELL_OUTSIDE_QUERY;
|
||||
}
|
||||
|
||||
@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("Line(" + lines[0].toGeoJSON() + ")");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return super.equals(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean equalsTo(Object o) {
|
||||
return super.equalsTo(o) && Arrays.equals(lines, ((XLatLonShapeLineQuery)o).lines);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = super.hashCode();
|
||||
hash = 31 * hash + Arrays.hashCode(lines);
|
||||
return hash;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* 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.XLatLonShape.QueryRelation;
|
||||
import org.apache.lucene.geo.GeoEncodingUtils;
|
||||
import org.apache.lucene.geo.Polygon;
|
||||
import org.apache.lucene.geo.Polygon2D;
|
||||
import org.apache.lucene.index.PointValues.Relation;
|
||||
import org.apache.lucene.util.NumericUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Finds all previously indexed shapes that intersect the specified arbitrary.
|
||||
*
|
||||
* <p>The field must be indexed using
|
||||
* {@link XLatLonShape#createIndexableFields} added per document.
|
||||
*
|
||||
* @lucene.experimental
|
||||
**/
|
||||
final class XLatLonShapePolygonQuery extends XLatLonShapeQuery {
|
||||
final Polygon[] polygons;
|
||||
private final Polygon2D poly2D;
|
||||
|
||||
/**
|
||||
* Creates a query that matches all indexed shapes to the provided polygons
|
||||
*/
|
||||
XLatLonShapePolygonQuery(String field, QueryRelation queryRelation, Polygon... polygons) {
|
||||
super(field, queryRelation);
|
||||
if (polygons == null) {
|
||||
throw new IllegalArgumentException("polygons must not be null");
|
||||
}
|
||||
if (polygons.length == 0) {
|
||||
throw new IllegalArgumentException("polygons must not be empty");
|
||||
}
|
||||
for (int i = 0; i < polygons.length; i++) {
|
||||
if (polygons[i] == null) {
|
||||
throw new IllegalArgumentException("polygon[" + i + "] must not be null");
|
||||
} else if (polygons[i].minLon > polygons[i].maxLon) {
|
||||
throw new IllegalArgumentException("LatLonShapePolygonQuery does not currently support querying across dateline.");
|
||||
}
|
||||
}
|
||||
this.polygons = polygons.clone();
|
||||
this.poly2D = Polygon2D.create(polygons);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Relation relateRangeBBoxToQuery(int minXOffset, int minYOffset, byte[] minTriangle,
|
||||
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));
|
||||
double maxLon = GeoEncodingUtils.decodeLongitude(NumericUtils.sortableBytesToInt(maxTriangle, maxXOffset));
|
||||
|
||||
// check internal node against query
|
||||
return poly2D.relate(minLat, maxLat, minLon, maxLon);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean queryMatches(byte[] t, int[] scratchTriangle) {
|
||||
XLatLonShape.decodeTriangle(t, scratchTriangle);
|
||||
|
||||
double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle[0]);
|
||||
double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle[1]);
|
||||
double blat = GeoEncodingUtils.decodeLatitude(scratchTriangle[2]);
|
||||
double blon = GeoEncodingUtils.decodeLongitude(scratchTriangle[3]);
|
||||
double clat = GeoEncodingUtils.decodeLatitude(scratchTriangle[4]);
|
||||
double clon = GeoEncodingUtils.decodeLongitude(scratchTriangle[5]);
|
||||
|
||||
if (queryRelation == QueryRelation.WITHIN) {
|
||||
return poly2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_INSIDE_QUERY;
|
||||
}
|
||||
// INTERSECTS
|
||||
return poly2D.relateTriangle(alon, alat, blon, blat, clon, clat) != Relation.CELL_OUTSIDE_QUERY;
|
||||
}
|
||||
|
||||
@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("Polygon(" + polygons[0].toGeoJSON() + ")");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return super.equals(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean equalsTo(Object o) {
|
||||
return super.equalsTo(o) && Arrays.equals(polygons, ((XLatLonShapePolygonQuery)o).polygons);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = super.hashCode();
|
||||
hash = 31 * hash + Arrays.hashCode(polygons);
|
||||
return hash;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,364 @@
|
|||
/*
|
||||
* 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.XLatLonShape.QueryRelation;
|
||||
import org.apache.lucene.index.FieldInfo;
|
||||
import org.apache.lucene.index.LeafReader;
|
||||
import org.apache.lucene.index.LeafReaderContext;
|
||||
import org.apache.lucene.index.PointValues;
|
||||
import org.apache.lucene.index.PointValues.IntersectVisitor;
|
||||
import org.apache.lucene.index.PointValues.Relation;
|
||||
import org.apache.lucene.search.ConstantScoreScorer;
|
||||
import org.apache.lucene.search.ConstantScoreWeight;
|
||||
import org.apache.lucene.search.DocIdSetIterator;
|
||||
import org.apache.lucene.search.IndexSearcher;
|
||||
import org.apache.lucene.search.Query;
|
||||
import org.apache.lucene.search.ScoreMode;
|
||||
import org.apache.lucene.search.Scorer;
|
||||
import org.apache.lucene.search.ScorerSupplier;
|
||||
import org.apache.lucene.search.Weight;
|
||||
import org.apache.lucene.util.BitSetIterator;
|
||||
import org.apache.lucene.util.DocIdSetBuilder;
|
||||
import org.apache.lucene.util.FixedBitSet;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Base LatLonShape Query class providing common query logic for
|
||||
* {@link XLatLonShapeBoundingBoxQuery} and {@link XLatLonShapePolygonQuery}
|
||||
*
|
||||
* Note: this class implements the majority of the INTERSECTS, WITHIN, DISJOINT relation logic
|
||||
*
|
||||
* @lucene.experimental
|
||||
**/
|
||||
abstract class XLatLonShapeQuery extends Query {
|
||||
/** field name */
|
||||
final String field;
|
||||
/** query relation
|
||||
* disjoint: {@code CELL_OUTSIDE_QUERY}
|
||||
* intersects: {@code CELL_CROSSES_QUERY},
|
||||
* within: {@code CELL_WITHIN_QUERY} */
|
||||
final XLatLonShape.QueryRelation queryRelation;
|
||||
|
||||
protected XLatLonShapeQuery(String field, final QueryRelation queryType) {
|
||||
if (field == null) {
|
||||
throw new IllegalArgumentException("field must not be null");
|
||||
}
|
||||
this.field = field;
|
||||
this.queryRelation = queryType;
|
||||
}
|
||||
|
||||
/**
|
||||
* relates an internal node (bounding box of a range of triangles) to the target query
|
||||
* Note: logic is specific to query type
|
||||
* see {@link XLatLonShapeBoundingBoxQuery#relateRangeToQuery} and {@link XLatLonShapePolygonQuery#relateRangeToQuery}
|
||||
*/
|
||||
protected abstract Relation relateRangeBBoxToQuery(int minXOffset, int minYOffset, byte[] minTriangle,
|
||||
int maxXOffset, int maxYOffset, byte[] maxTriangle);
|
||||
|
||||
/** returns true if the provided triangle matches the query */
|
||||
protected abstract boolean queryMatches(byte[] triangle, int[] scratchTriangle);
|
||||
|
||||
/** relates a range of triangles (internal node) to the query */
|
||||
protected Relation relateRangeToQuery(byte[] minTriangle, byte[] maxTriangle) {
|
||||
// compute bounding box of internal node
|
||||
Relation r = relateRangeBBoxToQuery(XLatLonShape.BYTES, 0, minTriangle, 3 * XLatLonShape.BYTES,
|
||||
2 * XLatLonShape.BYTES, maxTriangle);
|
||||
if (queryRelation == QueryRelation.DISJOINT) {
|
||||
return transposeRelation(r);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Weight createWeight(IndexSearcher searcher, ScoreMode scoreMode, float boost) throws IOException {
|
||||
|
||||
return new ConstantScoreWeight(this, boost) {
|
||||
|
||||
/** create a visitor that adds documents that match the query using a sparse bitset. (Used by INTERSECT) */
|
||||
protected IntersectVisitor getSparseIntersectVisitor(DocIdSetBuilder result) {
|
||||
return new IntersectVisitor() {
|
||||
final int[] scratchTriangle = new int[6];
|
||||
DocIdSetBuilder.BulkAdder adder;
|
||||
|
||||
@Override
|
||||
public void grow(int count) {
|
||||
adder = result.grow(count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(int docID) throws IOException {
|
||||
adder.add(docID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(int docID, byte[] t) throws IOException {
|
||||
if (queryMatches(t, scratchTriangle)) {
|
||||
adder.add(docID);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Relation compare(byte[] minTriangle, byte[] maxTriangle) {
|
||||
return relateRangeToQuery(minTriangle, maxTriangle);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** create a visitor that adds documents that match the query using a dense bitset. (Used by WITHIN, DISJOINT) */
|
||||
protected IntersectVisitor getDenseIntersectVisitor(FixedBitSet intersect, FixedBitSet disjoint) {
|
||||
return new IntersectVisitor() {
|
||||
final int[] scratchTriangle = new int[6];
|
||||
@Override
|
||||
public void visit(int docID) throws IOException {
|
||||
if (queryRelation == QueryRelation.DISJOINT) {
|
||||
// if DISJOINT query set the doc in the disjoint bitset
|
||||
disjoint.set(docID);
|
||||
} else {
|
||||
// for INTERSECT, and WITHIN queries we set the intersect bitset
|
||||
intersect.set(docID);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(int docID, byte[] t) throws IOException {
|
||||
if (queryMatches(t, scratchTriangle)) {
|
||||
intersect.set(docID);
|
||||
} else {
|
||||
disjoint.set(docID);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Relation compare(byte[] minTriangle, byte[] maxTriangle) {
|
||||
return relateRangeToQuery(minTriangle, maxTriangle);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** get a scorer supplier for INTERSECT queries */
|
||||
protected ScorerSupplier getIntersectScorerSupplier(LeafReader reader, PointValues values, Weight weight,
|
||||
ScoreMode scoreMode) throws IOException {
|
||||
DocIdSetBuilder result = new DocIdSetBuilder(reader.maxDoc(), values, field);
|
||||
IntersectVisitor visitor = getSparseIntersectVisitor(result);
|
||||
return new RelationScorerSupplier(values, visitor) {
|
||||
@Override
|
||||
public Scorer get(long leadCost) throws IOException {
|
||||
return getIntersectsScorer(XLatLonShapeQuery.this, reader, weight, result, score(), scoreMode);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** get a scorer supplier for all other queries (DISJOINT, WITHIN) */
|
||||
protected ScorerSupplier getScorerSupplier(LeafReader reader, PointValues values, Weight weight,
|
||||
ScoreMode scoreMode) throws IOException {
|
||||
if (queryRelation == QueryRelation.INTERSECTS) {
|
||||
return getIntersectScorerSupplier(reader, values, weight, scoreMode);
|
||||
}
|
||||
|
||||
FixedBitSet intersect = new FixedBitSet(reader.maxDoc());
|
||||
FixedBitSet disjoint = new FixedBitSet(reader.maxDoc());
|
||||
IntersectVisitor visitor = getDenseIntersectVisitor(intersect, disjoint);
|
||||
return new RelationScorerSupplier(values, visitor) {
|
||||
@Override
|
||||
public Scorer get(long leadCost) throws IOException {
|
||||
return getScorer(XLatLonShapeQuery.this, weight, intersect, disjoint, score(), scoreMode);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScorerSupplier scorerSupplier(LeafReaderContext context) throws IOException {
|
||||
LeafReader reader = context.reader();
|
||||
PointValues values = reader.getPointValues(field);
|
||||
if (values == null) {
|
||||
// No docs in this segment had any points fields
|
||||
return null;
|
||||
}
|
||||
FieldInfo fieldInfo = reader.getFieldInfos().fieldInfo(field);
|
||||
if (fieldInfo == null) {
|
||||
// No docs in this segment indexed this field at all
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean allDocsMatch = true;
|
||||
if (values.getDocCount() != reader.maxDoc() ||
|
||||
relateRangeToQuery(values.getMinPackedValue(), values.getMaxPackedValue()) != Relation.CELL_INSIDE_QUERY) {
|
||||
allDocsMatch = false;
|
||||
}
|
||||
|
||||
final Weight weight = this;
|
||||
if (allDocsMatch) {
|
||||
return new ScorerSupplier() {
|
||||
@Override
|
||||
public Scorer get(long leadCost) throws IOException {
|
||||
return new ConstantScoreScorer(weight, score(), scoreMode, DocIdSetIterator.all(reader.maxDoc()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long cost() {
|
||||
return reader.maxDoc();
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return getScorerSupplier(reader, values, weight, scoreMode);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Scorer scorer(LeafReaderContext context) throws IOException {
|
||||
ScorerSupplier scorerSupplier = scorerSupplier(context);
|
||||
if (scorerSupplier == null) {
|
||||
return null;
|
||||
}
|
||||
return scorerSupplier.get(Long.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCacheable(LeafReaderContext ctx) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** returns the field name */
|
||||
public String getField() {
|
||||
return field;
|
||||
}
|
||||
|
||||
/** returns the query relation */
|
||||
public QueryRelation getQueryRelation() {
|
||||
return queryRelation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = classHash();
|
||||
hash = 31 * hash + field.hashCode();
|
||||
hash = 31 * hash + queryRelation.hashCode();
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return sameClassAs(o) && equalsTo(o);
|
||||
}
|
||||
|
||||
protected boolean equalsTo(Object o) {
|
||||
return Objects.equals(field, ((XLatLonShapeQuery)o).field) && this.queryRelation == ((XLatLonShapeQuery)o).queryRelation;
|
||||
}
|
||||
|
||||
/** transpose the relation; INSIDE becomes OUTSIDE, OUTSIDE becomes INSIDE, CROSSES remains unchanged */
|
||||
private static Relation transposeRelation(Relation r) {
|
||||
if (r == Relation.CELL_INSIDE_QUERY) {
|
||||
return Relation.CELL_OUTSIDE_QUERY;
|
||||
} else if (r == Relation.CELL_OUTSIDE_QUERY) {
|
||||
return Relation.CELL_INSIDE_QUERY;
|
||||
}
|
||||
return Relation.CELL_CROSSES_QUERY;
|
||||
}
|
||||
|
||||
/** utility class for implementing constant score logic specific to INTERSECT, WITHIN, and DISJOINT */
|
||||
private abstract static class RelationScorerSupplier extends ScorerSupplier {
|
||||
PointValues values;
|
||||
IntersectVisitor visitor;
|
||||
long cost = -1;
|
||||
|
||||
RelationScorerSupplier(PointValues values, IntersectVisitor visitor) {
|
||||
this.values = values;
|
||||
this.visitor = visitor;
|
||||
}
|
||||
|
||||
/** create a visitor that clears documents that do NOT match the polygon query; used with INTERSECTS */
|
||||
private IntersectVisitor getInverseIntersectVisitor(XLatLonShapeQuery query, FixedBitSet result, int[] cost) {
|
||||
return new IntersectVisitor() {
|
||||
int[] scratchTriangle = new int[6];
|
||||
@Override
|
||||
public void visit(int docID) {
|
||||
result.clear(docID);
|
||||
cost[0]--;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(int docID, byte[] packedTriangle) {
|
||||
if (query.queryMatches(packedTriangle, scratchTriangle) == false) {
|
||||
result.clear(docID);
|
||||
cost[0]--;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
|
||||
return transposeRelation(query.relateRangeToQuery(minPackedValue, maxPackedValue));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** returns a Scorer for INTERSECT queries that uses a sparse bitset */
|
||||
protected Scorer getIntersectsScorer(XLatLonShapeQuery query, LeafReader reader, Weight weight,
|
||||
DocIdSetBuilder docIdSetBuilder, final float boost,
|
||||
ScoreMode scoreMode) throws IOException {
|
||||
if (values.getDocCount() == reader.maxDoc()
|
||||
&& values.getDocCount() == values.size()
|
||||
&& cost() > reader.maxDoc() / 2) {
|
||||
// If all docs have exactly one value and the cost is greater
|
||||
// than half the leaf size then maybe we can make things faster
|
||||
// by computing the set of documents that do NOT match the query
|
||||
final FixedBitSet result = new FixedBitSet(reader.maxDoc());
|
||||
result.set(0, reader.maxDoc());
|
||||
int[] cost = new int[]{reader.maxDoc()};
|
||||
values.intersect(getInverseIntersectVisitor(query, result, cost));
|
||||
final DocIdSetIterator iterator = new BitSetIterator(result, cost[0]);
|
||||
return new ConstantScoreScorer(weight, boost, scoreMode, iterator);
|
||||
}
|
||||
|
||||
values.intersect(visitor);
|
||||
DocIdSetIterator iterator = docIdSetBuilder.build().iterator();
|
||||
return new ConstantScoreScorer(weight, boost, scoreMode, iterator);
|
||||
}
|
||||
|
||||
/** returns a Scorer for all other (non INTERSECT) queries */
|
||||
protected Scorer getScorer(XLatLonShapeQuery query, Weight weight,
|
||||
FixedBitSet intersect, FixedBitSet disjoint, final float boost,
|
||||
ScoreMode scoreMode) throws IOException {
|
||||
values.intersect(visitor);
|
||||
DocIdSetIterator iterator;
|
||||
if (query.queryRelation == QueryRelation.DISJOINT) {
|
||||
disjoint.andNot(intersect);
|
||||
iterator = new BitSetIterator(disjoint, cost());
|
||||
} else if (query.queryRelation == QueryRelation.WITHIN) {
|
||||
intersect.andNot(disjoint);
|
||||
iterator = new BitSetIterator(intersect, cost());
|
||||
} else {
|
||||
iterator = new BitSetIterator(intersect, cost());
|
||||
}
|
||||
return new ConstantScoreScorer(weight, boost, scoreMode, iterator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long cost() {
|
||||
if (cost == -1) {
|
||||
// Computing the cost may be expensive, so only do it if necessary
|
||||
cost = values.estimatePointCount(visitor);
|
||||
assert cost >= 0;
|
||||
}
|
||||
return cost;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,317 @@
|
|||
/*
|
||||
* 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.document.XLatLonShape;
|
||||
import org.apache.lucene.index.PointValues;
|
||||
import org.apache.lucene.util.FutureArrays;
|
||||
import org.apache.lucene.util.NumericUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.apache.lucene.document.XLatLonShape.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.decodeLatitude;
|
||||
import static org.apache.lucene.geo.GeoEncodingUtils.decodeLongitude;
|
||||
import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitude;
|
||||
import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitudeCeil;
|
||||
import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude;
|
||||
import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitudeCeil;
|
||||
import static org.apache.lucene.geo.GeoUtils.orient;
|
||||
|
||||
/**
|
||||
* 2D rectangle implementation containing spatial logic.
|
||||
*
|
||||
* @lucene.internal
|
||||
*/
|
||||
public class XRectangle2D {
|
||||
final byte[] bbox;
|
||||
final byte[] west;
|
||||
final int minX;
|
||||
final int maxX;
|
||||
final int minY;
|
||||
final int maxY;
|
||||
|
||||
private XRectangle2D(double minLat, double maxLat, double minLon, double maxLon) {
|
||||
this.bbox = new byte[4 * BYTES];
|
||||
int minXenc = encodeLongitudeCeil(minLon);
|
||||
int maxXenc = encodeLongitude(maxLon);
|
||||
int minYenc = encodeLatitudeCeil(minLat);
|
||||
int maxYenc = encodeLatitude(maxLat);
|
||||
if (minYenc > maxYenc) {
|
||||
minYenc = maxYenc;
|
||||
}
|
||||
this.minY = minYenc;
|
||||
this.maxY = maxYenc;
|
||||
|
||||
if (minLon > maxLon == true) {
|
||||
// crossing dateline is split into east/west boxes
|
||||
this.west = new byte[4 * BYTES];
|
||||
this.minX = minXenc;
|
||||
this.maxX = maxXenc;
|
||||
encode(MIN_LON_ENCODED, this.maxX, this.minY, this.maxY, this.west);
|
||||
encode(this.minX, MAX_LON_ENCODED, this.minY, this.maxY, this.bbox);
|
||||
} else {
|
||||
// encodeLongitudeCeil may cause minX to be > maxX iff
|
||||
// the delta between the longitude < the encoding resolution
|
||||
if (minXenc > maxXenc) {
|
||||
minXenc = maxXenc;
|
||||
}
|
||||
this.west = null;
|
||||
this.minX = minXenc;
|
||||
this.maxX = maxXenc;
|
||||
encode(this.minX, this.maxX, this.minY, this.maxY, bbox);
|
||||
}
|
||||
}
|
||||
|
||||
/** Builds a XRectangle2D from rectangle */
|
||||
public static XRectangle2D create(Rectangle rectangle) {
|
||||
return new XRectangle2D(rectangle.minLat, rectangle.maxLat, rectangle.minLon, rectangle.maxLon);
|
||||
}
|
||||
|
||||
public boolean crossesDateline() {
|
||||
return minX > maxX;
|
||||
}
|
||||
|
||||
/** Checks if the rectangle contains the provided point **/
|
||||
public boolean queryContainsPoint(int x, int y) {
|
||||
if (this.crossesDateline() == true) {
|
||||
return bboxContainsPoint(x, y, MIN_LON_ENCODED, this.maxX, this.minY, this.maxY)
|
||||
|| bboxContainsPoint(x, y, this.minX, MAX_LON_ENCODED, this.minY, this.maxY);
|
||||
}
|
||||
return bboxContainsPoint(x, y, this.minX, this.maxX, this.minY, this.maxY);
|
||||
}
|
||||
|
||||
/** compare this to a provided rangle bounding box **/
|
||||
public PointValues.Relation relateRangeBBox(int minXOffset, int minYOffset, byte[] minTriangle,
|
||||
int maxXOffset, int maxYOffset, byte[] maxTriangle) {
|
||||
PointValues.Relation eastRelation = compareBBoxToRangeBBox(this.bbox, minXOffset, minYOffset, minTriangle,
|
||||
maxXOffset, maxYOffset, maxTriangle);
|
||||
if (this.crossesDateline() && eastRelation == PointValues.Relation.CELL_OUTSIDE_QUERY) {
|
||||
return compareBBoxToRangeBBox(this.west, minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle);
|
||||
}
|
||||
return eastRelation;
|
||||
}
|
||||
|
||||
/** Checks if the rectangle intersects the provided triangle **/
|
||||
public boolean intersectsTriangle(int aX, int aY, int bX, int bY, int cX, int cY) {
|
||||
// 1. query contains any triangle points
|
||||
if (queryContainsPoint(aX, aY) || queryContainsPoint(bX, bY) || queryContainsPoint(cX, cY)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// compute bounding box of triangle
|
||||
int tMinX = StrictMath.min(StrictMath.min(aX, bX), cX);
|
||||
int tMaxX = StrictMath.max(StrictMath.max(aX, bX), cX);
|
||||
int tMinY = StrictMath.min(StrictMath.min(aY, bY), cY);
|
||||
int tMaxY = StrictMath.max(StrictMath.max(aY, bY), cY);
|
||||
|
||||
// 2. check bounding boxes are disjoint
|
||||
if (this.crossesDateline() == true) {
|
||||
if (boxesAreDisjoint(tMinX, tMaxX, tMinY, tMaxY, MIN_LON_ENCODED, this.maxX, this.minY, this.maxY)
|
||||
&& boxesAreDisjoint(tMinX, tMaxX, tMinY, tMaxY, this.minX, MAX_LON_ENCODED, this.minY, this.maxY)) {
|
||||
return false;
|
||||
}
|
||||
} else if (tMaxX < minX || tMinX > maxX || tMinY > maxY || tMaxY < minY) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. check triangle contains any query points
|
||||
if (XTessellator.pointInTriangle(minX, minY, aX, aY, bX, bY, cX, cY)) {
|
||||
return true;
|
||||
} else if (XTessellator.pointInTriangle(maxX, minY, aX, aY, bX, bY, cX, cY)) {
|
||||
return true;
|
||||
} else if (XTessellator.pointInTriangle(maxX, maxY, aX, aY, bX, bY, cX, cY)) {
|
||||
return true;
|
||||
} else if (XTessellator.pointInTriangle(minX, maxY, aX, aY, bX, bY, cX, cY)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 4. last ditch effort: check crossings
|
||||
if (queryIntersects(aX, aY, bX, bY, cX, cY)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Checks if the rectangle contains the provided triangle **/
|
||||
public boolean containsTriangle(int ax, int ay, int bx, int by, int cx, int cy) {
|
||||
if (this.crossesDateline() == true) {
|
||||
return bboxContainsTriangle(ax, ay, bx, by, cx, cy, MIN_LON_ENCODED, this.maxX, this.minY, this.maxY)
|
||||
|| bboxContainsTriangle(ax, ay, bx, by, cx, cy, this.minX, MAX_LON_ENCODED, this.minY, this.maxY);
|
||||
}
|
||||
return bboxContainsTriangle(ax, ay, bx, by, cx, cy, minX, maxX, minY, maxY);
|
||||
}
|
||||
|
||||
/** static utility method to compare a bbox with a range of triangles (just the bbox of the triangle collection) */
|
||||
private static PointValues.Relation compareBBoxToRangeBBox(final byte[] bbox,
|
||||
int minXOffset, int minYOffset, byte[] minTriangle,
|
||||
int maxXOffset, int maxYOffset, byte[] maxTriangle) {
|
||||
// check bounding box (DISJOINT)
|
||||
if (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) {
|
||||
return PointValues.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 PointValues.Relation.CELL_INSIDE_QUERY;
|
||||
}
|
||||
return PointValues.Relation.CELL_CROSSES_QUERY;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 * XLatLonShape.BYTES];
|
||||
}
|
||||
NumericUtils.intToSortableBytes(minY, b, 0);
|
||||
NumericUtils.intToSortableBytes(minX, b, BYTES);
|
||||
NumericUtils.intToSortableBytes(maxY, b, 2 * BYTES);
|
||||
NumericUtils.intToSortableBytes(maxX, b, 3 * BYTES);
|
||||
}
|
||||
|
||||
/** returns true if the query intersects the provided triangle (in encoded space) */
|
||||
private boolean queryIntersects(int ax, int ay, int bx, int by, int cx, int cy) {
|
||||
// check each edge of the triangle against the query
|
||||
if (edgeIntersectsQuery(ax, ay, bx, by) ||
|
||||
edgeIntersectsQuery(bx, by, cx, cy) ||
|
||||
edgeIntersectsQuery(cx, cy, ax, ay)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** returns true if the edge (defined by (ax, ay) (bx, by)) intersects the query */
|
||||
private boolean edgeIntersectsQuery(int ax, int ay, int bx, int by) {
|
||||
if (this.crossesDateline() == true) {
|
||||
return edgeIntersectsBox(ax, ay, bx, by, MIN_LON_ENCODED, this.maxX, this.minY, this.maxY)
|
||||
|| edgeIntersectsBox(ax, ay, bx, by, this.minX, MAX_LON_ENCODED, this.minY, this.maxY);
|
||||
}
|
||||
return edgeIntersectsBox(ax, ay, bx, by, this.minX, this.maxX, this.minY, this.maxY);
|
||||
}
|
||||
|
||||
/** static utility method to check if a bounding box contains a point */
|
||||
private static boolean bboxContainsPoint(int x, int y, int minX, int maxX, int minY, int maxY) {
|
||||
return (x < minX || x > maxX || y < minY || y > maxY) == false;
|
||||
}
|
||||
|
||||
/** static utility method to check if a bounding box contains a triangle */
|
||||
private static boolean bboxContainsTriangle(int ax, int ay, int bx, int by, int cx, int cy,
|
||||
int minX, int maxX, int minY, int maxY) {
|
||||
return bboxContainsPoint(ax, ay, minX, maxX, minY, maxY)
|
||||
&& bboxContainsPoint(bx, by, minX, maxX, minY, maxY)
|
||||
&& bboxContainsPoint(cx, cy, minX, maxX, minY, maxY);
|
||||
}
|
||||
|
||||
/** returns true if the edge (defined by (ax, ay) (bx, by)) intersects the query */
|
||||
private static boolean edgeIntersectsBox(int ax, int ay, int bx, int by,
|
||||
int minX, int maxX, int minY, int maxY) {
|
||||
// shortcut: if edge is a point (occurs w/ Line shapes); simply check bbox w/ point
|
||||
if (ax == bx && ay == by) {
|
||||
return Rectangle.containsPoint(ay, ax, minY, maxY, minX, maxX);
|
||||
}
|
||||
|
||||
// shortcut: check if either of the end points fall inside the box
|
||||
if (bboxContainsPoint(ax, ay, minX, maxX, minY, maxY)
|
||||
|| bboxContainsPoint(bx, by, minX, maxX, minY, maxY)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// shortcut: check bboxes of edges are disjoint
|
||||
if (boxesAreDisjoint(Math.min(ax, bx), Math.max(ax, bx), Math.min(ay, by), Math.max(ay, by),
|
||||
minX, maxX, minY, maxY)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// shortcut: edge is a point
|
||||
if (ax == bx && ay == by) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// top
|
||||
if (orient(ax, ay, bx, by, minX, maxY) * orient(ax, ay, bx, by, maxX, maxY) <= 0 &&
|
||||
orient(minX, maxY, maxX, maxY, ax, ay) * orient(minX, maxY, maxX, maxY, bx, by) <= 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// right
|
||||
if (orient(ax, ay, bx, by, maxX, maxY) * orient(ax, ay, bx, by, maxX, minY) <= 0 &&
|
||||
orient(maxX, maxY, maxX, minY, ax, ay) * orient(maxX, maxY, maxX, minY, bx, by) <= 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// bottom
|
||||
if (orient(ax, ay, bx, by, maxX, minY) * orient(ax, ay, bx, by, minX, minY) <= 0 &&
|
||||
orient(maxX, minY, minX, minY, ax, ay) * orient(maxX, minY, minX, minY, bx, by) <= 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// left
|
||||
if (orient(ax, ay, bx, by, minX, minY) * orient(ax, ay, bx, by, minX, maxY) <= 0 &&
|
||||
orient(minX, minY, minX, maxY, ax, ay) * orient(minX, minY, minX, maxY, bx, by) <= 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** utility method to check if two boxes are disjoint */
|
||||
private static boolean boxesAreDisjoint(final int aMinX, final int aMaxX, final int aMinY, final int aMaxY,
|
||||
final int bMinX, final int bMaxX, final int bMinY, final int bMaxY) {
|
||||
return (aMaxX < bMinX || aMinX > bMaxX || aMaxY < bMinY || aMinY > bMaxY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return Arrays.equals(bbox, ((XRectangle2D)o).bbox)
|
||||
&& Arrays.equals(west, ((XRectangle2D)o).west);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = super.hashCode();
|
||||
hash = 31 * hash + Arrays.hashCode(bbox);
|
||||
hash = 31 * hash + Arrays.hashCode(west);
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append("Rectangle(lat=");
|
||||
sb.append(decodeLatitude(minY));
|
||||
sb.append(" TO ");
|
||||
sb.append(decodeLatitude(maxY));
|
||||
sb.append(" lon=");
|
||||
sb.append(decodeLongitude(minX));
|
||||
sb.append(" TO ");
|
||||
sb.append(decodeLongitude(maxX));
|
||||
if (maxX < minX) {
|
||||
sb.append(" [crosses dateline!]");
|
||||
}
|
||||
sb.append(")");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,889 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.lucene.geo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.lucene.geo.GeoUtils.WindingOrder;
|
||||
import org.apache.lucene.util.BitUtil;
|
||||
|
||||
import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitude;
|
||||
import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude;
|
||||
import static org.apache.lucene.geo.GeoUtils.orient;
|
||||
|
||||
/**
|
||||
* Computes a triangular mesh tessellation for a given polygon.
|
||||
* <p>
|
||||
* This is inspired by mapbox's earcut algorithm (https://github.com/mapbox/earcut)
|
||||
* which is a modification to FIST (https://www.cosy.sbg.ac.at/~held/projects/triang/triang.html)
|
||||
* written by Martin Held, and ear clipping (https://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf)
|
||||
* written by David Eberly.
|
||||
* <p>
|
||||
* Notes:
|
||||
* <ul>
|
||||
* <li>Requires valid polygons:
|
||||
* <ul>
|
||||
* <li>No self intersections
|
||||
* <li>Holes may only touch at one vertex
|
||||
* <li>Polygon must have an area (e.g., no "line" boxes)
|
||||
* <li>sensitive to overflow (e.g, subatomic values such as E-200 can cause unexpected behavior)
|
||||
* </ul>
|
||||
* </ul>
|
||||
* <p>
|
||||
* The code is a modified version of the javascript implementation provided by MapBox
|
||||
* under the following license:
|
||||
* <p>
|
||||
* ISC License
|
||||
* <p>
|
||||
* Copyright (c) 2016, Mapbox
|
||||
* <p>
|
||||
* Permission to use, copy, modify, and/or distribute this software for any purpose
|
||||
* with or without fee is hereby granted, provided that the above copyright notice
|
||||
* and this permission notice appear in all copies.
|
||||
* <p>
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH'
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
* FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
* THIS SOFTWARE.
|
||||
*
|
||||
* @lucene.experimental
|
||||
*/
|
||||
public final class XTessellator {
|
||||
// this is a dumb heuristic to control whether we cut over to sorted morton values
|
||||
private static final int VERTEX_THRESHOLD = 80;
|
||||
|
||||
/** state of the tessellated split - avoids recursion */
|
||||
private enum State {
|
||||
INIT, CURE, SPLIT
|
||||
}
|
||||
|
||||
// No Instance:
|
||||
private XTessellator() {}
|
||||
|
||||
/** Produces an array of vertices representing the triangulated result set of the Points array */
|
||||
public static List<Triangle> tessellate(final Polygon polygon) {
|
||||
// Attempt to establish a doubly-linked list of the provided shell points (should be CCW, but this will correct);
|
||||
// then filter instances of intersections.
|
||||
Node outerNode = createDoublyLinkedList(polygon, 0, WindingOrder.CW);
|
||||
// If an outer node hasn't been detected, the shape is malformed. (must comply with OGC SFA specification)
|
||||
if(outerNode == null) {
|
||||
throw new IllegalArgumentException("Malformed shape detected in XTessellator!");
|
||||
}
|
||||
|
||||
// Determine if the specified list of points contains holes
|
||||
if (polygon.numHoles() > 0) {
|
||||
// Eliminate the hole triangulation.
|
||||
outerNode = eliminateHoles(polygon, outerNode);
|
||||
}
|
||||
|
||||
// If the shape crosses VERTEX_THRESHOLD, use z-order curve hashing:
|
||||
final boolean mortonOptimized;
|
||||
{
|
||||
int threshold = VERTEX_THRESHOLD - polygon.numPoints();
|
||||
for (int i = 0; threshold >= 0 && i < polygon.numHoles(); ++i) {
|
||||
threshold -= polygon.getHole(i).numPoints();
|
||||
}
|
||||
|
||||
// Link polygon nodes in Z-Order
|
||||
mortonOptimized = threshold < 0;
|
||||
if (mortonOptimized == true) {
|
||||
sortByMorton(outerNode);
|
||||
}
|
||||
}
|
||||
// Calculate the tessellation using the doubly LinkedList.
|
||||
List<Triangle> result = earcutLinkedList(outerNode, new ArrayList<>(), State.INIT, mortonOptimized);
|
||||
if (result.size() == 0) {
|
||||
throw new IllegalArgumentException("Unable to Tessellate shape [" + polygon + "]. Possible malformed shape detected.");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Creates a circular doubly linked list using polygon points. The order is governed by the specified winding order */
|
||||
private static Node createDoublyLinkedList(final Polygon polygon, int startIndex, final WindingOrder windingOrder) {
|
||||
Node lastNode = null;
|
||||
// Link points into the circular doubly-linked list in the specified winding order
|
||||
if (windingOrder == polygon.getWindingOrder()) {
|
||||
for (int i = 0; i < polygon.numPoints(); ++i) {
|
||||
lastNode = insertNode(polygon, startIndex++, i, lastNode);
|
||||
}
|
||||
} else {
|
||||
for (int i = polygon.numPoints() - 1; i >= 0; --i) {
|
||||
lastNode = insertNode(polygon, startIndex++, i, lastNode);
|
||||
}
|
||||
}
|
||||
// if first and last node are the same then remove the end node and set lastNode to the start
|
||||
if (lastNode != null && isVertexEquals(lastNode, lastNode.next)) {
|
||||
removeNode(lastNode);
|
||||
lastNode = lastNode.next;
|
||||
}
|
||||
|
||||
// Return the last node in the Doubly-Linked List
|
||||
return filterPoints(lastNode, null);
|
||||
}
|
||||
|
||||
/** Links every hole into the outer loop, producing a single-ring polygon without holes. **/
|
||||
private static Node eliminateHoles(final Polygon polygon, Node outerNode) {
|
||||
// Define a list to hole a reference to each filtered hole list.
|
||||
final List<Node> holeList = new ArrayList<>();
|
||||
// Iterate through each array of hole vertices.
|
||||
Polygon[] holes = polygon.getHoles();
|
||||
int nodeIndex = polygon.numPoints();
|
||||
for(int i = 0; i < polygon.numHoles(); ++i) {
|
||||
// create the doubly-linked hole list
|
||||
Node list = createDoublyLinkedList(holes[i], nodeIndex, WindingOrder.CCW);
|
||||
if (list == list.next) {
|
||||
list.isSteiner = true;
|
||||
}
|
||||
// Determine if the resulting hole polygon was successful.
|
||||
if(list != null) {
|
||||
// Add the leftmost vertex of the hole.
|
||||
holeList.add(fetchLeftmost(list));
|
||||
}
|
||||
nodeIndex += holes[i].numPoints();
|
||||
}
|
||||
|
||||
// Sort the hole vertices by x coordinate
|
||||
holeList.sort((Node pNodeA, Node pNodeB) ->
|
||||
pNodeA.getX() < pNodeB.getX() ? -1 : pNodeA.getX() == pNodeB.getX() ? 0 : 1);
|
||||
|
||||
// Process holes from left to right.
|
||||
for(int i = 0; i < holeList.size(); ++i) {
|
||||
// Eliminate hole triangles from the result set
|
||||
final Node holeNode = holeList.get(i);
|
||||
eliminateHole(holeNode, outerNode);
|
||||
// Filter the new polygon.
|
||||
outerNode = filterPoints(outerNode, outerNode.next);
|
||||
}
|
||||
// Return a pointer to the list.
|
||||
return outerNode;
|
||||
}
|
||||
|
||||
/** Finds a bridge between vertices that connects a hole with an outer ring, and links it */
|
||||
private static void eliminateHole(final Node holeNode, Node outerNode) {
|
||||
// Attempt to find a logical bridge between the HoleNode and OuterNode.
|
||||
outerNode = fetchHoleBridge(holeNode, outerNode);
|
||||
// Determine whether a hole bridge could be fetched.
|
||||
if(outerNode != null) {
|
||||
// Split the resulting polygon.
|
||||
Node node = splitPolygon(outerNode, holeNode);
|
||||
// Filter the split nodes.
|
||||
filterPoints(node, node.next);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* David Eberly's algorithm for finding a bridge between a hole and outer polygon
|
||||
*
|
||||
* see: http://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf
|
||||
**/
|
||||
private static Node fetchHoleBridge(final Node holeNode, final Node outerNode) {
|
||||
Node p = outerNode;
|
||||
double qx = Double.NEGATIVE_INFINITY;
|
||||
final double hx = holeNode.getX();
|
||||
final double hy = holeNode.getY();
|
||||
Node connection = null;
|
||||
// 1. find a segment intersected by a ray from the hole's leftmost point to the left;
|
||||
// segment's endpoint with lesser x will be potential connection point
|
||||
{
|
||||
do {
|
||||
if (hy <= p.getY() && hy >= p.next.getY() && p.next.getY() != p.getY()) {
|
||||
final double x = p.getX() + (hy - p.getY()) * (p.next.getX() - p.getX()) / (p.next.getY() - p.getY());
|
||||
if (x <= hx && x > qx) {
|
||||
qx = x;
|
||||
if (x == hx) {
|
||||
if (hy == p.getY()) return p;
|
||||
if (hy == p.next.getY()) return p.next;
|
||||
}
|
||||
connection = p.getX() < p.next.getX() ? p : p.next;
|
||||
}
|
||||
}
|
||||
p = p.next;
|
||||
} while (p != outerNode);
|
||||
}
|
||||
|
||||
if (connection == null) {
|
||||
return null;
|
||||
} else if (hx == qx) {
|
||||
return connection.previous;
|
||||
}
|
||||
|
||||
// 2. look for points inside the triangle of hole point, segment intersection, and endpoint
|
||||
// its a valid connection iff there are no points found;
|
||||
// otherwise choose the point of the minimum angle with the ray as the connection point
|
||||
Node stop = connection;
|
||||
final double mx = connection.getX();
|
||||
final double my = connection.getY();
|
||||
double tanMin = Double.POSITIVE_INFINITY;
|
||||
double tan;
|
||||
p = connection.next;
|
||||
{
|
||||
while (p != stop) {
|
||||
if (hx >= p.getX() && p.getX() >= mx && hx != p.getX()
|
||||
&& pointInEar(p.getX(), p.getY(), hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy)) {
|
||||
tan = Math.abs(hy - p.getY()) / (hx - p.getX()); // tangential
|
||||
if ((tan < tanMin || (tan == tanMin && p.getX() > connection.getX())) && isLocallyInside(p, holeNode)) {
|
||||
connection = p;
|
||||
tanMin = tan;
|
||||
}
|
||||
}
|
||||
p = p.next;
|
||||
}
|
||||
}
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
/** Finds the left-most hole of a polygon ring. **/
|
||||
private static Node fetchLeftmost(final Node start) {
|
||||
Node node = start;
|
||||
Node leftMost = start;
|
||||
do {
|
||||
// Determine if the current node possesses a lesser X position.
|
||||
if (node.getX() < leftMost.getX()) {
|
||||
// Maintain a reference to this Node.
|
||||
leftMost = node;
|
||||
}
|
||||
// Progress the search to the next node in the doubly-linked list.
|
||||
node = node.next;
|
||||
} while (node != start);
|
||||
|
||||
// Return the node with the smallest X value.
|
||||
return leftMost;
|
||||
}
|
||||
|
||||
/** Main ear slicing loop which triangulates the vertices of a polygon, provided as a doubly-linked list. **/
|
||||
private static List<Triangle> earcutLinkedList(Node currEar, final List<Triangle> tessellation,
|
||||
State state, final boolean mortonOptimized) {
|
||||
earcut : do {
|
||||
if (currEar == null || currEar.previous == currEar.next) {
|
||||
return tessellation;
|
||||
}
|
||||
|
||||
Node stop = currEar;
|
||||
Node prevNode;
|
||||
Node nextNode;
|
||||
|
||||
// Iteratively slice ears
|
||||
do {
|
||||
prevNode = currEar.previous;
|
||||
nextNode = currEar.next;
|
||||
// Determine whether the current triangle must be cut off.
|
||||
final boolean isReflex = area(prevNode.getX(), prevNode.getY(), currEar.getX(), currEar.getY(),
|
||||
nextNode.getX(), nextNode.getY()) >= 0;
|
||||
if (isReflex == false && isEar(currEar, mortonOptimized) == true) {
|
||||
// Return the triangulated data
|
||||
tessellation.add(new Triangle(prevNode, currEar, nextNode));
|
||||
// Remove the ear node.
|
||||
removeNode(currEar);
|
||||
|
||||
// Skipping to the next node leaves fewer slither triangles.
|
||||
currEar = nextNode.next;
|
||||
stop = nextNode.next;
|
||||
continue;
|
||||
}
|
||||
currEar = nextNode;
|
||||
|
||||
// If the whole polygon has been iterated over and no more ears can be found.
|
||||
if (currEar == stop) {
|
||||
switch (state) {
|
||||
case INIT:
|
||||
// try filtering points and slicing again
|
||||
currEar = filterPoints(currEar, null);
|
||||
state = State.CURE;
|
||||
continue earcut;
|
||||
case CURE:
|
||||
// if this didn't work, try curing all small self-intersections locally
|
||||
currEar = cureLocalIntersections(currEar, tessellation);
|
||||
state = State.SPLIT;
|
||||
continue earcut;
|
||||
case SPLIT:
|
||||
// as a last resort, try splitting the remaining polygon into two
|
||||
if (splitEarcut(currEar, tessellation, mortonOptimized) == false) {
|
||||
//we could not process all points. Tessellation failed
|
||||
tessellation.clear();
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} while (currEar.previous != currEar.next);
|
||||
break;
|
||||
} while (true);
|
||||
// Return the calculated tessellation
|
||||
return tessellation;
|
||||
}
|
||||
|
||||
/** Determines whether a polygon node forms a valid ear with adjacent nodes. **/
|
||||
private static boolean isEar(final Node ear, final boolean mortonOptimized) {
|
||||
if (mortonOptimized == true) {
|
||||
return mortonIsEar(ear);
|
||||
}
|
||||
|
||||
// make sure there aren't other points inside the potential ear
|
||||
Node node = ear.next.next;
|
||||
while (node != ear.previous) {
|
||||
if (pointInEar(node.getX(), node.getY(), ear.previous.getX(), ear.previous.getY(), ear.getX(), ear.getY(),
|
||||
ear.next.getX(), ear.next.getY())
|
||||
&& area(node.previous.getX(), node.previous.getY(), node.getX(), node.getY(),
|
||||
node.next.getX(), node.next.getY()) >= 0) {
|
||||
return false;
|
||||
}
|
||||
node = node.next;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Uses morton code for speed to determine whether or a polygon node forms a valid ear w/ adjacent nodes */
|
||||
private static boolean mortonIsEar(final Node ear) {
|
||||
// triangle bbox (flip the bits so negative encoded values are < positive encoded values)
|
||||
int minTX = StrictMath.min(StrictMath.min(ear.previous.x, ear.x), ear.next.x) ^ 0x80000000;
|
||||
int minTY = StrictMath.min(StrictMath.min(ear.previous.y, ear.y), ear.next.y) ^ 0x80000000;
|
||||
int maxTX = StrictMath.max(StrictMath.max(ear.previous.x, ear.x), ear.next.x) ^ 0x80000000;
|
||||
int maxTY = StrictMath.max(StrictMath.max(ear.previous.y, ear.y), ear.next.y) ^ 0x80000000;
|
||||
|
||||
// z-order range for the current triangle bbox;
|
||||
long minZ = BitUtil.interleave(minTX, minTY);
|
||||
long maxZ = BitUtil.interleave(maxTX, maxTY);
|
||||
|
||||
// now make sure we don't have other points inside the potential ear;
|
||||
|
||||
// look for points inside the triangle in both directions
|
||||
Node p = ear.previousZ;
|
||||
Node n = ear.nextZ;
|
||||
while (p != null && Long.compareUnsigned(p.morton, minZ) >= 0
|
||||
&& n != null && Long.compareUnsigned(n.morton, maxZ) <= 0) {
|
||||
if (p.idx != ear.previous.idx && p.idx != ear.next.idx &&
|
||||
pointInEar(p.getX(), p.getY(), ear.previous.getX(), ear.previous.getY(), ear.getX(), ear.getY(),
|
||||
ear.next.getX(), ear.next.getY()) &&
|
||||
area(p.previous.getX(), p.previous.getY(), p.getX(), p.getY(), p.next.getX(), p.next.getY()) >= 0) return false;
|
||||
p = p.previousZ;
|
||||
|
||||
if (n.idx != ear.previous.idx && n.idx != ear.next.idx &&
|
||||
pointInEar(n.getX(), n.getY(), ear.previous.getX(), ear.previous.getY(), ear.getX(), ear.getY(),
|
||||
ear.next.getX(), ear.next.getY()) &&
|
||||
area(n.previous.getX(), n.previous.getY(), n.getX(), n.getY(), n.next.getX(), n.next.getY()) >= 0) return false;
|
||||
n = n.nextZ;
|
||||
}
|
||||
|
||||
// first look for points inside the triangle in decreasing z-order
|
||||
while (p != null && Long.compareUnsigned(p.morton, minZ) >= 0) {
|
||||
if (p.idx != ear.previous.idx && p.idx != ear.next.idx
|
||||
&& pointInEar(p.getX(), p.getY(), ear.previous.getX(), ear.previous.getY(), ear.getX(), ear.getY(),
|
||||
ear.next.getX(), ear.next.getY())
|
||||
&& area(p.previous.getX(), p.previous.getY(), p.getX(), p.getY(), p.next.getX(), p.next.getY()) >= 0) {
|
||||
return false;
|
||||
}
|
||||
p = p.previousZ;
|
||||
}
|
||||
// then look for points in increasing z-order
|
||||
while (n != null &&
|
||||
Long.compareUnsigned(n.morton, maxZ) <= 0) {
|
||||
if (n.idx != ear.previous.idx && n.idx != ear.next.idx
|
||||
&& pointInEar(n.getX(), n.getY(), ear.previous.getX(), ear.previous.getY(), ear.getX(), ear.getY(),
|
||||
ear.next.getX(), ear.next.getY())
|
||||
&& area(n.previous.getX(), n.previous.getY(), n.getX(), n.getY(), n.next.getX(), n.next.getY()) >= 0) {
|
||||
return false;
|
||||
}
|
||||
n = n.nextZ;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Iterate through all polygon nodes and remove small local self-intersections **/
|
||||
private static Node cureLocalIntersections(Node startNode, final List<Triangle> tessellation) {
|
||||
Node node = startNode;
|
||||
Node nextNode;
|
||||
do {
|
||||
nextNode = node.next;
|
||||
Node a = node.previous;
|
||||
Node b = nextNode.next;
|
||||
|
||||
// a self-intersection where edge (v[i-1],v[i]) intersects (v[i+1],v[i+2])
|
||||
if (isVertexEquals(a, b) == false
|
||||
&& isIntersectingPolygon(a, a.getX(), a.getY(), b.getX(), b.getY()) == false
|
||||
&& linesIntersect(a.getX(), a.getY(), node.getX(), node.getY(), nextNode.getX(), nextNode.getY(), b.getX(), b.getY())
|
||||
&& isLocallyInside(a, b) && isLocallyInside(b, a)) {
|
||||
// Return the triangulated vertices to the tessellation
|
||||
tessellation.add(new Triangle(a, node, b));
|
||||
|
||||
// remove two nodes involved
|
||||
removeNode(node);
|
||||
removeNode(node.next);
|
||||
node = startNode = b;
|
||||
}
|
||||
node = node.next;
|
||||
} while (node != startNode);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/** Attempt to split a polygon and independently triangulate each side. Return true if the polygon was splitted **/
|
||||
private static boolean splitEarcut(final Node start, final List<Triangle> tessellation, final boolean mortonIndexed) {
|
||||
// Search for a valid diagonal that divides the polygon into two.
|
||||
Node searchNode = start;
|
||||
Node nextNode;
|
||||
do {
|
||||
nextNode = searchNode.next;
|
||||
Node diagonal = nextNode.next;
|
||||
while (diagonal != searchNode.previous) {
|
||||
if(isValidDiagonal(searchNode, diagonal)) {
|
||||
// Split the polygon into two at the point of the diagonal
|
||||
Node splitNode = splitPolygon(searchNode, diagonal);
|
||||
// Filter the resulting polygon.
|
||||
searchNode = filterPoints(searchNode, searchNode.next);
|
||||
splitNode = filterPoints(splitNode, splitNode.next);
|
||||
// Attempt to earcut both of the resulting polygons
|
||||
if (mortonIndexed) {
|
||||
sortByMortonWithReset(searchNode);
|
||||
sortByMortonWithReset(splitNode);
|
||||
}
|
||||
earcutLinkedList(searchNode, tessellation, State.INIT, mortonIndexed);
|
||||
earcutLinkedList(splitNode, tessellation, State.INIT, mortonIndexed);
|
||||
// Finish the iterative search
|
||||
return true;
|
||||
}
|
||||
diagonal = diagonal.next;
|
||||
}
|
||||
searchNode = searchNode.next;
|
||||
} while (searchNode != start);
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Links two polygon vertices using a bridge. **/
|
||||
private static Node splitPolygon(final Node a, final Node b) {
|
||||
final Node a2 = new Node(a);
|
||||
final Node b2 = new Node(b);
|
||||
final Node an = a.next;
|
||||
final Node bp = b.previous;
|
||||
|
||||
a.next = b;
|
||||
a.nextZ = b;
|
||||
b.previous = a;
|
||||
b.previousZ = a;
|
||||
a2.next = an;
|
||||
a2.nextZ = an;
|
||||
an.previous = a2;
|
||||
an.previousZ = a2;
|
||||
b2.next = a2;
|
||||
b2.nextZ = a2;
|
||||
a2.previous = b2;
|
||||
a2.previousZ = b2;
|
||||
bp.next = b2;
|
||||
bp.nextZ = b2;
|
||||
|
||||
return b2;
|
||||
}
|
||||
|
||||
/** Determines whether a diagonal between two polygon nodes lies within a polygon interior.
|
||||
* (This determines the validity of the ray.) **/
|
||||
private static boolean isValidDiagonal(final Node a, final Node b) {
|
||||
return a.next.idx != b.idx && a.previous.idx != b.idx
|
||||
&& isIntersectingPolygon(a, a.getX(), a.getY(), b.getX(), b.getY()) == false
|
||||
&& isLocallyInside(a, b) && isLocallyInside(b, a)
|
||||
&& middleInsert(a, a.getX(), a.getY(), b.getX(), b.getY());
|
||||
}
|
||||
|
||||
private static boolean isLocallyInside(final Node a, final Node b) {
|
||||
// if a is cw
|
||||
if (area(a.previous.getX(), a.previous.getY(), a.getX(), a.getY(), a.next.getX(), a.next.getY()) < 0) {
|
||||
return area(a.getX(), a.getY(), b.getX(), b.getY(), a.next.getX(), a.next.getY()) >= 0
|
||||
&& area(a.getX(), a.getY(), a.previous.getX(), a.previous.getY(), b.getX(), b.getY()) >= 0;
|
||||
}
|
||||
// ccw
|
||||
return area(a.getX(), a.getY(), b.getX(), b.getY(), a.previous.getX(), a.previous.getY()) < 0
|
||||
|| area(a.getX(), a.getY(), a.next.getX(), a.next.getY(), b.getX(), b.getY()) < 0;
|
||||
}
|
||||
|
||||
/** Determine whether the middle point of a polygon diagonal is contained within the polygon */
|
||||
private static boolean middleInsert(final Node start, final double x0, final double y0,
|
||||
final double x1, final double y1) {
|
||||
Node node = start;
|
||||
Node nextNode;
|
||||
boolean lIsInside = false;
|
||||
final double lDx = (x0 + x1) / 2.0f;
|
||||
final double lDy = (y0 + y1) / 2.0f;
|
||||
do {
|
||||
nextNode = node.next;
|
||||
if (node.getY() > lDy != nextNode.getY() > lDy &&
|
||||
lDx < (nextNode.getX() - node.getX()) * (lDy - node.getY()) / (nextNode.getY() - node.getY()) + node.getX()) {
|
||||
lIsInside = !lIsInside;
|
||||
}
|
||||
node = node.next;
|
||||
} while (node != start);
|
||||
return lIsInside;
|
||||
}
|
||||
|
||||
/** Determines if the diagonal of a polygon is intersecting with any polygon elements. **/
|
||||
private static boolean isIntersectingPolygon(final Node start, final double x0, final double y0,
|
||||
final double x1, final double y1) {
|
||||
Node node = start;
|
||||
Node nextNode;
|
||||
do {
|
||||
nextNode = node.next;
|
||||
if(isVertexEquals(node, x0, y0) == false && isVertexEquals(node, x1, y1) == false) {
|
||||
if (linesIntersect(node.getX(), node.getY(), nextNode.getX(), nextNode.getY(), x0, y0, x1, y1)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
node = nextNode;
|
||||
} while (node != start);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Determines whether two line segments intersect. **/
|
||||
public static boolean linesIntersect(final double aX0, final double aY0, final double aX1, final double aY1,
|
||||
final double bX0, final double bY0, final double bX1, final double bY1) {
|
||||
return (area(aX0, aY0, aX1, aY1, bX0, bY0) > 0) != (area(aX0, aY0, aX1, aY1, bX1, bY1) > 0)
|
||||
&& (area(bX0, bY0, bX1, bY1, aX0, aY0) > 0) != (area(bX0, bY0, bX1, bY1, aX1, aY1) > 0);
|
||||
}
|
||||
|
||||
/** Interlinks polygon nodes in Z-Order. It reset the values on the z values**/
|
||||
private static void sortByMortonWithReset(Node start) {
|
||||
Node next = start;
|
||||
do {
|
||||
next.previousZ = next.previous;
|
||||
next.nextZ = next.next;
|
||||
next = next.next;
|
||||
} while (next != start);
|
||||
sortByMorton(start);
|
||||
}
|
||||
|
||||
/** Interlinks polygon nodes in Z-Order. **/
|
||||
private static void sortByMorton(Node start) {
|
||||
start.previousZ.nextZ = null;
|
||||
start.previousZ = null;
|
||||
// Sort the generated ring using Z ordering.
|
||||
tathamSort(start);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simon Tatham's doubly-linked list O(n log n) mergesort
|
||||
* see: http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
|
||||
**/
|
||||
private static void tathamSort(Node list) {
|
||||
Node p, q, e, tail;
|
||||
int i, numMerges, pSize, qSize;
|
||||
int inSize = 1;
|
||||
|
||||
if (list == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
do {
|
||||
p = list;
|
||||
list = null;
|
||||
tail = null;
|
||||
// count number of merges in this pass
|
||||
numMerges = 0;
|
||||
|
||||
while(p != null) {
|
||||
++numMerges;
|
||||
// step 'insize' places along from p
|
||||
q = p;
|
||||
for (i = 0, pSize = 0; i < inSize && q != null; ++i, ++pSize, q = q.nextZ);
|
||||
// if q hasn't fallen off end, we have two lists to merge
|
||||
qSize = inSize;
|
||||
|
||||
// now we have two lists; merge
|
||||
while (pSize > 0 || (qSize > 0 && q != null)) {
|
||||
if (pSize != 0 && (qSize == 0 || q == null || Long.compareUnsigned(p.morton, q.morton) <= 0)) {
|
||||
e = p;
|
||||
p = p.nextZ;
|
||||
--pSize;
|
||||
} else {
|
||||
e = q;
|
||||
q = q.nextZ;
|
||||
--qSize;
|
||||
}
|
||||
|
||||
if (tail != null) {
|
||||
tail.nextZ = e;
|
||||
} else {
|
||||
list = e;
|
||||
}
|
||||
// maintain reverse pointers
|
||||
e.previousZ = tail;
|
||||
tail = e;
|
||||
}
|
||||
// now p has stepped 'insize' places along, and q has too
|
||||
p = q;
|
||||
}
|
||||
|
||||
tail.nextZ = null;
|
||||
inSize *= 2;
|
||||
} while (numMerges > 1);
|
||||
}
|
||||
|
||||
/** Eliminate colinear/duplicate points from the doubly linked list */
|
||||
private static Node filterPoints(final Node start, Node end) {
|
||||
if (start == null) {
|
||||
return start;
|
||||
}
|
||||
|
||||
if(end == null) {
|
||||
end = start;
|
||||
}
|
||||
|
||||
Node node = start;
|
||||
Node nextNode;
|
||||
Node prevNode;
|
||||
boolean continueIteration;
|
||||
|
||||
do {
|
||||
continueIteration = false;
|
||||
nextNode = node.next;
|
||||
prevNode = node.previous;
|
||||
if (node.isSteiner == false && isVertexEquals(node, nextNode)
|
||||
|| area(prevNode.getX(), prevNode.getY(), node.getX(), node.getY(), nextNode.getX(), nextNode.getY()) == 0) {
|
||||
// Remove the node
|
||||
removeNode(node);
|
||||
node = end = prevNode;
|
||||
|
||||
if (node == nextNode) {
|
||||
break;
|
||||
}
|
||||
continueIteration = true;
|
||||
} else {
|
||||
node = nextNode;
|
||||
}
|
||||
} while (continueIteration || node != end);
|
||||
return end;
|
||||
}
|
||||
|
||||
/** Creates a node and optionally links it with a previous node in a circular doubly-linked list */
|
||||
private static Node insertNode(final Polygon polygon, int index, int vertexIndex, final Node lastNode) {
|
||||
final Node node = new Node(polygon, index, vertexIndex);
|
||||
if(lastNode == null) {
|
||||
node.previous = node;
|
||||
node.previousZ = node;
|
||||
node.next = node;
|
||||
node.nextZ = node;
|
||||
} else {
|
||||
node.next = lastNode.next;
|
||||
node.nextZ = lastNode.next;
|
||||
node.previous = lastNode;
|
||||
node.previousZ = lastNode;
|
||||
lastNode.next.previous = node;
|
||||
lastNode.nextZ.previousZ = node;
|
||||
lastNode.next = node;
|
||||
lastNode.nextZ = node;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
/** Removes a node from the doubly linked list */
|
||||
private static void removeNode(Node node) {
|
||||
node.next.previous = node.previous;
|
||||
node.previous.next = node.next;
|
||||
|
||||
if (node.previousZ != null) {
|
||||
node.previousZ.nextZ = node.nextZ;
|
||||
}
|
||||
if (node.nextZ != null) {
|
||||
node.nextZ.previousZ = node.previousZ;
|
||||
}
|
||||
}
|
||||
|
||||
/** Determines if two point vertices are equal. **/
|
||||
private static boolean isVertexEquals(final Node a, final Node b) {
|
||||
return isVertexEquals(a, b.getX(), b.getY());
|
||||
}
|
||||
|
||||
/** Determines if two point vertices are equal. **/
|
||||
private static boolean isVertexEquals(final Node a, final double x, final double y) {
|
||||
return a.getX() == x && a.getY() == y;
|
||||
}
|
||||
|
||||
/** Compute signed area of triangle */
|
||||
private static double area(final double aX, final double aY, final double bX, final double bY,
|
||||
final double cX, final double cY) {
|
||||
return (bY - aY) * (cX - bX) - (bX - aX) * (cY - bY);
|
||||
}
|
||||
|
||||
/** Compute whether point is in a candidate ear */
|
||||
private static boolean pointInEar(final double x, final double y, final double ax, final double ay,
|
||||
final double bx, final double by, final double cx, final double cy) {
|
||||
return (cx - x) * (ay - y) - (ax - x) * (cy - y) >= 0 &&
|
||||
(ax - x) * (by - y) - (bx - x) * (ay - y) >= 0 &&
|
||||
(bx - x) * (cy - y) - (cx - x) * (by - y) >= 0;
|
||||
}
|
||||
|
||||
/** compute whether the given x, y point is in a triangle; uses the winding order method */
|
||||
public static boolean pointInTriangle (double x, double y, double ax, double ay, double bx, double by, double cx, double cy) {
|
||||
int a = orient(x, y, ax, ay, bx, by);
|
||||
int b = orient(x, y, bx, by, cx, cy);
|
||||
if (a == 0 || b == 0 || a < 0 == b < 0) {
|
||||
int c = orient(x, y, cx, cy, ax, ay);
|
||||
return c == 0 || (c < 0 == (b < 0 || a < 0));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Brute force compute if a point is in the polygon by traversing entire triangulation
|
||||
* todo: speed this up using either binary tree or prefix coding (filtering by bounding box of triangle)
|
||||
**/
|
||||
public static boolean pointInPolygon(final List<Triangle> tessellation, double lat, double lon) {
|
||||
// each triangle
|
||||
for (int i = 0; i < tessellation.size(); ++i) {
|
||||
if (tessellation.get(i).containsPoint(lat, lon)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Circular Doubly-linked list used for polygon coordinates */
|
||||
protected static class Node {
|
||||
// node index in the linked list
|
||||
private final int idx;
|
||||
// vertex index in the polygon
|
||||
private final int vrtxIdx;
|
||||
// reference to the polygon for lat/lon values
|
||||
private final Polygon polygon;
|
||||
// encoded x value
|
||||
private final int x;
|
||||
// encoded y value
|
||||
private final int y;
|
||||
// morton code for sorting
|
||||
private final long morton;
|
||||
|
||||
// previous node
|
||||
private Node previous;
|
||||
// next node
|
||||
private Node next;
|
||||
// previous z node
|
||||
private Node previousZ;
|
||||
// next z node
|
||||
private Node nextZ;
|
||||
// triangle center
|
||||
private boolean isSteiner = false;
|
||||
|
||||
protected Node(final Polygon polygon, final int index, final int vertexIndex) {
|
||||
this.idx = index;
|
||||
this.vrtxIdx = vertexIndex;
|
||||
this.polygon = polygon;
|
||||
this.y = encodeLatitude(polygon.getPolyLat(vrtxIdx));
|
||||
this.x = encodeLongitude(polygon.getPolyLon(vrtxIdx));
|
||||
this.morton = BitUtil.interleave(x ^ 0x80000000, y ^ 0x80000000);
|
||||
this.previous = null;
|
||||
this.next = null;
|
||||
this.previousZ = null;
|
||||
this.nextZ = null;
|
||||
}
|
||||
|
||||
/** simple deep copy constructor */
|
||||
protected Node(Node other) {
|
||||
this.idx = other.idx;
|
||||
this.vrtxIdx = other.vrtxIdx;
|
||||
this.polygon = other.polygon;
|
||||
this.morton = other.morton;
|
||||
this.x = other.x;
|
||||
this.y = other.y;
|
||||
this.previous = other.previous;
|
||||
this.next = other.next;
|
||||
this.previousZ = other.previousZ;
|
||||
this.nextZ = other.nextZ;
|
||||
this.isSteiner = other.isSteiner;
|
||||
}
|
||||
|
||||
/** get the x value */
|
||||
public final double getX() {
|
||||
return polygon.getPolyLon(vrtxIdx);
|
||||
}
|
||||
|
||||
/** get the y value */
|
||||
public final double getY() {
|
||||
return polygon.getPolyLat(vrtxIdx);
|
||||
}
|
||||
|
||||
/** get the longitude value */
|
||||
public final double getLon() {
|
||||
return polygon.getPolyLon(vrtxIdx);
|
||||
}
|
||||
|
||||
/** get the latitude value */
|
||||
public final double getLat() {
|
||||
return polygon.getPolyLat(vrtxIdx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (this.previous == null)
|
||||
builder.append("||-");
|
||||
else
|
||||
builder.append(this.previous.idx + " <- ");
|
||||
builder.append(this.idx);
|
||||
if (this.next == null)
|
||||
builder.append(" -||");
|
||||
else
|
||||
builder.append(" -> " + this.next.idx);
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/** Triangle in the tessellated mesh */
|
||||
public static final class Triangle {
|
||||
Node[] vertex;
|
||||
|
||||
protected Triangle(Node a, Node b, Node c) {
|
||||
this.vertex = new Node[] {a, b, c};
|
||||
}
|
||||
|
||||
/** get quantized x value for the given vertex */
|
||||
public int getEncodedX(int vertex) {
|
||||
return this.vertex[vertex].x;
|
||||
}
|
||||
|
||||
/** get quantized y value for the given vertex */
|
||||
public int getEncodedY(int vertex) {
|
||||
return this.vertex[vertex].y;
|
||||
}
|
||||
|
||||
/** get latitude value for the given vertex */
|
||||
public double getLat(int vertex) {
|
||||
return this.vertex[vertex].getLat();
|
||||
}
|
||||
|
||||
/** get longitude value for the given vertex */
|
||||
public double getLon(int vertex) {
|
||||
return this.vertex[vertex].getLon();
|
||||
}
|
||||
|
||||
/** utility method to compute whether the point is in the triangle */
|
||||
protected boolean containsPoint(double lat, double lon) {
|
||||
return pointInTriangle(lon, lat,
|
||||
vertex[0].getLon(), vertex[0].getLat(),
|
||||
vertex[1].getLon(), vertex[1].getLat(),
|
||||
vertex[2].getLon(), vertex[2].getLat());
|
||||
}
|
||||
|
||||
/** pretty print the triangle vertices */
|
||||
public String toString() {
|
||||
String result = vertex[0].x + ", " + vertex[0].y + " " +
|
||||
vertex[1].x + ", " + vertex[1].y + " " +
|
||||
vertex[2].x + ", " + vertex[2].y;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
package org.elasticsearch.common.geo;
|
||||
|
||||
import org.apache.lucene.document.LatLonShape.QueryRelation;
|
||||
import org.apache.lucene.document.XLatLonShape.QueryRelation;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.common.io.stream.Writeable;
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
package org.elasticsearch.index.mapper;
|
||||
|
||||
import org.apache.lucene.document.Field;
|
||||
import org.apache.lucene.document.LatLonShape;
|
||||
import org.apache.lucene.document.XLatLonShape;
|
||||
import org.apache.lucene.geo.Line;
|
||||
import org.apache.lucene.geo.Polygon;
|
||||
import org.apache.lucene.geo.Rectangle;
|
||||
|
@ -35,7 +35,7 @@ import java.util.ArrayList;
|
|||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* FieldMapper for indexing {@link org.apache.lucene.document.LatLonShape}s.
|
||||
* FieldMapper for indexing {@link XLatLonShape}s.
|
||||
* <p>
|
||||
* Currently Shapes can only be indexed and can only be queried using
|
||||
* {@link org.elasticsearch.index.query.GeoShapeQueryBuilder}, consequently
|
||||
|
@ -97,7 +97,7 @@ public class GeoShapeFieldMapper extends BaseGeoShapeFieldMapper {
|
|||
return (GeoShapeFieldType) super.fieldType();
|
||||
}
|
||||
|
||||
/** parsing logic for {@link LatLonShape} indexing */
|
||||
/** parsing logic for {@link XLatLonShape} indexing */
|
||||
@Override
|
||||
public void parse(ParseContext context) throws IOException {
|
||||
try {
|
||||
|
@ -122,35 +122,35 @@ public class GeoShapeFieldMapper extends BaseGeoShapeFieldMapper {
|
|||
private void indexShape(ParseContext context, Object luceneShape) {
|
||||
if (luceneShape instanceof GeoPoint) {
|
||||
GeoPoint pt = (GeoPoint) luceneShape;
|
||||
indexFields(context, LatLonShape.createIndexableFields(name(), pt.lat(), pt.lon()));
|
||||
indexFields(context, XLatLonShape.createIndexableFields(name(), pt.lat(), pt.lon()));
|
||||
} else if (luceneShape instanceof double[]) {
|
||||
double[] pt = (double[]) luceneShape;
|
||||
indexFields(context, LatLonShape.createIndexableFields(name(), pt[1], pt[0]));
|
||||
indexFields(context, XLatLonShape.createIndexableFields(name(), pt[1], pt[0]));
|
||||
} else if (luceneShape instanceof Line) {
|
||||
indexFields(context, LatLonShape.createIndexableFields(name(), (Line)luceneShape));
|
||||
indexFields(context, XLatLonShape.createIndexableFields(name(), (Line)luceneShape));
|
||||
} else if (luceneShape instanceof Polygon) {
|
||||
indexFields(context, LatLonShape.createIndexableFields(name(), (Polygon) luceneShape));
|
||||
indexFields(context, XLatLonShape.createIndexableFields(name(), (Polygon) luceneShape));
|
||||
} else if (luceneShape instanceof double[][]) {
|
||||
double[][] pts = (double[][])luceneShape;
|
||||
for (int i = 0; i < pts.length; ++i) {
|
||||
indexFields(context, LatLonShape.createIndexableFields(name(), pts[i][1], pts[i][0]));
|
||||
indexFields(context, XLatLonShape.createIndexableFields(name(), pts[i][1], pts[i][0]));
|
||||
}
|
||||
} else if (luceneShape instanceof Line[]) {
|
||||
Line[] lines = (Line[]) luceneShape;
|
||||
for (int i = 0; i < lines.length; ++i) {
|
||||
indexFields(context, LatLonShape.createIndexableFields(name(), lines[i]));
|
||||
indexFields(context, XLatLonShape.createIndexableFields(name(), lines[i]));
|
||||
}
|
||||
} else if (luceneShape instanceof Polygon[]) {
|
||||
Polygon[] polys = (Polygon[]) luceneShape;
|
||||
for (int i = 0; i < polys.length; ++i) {
|
||||
indexFields(context, LatLonShape.createIndexableFields(name(), polys[i]));
|
||||
indexFields(context, XLatLonShape.createIndexableFields(name(), polys[i]));
|
||||
}
|
||||
} else if (luceneShape instanceof Rectangle) {
|
||||
// index rectangle as a polygon
|
||||
Rectangle r = (Rectangle) luceneShape;
|
||||
Polygon p = new Polygon(new double[]{r.minLat, r.minLat, r.maxLat, r.maxLat, r.minLat},
|
||||
new double[]{r.minLon, r.maxLon, r.maxLon, r.minLon, r.minLon});
|
||||
indexFields(context, LatLonShape.createIndexableFields(name(), p));
|
||||
indexFields(context, XLatLonShape.createIndexableFields(name(), p));
|
||||
} else if (luceneShape instanceof Object[]) {
|
||||
// recurse to index geometry collection
|
||||
for (Object o : (Object[])luceneShape) {
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
package org.elasticsearch.index.query;
|
||||
|
||||
import org.apache.lucene.document.LatLonShape;
|
||||
import org.apache.lucene.document.XLatLonShape;
|
||||
import org.apache.lucene.geo.Line;
|
||||
import org.apache.lucene.geo.Polygon;
|
||||
import org.apache.lucene.geo.Rectangle;
|
||||
|
@ -429,16 +429,16 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
|
|||
private Query getVectorQueryFromShape(QueryShardContext context, Object queryShape) {
|
||||
Query geoQuery;
|
||||
if (queryShape instanceof Line[]) {
|
||||
geoQuery = LatLonShape.newLineQuery(fieldName(), relation.getLuceneRelation(), (Line[]) queryShape);
|
||||
geoQuery = XLatLonShape.newLineQuery(fieldName(), relation.getLuceneRelation(), (Line[]) queryShape);
|
||||
} else if (queryShape instanceof Polygon[]) {
|
||||
geoQuery = LatLonShape.newPolygonQuery(fieldName(), relation.getLuceneRelation(), (Polygon[]) queryShape);
|
||||
geoQuery = XLatLonShape.newPolygonQuery(fieldName(), relation.getLuceneRelation(), (Polygon[]) queryShape);
|
||||
} else if (queryShape instanceof Line) {
|
||||
geoQuery = LatLonShape.newLineQuery(fieldName(), relation.getLuceneRelation(), (Line) queryShape);
|
||||
geoQuery = XLatLonShape.newLineQuery(fieldName(), relation.getLuceneRelation(), (Line) queryShape);
|
||||
} else if (queryShape instanceof Polygon) {
|
||||
geoQuery = LatLonShape.newPolygonQuery(fieldName(), relation.getLuceneRelation(), (Polygon) queryShape);
|
||||
geoQuery = XLatLonShape.newPolygonQuery(fieldName(), relation.getLuceneRelation(), (Polygon) queryShape);
|
||||
} else if (queryShape instanceof Rectangle) {
|
||||
Rectangle r = (Rectangle) queryShape;
|
||||
geoQuery = LatLonShape.newBoxQuery(fieldName(), relation.getLuceneRelation(),
|
||||
geoQuery = XLatLonShape.newBoxQuery(fieldName(), relation.getLuceneRelation(),
|
||||
r.minLat, r.maxLat, r.minLon, r.maxLon);
|
||||
} else if (queryShape instanceof double[][]) {
|
||||
// note: we decompose point queries into a bounding box query with min values == max values
|
||||
|
@ -457,7 +457,7 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
|
|||
+ "But found length " + pt.length + " for field [" + fieldName + "]");
|
||||
}
|
||||
}
|
||||
return LatLonShape.newBoxQuery(fieldName, relation.getLuceneRelation(), pt[1], pt[1], pt[0], pt[0]);
|
||||
return XLatLonShape.newBoxQuery(fieldName, relation.getLuceneRelation(), pt[1], pt[1], pt[0], pt[0]);
|
||||
} else if (queryShape instanceof Object[]) {
|
||||
geoQuery = createGeometryCollectionQuery(context, (Object[]) queryShape);
|
||||
} else {
|
||||
|
|
|
@ -74,8 +74,9 @@ artifacts {
|
|||
nodeps nodepsJar
|
||||
}
|
||||
|
||||
|
||||
tasks.withType(CheckForbiddenApis) {
|
||||
forbiddenApisMain {
|
||||
//sql does not depend on server, so only jdk signatures should be checked
|
||||
replaceSignatureFiles 'jdk-signatures'
|
||||
signaturesFiles += files('src/forbidden/cli-signatures.txt')
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue