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:
Nick Knize 2019-01-03 12:48:36 -06:00 committed by GitHub
parent e4ec51879e
commit e613bcae43
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 20 additions and 2321 deletions

View File

@ -147,15 +147,3 @@ 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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) {

View File

@ -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 {