Remove XLatLonShape classes (#37094)
This commit removes local XLatLonShape classes and replaces with current LatLonShape classes in latest lucene snapshot
This commit is contained in:
parent
e4ec51879e
commit
e613bcae43
|
@ -146,16 +146,4 @@ org.apache.logging.log4j.Logger#warn(java.lang.Object, java.lang.Throwable)
|
|||
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
|
||||
org.apache.logging.log4j.Logger#fatal(java.lang.Object, java.lang.Throwable)
|
|
@ -1,372 +0,0 @@
|
|||
/*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -1,95 +0,0 @@
|
|||
/*
|
||||
* 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.
|
||||
*
|
||||
**/
|
||||
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();
|
||||
}
|
||||
}
|
|
@ -1,133 +0,0 @@
|
|||
/*
|
||||
* 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.
|
||||
*
|
||||
**/
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -1,122 +0,0 @@
|
|||
/*
|
||||
* 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.
|
||||
*
|
||||
**/
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -1,363 +0,0 @@
|
|||
/*
|
||||
* 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
|
||||
*
|
||||
**/
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,316 +0,0 @@
|
|||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
|
@ -1,888 +0,0 @@
|
|||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
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.XLatLonShape.QueryRelation;
|
||||
import org.apache.lucene.document.LatLonShape.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.XLatLonShape;
|
||||
import org.apache.lucene.document.LatLonShape;
|
||||
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 XLatLonShape}s.
|
||||
* FieldMapper for indexing {@link LatLonShape}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 XLatLonShape} indexing */
|
||||
/** parsing logic for {@link LatLonShape} 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, XLatLonShape.createIndexableFields(name(), pt.lat(), pt.lon()));
|
||||
indexFields(context, LatLonShape.createIndexableFields(name(), pt.lat(), pt.lon()));
|
||||
} else if (luceneShape instanceof double[]) {
|
||||
double[] pt = (double[]) luceneShape;
|
||||
indexFields(context, XLatLonShape.createIndexableFields(name(), pt[1], pt[0]));
|
||||
indexFields(context, LatLonShape.createIndexableFields(name(), pt[1], pt[0]));
|
||||
} else if (luceneShape instanceof Line) {
|
||||
indexFields(context, XLatLonShape.createIndexableFields(name(), (Line)luceneShape));
|
||||
indexFields(context, LatLonShape.createIndexableFields(name(), (Line)luceneShape));
|
||||
} else if (luceneShape instanceof Polygon) {
|
||||
indexFields(context, XLatLonShape.createIndexableFields(name(), (Polygon) luceneShape));
|
||||
indexFields(context, LatLonShape.createIndexableFields(name(), (Polygon) luceneShape));
|
||||
} else if (luceneShape instanceof double[][]) {
|
||||
double[][] pts = (double[][])luceneShape;
|
||||
for (int i = 0; i < pts.length; ++i) {
|
||||
indexFields(context, XLatLonShape.createIndexableFields(name(), pts[i][1], pts[i][0]));
|
||||
indexFields(context, LatLonShape.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, XLatLonShape.createIndexableFields(name(), lines[i]));
|
||||
indexFields(context, LatLonShape.createIndexableFields(name(), lines[i]));
|
||||
}
|
||||
} else if (luceneShape instanceof Polygon[]) {
|
||||
Polygon[] polys = (Polygon[]) luceneShape;
|
||||
for (int i = 0; i < polys.length; ++i) {
|
||||
indexFields(context, XLatLonShape.createIndexableFields(name(), polys[i]));
|
||||
indexFields(context, LatLonShape.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, XLatLonShape.createIndexableFields(name(), p));
|
||||
indexFields(context, LatLonShape.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.XLatLonShape;
|
||||
import org.apache.lucene.document.LatLonShape;
|
||||
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 = XLatLonShape.newLineQuery(fieldName(), relation.getLuceneRelation(), (Line[]) queryShape);
|
||||
geoQuery = LatLonShape.newLineQuery(fieldName(), relation.getLuceneRelation(), (Line[]) queryShape);
|
||||
} else if (queryShape instanceof Polygon[]) {
|
||||
geoQuery = XLatLonShape.newPolygonQuery(fieldName(), relation.getLuceneRelation(), (Polygon[]) queryShape);
|
||||
geoQuery = LatLonShape.newPolygonQuery(fieldName(), relation.getLuceneRelation(), (Polygon[]) queryShape);
|
||||
} else if (queryShape instanceof Line) {
|
||||
geoQuery = XLatLonShape.newLineQuery(fieldName(), relation.getLuceneRelation(), (Line) queryShape);
|
||||
geoQuery = LatLonShape.newLineQuery(fieldName(), relation.getLuceneRelation(), (Line) queryShape);
|
||||
} else if (queryShape instanceof Polygon) {
|
||||
geoQuery = XLatLonShape.newPolygonQuery(fieldName(), relation.getLuceneRelation(), (Polygon) queryShape);
|
||||
geoQuery = LatLonShape.newPolygonQuery(fieldName(), relation.getLuceneRelation(), (Polygon) queryShape);
|
||||
} else if (queryShape instanceof Rectangle) {
|
||||
Rectangle r = (Rectangle) queryShape;
|
||||
geoQuery = XLatLonShape.newBoxQuery(fieldName(), relation.getLuceneRelation(),
|
||||
geoQuery = LatLonShape.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 XLatLonShape.newBoxQuery(fieldName, relation.getLuceneRelation(), pt[1], pt[1], pt[0], pt[0]);
|
||||
return LatLonShape.newBoxQuery(fieldName, relation.getLuceneRelation(), pt[1], pt[1], pt[0], pt[0]);
|
||||
} else if (queryShape instanceof Object[]) {
|
||||
geoQuery = createGeometryCollectionQuery(context, (Object[]) queryShape);
|
||||
} else {
|
||||
|
|
Loading…
Reference in New Issue