fields = new ArrayList<>(tessellation.size());
+ for (Tessellator.Triangle t : tessellation) {
+ fields.add(new Triangle(fieldName, t));
+ }
+ return fields.toArray(new Field[fields.size()]);
+ }
+
+ /** create indexable fields for cartesian line geometry */
+ public static Field[] createIndexableFields(String fieldName, XYLine 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 Triangle(fieldName,
+ encode(line.getX(i)), encode(line.getY(i)),
+ encode(line.getX(j)), encode(line.getY(j)),
+ encode(line.getX(i)), encode(line.getY(i)));
+ }
+ return fields;
+ }
+
+ /** create indexable fields for cartesian point geometry */
+ public static Field[] createIndexableFields(String fieldName, float x, float y) {
+ return new Field[] {new Triangle(fieldName,
+ encode(x), encode(y), encode(x), encode(y), encode(x), encode(y))};
+ }
+
+ /** create a query to find all cartesian shapes that intersect a defined bounding box **/
+ public static Query newBoxQuery(String field, QueryRelation queryRelation, float minX, float maxX, float minY, float maxY) {
+ return new XYShapeBoundingBoxQuery(field, queryRelation, minX, maxX, minY, maxY);
+ }
+
+ /** create a query to find all cartesian shapes that intersect a provided linestring (or array of linestrings) **/
+ public static Query newLineQuery(String field, QueryRelation queryRelation, XYLine... lines) {
+ return new XYShapeLineQuery(field, queryRelation, lines);
+ }
+
+ /** create a query to find all cartesian shapes that intersect a provided polygon (or array of polygons) **/
+ public static Query newPolygonQuery(String field, QueryRelation queryRelation, XYPolygon... polygons) {
+ return new XYShapePolygonQuery(field, queryRelation, polygons);
+ }
+}
diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/XYShapeBoundingBoxQuery.java b/lucene/sandbox/src/java/org/apache/lucene/document/XYShapeBoundingBoxQuery.java
new file mode 100644
index 000000000000..21fa5b48d51d
--- /dev/null
+++ b/lucene/sandbox/src/java/org/apache/lucene/document/XYShapeBoundingBoxQuery.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.lucene.document;
+
+import org.apache.lucene.document.ShapeField.QueryRelation;
+import org.apache.lucene.geo.XYRectangle;
+import org.apache.lucene.geo.XYRectangle2D;
+import org.apache.lucene.index.PointValues;
+
+/**
+ * Finds all previously indexed cartesian shapes that intersect the specified bounding box.
+ *
+ * The field must be indexed using
+ * {@link org.apache.lucene.document.XYShape#createIndexableFields} added per document.
+ *
+ * @lucene.experimental
+ **/
+public class XYShapeBoundingBoxQuery extends ShapeQuery {
+ final XYRectangle2D rectangle2D;
+
+ public XYShapeBoundingBoxQuery(String field, QueryRelation queryRelation, double minX, double maxX, double minY, double maxY) {
+ super(field, queryRelation);
+ XYRectangle rectangle = new XYRectangle(minX, maxX, minY, maxY);
+ this.rectangle2D = XYRectangle2D.create(rectangle);
+ }
+
+ @Override
+ protected PointValues.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, QueryRelation queryRelation) {
+ // decode indexed triangle
+ ShapeField.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 == 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(((XYShapeBoundingBoxQuery)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();
+ }
+}
diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/XYShapeLineQuery.java b/lucene/sandbox/src/java/org/apache/lucene/document/XYShapeLineQuery.java
new file mode 100644
index 000000000000..b8ec71094d7f
--- /dev/null
+++ b/lucene/sandbox/src/java/org/apache/lucene/document/XYShapeLineQuery.java
@@ -0,0 +1,131 @@
+/*
+ * 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 java.util.Arrays;
+
+import org.apache.lucene.document.ShapeField.QueryRelation;
+import org.apache.lucene.geo.Line2D;
+import org.apache.lucene.geo.XYLine;
+import org.apache.lucene.index.PointValues.Relation;
+import org.apache.lucene.util.NumericUtils;
+
+import static org.apache.lucene.geo.XYEncodingUtils.decode;
+
+/**
+ * Finds all previously indexed cartesian shapes that intersect the specified arbitrary {@code XYLine}.
+ *
+ * Note:
+ *
+ * - {@code QueryRelation.WITHIN} queries are not yet supported
+ *
+ *
+ * todo:
+ *
+ * - Add distance support for buffered queries
+ *
+ * The field must be indexed using
+ * {@link org.apache.lucene.document.XYShape#createIndexableFields} added per document.
+ *
+ * @lucene.experimental
+ **/
+final class XYShapeLineQuery extends ShapeQuery {
+ final XYLine[] lines;
+ final private Line2D line2D;
+
+ public XYShapeLineQuery(String field, QueryRelation queryRelation, XYLine... lines) {
+ super(field, queryRelation);
+ /** line queries do not support within relations, only intersects and disjoint */
+ if (queryRelation == QueryRelation.WITHIN) {
+ throw new IllegalArgumentException("XYShapeLineQuery 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].minX > lines[i].maxX) {
+ throw new IllegalArgumentException("XYShapeLineQuery: minX cannot be greater than maxX.");
+ } else if (lines[i].minY > lines[i].maxY) {
+ throw new IllegalArgumentException("XYShapeLineQuery: minY cannot be greater than maxY.");
+ }
+ }
+ 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 = decode(NumericUtils.sortableBytesToInt(minTriangle, minYOffset));
+ double minLon = decode(NumericUtils.sortableBytesToInt(minTriangle, minXOffset));
+ double maxLat = decode(NumericUtils.sortableBytesToInt(maxTriangle, maxYOffset));
+ double maxLon = decode(NumericUtils.sortableBytesToInt(maxTriangle, maxXOffset));
+
+ // check internal node against query
+ return line2D.relate(minLat, maxLat, minLon, maxLon);
+ }
+
+ @Override
+ protected boolean queryMatches(byte[] t, int[] scratchTriangle, QueryRelation queryRelation) {
+ ShapeField.decodeTriangle(t, scratchTriangle);
+
+ double alat = decode(scratchTriangle[0]);
+ double alon = decode(scratchTriangle[1]);
+ double blat = decode(scratchTriangle[2]);
+ double blon = decode(scratchTriangle[3]);
+ double clat = decode(scratchTriangle[4]);
+ double clon = decode(scratchTriangle[5]);
+
+ if (queryRelation == 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("XYLine(").append(lines[0].toGeoJSON()).append(")");
+ return sb.toString();
+ }
+
+ @Override
+ protected boolean equalsTo(Object o) {
+ return super.equalsTo(o) && Arrays.equals(lines, ((XYShapeLineQuery)o).lines);
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = super.hashCode();
+ hash = 31 * hash + Arrays.hashCode(lines);
+ return hash;
+ }
+}
diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/XYShapePolygonQuery.java b/lucene/sandbox/src/java/org/apache/lucene/document/XYShapePolygonQuery.java
new file mode 100644
index 000000000000..e1b4e9916b37
--- /dev/null
+++ b/lucene/sandbox/src/java/org/apache/lucene/document/XYShapePolygonQuery.java
@@ -0,0 +1,121 @@
+/*
+ * 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 java.util.Arrays;
+
+import org.apache.lucene.document.ShapeField.QueryRelation;
+import org.apache.lucene.geo.Polygon2D;
+import org.apache.lucene.geo.XYEncodingUtils;
+import org.apache.lucene.geo.XYPolygon;
+import org.apache.lucene.geo.XYPolygon2D;
+import org.apache.lucene.index.PointValues.Relation;
+import org.apache.lucene.util.NumericUtils;
+
+/**
+ * Finds all previously indexed cartesian shapes that intersect the specified arbitrary cartesian {@link XYPolygon}.
+ *
+ *
The field must be indexed using
+ * {@link org.apache.lucene.document.XYShape#createIndexableFields} added per document.
+ *
+ * @lucene.experimental
+ **/
+final class XYShapePolygonQuery extends ShapeQuery {
+ final XYPolygon[] polygons;
+ final private Polygon2D poly2D;
+
+ /**
+ * Creates a query that matches all indexed shapes to the provided polygons
+ */
+ public XYShapePolygonQuery(String field, QueryRelation queryRelation, XYPolygon... 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].minX > polygons[i].maxX) {
+ throw new IllegalArgumentException("XYShapePolygonQuery: minX cannot be greater than maxX.");
+ } else if (polygons[i].minY > polygons[i].maxY) {
+ throw new IllegalArgumentException("XYShapePolygonQuery: minY cannot be greater than maxY.");
+ }
+ }
+ this.polygons = polygons.clone();
+ this.poly2D = XYPolygon2D.create(polygons);
+ }
+
+ @Override
+ protected Relation relateRangeBBoxToQuery(int minXOffset, int minYOffset, byte[] minTriangle,
+ int maxXOffset, int maxYOffset, byte[] maxTriangle) {
+
+ double minLat = XYEncodingUtils.decode(NumericUtils.sortableBytesToInt(minTriangle, minYOffset));
+ double minLon = XYEncodingUtils.decode(NumericUtils.sortableBytesToInt(minTriangle, minXOffset));
+ double maxLat = XYEncodingUtils.decode(NumericUtils.sortableBytesToInt(maxTriangle, maxYOffset));
+ double maxLon = XYEncodingUtils.decode(NumericUtils.sortableBytesToInt(maxTriangle, maxXOffset));
+
+ // check internal node against query
+ return poly2D.relate(minLat, maxLat, minLon, maxLon);
+ }
+
+ @Override
+ protected boolean queryMatches(byte[] t, int[] scratchTriangle, QueryRelation queryRelation) {
+ ShapeField.decodeTriangle(t, scratchTriangle);
+
+ double alat = XYEncodingUtils.decode(scratchTriangle[0]);
+ double alon = XYEncodingUtils.decode(scratchTriangle[1]);
+ double blat = XYEncodingUtils.decode(scratchTriangle[2]);
+ double blon = XYEncodingUtils.decode(scratchTriangle[3]);
+ double clat = XYEncodingUtils.decode(scratchTriangle[4]);
+ double clon = XYEncodingUtils.decode(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("XYPolygon(").append(polygons[0].toGeoJSON()).append(")");
+ return sb.toString();
+ }
+
+ @Override
+ protected boolean equalsTo(Object o) {
+ return super.equalsTo(o) && Arrays.equals(polygons, ((XYShapePolygonQuery)o).polygons);
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = super.hashCode();
+ hash = 31 * hash + Arrays.hashCode(polygons);
+ return hash;
+ }
+}
\ No newline at end of file
diff --git a/lucene/sandbox/src/java/org/apache/lucene/geo/Line.java b/lucene/sandbox/src/java/org/apache/lucene/geo/Line.java
index 489e5cf0a3d1..3c28607218cb 100644
--- a/lucene/sandbox/src/java/org/apache/lucene/geo/Line.java
+++ b/lucene/sandbox/src/java/org/apache/lucene/geo/Line.java
@@ -129,9 +129,9 @@ public String toString() {
sb.append("LINE(");
for (int i = 0; i < lats.length; i++) {
sb.append("[")
- .append(lats[i])
- .append(", ")
.append(lons[i])
+ .append(", ")
+ .append(lats[i])
.append("]");
}
sb.append(')');
diff --git a/lucene/sandbox/src/java/org/apache/lucene/geo/Line2D.java b/lucene/sandbox/src/java/org/apache/lucene/geo/Line2D.java
index 9f413195b8f3..3c7f4d110515 100644
--- a/lucene/sandbox/src/java/org/apache/lucene/geo/Line2D.java
+++ b/lucene/sandbox/src/java/org/apache/lucene/geo/Line2D.java
@@ -21,7 +21,7 @@
import static org.apache.lucene.geo.GeoUtils.orient;
/**
- * 2D line implementation represented as a balanced interval tree of edges.
+ * 2D geo line implementation represented as a balanced interval tree of edges.
*
* Line {@code Line2D} Construction takes {@code O(n log n)} time for sorting and tree construction.
* {@link #relate relate()} are {@code O(n)}, but for most practical lines are much faster than brute force.
@@ -33,6 +33,10 @@ private Line2D(Line line) {
super(line.minLat, line.maxLat, line.minLon, line.maxLon, line.getLats(), line.getLons());
}
+ private Line2D(XYLine line) {
+ super(line.minY, line.maxY, line.minX, line.maxX, line.getY(), line.getX());
+ }
+
/** create a Line2D edge tree from provided array of Linestrings */
public static Line2D create(Line... lines) {
Line2D components[] = new Line2D[lines.length];
@@ -42,6 +46,15 @@ public static Line2D create(Line... lines) {
return (Line2D)createTree(components, 0, components.length - 1, false);
}
+ /** create a Line2D edge tree from provided array of Linestrings */
+ public static Line2D create(XYLine... lines) {
+ Line2D components[] = new Line2D[lines.length];
+ for (int i = 0; i < components.length; ++i) {
+ components[i] = new Line2D(lines[i]);
+ }
+ return (Line2D)createTree(components, 0, components.length - 1, false);
+ }
+
@Override
protected Relation componentRelate(double minLat, double maxLat, double minLon, double maxLon) {
if (tree.crossesBox(minLat, maxLat, minLon, maxLon, true)) {
diff --git a/lucene/sandbox/src/java/org/apache/lucene/geo/Rectangle2D.java b/lucene/sandbox/src/java/org/apache/lucene/geo/Rectangle2D.java
index 223d000abfcc..c2005373799a 100644
--- a/lucene/sandbox/src/java/org/apache/lucene/geo/Rectangle2D.java
+++ b/lucene/sandbox/src/java/org/apache/lucene/geo/Rectangle2D.java
@@ -18,8 +18,9 @@
package org.apache.lucene.geo;
import java.util.Arrays;
+import java.util.Objects;
-import org.apache.lucene.index.PointValues;
+import org.apache.lucene.index.PointValues.Relation;
import org.apache.lucene.util.NumericUtils;
import static java.lang.Integer.BYTES;
@@ -32,19 +33,19 @@
import static org.apache.lucene.geo.GeoUtils.orient;
/**
- * 2D rectangle implementation containing spatial logic.
+ * 2D rectangle implementation containing geo spatial logic.
*
* @lucene.internal
*/
public class Rectangle2D {
- final byte[] bbox;
- final byte[] west;
- final int minX;
- final int maxX;
- final int minY;
- final int maxY;
-
- private Rectangle2D(double minLat, double maxLat, double minLon, double maxLon) {
+ protected final byte[] bbox;
+ private final byte[] west;
+ protected final int minX;
+ protected final int maxX;
+ protected final int minY;
+ protected final int maxY;
+
+ protected Rectangle2D(double minLat, double maxLat, double minLon, double maxLon) {
this.bbox = new byte[4 * BYTES];
int minXenc = encodeLongitudeCeil(minLon);
int maxXenc = encodeLongitude(maxLon);
@@ -76,6 +77,16 @@ private Rectangle2D(double minLat, double maxLat, double minLon, double maxLon)
}
}
+ protected Rectangle2D(int minX, int maxX, int minY, int maxY) {
+ this.bbox = new byte[4 * BYTES];
+ this.west = null;
+ this.minX = minX;
+ this.maxX = maxX;
+ this.minY = minY;
+ this.maxY = maxY;
+ encode(this.minX, this.maxX, this.minY, this.maxY, bbox);
+ }
+
/** Builds a Rectangle2D from rectangle */
public static Rectangle2D create(Rectangle rectangle) {
return new Rectangle2D(rectangle.minLat, rectangle.maxLat, rectangle.minLon, rectangle.maxLon);
@@ -95,10 +106,10 @@ public boolean queryContainsPoint(int x, int y) {
}
/** 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) {
+ public Relation relateRangeBBox(int minXOffset, int minYOffset, byte[] minTriangle,
+ int maxXOffset, int maxYOffset, byte[] maxTriangle) {
+ Relation eastRelation = compareBBoxToRangeBBox(this.bbox, minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle);
+ if (this.crossesDateline() && eastRelation == Relation.CELL_OUTSIDE_QUERY) {
return compareBBoxToRangeBBox(this.west, minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle);
}
return eastRelation;
@@ -155,24 +166,24 @@ public boolean containsTriangle(int ax, int ay, int bx, int by, int cx, int cy)
}
/** 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) {
+ private static Relation compareBBoxToRangeBBox(final byte[] bbox,
+ int minXOffset, int minYOffset, byte[] minTriangle,
+ int maxXOffset, int maxYOffset, byte[] maxTriangle) {
// check bounding box (DISJOINT)
if (Arrays.compareUnsigned(minTriangle, minXOffset, minXOffset + BYTES, bbox, 3 * BYTES, 4 * BYTES) > 0 ||
Arrays.compareUnsigned(maxTriangle, maxXOffset, maxXOffset + BYTES, bbox, BYTES, 2 * BYTES) < 0 ||
Arrays.compareUnsigned(minTriangle, minYOffset, minYOffset + BYTES, bbox, 2 * BYTES, 3 * BYTES) > 0 ||
Arrays.compareUnsigned(maxTriangle, maxYOffset, maxYOffset + BYTES, bbox, 0, BYTES) < 0) {
- return PointValues.Relation.CELL_OUTSIDE_QUERY;
+ return Relation.CELL_OUTSIDE_QUERY;
}
if (Arrays.compareUnsigned(minTriangle, minXOffset, minXOffset + BYTES, bbox, BYTES, 2 * BYTES) >= 0 &&
Arrays.compareUnsigned(maxTriangle, maxXOffset, maxXOffset + BYTES, bbox, 3 * BYTES, 4 * BYTES) <= 0 &&
Arrays.compareUnsigned(minTriangle, minYOffset, minYOffset + BYTES, bbox, 0, BYTES) >= 0 &&
Arrays.compareUnsigned(maxTriangle, maxYOffset, maxYOffset + BYTES, bbox, 2 * BYTES, 3 * BYTES) <= 0) {
- return PointValues.Relation.CELL_INSIDE_QUERY;
+ return Relation.CELL_INSIDE_QUERY;
}
- return PointValues.Relation.CELL_CROSSES_QUERY;
+ return Relation.CELL_CROSSES_QUERY;
}
/**
@@ -272,4 +283,25 @@ private static boolean boxesAreDisjoint(final int aMinX, final int aMaxX, final
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) {
+ if (this == o) return true;
+ if (!(o instanceof Rectangle2D)) return false;
+ Rectangle2D that = (Rectangle2D) o;
+ return minX == that.minX &&
+ maxX == that.maxX &&
+ minY == that.minY &&
+ maxY == that.maxY &&
+ Arrays.equals(bbox, that.bbox) &&
+ Arrays.equals(west, that.west);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Objects.hash(minX, maxX, minY, maxY);
+ result = 31 * result + Arrays.hashCode(bbox);
+ result = 31 * result + Arrays.hashCode(west);
+ return result;
+ }
}
diff --git a/lucene/sandbox/src/java/org/apache/lucene/geo/Tessellator.java b/lucene/sandbox/src/java/org/apache/lucene/geo/Tessellator.java
index 9e96a047ff41..e46df18af1ae 100644
--- a/lucene/sandbox/src/java/org/apache/lucene/geo/Tessellator.java
+++ b/lucene/sandbox/src/java/org/apache/lucene/geo/Tessellator.java
@@ -80,11 +80,51 @@ private enum State {
// No Instance:
private Tessellator() {}
- /** Produces an array of vertices representing the triangulated result set of the Points array */
public static final List 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);
+ Node outerNode = createDoublyLinkedList(polygon.getPolyLons(), polygon.getPolyLats(),polygon.getWindingOrder(), true,
+ 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 Tessellator!");
+ }
+
+ // 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 result = earcutLinkedList(polygon, outerNode, new ArrayList<>(), State.INIT, mortonOptimized);
+ if (result.size() == 0) {
+ throw new IllegalArgumentException("Unable to Tessellate shape [" + polygon + "]. Possible malformed shape detected.");
+ }
+
+ return result;
+ }
+
+
+ public static final List tessellate(final XYPolygon 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.getPolyX(), polygon.getPolyY(), polygon.getWindingOrder(), false,
+ 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 Tessellator!");
@@ -120,16 +160,17 @@ public static final List tessellate(final Polygon polygon) {
}
/** Creates a circular doubly linked list using polygon points. The order is governed by the specified winding order */
- private static final Node createDoublyLinkedList(final Polygon polygon, int startIndex, final WindingOrder windingOrder) {
+ private static final Node createDoublyLinkedList(final double[] x, final double[] y, final WindingOrder polyWindingOrder,
+ boolean isGeo, 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);
+ if (windingOrder == polyWindingOrder) {
+ for (int i = 0; i < x.length; ++i) {
+ lastNode = insertNode(x, y, startIndex++, i, lastNode, isGeo);
}
} else {
- for (int i = polygon.numPoints() - 1; i >= 0; --i) {
- lastNode = insertNode(polygon, startIndex++, i, lastNode);
+ for (int i = x.length - 1; i >= 0; --i) {
+ lastNode = insertNode(x, y, startIndex++, i, lastNode, isGeo);
}
}
// if first and last node are the same then remove the end node and set lastNode to the start
@@ -142,6 +183,29 @@ private static final Node createDoublyLinkedList(final Polygon polygon, int star
return filterPoints(lastNode, null);
}
+ private static final Node eliminateHoles(final XYPolygon polygon, Node outerNode) {
+ // Define a list to hole a reference to each filtered hole list.
+ final List holeList = new ArrayList<>();
+ // keep a reference to the hole
+ final Map holeListPolygons = new HashMap<>();
+ // Iterate through each array of hole vertices.
+ XYPolygon[] 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].getPolyX(), holes[i].getPolyY(), holes[i].getWindingOrder(), false, nodeIndex, WindingOrder.CCW);
+ // Determine if the resulting hole polygon was successful.
+ if(list != null) {
+ // Add the leftmost vertex of the hole.
+ Node leftMost = fetchLeftmost(list);
+ holeList.add(leftMost);
+ holeListPolygons.put(leftMost, holes[i]);
+ }
+ nodeIndex += holes[i].numPoints();
+ }
+ return eliminateHoles(holeList, holeListPolygons, outerNode);
+ }
+
/** Links every hole into the outer loop, producing a single-ring polygon without holes. **/
private static final Node eliminateHoles(final Polygon polygon, Node outerNode) {
// Define a list to hole a reference to each filtered hole list.
@@ -153,8 +217,7 @@ private static final Node eliminateHoles(final Polygon polygon, Node outerNode)
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);
-
+ Node list = createDoublyLinkedList(holes[i].getPolyLons(), holes[i].getPolyLats(), holes[i].getWindingOrder(), true, nodeIndex, WindingOrder.CCW);
if (list == list.next) {
throw new IllegalArgumentException("Points are all coplanar in hole: " + holes[i]);
}
@@ -167,7 +230,10 @@ private static final Node eliminateHoles(final Polygon polygon, Node outerNode)
}
nodeIndex += holes[i].numPoints();
}
+ return eliminateHoles(holeList, holeListPolygons, outerNode);
+ }
+ private static final Node eliminateHoles(List holeList, final Map holeListPolygons, Node outerNode) {
// Sort the hole vertices by x coordinate
holeList.sort((Node pNodeA, Node pNodeB) ->
{
@@ -188,8 +254,22 @@ private static final Node eliminateHoles(final Polygon polygon, Node outerNode)
for(int i = 0; i < holeList.size(); ++i) {
// Eliminate hole triangles from the result set
final Node holeNode = holeList.get(i);
- final Polygon hole = holeListPolygons.get(holeNode);
- eliminateHole(holeNode, outerNode, hole);
+ double holeMinX, holeMaxX, holeMinY, holeMaxY;
+ Object h = holeListPolygons.get(holeNode);
+ if (h instanceof Polygon) {
+ Polygon holePoly = (Polygon)h;
+ holeMinX = holePoly.minLon;
+ holeMaxX = holePoly.maxLon;
+ holeMinY = holePoly.minLat;
+ holeMaxY = holePoly.maxLat;
+ } else {
+ XYPolygon holePoly = (XYPolygon)h;
+ holeMinX = holePoly.minX;
+ holeMaxX = holePoly.maxX;
+ holeMinY = holePoly.minY;
+ holeMaxY = holePoly.maxY;
+ }
+ eliminateHole(holeNode, outerNode, holeMinX, holeMaxX, holeMinY, holeMaxY);
// Filter the new polygon.
outerNode = filterPoints(outerNode, outerNode.next);
}
@@ -198,11 +278,11 @@ private static final Node eliminateHoles(final Polygon polygon, Node outerNode)
}
/** Finds a bridge between vertices that connects a hole with an outer ring, and links it */
- private static final void eliminateHole(final Node holeNode, Node outerNode, Polygon hole) {
+ private static final void eliminateHole(final Node holeNode, Node outerNode, double holeMinX, double holeMaxX, double holeMinY, double holeMaxY) {
// Attempt to find a common point between the HoleNode and OuterNode.
Node next = outerNode;
do {
- if (Rectangle.containsPoint(next.getLat(), next.getLon(), hole.minLat, hole.maxLat, hole.minLon, hole.maxLon)) {
+ if (Rectangle.containsPoint(next.getY(), next.getX(), holeMinY, holeMaxY, holeMinX, holeMaxX)) {
Node sharedVertex = getSharedVertex(holeNode, next);
if (sharedVertex != null) {
// Split the resulting polygon.
@@ -319,7 +399,7 @@ private static final Node fetchLeftmost(final Node start) {
}
/** Main ear slicing loop which triangulates the vertices of a polygon, provided as a doubly-linked list. **/
- private static final List earcutLinkedList(Polygon polygon, Node currEar, final List tessellation,
+ private static final List earcutLinkedList(Object polygon, Node currEar, final List tessellation,
State state, final boolean mortonOptimized) {
earcut : do {
if (currEar == null || currEar.previous == currEar.next) {
@@ -479,7 +559,7 @@ && isLocallyInside(a, b) && isLocallyInside(b, a)) {
}
/** Attempt to split a polygon and independently triangulate each side. Return true if the polygon was splitted **/
- private static final boolean splitEarcut(Polygon polygon, final Node start, final List tessellation, final boolean mortonIndexed) {
+ private static final boolean splitEarcut(Object polygon, final Node start, final List tessellation, final boolean mortonIndexed) {
// Search for a valid diagonal that divides the polygon into two.
Node searchNode = start;
Node nextNode;
@@ -553,7 +633,7 @@ private static boolean isCWPolygon(Node start, Node end) {
double windingSum = 0;
do {
// compute signed area
- windingSum += area(next.getLon(), next.getLat(), next.next.getLon(), next.next.getLat(), end.getLon(), end.getLat());
+ windingSum += area(next.getX(), next.getY(), next.next.getX(), next.next.getY(), end.getX(), end.getY());
next = next.next;
} while (next.next != end);
//The polygon must be CW
@@ -734,8 +814,8 @@ private static final Node filterPoints(final Node start, Node end) {
}
/** Creates a node and optionally links it with a previous node in a circular doubly-linked list */
- private static final Node insertNode(final Polygon polygon, int index, int vertexIndex, final Node lastNode) {
- final Node node = new Node(polygon, index, vertexIndex);
+ private static final Node insertNode(final double[] x, final double[] y, int index, int vertexIndex, final Node lastNode, boolean isGeo) {
+ final Node node = new Node(x, y, index, vertexIndex, isGeo);
if(lastNode == null) {
node.previous = node;
node.previousZ = node;
@@ -822,7 +902,9 @@ protected static class Node {
// vertex index in the polygon
private final int vrtxIdx;
// reference to the polygon for lat/lon values
- private final Polygon polygon;
+// private final Polygon polygon;
+ private final double[] polyX;
+ private final double[] polyY;
// encoded x value
private final int x;
// encoded y value
@@ -839,13 +921,14 @@ protected static class Node {
// next z node
private Node nextZ;
- protected Node(final Polygon polygon, final int index, final int vertexIndex) {
+ protected Node(final double[] x, final double[] y, final int index, final int vertexIndex, final boolean isGeo) {
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.polyX = x;
+ this.polyY = y;
+ this.y = isGeo ? encodeLatitude(polyY[vrtxIdx]) : XYEncodingUtils.encode(polyY[vrtxIdx]);
+ this.x = isGeo ? encodeLongitude(polyX[vrtxIdx]) : XYEncodingUtils.encode(polyX[vrtxIdx]);
+ this.morton = BitUtil.interleave(this.x ^ 0x80000000, this.y ^ 0x80000000);
this.previous = null;
this.next = null;
this.previousZ = null;
@@ -856,7 +939,8 @@ protected Node(final Polygon polygon, final int index, final int vertexIndex) {
protected Node(Node other) {
this.idx = other.idx;
this.vrtxIdx = other.vrtxIdx;
- this.polygon = other.polygon;
+ this.polyX = other.polyX;
+ this.polyY = other.polyY;
this.morton = other.morton;
this.x = other.x;
this.y = other.y;
@@ -868,22 +952,12 @@ protected Node(Node other) {
/** get the x value */
public final double getX() {
- return polygon.getPolyLon(vrtxIdx);
+ return polyX[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);
+ return polyY[vrtxIdx];
}
@Override
@@ -920,22 +994,22 @@ 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 y value for the given vertex */
+ public double getY(int vertex) {
+ return this.vertex[vertex].getY();
}
- /** get longitude value for the given vertex */
- public double getLon(int vertex) {
- return this.vertex[vertex].getLon();
+ /** get x value for the given vertex */
+ public double getX(int vertex) {
+ return this.vertex[vertex].getX();
}
/** 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());
+ vertex[0].getX(), vertex[0].getY(),
+ vertex[1].getX(), vertex[1].getY(),
+ vertex[2].getX(), vertex[2].getY());
}
/** pretty print the triangle vertices */
diff --git a/lucene/sandbox/src/java/org/apache/lucene/geo/XYEncodingUtils.java b/lucene/sandbox/src/java/org/apache/lucene/geo/XYEncodingUtils.java
new file mode 100644
index 000000000000..025504fd1e85
--- /dev/null
+++ b/lucene/sandbox/src/java/org/apache/lucene/geo/XYEncodingUtils.java
@@ -0,0 +1,74 @@
+/*
+ * 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.util.NumericUtils;
+
+
+/**
+ * reusable cartesian geometry encoding methods
+ *
+ * @lucene.experimental
+ */
+public final class XYEncodingUtils {
+
+ public static final double MIN_VAL_INCL = -Float.MAX_VALUE;
+ public static final double MAX_VAL_INCL = Float.MAX_VALUE;
+
+ // No instance:
+ private XYEncodingUtils() {
+ }
+
+ /** validates value is within +/-{@link Float#MAX_VALUE} coordinate bounds */
+ public static void checkVal(double x) {
+ if (Double.isNaN(x) || x < MIN_VAL_INCL || x > MAX_VAL_INCL) {
+ throw new IllegalArgumentException("invalid value " + x + "; must be between " + MIN_VAL_INCL + " and " + MAX_VAL_INCL);
+ }
+ }
+
+ /**
+ * Quantizes double (64 bit) values into 32 bits
+ * @param x cartesian value
+ * @return encoded value as a 32-bit {@code int}
+ * @throws IllegalArgumentException if value is out of bounds
+ */
+ public static int encode(double x) {
+ checkVal(x);
+ return NumericUtils.floatToSortableInt((float)x);
+ }
+
+ /**
+ * Turns quantized value from {@link #encode} back into a double.
+ * @param encoded encoded value: 32-bit quantized value.
+ * @return decoded value value.
+ */
+ public static double decode(int encoded) {
+ double result = NumericUtils.sortableIntToFloat(encoded);
+ assert result >= MIN_VAL_INCL && result <= MAX_VAL_INCL;
+ return result;
+ }
+
+ /**
+ * Turns quantized value from byte array back into a double.
+ * @param src byte array containing 4 bytes to decode at {@code offset}
+ * @param offset offset into {@code src} to decode from.
+ * @return decoded value.
+ */
+ public static double decode(byte[] src, int offset) {
+ return decode(NumericUtils.sortableBytesToInt(src, offset));
+ }
+}
diff --git a/lucene/sandbox/src/java/org/apache/lucene/geo/XYLine.java b/lucene/sandbox/src/java/org/apache/lucene/geo/XYLine.java
new file mode 100644
index 000000000000..9ece6594c3d1
--- /dev/null
+++ b/lucene/sandbox/src/java/org/apache/lucene/geo/XYLine.java
@@ -0,0 +1,146 @@
+/*
+ * 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.Arrays;
+
+/**
+ * Represents a line in cartesian space. You can construct the Line directly with {@code double[]}, {@code double[]} x, y arrays
+ * coordinates.
+ *
+ * @lucene.experimental
+ */
+public class XYLine {
+ /** array of x coordinates */
+ private final double[] x;
+ /** array of y coordinates */
+ private final double[] y;
+
+ /** minimum x of this line's bounding box */
+ public final double minX;
+ /** maximum x of this line's bounding box */
+ public final double maxX;
+ /** minimum y of this line's bounding box */
+ public final double minY;
+ /** maximum y of this line's bounding box */
+ public final double maxY;
+
+ /**
+ * Creates a new Line from the supplied x/y array.
+ */
+ public XYLine(float[] x, float[] y) {
+ if (x == null) {
+ throw new IllegalArgumentException("x must not be null");
+ }
+ if (y == null) {
+ throw new IllegalArgumentException("y must not be null");
+ }
+ if (x.length != y.length) {
+ throw new IllegalArgumentException("x and y must be equal length");
+ }
+ if (x.length < 2) {
+ throw new IllegalArgumentException("at least 2 line points required");
+ }
+
+ // compute bounding box
+ double minX = x[0];
+ double minY = y[0];
+ double maxX = x[0];
+ double maxY = y[0];
+ for (int i = 0; i < x.length; ++i) {
+ minX = Math.min(x[i], minX);
+ minY = Math.min(y[i], minY);
+ maxX = Math.max(x[i], maxX);
+ maxY = Math.max(y[i], maxY);
+ }
+
+ this.x = new double[x.length];
+ this.y = new double[y.length];
+ for (int i = 0; i < x.length; ++i) {
+ this.x[i] = (double)x[i];
+ this.y[i] = (double)y[i];
+ }
+ this.minX = minX;
+ this.maxX = maxX;
+ this.minY = minY;
+ this.maxY = maxY;
+ }
+
+ /** returns the number of vertex points */
+ public int numPoints() {
+ return x.length;
+ }
+
+ /** Returns x value at given index */
+ public double getX(int vertex) {
+ return x[vertex];
+ }
+
+ /** Returns y value at given index */
+ public double getY(int vertex) {
+ return y[vertex];
+ }
+
+ /** Returns a copy of the internal x array */
+ public double[] getX() {
+ return x.clone();
+ }
+
+ /** Returns a copy of the internal y array */
+ public double[] getY() {
+ return y.clone();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof XYLine)) return false;
+ XYLine line = (XYLine) o;
+ return Arrays.equals(x, line.x) && Arrays.equals(y, line.y);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Arrays.hashCode(x);
+ result = 31 * result + Arrays.hashCode(y);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("XYLINE(");
+ for (int i = 0; i < x.length; i++) {
+ sb.append("[")
+ .append(x[i])
+ .append(", ")
+ .append(y[i])
+ .append("]");
+ }
+ sb.append(')');
+ return sb.toString();
+ }
+
+ /** prints polygons as geojson */
+ public String toGeoJSON() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("[");
+ sb.append(Polygon.verticesToGeoJSON(x, y));
+ sb.append("]");
+ return sb.toString();
+ }
+}
diff --git a/lucene/sandbox/src/java/org/apache/lucene/geo/XYPolygon.java b/lucene/sandbox/src/java/org/apache/lucene/geo/XYPolygon.java
new file mode 100644
index 000000000000..585283d1a703
--- /dev/null
+++ b/lucene/sandbox/src/java/org/apache/lucene/geo/XYPolygon.java
@@ -0,0 +1,201 @@
+/*
+ * 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.Arrays;
+
+/**
+ * Represents a polygon in cartesian space. You can construct the Polygon directly with {@code double[]}, {@code double[]} x, y arrays
+ * coordinates.
+ *
+ * @lucene.experimental
+ */
+public class XYPolygon {
+ private final double[] x;
+ private final double[] y;
+ private final XYPolygon[] holes;
+
+ /** minimum x of this polygon's bounding box area */
+ public final double minX;
+ /** maximum x of this polygon's bounding box area */
+ public final double maxX;
+ /** minimum y of this polygon's bounding box area */
+ public final double minY;
+ /** maximum y of this polygon's bounding box area */
+ public final double maxY;
+ /** winding order of the vertices */
+ private final GeoUtils.WindingOrder windingOrder;
+
+ /**
+ * Creates a new Polygon from the supplied x, y arrays, and optionally any holes.
+ */
+ public XYPolygon(float[] x, float[] y, XYPolygon... holes) {
+ if (x == null) {
+ throw new IllegalArgumentException("x must not be null");
+ }
+ if (y == null) {
+ throw new IllegalArgumentException("y must not be null");
+ }
+ if (holes == null) {
+ throw new IllegalArgumentException("holes must not be null");
+ }
+ if (x.length != y.length) {
+ throw new IllegalArgumentException("x and y must be equal length");
+ }
+ if (x.length < 4) {
+ throw new IllegalArgumentException("at least 4 polygon points required");
+ }
+ if (x[0] != x[x.length-1]) {
+ throw new IllegalArgumentException("first and last points of the polygon must be the same (it must close itself): x[0]=" + x[0] + " x[" + (x.length-1) + "]=" + x[x.length-1]);
+ }
+ if (y[0] != y[y.length-1]) {
+ throw new IllegalArgumentException("first and last points of the polygon must be the same (it must close itself): y[0]=" + y[0] + " y[" + (y.length-1) + "]=" + y[y.length-1]);
+ }
+ for (int i = 0; i < holes.length; i++) {
+ XYPolygon inner = holes[i];
+ if (inner.holes.length > 0) {
+ throw new IllegalArgumentException("holes may not contain holes: polygons may not nest.");
+ }
+ }
+ this.x = new double[x.length];
+ this.y = new double[y.length];
+ for (int i = 0; i < x.length; ++i) {
+ this.x[i] = (double)x[i];
+ this.y[i] = (double)y[i];
+ }
+ this.holes = holes.clone();
+
+ // compute bounding box
+ double minX = x[0];
+ double maxX = x[0];
+ double minY = y[0];
+ double maxY = y[0];
+
+ double windingSum = 0d;
+ final int numPts = x.length - 1;
+ for (int i = 1, j = 0; i < numPts; j = i++) {
+ minX = Math.min(x[i], minX);
+ maxX = Math.max(x[i], maxX);
+ minY = Math.min(y[i], minY);
+ maxY = Math.max(y[i], maxY);
+ // compute signed area
+ windingSum += (x[j] - x[numPts])*(y[i] - y[numPts])
+ - (y[j] - y[numPts])*(x[i] - x[numPts]);
+ }
+ this.minX = minX;
+ this.maxX = maxX;
+ this.minY = minY;
+ this.maxY = maxY;
+ this.windingOrder = (windingSum < 0) ? GeoUtils.WindingOrder.CCW : GeoUtils.WindingOrder.CW;
+ }
+
+ /** returns the number of vertex points */
+ public int numPoints() {
+ return x.length;
+ }
+
+ /** Returns a copy of the internal x array */
+ public double[] getPolyX() {
+ return x.clone();
+ }
+
+ /** Returns x value at given index */
+ public double getPolyX(int vertex) {
+ return x[vertex];
+ }
+
+ /** Returns a copy of the internal y array */
+ public double[] getPolyY() {
+ return y.clone();
+ }
+
+ /** Returns y value at given index */
+ public double getPolyY(int vertex) {
+ return y[vertex];
+ }
+
+ /** Returns a copy of the internal holes array */
+ public XYPolygon[] getHoles() {
+ return holes.clone();
+ }
+
+ XYPolygon getHole(int i) {
+ return holes[i];
+ }
+
+ /** Returns the winding order (CW, COLINEAR, CCW) for the polygon shell */
+ public GeoUtils.WindingOrder getWindingOrder() {
+ return this.windingOrder;
+ }
+
+ /** returns the number of holes for the polygon */
+ public int numHoles() {
+ return holes.length;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + Arrays.hashCode(holes);
+ result = prime * result + Arrays.hashCode(x);
+ result = prime * result + Arrays.hashCode(y);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+ XYPolygon other = (XYPolygon) obj;
+ if (!Arrays.equals(holes, other.holes)) return false;
+ if (!Arrays.equals(x, other.x)) return false;
+ if (!Arrays.equals(y, other.y)) return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < x.length; i++) {
+ sb.append("[")
+ .append(x[i])
+ .append(", ")
+ .append(y[i])
+ .append("] ");
+ }
+ if (holes.length > 0) {
+ sb.append(", holes=");
+ sb.append(Arrays.toString(holes));
+ }
+ return sb.toString();
+ }
+
+ /** prints polygons as geojson */
+ public String toGeoJSON() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("[");
+ sb.append(Polygon.verticesToGeoJSON(y, x));
+ for (XYPolygon hole : holes) {
+ sb.append(",");
+ sb.append(Polygon.verticesToGeoJSON(hole.y, hole.x));
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+}
diff --git a/lucene/sandbox/src/java/org/apache/lucene/geo/XYPolygon2D.java b/lucene/sandbox/src/java/org/apache/lucene/geo/XYPolygon2D.java
new file mode 100644
index 000000000000..1dba72bb11b8
--- /dev/null
+++ b/lucene/sandbox/src/java/org/apache/lucene/geo/XYPolygon2D.java
@@ -0,0 +1,44 @@
+/*
+ * 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;
+
+/**
+ * 2D cartesian polygon implementation represented as a balanced interval tree of edges.
+ *
+ * @lucene.internal
+ */
+public class XYPolygon2D extends Polygon2D {
+
+ protected XYPolygon2D(XYPolygon polygon, XYPolygon2D holes) {
+ super(polygon.minY, polygon.maxY, polygon.minX, polygon.maxX, polygon.getPolyY(), polygon.getPolyX(), holes);
+ }
+
+ /** Builds a Polygon2D from multipolygon */
+ public static XYPolygon2D create(XYPolygon... polygons) {
+ XYPolygon2D components[] = new XYPolygon2D[polygons.length];
+ for (int i = 0; i < components.length; i++) {
+ XYPolygon gon = polygons[i];
+ XYPolygon gonHoles[] = gon.getHoles();
+ XYPolygon2D holes = null;
+ if (gonHoles.length > 0) {
+ holes = create(gonHoles);
+ }
+ components[i] = new XYPolygon2D(gon, holes);
+ }
+ return (XYPolygon2D)createTree(components, 0, components.length - 1, false);
+ }
+}
diff --git a/lucene/sandbox/src/java/org/apache/lucene/geo/XYRectangle.java b/lucene/sandbox/src/java/org/apache/lucene/geo/XYRectangle.java
new file mode 100644
index 000000000000..4751cfc163d3
--- /dev/null
+++ b/lucene/sandbox/src/java/org/apache/lucene/geo/XYRectangle.java
@@ -0,0 +1,84 @@
+/*
+ * 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;
+
+/** Represents a x/y cartesian rectangle. */
+public class XYRectangle {
+ /** minimum x value */
+ public final double minX;
+ /** minimum y value */
+ public final double maxX;
+ /** maximum x value */
+ public final double minY;
+ /** maximum y value */
+ public final double maxY;
+
+ /** Constructs a bounding box by first validating the provided x and y coordinates */
+ public XYRectangle(double minX, double maxX, double minY, double maxY) {
+ this.minX = minX;
+ this.maxX = maxX;
+ this.minY = minY;
+ this.maxY = maxY;
+ assert minX <= maxX;
+ assert minY <= maxY;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ XYRectangle rectangle = (XYRectangle) o;
+
+ if (Double.compare(rectangle.minX, minX) != 0) return false;
+ if (Double.compare(rectangle.minY, minY) != 0) return false;
+ if (Double.compare(rectangle.maxX, maxX) != 0) return false;
+ return Double.compare(rectangle.maxY, maxY) == 0;
+
+ }
+
+ @Override
+ public int hashCode() {
+ int result;
+ long temp;
+ temp = Double.doubleToLongBits(minX);
+ result = (int) (temp ^ (temp >>> 32));
+ temp = Double.doubleToLongBits(minY);
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ temp = Double.doubleToLongBits(maxX);
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ temp = Double.doubleToLongBits(maxY);
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder();
+ b.append("XYRectangle(x=");
+ b.append(minX);
+ b.append(" TO ");
+ b.append(maxX);
+ b.append(" y=");
+ b.append(minY);
+ b.append(" TO ");
+ b.append(maxY);
+ b.append(")");
+
+ return b.toString();
+ }
+}
diff --git a/lucene/sandbox/src/java/org/apache/lucene/geo/XYRectangle2D.java b/lucene/sandbox/src/java/org/apache/lucene/geo/XYRectangle2D.java
new file mode 100644
index 000000000000..a1259166a34d
--- /dev/null
+++ b/lucene/sandbox/src/java/org/apache/lucene/geo/XYRectangle2D.java
@@ -0,0 +1,57 @@
+/*
+ * 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 static org.apache.lucene.geo.XYEncodingUtils.decode;
+import static org.apache.lucene.geo.XYEncodingUtils.encode;
+
+/**
+ * 2D rectangle implementation containing cartesian spatial logic.
+ *
+ * @lucene.internal
+ */
+public class XYRectangle2D extends Rectangle2D {
+
+ protected XYRectangle2D(double minX, double maxX, double minY, double maxY) {
+ super(encode(minX), encode(maxX), encode(minY), encode(maxY));
+ }
+
+ /** Builds a Rectangle2D from rectangle */
+ public static XYRectangle2D create(XYRectangle rectangle) {
+ return new XYRectangle2D(rectangle.minX, rectangle.maxX, rectangle.minY, rectangle.maxY);
+ }
+
+ @Override
+ public boolean crossesDateline() {
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("XYRectangle(x=");
+ sb.append(decode(minX));
+ sb.append(" TO ");
+ sb.append(decode(maxX));
+ sb.append(" y=");
+ sb.append(decode(minY));
+ sb.append(" TO ");
+ sb.append(decode(maxY));
+ sb.append(")");
+ return sb.toString();
+ }
+}
diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/BaseLatLonShapeTestCase.java b/lucene/sandbox/src/test/org/apache/lucene/document/BaseLatLonShapeTestCase.java
index fc7e290a0267..b74bfccaef0f 100644
--- a/lucene/sandbox/src/test/org/apache/lucene/document/BaseLatLonShapeTestCase.java
+++ b/lucene/sandbox/src/test/org/apache/lucene/document/BaseLatLonShapeTestCase.java
@@ -16,43 +16,19 @@
*/
package org.apache.lucene.document;
-import java.io.IOException;
import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
import com.carrotsearch.randomizedtesting.generators.RandomPicks;
-import org.apache.lucene.document.LatLonShape.QueryRelation;
+import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.GeoTestUtil;
import org.apache.lucene.geo.Line;
import org.apache.lucene.geo.Line2D;
import org.apache.lucene.geo.Polygon;
import org.apache.lucene.geo.Polygon2D;
import org.apache.lucene.geo.Rectangle;
-import org.apache.lucene.index.DirectoryReader;
-import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.index.IndexWriter;
-import org.apache.lucene.index.IndexWriterConfig;
-import org.apache.lucene.index.LeafReaderContext;
-import org.apache.lucene.index.MultiBits;
-import org.apache.lucene.index.MultiDocValues;
-import org.apache.lucene.index.NumericDocValues;
-import org.apache.lucene.index.SerialMergeScheduler;
-import org.apache.lucene.index.Term;
-import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.QueryUtils;
-import org.apache.lucene.search.ScoreMode;
-import org.apache.lucene.search.SimpleCollector;
-import org.apache.lucene.store.Directory;
-import org.apache.lucene.util.Bits;
-import org.apache.lucene.util.FixedBitSet;
-import org.apache.lucene.util.IOUtils;
-import org.apache.lucene.util.LuceneTestCase;
-import org.apache.lucene.util.TestUtil;
-
-import static com.carrotsearch.randomizedtesting.RandomizedTest.randomBoolean;
-import static com.carrotsearch.randomizedtesting.RandomizedTest.randomIntBetween;
+
import static org.apache.lucene.geo.GeoEncodingUtils.decodeLatitude;
import static org.apache.lucene.geo.GeoEncodingUtils.decodeLongitude;
import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitude;
@@ -62,13 +38,8 @@
import static org.apache.lucene.geo.GeoTestUtil.nextLatitude;
import static org.apache.lucene.geo.GeoTestUtil.nextLongitude;
-/** base test class for {@link TestLatLonLineShapeQueries}, {@link TestLatLonPointShapeQueries},
- * and {@link TestLatLonPolygonShapeQueries} */
-public abstract class BaseLatLonShapeTestCase extends LuceneTestCase {
-
- /** name of the LatLonShape indexed field */
- protected static final String FIELD_NAME = "shape";
- private static final QueryRelation[] POINT_LINE_RELATIONS = {QueryRelation.INTERSECTS, QueryRelation.DISJOINT};
+/** Base test case for testing geospatial indexing and search functionality **/
+public abstract class BaseLatLonShapeTestCase extends BaseShapeTestCase {
protected abstract ShapeType getShapeType();
@@ -76,98 +47,75 @@ protected Object nextShape() {
return getShapeType().nextShape();
}
- /** quantizes a latitude value to be consistent with index encoding */
- protected static double quantizeLat(double rawLat) {
- return decodeLatitude(encodeLatitude(rawLat));
+ /** factory method to create a new bounding box query */
+ @Override
+ protected Query newRectQuery(String field, QueryRelation queryRelation, double minLon, double maxLon, double minLat, double maxLat) {
+ return LatLonShape.newBoxQuery(field, queryRelation, minLat, maxLat, minLon, maxLon);
}
- /** quantizes a provided latitude value rounded up to the nearest encoded integer */
- protected static double quantizeLatCeil(double rawLat) {
- return decodeLatitude(encodeLatitudeCeil(rawLat));
+ /** factory method to create a new line query */
+ @Override
+ protected Query newLineQuery(String field, QueryRelation queryRelation, Object... lines) {
+ return LatLonShape.newLineQuery(field, queryRelation, Arrays.stream(lines).toArray(Line[]::new));
}
- /** quantizes a longitude value to be consistent with index encoding */
- protected static double quantizeLon(double rawLon) {
- return decodeLongitude(encodeLongitude(rawLon));
+ /** factory method to create a new polygon query */
+ @Override
+ protected Query newPolygonQuery(String field, QueryRelation queryRelation, Object... polygons) {
+ return LatLonShape.newPolygonQuery(field, queryRelation, Arrays.stream(polygons).toArray(Polygon[]::new));
}
- /** quantizes a provided longitude value rounded up to the nearest encoded integer */
- protected static double quantizeLonCeil(double rawLon) {
- return decodeLongitude(encodeLongitudeCeil(rawLon));
+ @Override
+ protected Line2D toLine2D(Object... lines) {
+ return Line2D.create(Arrays.stream(lines).toArray(Line[]::new));
}
- /** quantizes a triangle to be consistent with index encoding */
- protected static double[] quantizeTriangle(double ax, double ay, double bx, double by, double cx, double cy) {
- int[] decoded = encodeDecodeTriangle(ax, ay, bx, by, cx, cy);
- return new double[]{decodeLatitude(decoded[0]), decodeLongitude(decoded[1]), decodeLatitude(decoded[2]), decodeLongitude(decoded[3]), decodeLatitude(decoded[4]), decodeLongitude(decoded[5])};
+ @Override
+ protected Polygon2D toPolygon2D(Object... polygons) {
+ return Polygon2D.create(Arrays.stream(polygons).toArray(Polygon[]::new));
}
- /** encode/decode a triangle */
- protected static int[] encodeDecodeTriangle(double ax, double ay, double bx, double by, double cx, double cy) {
- byte[] encoded = new byte[7 * LatLonShape.BYTES];
- LatLonShape.encodeTriangle(encoded, encodeLatitude(ay), encodeLongitude(ax), encodeLatitude(by), encodeLongitude(bx), encodeLatitude(cy), encodeLongitude(cx));
- int[] decoded = new int[6];
- LatLonShape.decodeTriangle(encoded, decoded);
- return decoded;
+ @Override
+ public Rectangle randomQueryBox() {
+ return GeoTestUtil.nextBox();
}
- /** use {@link GeoTestUtil#nextPolygon()} to create a random line; TODO: move to GeoTestUtil */
- public static Line nextLine() {
- Polygon poly = GeoTestUtil.nextPolygon();
- double[] lats = new double[poly.numPoints() - 1];
- double[] lons = new double[lats.length];
- System.arraycopy(poly.getPolyLats(), 0, lats, 0, lats.length);
- System.arraycopy(poly.getPolyLons(), 0, lons, 0, lons.length);
-
- return new Line(lats, lons);
+ @Override
+ protected double rectMinX(Object rect) {
+ return ((Rectangle)rect).minLon;
}
- /**
- * return a semi-random line used for queries
- *
- * note: shapes parameter may be used to ensure some queries intersect indexed shapes
- **/
- protected Line randomQueryLine(Object... shapes) {
- return nextLine();
+ @Override
+ protected double rectMaxX(Object rect) {
+ return ((Rectangle)rect).maxLon;
}
- /** creates the array of LatLonShape.Triangle values that are used to index the shape */
- protected abstract Field[] createIndexableFields(String field, Object shape);
-
- /** adds a shape to a provided document */
- private void addShapeToDoc(String field, Document doc, Object shape) {
- Field[] fields = createIndexableFields(field, shape);
- for (Field f : fields) {
- doc.add(f);
- }
- }
-
- /** factory method to create a new bounding box query */
- protected Query newRectQuery(String field, QueryRelation queryRelation, double minLat, double maxLat, double minLon, double maxLon) {
- return LatLonShape.newBoxQuery(field, queryRelation, minLat, maxLat, minLon, maxLon);
+ @Override
+ protected double rectMinY(Object rect) {
+ return ((Rectangle)rect).minLat;
}
public void testBoxQueryEqualsAndHashcode() {
Rectangle rectangle = GeoTestUtil.nextBox();
QueryRelation queryRelation = RandomPicks.randomFrom(random(), QueryRelation.values());
String fieldName = "foo";
- Query q1 = newRectQuery(fieldName, queryRelation, rectangle.minLat, rectangle.maxLat, rectangle.minLon, rectangle.maxLon);
- Query q2 = newRectQuery(fieldName, queryRelation, rectangle.minLat, rectangle.maxLat, rectangle.minLon, rectangle.maxLon);
+ Query q1 = newRectQuery(fieldName, queryRelation, rectangle.minLon, rectangle.maxLon, rectangle.minLat, rectangle.maxLat);
+ Query q2 = newRectQuery(fieldName, queryRelation, rectangle.minLon, rectangle.maxLon, rectangle.minLat, rectangle.maxLat);
QueryUtils.checkEqual(q1, q2);
//different field name
- Query q3 = newRectQuery("bar", queryRelation, rectangle.minLat, rectangle.maxLat, rectangle.minLon, rectangle.maxLon);
+ Query q3 = newRectQuery("bar", queryRelation, rectangle.minLon, rectangle.maxLon, rectangle.minLat, rectangle.maxLat);
QueryUtils.checkUnequal(q1, q3);
//different query relation
QueryRelation newQueryRelation = RandomPicks.randomFrom(random(), QueryRelation.values());
- Query q4 = newRectQuery(fieldName, newQueryRelation, rectangle.minLat, rectangle.maxLat, rectangle.minLon, rectangle.maxLon);
+ Query q4 = newRectQuery(fieldName, newQueryRelation, rectangle.minLon, rectangle.maxLon, rectangle.minLat, rectangle.maxLat);
if (queryRelation == newQueryRelation) {
QueryUtils.checkEqual(q1, q4);
} else {
QueryUtils.checkUnequal(q1, q4);
}
//different shape
- Rectangle newRectangle = GeoTestUtil.nextBox();;
- Query q5 = newRectQuery(fieldName, queryRelation, newRectangle.minLat, newRectangle.maxLat, newRectangle.minLon, newRectangle.maxLon);
+ Rectangle newRectangle = GeoTestUtil.nextBox();
+ Query q5 = newRectQuery(fieldName, queryRelation, rectangle.minLon, rectangle.maxLon, newRectangle.minLat, newRectangle.maxLat);
if (rectangle.equals(newRectangle)) {
QueryUtils.checkEqual(q1, q5);
} else {
@@ -241,458 +189,102 @@ public void testPolygonQueryEqualsAndHashcode() {
}
}
- // A particularly tricky adversary for BKD tree:
- public void testSameShapeManyTimes() throws Exception {
- int numShapes = atLeast(500);
-
- // Every doc has 2 points:
- Object theShape = nextShape();
-
- Object[] shapes = new Object[numShapes];
- Arrays.fill(shapes, theShape);
-
- verify(shapes);
- }
-
- // Force low cardinality leaves
- public void testLowCardinalityShapeManyTimes() throws Exception {
- int numShapes = atLeast(500);
- int cardinality = TestUtil.nextInt(random(), 2, 20);
-
- Object[] diffShapes = new Object[cardinality];
- for (int i = 0; i < cardinality; i++) {
- diffShapes[i] = nextShape();
- }
-
- Object[] shapes = new Object[numShapes];
- for (int i = 0; i < numShapes; i++) {
- shapes[i] = diffShapes[random().nextInt(cardinality)];
- }
-
- verify(shapes);
- }
-
- public void testRandomTiny() throws Exception {
- // Make sure single-leaf-node case is OK:
- doTestRandom(10);
+ @Override
+ protected double rectMaxY(Object rect) {
+ return ((Rectangle)rect).maxLat;
}
- @Slow
- public void testRandomMedium() throws Exception {
- doTestRandom(1000);
+ @Override
+ protected boolean rectCrossesDateline(Object rect) {
+ return ((Rectangle)rect).crossesDateline();
}
- @Slow
- @Nightly
- public void testRandomBig() throws Exception {
- doTestRandom(20000);
+ /** use {@link GeoTestUtil#nextPolygon()} to create a random line; TODO: move to GeoTestUtil */
+ @Override
+ public Line nextLine() {
+ return getNextLine();
}
- protected void doTestRandom(int count) throws Exception {
- int numShapes = atLeast(count);
- ShapeType type = getShapeType();
-
- if (VERBOSE) {
- System.out.println("TEST: number of " + type.name() + " shapes=" + numShapes);
- }
+ public static Line getNextLine() {
+ Polygon poly = GeoTestUtil.nextPolygon();
+ double[] lats = new double[poly.numPoints() - 1];
+ double[] lons = new double[lats.length];
+ System.arraycopy(poly.getPolyLats(), 0, lats, 0, lats.length);
+ System.arraycopy(poly.getPolyLons(), 0, lons, 0, lons.length);
- Object[] shapes = new Object[numShapes];
- for (int id = 0; id < numShapes; ++id) {
- int x = randomIntBetween(0, 20);
- if (x == 17) {
- shapes[id] = null;
- if (VERBOSE) {
- System.out.println(" id=" + id + " is missing");
- }
- } else {
- // create a new shape
- shapes[id] = nextShape();
- }
- }
- verify(shapes);
+ return new Line(lats, lons);
}
- private void verify(Object... shapes) throws Exception {
- IndexWriterConfig iwc = newIndexWriterConfig();
- iwc.setMergeScheduler(new SerialMergeScheduler());
- int mbd = iwc.getMaxBufferedDocs();
- if (mbd != -1 && mbd < shapes.length / 100) {
- iwc.setMaxBufferedDocs(shapes.length / 100);
- }
- Directory dir;
- if (shapes.length > 1000) {
- dir = newFSDirectory(createTempDir(getClass().getSimpleName()));
- } else {
- dir = newDirectory();
- }
- IndexWriter w = new IndexWriter(dir, iwc);
-
- // index random polygons
- indexRandomShapes(w, shapes);
-
- // query testing
- final IndexReader reader = DirectoryReader.open(w);
-
- // test random bbox queries
- verifyRandomBBoxQueries(reader, shapes);
- // test random line queries
- verifyRandomLineQueries(reader, shapes);
- // test random polygon queries
- verifyRandomPolygonQueries(reader, shapes);
-
- IOUtils.close(w, reader, dir);
+ @Override
+ protected Polygon nextPolygon() {
+ return GeoTestUtil.nextPolygon();
}
- protected void indexRandomShapes(IndexWriter w, Object... shapes) throws Exception {
- Set deleted = new HashSet<>();
- for (int id = 0; id < shapes.length; ++id) {
- Document doc = new Document();
- doc.add(newStringField("id", "" + id, Field.Store.NO));
- doc.add(new NumericDocValuesField("id", id));
- if (shapes[id] != null) {
- addShapeToDoc(FIELD_NAME, doc, shapes[id]);
+ @Override
+ protected Encoder getEncoder() {
+ return new Encoder() {
+ @Override
+ double quantizeX(double raw) {
+ return decodeLongitude(encodeLongitude(raw));
}
- w.addDocument(doc);
- if (id > 0 && random().nextInt(100) == 42) {
- int idToDelete = random().nextInt(id);
- w.deleteDocuments(new Term("id", ""+idToDelete));
- deleted.add(idToDelete);
- if (VERBOSE) {
- System.out.println(" delete id=" + idToDelete);
- }
- }
- }
- if (randomBoolean()) {
- w.forceMerge(1);
- }
- }
-
- /** test random generated bounding boxes */
- protected void verifyRandomBBoxQueries(IndexReader reader, Object... shapes) throws Exception {
- IndexSearcher s = newSearcher(reader);
-
- final int iters = scaledIterationCount(shapes.length);
-
- Bits liveDocs = MultiBits.getLiveDocs(s.getIndexReader());
- int maxDoc = s.getIndexReader().maxDoc();
-
- for (int iter = 0; iter < iters; ++iter) {
- if (VERBOSE) {
- System.out.println("\nTEST: iter=" + (iter+1) + " of " + iters + " s=" + s);
+ @Override
+ double quantizeXCeil(double raw) {
+ return decodeLongitude(encodeLongitudeCeil(raw));
}
- // BBox
- Rectangle rect = GeoTestUtil.nextBox();
- QueryRelation queryRelation = RandomPicks.randomFrom(random(), QueryRelation.values());
- Query query = newRectQuery(FIELD_NAME, queryRelation, rect.minLat, rect.maxLat, rect.minLon, rect.maxLon);
-
- if (VERBOSE) {
- System.out.println(" query=" + query + ", relation=" + queryRelation);
+ @Override
+ double quantizeY(double raw) {
+ return decodeLatitude(encodeLatitude(raw));
}
- final FixedBitSet hits = new FixedBitSet(maxDoc);
- s.search(query, new SimpleCollector() {
-
- private int docBase;
-
- @Override
- public ScoreMode scoreMode() {
- return ScoreMode.COMPLETE_NO_SCORES;
- }
-
- @Override
- protected void doSetNextReader(LeafReaderContext context) throws IOException {
- docBase = context.docBase;
- }
-
- @Override
- public void collect(int doc) throws IOException {
- hits.set(docBase+doc);
- }
- });
-
- boolean fail = false;
- NumericDocValues docIDToID = MultiDocValues.getNumericValues(reader, "id");
- for (int docID = 0; docID < maxDoc; ++docID) {
- assertEquals(docID, docIDToID.nextDoc());
- int id = (int) docIDToID.longValue();
- boolean expected;
- double qMinLon = quantizeLonCeil(rect.minLon);
- double qMaxLon = quantizeLon(rect.maxLon);
- double qMinLat = quantizeLatCeil(rect.minLat);
- double qMaxLat = quantizeLat(rect.maxLat);
- if (liveDocs != null && liveDocs.get(docID) == false) {
- // document is deleted
- expected = false;
- } else if (shapes[id] == null) {
- expected = false;
- } else {
- // check quantized poly against quantized query
- if (qMinLon > qMaxLon && rect.crossesDateline() == false) {
- // if the quantization creates a false dateline crossing (because of encodeCeil):
- // then do not use encodeCeil
- qMinLon = quantizeLon(rect.minLon);
- }
-
- if (qMinLat > qMaxLat) {
- qMinLat = quantizeLat(rect.maxLat);
- }
- expected = getValidator(queryRelation).testBBoxQuery(qMinLat, qMaxLat, qMinLon, qMaxLon, shapes[id]);
- }
-
- if (hits.get(docID) != expected) {
- StringBuilder b = new StringBuilder();
-
- if (expected) {
- b.append("FAIL: id=" + id + " should match but did not\n");
- } else {
- b.append("FAIL: id=" + id + " should not match but did\n");
- }
- b.append(" relation=" + queryRelation + "\n");
- b.append(" query=" + query + " docID=" + docID + "\n");
- if (shapes[id] instanceof Object[]) {
- b.append(" shape=" + Arrays.toString((Object[]) shapes[id]) + "\n");
- } else {
- b.append(" shape=" + shapes[id] + "\n");
- }
- b.append(" deleted?=" + (liveDocs != null && liveDocs.get(docID) == false));
- b.append(" rect=Rectangle(lat=" + quantizeLatCeil(rect.minLat) + " TO " + quantizeLat(rect.maxLat) + " lon=" + qMinLon + " TO " + quantizeLon(rect.maxLon) + ")\n"); if (true) {
- fail("wrong hit (first of possibly more):\n\n" + b);
- } else {
- System.out.println(b.toString());
- fail = true;
- }
- }
- }
- if (fail) {
- fail("some hits were wrong");
+ @Override
+ double quantizeYCeil(double raw) {
+ return decodeLatitude(encodeLatitudeCeil(raw));
}
- }
- }
- private int scaledIterationCount(int shapes) {
- if (shapes < 500) {
- return atLeast(50);
- } else if (shapes < 5000) {
- return atLeast(25);
- } else if (shapes < 25000) {
- return atLeast(5);
- } else {
- return atLeast(2);
- }
- }
-
- /** test random generated lines */
- protected void verifyRandomLineQueries(IndexReader reader, Object... shapes) throws Exception {
- IndexSearcher s = newSearcher(reader);
-
- final int iters = scaledIterationCount(shapes.length);
-
- Bits liveDocs = MultiBits.getLiveDocs(s.getIndexReader());
- int maxDoc = s.getIndexReader().maxDoc();
-
- for (int iter = 0; iter < iters; ++iter) {
- if (VERBOSE) {
- System.out.println("\nTEST: iter=" + (iter + 1) + " of " + iters + " s=" + s);
+ /** quantizes a latitude value to be consistent with index encoding */
+ protected double quantizeLat(double rawLat) {
+ return quantizeY(rawLat);
}
- // line
- Line queryLine = randomQueryLine(shapes);
- Line2D queryLine2D = Line2D.create(queryLine);
- QueryRelation queryRelation = RandomPicks.randomFrom(random(), POINT_LINE_RELATIONS);
- Query query = newLineQuery(FIELD_NAME, queryRelation, queryLine);
-
- if (VERBOSE) {
- System.out.println(" query=" + query + ", relation=" + queryRelation);
+ /** quantizes a provided latitude value rounded up to the nearest encoded integer */
+ protected double quantizeLatCeil(double rawLat) {
+ return quantizeYCeil(rawLat);
}
- final FixedBitSet hits = new FixedBitSet(maxDoc);
- s.search(query, new SimpleCollector() {
-
- private int docBase;
-
- @Override
- public ScoreMode scoreMode() {
- return ScoreMode.COMPLETE_NO_SCORES;
- }
-
- @Override
- protected void doSetNextReader(LeafReaderContext context) throws IOException {
- docBase = context.docBase;
- }
-
- @Override
- public void collect(int doc) throws IOException {
- hits.set(docBase+doc);
- }
- });
-
- boolean fail = false;
- NumericDocValues docIDToID = MultiDocValues.getNumericValues(reader, "id");
- for (int docID = 0; docID < maxDoc; ++docID) {
- assertEquals(docID, docIDToID.nextDoc());
- int id = (int) docIDToID.longValue();
- boolean expected;
- if (liveDocs != null && liveDocs.get(docID) == false) {
- // document is deleted
- expected = false;
- } else if (shapes[id] == null) {
- expected = false;
- } else {
- expected = getValidator(queryRelation).testLineQuery(queryLine2D, shapes[id]);
- }
-
- if (hits.get(docID) != expected) {
- StringBuilder b = new StringBuilder();
-
- if (expected) {
- b.append("FAIL: id=" + id + " should match but did not\n");
- } else {
- b.append("FAIL: id=" + id + " should not match but did\n");
- }
- b.append(" relation=" + queryRelation + "\n");
- b.append(" query=" + query + " docID=" + docID + "\n");
- if (shapes[id] instanceof Object[]) {
- b.append(" shape=" + Arrays.toString((Object[]) shapes[id]) + "\n");
- } else {
- b.append(" shape=" + shapes[id] + "\n");
- }
- b.append(" deleted?=" + (liveDocs != null && liveDocs.get(docID) == false));
- b.append(" queryPolygon=" + queryLine.toGeoJSON());
- if (true) {
- fail("wrong hit (first of possibly more):\n\n" + b);
- } else {
- System.out.println(b.toString());
- fail = true;
- }
- }
- }
- if (fail) {
- fail("some hits were wrong");
+ /** quantizes a longitude value to be consistent with index encoding */
+ protected double quantizeLon(double rawLon) {
+ return quantizeX(rawLon);
}
- }
- }
-
- /** test random generated polygons */
- protected void verifyRandomPolygonQueries(IndexReader reader, Object... shapes) throws Exception {
- IndexSearcher s = newSearcher(reader);
-
- final int iters = scaledIterationCount(shapes.length);
-
- Bits liveDocs = MultiBits.getLiveDocs(s.getIndexReader());
- int maxDoc = s.getIndexReader().maxDoc();
- for (int iter = 0; iter < iters; ++iter) {
- if (VERBOSE) {
- System.out.println("\nTEST: iter=" + (iter + 1) + " of " + iters + " s=" + s);
+ /** quantizes a provided longitude value rounded up to the nearest encoded integer */
+ protected double quantizeLonCeil(double rawLon) {
+ return quantizeXCeil(rawLon);
}
- // Polygon
- Polygon queryPolygon = GeoTestUtil.nextPolygon();
- Polygon2D queryPoly2D = Polygon2D.create(queryPolygon);
- QueryRelation queryRelation = RandomPicks.randomFrom(random(), QueryRelation.values());
- Query query = newPolygonQuery(FIELD_NAME, queryRelation, queryPolygon);
-
- if (VERBOSE) {
- System.out.println(" query=" + query + ", relation=" + queryRelation);
+ @Override
+ double[] quantizeTriangle(double ax, double ay, double bx, double by, double cx, double cy) {
+ int[] decoded = encodeDecodeTriangle(ax, ay, bx, by, cx, cy);
+ return new double[]{decodeLatitude(decoded[0]), decodeLongitude(decoded[1]), decodeLatitude(decoded[2]), decodeLongitude(decoded[3]), decodeLatitude(decoded[4]), decodeLongitude(decoded[5])};
}
- final FixedBitSet hits = new FixedBitSet(maxDoc);
- s.search(query, new SimpleCollector() {
-
- private int docBase;
-
- @Override
- public ScoreMode scoreMode() {
- return ScoreMode.COMPLETE_NO_SCORES;
- }
-
- @Override
- protected void doSetNextReader(LeafReaderContext context) throws IOException {
- docBase = context.docBase;
- }
-
- @Override
- public void collect(int doc) throws IOException {
- hits.set(docBase+doc);
- }
- });
-
- boolean fail = false;
- NumericDocValues docIDToID = MultiDocValues.getNumericValues(reader, "id");
- for (int docID = 0; docID < maxDoc; ++docID) {
- assertEquals(docID, docIDToID.nextDoc());
- int id = (int) docIDToID.longValue();
- boolean expected;
- if (liveDocs != null && liveDocs.get(docID) == false) {
- // document is deleted
- expected = false;
- } else if (shapes[id] == null) {
- expected = false;
- } else {
- expected = getValidator(queryRelation).testPolygonQuery(queryPoly2D, shapes[id]);
- }
-
- if (hits.get(docID) != expected) {
- StringBuilder b = new StringBuilder();
-
- if (expected) {
- b.append("FAIL: id=" + id + " should match but did not\n");
- } else {
- b.append("FAIL: id=" + id + " should not match but did\n");
- }
- b.append(" relation=" + queryRelation + "\n");
- b.append(" query=" + query + " docID=" + docID + "\n");
- if (shapes[id] instanceof Object[]) {
- b.append(" shape=" + Arrays.toString((Object[]) shapes[id]) + "\n");
- } else {
- b.append(" shape=" + shapes[id] + "\n");
- }
- b.append(" deleted?=" + (liveDocs != null && liveDocs.get(docID) == false));
- b.append(" queryPolygon=" + queryPolygon.toGeoJSON());
- if (true) {
- fail("wrong hit (first of possibly more):\n\n" + b);
- } else {
- System.out.println(b.toString());
- fail = true;
- }
- }
+ @Override
+ int[] encodeDecodeTriangle(double ax, double ay, double bx, double by, double cx, double cy) {
+ byte[] encoded = new byte[7 * ShapeField.BYTES];
+ ShapeField.encodeTriangle(encoded, encodeLatitude(ay), encodeLongitude(ax), encodeLatitude(by), encodeLongitude(bx), encodeLatitude(cy), encodeLongitude(cx));
+ int[] decoded = new int[6];
+ ShapeField.decodeTriangle(encoded, decoded);
+ return decoded;
}
- if (fail) {
- fail("some hits were wrong");
- }
- }
- }
-
- protected abstract Validator getValidator(QueryRelation relation);
-
- /** internal point class for testing point shapes */
- protected static class Point {
- double lat;
- double lon;
-
- public Point(double lat, double lon) {
- this.lat = lat;
- this.lon = lon;
- }
-
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append("POINT(");
- sb.append(lon);
- sb.append(',');
- sb.append(lat);
- sb.append(')');
- return sb.toString();
- }
+ };
}
/** internal shape type for testing different shape types */
protected enum ShapeType {
POINT() {
public Point nextShape() {
- return new Point(nextLatitude(), nextLongitude());
+ return new Point(nextLongitude(), nextLatitude());
}
},
LINE() {
@@ -737,15 +329,25 @@ static ShapeType fromObject(Object shape) {
}
}
- /** validator class used to test query results against "ground truth" */
- protected static abstract class Validator {
- protected QueryRelation queryRelation = QueryRelation.INTERSECTS;
- public abstract boolean testBBoxQuery(double minLat, double maxLat, double minLon, double maxLon, Object shape);
- public abstract boolean testLineQuery(Line2D line2d, Object shape);
- public abstract boolean testPolygonQuery(Polygon2D poly2d, Object shape);
+ /** internal lat lon point class for testing point shapes */
+ protected static class Point {
+ double lon;
+ double lat;
+
+ public Point(double lon, double lat) {
+ this.lon = lon;
+ this.lat = lat;
+ }
- public void setRelation(QueryRelation relation) {
- this.queryRelation = relation;
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("POINT(");
+ sb.append(lon);
+ sb.append(',');
+ sb.append(lat);
+ sb.append(')');
+ return sb.toString();
}
}
+
}
diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/BaseShapeEncodingTestCase.java b/lucene/sandbox/src/test/org/apache/lucene/document/BaseShapeEncodingTestCase.java
new file mode 100644
index 000000000000..daa9bacb3800
--- /dev/null
+++ b/lucene/sandbox/src/test/org/apache/lucene/document/BaseShapeEncodingTestCase.java
@@ -0,0 +1,573 @@
+/*
+ * 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 java.util.Arrays;
+
+import org.apache.lucene.geo.GeoUtils;
+import org.apache.lucene.geo.Polygon2D;
+import org.apache.lucene.index.PointValues;
+import org.apache.lucene.util.LuceneTestCase;
+
+/** base shape encoding class for testing encoding of tessellated {@link org.apache.lucene.document.XYShape} and
+ * {@link LatLonShape}
+ */
+public abstract class BaseShapeEncodingTestCase extends LuceneTestCase {
+
+ protected abstract int encodeX(double x);
+ protected abstract double decodeX(int x);
+ protected abstract int encodeY(double y);
+ protected abstract double decodeY(int y);
+
+ protected abstract double nextX();
+ protected abstract double nextY();
+
+ protected abstract Object nextPolygon();
+ protected abstract Polygon2D createPolygon2D(Object polygon);
+
+ //One shared point with MBR -> MinY, MinX
+ public void testPolygonEncodingMinLatMinLon() {
+ double ay = 0.0;
+ double ax = 0.0;
+ double by = 1.0;
+ double blon = 2.0;
+ double cy = 2.0;
+ double cx = 1.0;
+ int ayEnc = encodeY(ay);
+ int axEnc = encodeX(ax);
+ int byEnc = encodeY(by);
+ int bxEnc = encodeX(blon);
+ int cyEnc = encodeY(cy);
+ int cxEnc = encodeX(cx);
+ verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc);
+ byte[] b = new byte[7 * ShapeField.BYTES];
+ ShapeField.encodeTriangle(b, ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc);
+ int[] encoded = new int[6];
+ ShapeField.decodeTriangle(b, encoded);
+ assertTrue(encoded[0] == ayEnc);
+ assertTrue(encoded[1] == axEnc);
+ assertTrue(encoded[2] == byEnc);
+ assertTrue(encoded[3] == bxEnc);
+ assertTrue(encoded[4] == cyEnc);
+ assertTrue(encoded[5] == cxEnc);
+ }
+
+ //One shared point with MBR -> MinLat, MaxLon
+ public void testPolygonEncodingMinLatMaxLon() {
+ double ay = 1.0;
+ double ax = 0.0;
+ double by = 0.0;
+ double blon = 2.0;
+ double cy = 2.0;
+ double cx = 1.0;
+ int ayEnc = encodeY(ay);
+ int axEnc = encodeX(ax);
+ int byEnc = encodeY(by);
+ int bxEnc = encodeX(blon);
+ int cyEnc = encodeY(cy);
+ int cxEnc = encodeX(cx);
+ verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc);
+ byte[] b = new byte[7 * ShapeField.BYTES];
+ ShapeField.encodeTriangle(b, ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc);
+ int[] encoded = new int[6];
+ ShapeField.decodeTriangle(b, encoded);
+ assertTrue(encoded[0] == ayEnc);
+ assertTrue(encoded[1] == axEnc);
+ assertTrue(encoded[2] == byEnc);
+ assertTrue(encoded[3] == bxEnc);
+ assertTrue(encoded[4] == cyEnc);
+ assertTrue(encoded[5] == cxEnc);
+ }
+
+ //One shared point with MBR -> MaxLat, MaxLon
+ public void testPolygonEncodingMaxLatMaxLon() {
+ double ay = 1.0;
+ double ax = 0.0;
+ double by = 2.0;
+ double blon = 2.0;
+ double cy = 0.0;
+ double cx = 1.0;
+ int ayEnc = encodeY(ay);
+ int axEnc = encodeX(ax);
+ int byEnc = encodeY(cy);
+ int bxEnc = encodeX(cx);
+ int cyEnc = encodeY(by);
+ int cxEnc = encodeX(blon);
+ verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc);
+ byte[] b = new byte[7 * ShapeField.BYTES];
+ ShapeField.encodeTriangle(b, ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc);
+ int[] encoded = new int[6];
+ ShapeField.decodeTriangle(b, encoded);
+ assertTrue(encoded[0] == ayEnc);
+ assertTrue(encoded[1] == axEnc);
+ assertTrue(encoded[2] == byEnc);
+ assertTrue(encoded[3] == bxEnc);
+ assertTrue(encoded[4] == cyEnc);
+ assertTrue(encoded[5] == cxEnc);
+ }
+
+ //One shared point with MBR -> MaxLat, MinLon
+ public void testPolygonEncodingMaxLatMinLon() {
+ double ay = 2.0;
+ double ax = 0.0;
+ double by = 1.0;
+ double blon = 2.0;
+ double cy = 0.0;
+ double cx = 1.0;
+ int ayEnc = encodeY(ay);
+ int axEnc = encodeX(ax);
+ int byEnc = encodeY(cy);
+ int bxEnc = encodeX(cx);
+ int cyEnc = encodeY(by);
+ int cxEnc = encodeX(blon);
+ verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc);
+ byte[] b = new byte[7 * ShapeField.BYTES];
+ ShapeField.encodeTriangle(b, ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc);
+ int[] encoded = new int[6];
+ ShapeField.decodeTriangle(b, encoded);
+ assertTrue(encoded[0] == ayEnc);
+ assertTrue(encoded[1] == axEnc);
+ assertTrue(encoded[2] == byEnc);
+ assertTrue(encoded[3] == bxEnc);
+ assertTrue(encoded[4] == cyEnc);
+ assertTrue(encoded[5] == cxEnc);
+ }
+
+ //Two shared point with MBR -> [MinLat, MinLon], [MaxLat, MaxLon], third point below
+ public void testPolygonEncodingMinLatMinLonMaxLatMaxLonBelow() {
+ double ay = 0.0;
+ double ax = 0.0;
+ double by = 0.25;
+ double blon = 0.75;
+ double cy = 2.0;
+ double cx = 2.0;
+ int ayEnc = encodeY(ay);
+ int axEnc = encodeX(ax);
+ int byEnc = encodeY(by);
+ int bxEnc = encodeX(blon);
+ int cyEnc = encodeY(cy);
+ int cxEnc = encodeX(cx);
+ verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc);
+ byte[] b = new byte[7 * ShapeField.BYTES];
+ ShapeField.encodeTriangle(b, ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc);
+ int[] encoded = new int[6];
+ ShapeField.decodeTriangle(b, encoded);
+ assertTrue(encoded[0] == ayEnc);
+ assertTrue(encoded[1] == axEnc);
+ assertTrue(encoded[2] == byEnc);
+ assertTrue(encoded[3] == bxEnc);
+ assertTrue(encoded[4] == cyEnc);
+ assertTrue(encoded[5] == cxEnc);
+ }
+
+ //Two shared point with MBR -> [MinLat, MinLon], [MaxLat, MaxLon], third point above
+ public void testPolygonEncodingMinLatMinLonMaxLatMaxLonAbove() {
+ double ay = 0.0;
+ double ax = 0.0;
+ double by = 2.0;
+ double bx = 2.0;
+ double cy = 1.75;
+ double cx = 1.25;
+ int ayEnc = encodeY(ay);
+ int axEnc = encodeX(ax);
+ int byEnc = encodeY(by);
+ int bxEnc = encodeX(bx);
+ int cyEnc = encodeY(cy);
+ int cxEnc = encodeX(cx);
+ verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc);
+ byte[] b = new byte[7 * ShapeField.BYTES];
+ ShapeField.encodeTriangle(b, ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc);
+ int[] encoded = new int[6];
+ ShapeField.decodeTriangle(b, encoded);
+ assertTrue(encoded[0] == ayEnc);
+ assertTrue(encoded[1] == axEnc);
+ assertTrue(encoded[2] == byEnc);
+ assertTrue(encoded[3] == bxEnc);
+ assertTrue(encoded[4] == cyEnc);
+ assertTrue(encoded[5] == cxEnc);
+ }
+
+ //Two shared point with MBR -> [MinLat, MaxLon], [MaxLat, MinLon], third point below
+ public void testPolygonEncodingMinLatMaxLonMaxLatMinLonBelow() {
+ double ay = 8.0;
+ double ax = 6.0;
+ double by = 6.25;
+ double bx = 6.75;
+ double cy = 6.0;
+ double cx = 8.0;
+ int ayEnc = encodeY(ay);
+ int axEnc = encodeX(ax);
+ int byEnc = encodeY(by);
+ int bxEnc = encodeX(bx);
+ int cyEnc = encodeY(cy);
+ int cxEnc = encodeX(cx);
+ verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc);
+ byte[] b = new byte[7 * ShapeField.BYTES];
+ ShapeField.encodeTriangle(b, ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc);
+ int[] encoded = new int[6];
+ ShapeField.decodeTriangle(b, encoded);
+ assertTrue(encoded[0] == ayEnc);
+ assertTrue(encoded[1] == axEnc);
+ assertTrue(encoded[2] == byEnc);
+ assertTrue(encoded[3] == bxEnc);
+ assertTrue(encoded[4] == cyEnc);
+ assertTrue(encoded[5] == cxEnc);
+ }
+
+ //Two shared point with MBR -> [MinLat, MaxLon], [MaxLat, MinLon], third point above
+ public void testPolygonEncodingMinLatMaxLonMaxLatMinLonAbove() {
+ double ay = 2.0;
+ double ax = 0.0;
+ double by = 0.0;
+ double bx = 2.0;
+ double cy = 1.75;
+ double cx = 1.25;
+ int ayEnc = encodeY(ay);
+ int axEnc = encodeX(ax);
+ int byEnc = encodeY(by);
+ int bxEnc = encodeX(bx);
+ int cyEnc = encodeY(cy);
+ int cxEnc = encodeX(cx);
+ verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc);
+ byte[] b = new byte[7 * ShapeField.BYTES];
+ ShapeField.encodeTriangle(b, ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc);
+ int[] encoded = new int[6];
+ ShapeField.decodeTriangle(b, encoded);
+ assertTrue(encoded[0] == ayEnc);
+ assertTrue(encoded[1] == axEnc);
+ assertTrue(encoded[2] == byEnc);
+ assertTrue(encoded[3] == bxEnc);
+ assertTrue(encoded[4] == cyEnc);
+ assertTrue(encoded[5] == cxEnc);
+ }
+
+ //all points shared with MBR
+ public void testPolygonEncodingAllSharedAbove() {
+ double ay = 0.0;
+ double ax = 0.0;
+ double by = 0.0;
+ double bx = 2.0;
+ double cy = 2.0;
+ double cx = 2.0;
+ int ayEnc = encodeY(ay);
+ int axEnc = encodeX(ax);
+ int byEnc = encodeY(by);
+ int bxEnc = encodeX(bx);
+ int cyEnc = encodeY(cy);
+ int cxEnc = encodeX(cx);
+ verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc);
+ byte[] b = new byte[7 * ShapeField.BYTES];
+ ShapeField.encodeTriangle(b, ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc);
+ int[] encoded = new int[6];
+ ShapeField.decodeTriangle(b, encoded);
+ assertTrue(encoded[0] == ayEnc);
+ assertTrue(encoded[1] == axEnc);
+ assertTrue(encoded[2] == byEnc);
+ assertTrue(encoded[3] == bxEnc);
+ assertTrue(encoded[4] == cyEnc);
+ assertTrue(encoded[5] == cxEnc);
+ }
+
+ //all points shared with MBR
+ public void testPolygonEncodingAllSharedBelow() {
+ double ay = 2.0;
+ double ax = 0.0;
+ double by = 0.0;
+ double bx = 0.0;
+ double cy = 2.0;
+ double cx = 2.0;
+ int ayEnc = encodeY(ay);
+ int axEnc = encodeX(ax);
+ int byEnc = encodeY(by);
+ int bxEnc = encodeX(bx);
+ int cyEnc = encodeY(cy);
+ int cxEnc = encodeX(cx);
+ byte[] b = new byte[7 * ShapeField.BYTES];
+ ShapeField.encodeTriangle(b, ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc);
+ int[] encoded = new int[6];
+ ShapeField.decodeTriangle(b, encoded);
+ assertTrue(encoded[0] == ayEnc);
+ assertTrue(encoded[1] == axEnc);
+ assertTrue(encoded[2] == byEnc);
+ assertTrue(encoded[3] == bxEnc);
+ assertTrue(encoded[4] == cyEnc);
+ assertTrue(encoded[5] == cxEnc);
+ }
+
+ //[a,b,c] == [c,a,b] == [b,c,a] == [c,b,a] == [b,a,c] == [a,c,b]
+ public void verifyEncodingPermutations(int ayEnc, int axEnc, int byEnc, int bxEnc, int cyEnc, int cxEnc) {
+ //this is only valid when points are not co-planar
+ assertTrue(GeoUtils.orient(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc) != 0);
+ byte[] b = new byte[7 * ShapeField.BYTES];
+ //[a,b,c]
+ ShapeField.encodeTriangle(b, ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc);
+ int[] encodedABC = new int[6];
+ ShapeField.decodeTriangle(b, encodedABC);
+ //[c,a,b]
+ ShapeField.encodeTriangle(b, cyEnc, cxEnc, ayEnc, axEnc, byEnc, bxEnc);
+ int[] encodedCAB = new int[6];
+ ShapeField.decodeTriangle(b, encodedCAB);
+ assertTrue(Arrays.equals(encodedABC, encodedCAB));
+ //[b,c,a]
+ ShapeField.encodeTriangle(b, byEnc, bxEnc, cyEnc, cxEnc, ayEnc, axEnc);
+ int[] encodedBCA = new int[6];
+ ShapeField.decodeTriangle(b, encodedBCA);
+ assertTrue(Arrays.equals(encodedABC, encodedBCA));
+ //[c,b,a]
+ ShapeField.encodeTriangle(b, cyEnc, cxEnc, byEnc, bxEnc, ayEnc, axEnc);
+ int[] encodedCBA= new int[6];
+ ShapeField.decodeTriangle(b, encodedCBA);
+ assertTrue(Arrays.equals(encodedABC, encodedCBA));
+ //[b,a,c]
+ ShapeField.encodeTriangle(b, byEnc, bxEnc, ayEnc, axEnc, cyEnc, cxEnc);
+ int[] encodedBAC= new int[6];
+ ShapeField.decodeTriangle(b, encodedBAC);
+ assertTrue(Arrays.equals(encodedABC, encodedBAC));
+ //[a,c,b]
+ ShapeField.encodeTriangle(b, ayEnc, axEnc, cyEnc, cxEnc, byEnc, bxEnc);
+ int[] encodedACB= new int[6];
+ ShapeField.decodeTriangle(b, encodedACB);
+ assertTrue(Arrays.equals(encodedABC, encodedACB));
+ }
+
+ public void testPointEncoding() {
+ double lat = 45.0;
+ double lon = 45.0;
+ int latEnc = encodeY(lat);
+ int lonEnc = encodeX(lon);
+ byte[] b = new byte[7 * ShapeField.BYTES];
+ ShapeField.encodeTriangle(b, latEnc, lonEnc, latEnc, lonEnc, latEnc, lonEnc);
+ int[] encoded = new int[6];
+ ShapeField.decodeTriangle(b, encoded);
+ assertTrue(encoded[0] == latEnc && encoded[2] == latEnc && encoded[4] == latEnc);
+ assertTrue(encoded[1] == lonEnc && encoded[3] == lonEnc && encoded[5] == lonEnc);
+ }
+
+ public void testLineEncodingSameLat() {
+ double lat = 2.0;
+ double ax = 0.0;
+ double bx = 2.0;
+ int latEnc = encodeY(lat);
+ int axEnc = encodeX(ax);
+ int bxEnc = encodeX(bx);
+ byte[] b = new byte[7 * ShapeField.BYTES];
+ ShapeField.encodeTriangle(b, latEnc, axEnc, latEnc, bxEnc, latEnc, axEnc);
+ int[] encoded = new int[6];
+ ShapeField.decodeTriangle(b, encoded);
+ assertTrue(encoded[0] == latEnc);
+ assertTrue(encoded[1] == axEnc);
+ assertTrue(encoded[2] == latEnc);
+ assertTrue(encoded[3] == bxEnc);
+ assertTrue(encoded[4] == latEnc);
+ assertTrue(encoded[5] == axEnc);
+ ShapeField.encodeTriangle(b, latEnc, axEnc, latEnc, axEnc, latEnc, bxEnc);
+ encoded = new int[6];
+ ShapeField.decodeTriangle(b, encoded);
+ assertTrue(encoded[0] == latEnc);
+ assertTrue(encoded[1] == axEnc);
+ assertTrue(encoded[2] == latEnc);
+ assertTrue(encoded[3] == axEnc);
+ assertTrue(encoded[4] == latEnc);
+ assertTrue(encoded[5] == bxEnc);
+ ShapeField.encodeTriangle(b, latEnc, bxEnc, latEnc, axEnc, latEnc, axEnc);
+ encoded = new int[6];
+ ShapeField.decodeTriangle(b, encoded);
+ assertTrue(encoded[0] == latEnc);
+ assertTrue(encoded[1] == axEnc);
+ assertTrue(encoded[2] == latEnc);
+ assertTrue(encoded[3] == bxEnc);
+ assertTrue(encoded[4] == latEnc);
+ assertTrue(encoded[5] == axEnc);
+ }
+
+ public void testLineEncodingSameLon() {
+ double ay = 0.0;
+ double by = 2.0;
+ double lon = 2.0;
+ int ayEnc = encodeY(ay);
+ int byEnc = encodeY(by);
+ int lonEnc = encodeX(lon);
+ byte[] b = new byte[7 * ShapeField.BYTES];
+ ShapeField.encodeTriangle(b, ayEnc, lonEnc, byEnc, lonEnc, ayEnc, lonEnc);
+ int[] encoded = new int[6];
+ ShapeField.decodeTriangle(b, encoded);
+ assertTrue(encoded[0] == ayEnc);
+ assertTrue(encoded[1] == lonEnc);
+ assertTrue(encoded[2] == byEnc);
+ assertTrue(encoded[3] == lonEnc);
+ assertTrue(encoded[4] == ayEnc);
+ assertTrue(encoded[5] == lonEnc);
+ ShapeField.encodeTriangle(b, ayEnc, lonEnc, ayEnc, lonEnc, byEnc, lonEnc);
+ encoded = new int[6];
+ ShapeField.decodeTriangle(b, encoded);
+ assertTrue(encoded[0] == ayEnc);
+ assertTrue(encoded[1] == lonEnc);
+ assertTrue(encoded[2] == ayEnc);
+ assertTrue(encoded[3] == lonEnc);
+ assertTrue(encoded[4] == byEnc);
+ assertTrue(encoded[5] == lonEnc);
+ ShapeField.encodeTriangle(b, byEnc, lonEnc, ayEnc, lonEnc, ayEnc, lonEnc);
+ encoded = new int[6];
+ ShapeField.decodeTriangle(b, encoded);
+ assertTrue(encoded[0] == ayEnc);
+ assertTrue(encoded[1] == lonEnc);
+ assertTrue(encoded[2] == byEnc);
+ assertTrue(encoded[3] == lonEnc);
+ assertTrue(encoded[4] == ayEnc);
+ assertTrue(encoded[5] == lonEnc);
+ }
+
+ public void testLineEncoding() {
+ double ay = 0.0;
+ double by = 2.0;
+ double ax = 0.0;
+ double bx = 2.0;
+ int ayEnc = encodeY(ay);
+ int byEnc = encodeY(by);
+ int axEnc = encodeX(ax);
+ int bxEnc = encodeX(bx);
+ byte[] b = new byte[7 * ShapeField.BYTES];
+ ShapeField.encodeTriangle(b, ayEnc, axEnc, byEnc, bxEnc, ayEnc, axEnc);
+ int[] encoded = new int[6];
+ ShapeField.decodeTriangle(b, encoded);
+ assertTrue(encoded[0] == ayEnc);
+ assertTrue(encoded[1] == axEnc);
+ assertTrue(encoded[2] == byEnc);
+ assertTrue(encoded[3] == bxEnc);
+ assertTrue(encoded[4] == ayEnc);
+ assertTrue(encoded[5] == axEnc);
+ ShapeField.encodeTriangle(b, ayEnc, axEnc, ayEnc, axEnc, byEnc, bxEnc);
+ encoded = new int[6];
+ ShapeField.decodeTriangle(b, encoded);
+ assertTrue(encoded[0] == ayEnc);
+ assertTrue(encoded[1] == axEnc);
+ assertTrue(encoded[2] == ayEnc);
+ assertTrue(encoded[3] == axEnc);
+ assertTrue(encoded[4] == byEnc);
+ assertTrue(encoded[5] == bxEnc);
+ ShapeField.encodeTriangle(b, byEnc, bxEnc, ayEnc, axEnc, ayEnc, axEnc);
+ encoded = new int[6];
+ ShapeField.decodeTriangle(b, encoded);
+ assertTrue(encoded[0] == ayEnc);
+ assertTrue(encoded[1] == axEnc);
+ assertTrue(encoded[2] == byEnc);
+ assertTrue(encoded[3] == bxEnc);
+ assertTrue(encoded[4] == ayEnc);
+ assertTrue(encoded[5] == axEnc);
+ }
+
+ public void testRandomPointEncoding() {
+ double ay = nextY();
+ double ax = nextX();
+ verifyEncoding(ay, ax, ay, ax, ay, ax);
+ }
+
+ public void testRandomLineEncoding() {
+ double ay = nextY();
+ double ax = nextX();
+ double by = nextY();
+ double bx = nextX();
+ verifyEncoding(ay, ax, by, bx, ay, ax);
+ }
+
+ public void testRandomPolygonEncoding() {
+ double ay = nextY();
+ double ax = nextX();
+ double by = nextY();
+ double bx = nextX();
+ double cy = nextY();
+ double cx = nextX();
+ verifyEncoding(ay, ax, by, bx, cy, cx);
+ }
+
+ private void verifyEncoding(double ay, double ax, double by, double bx, double cy, double cx) {
+ int[] original = new int[]{
+ encodeY(ay),
+ encodeX(ax),
+ encodeY(by),
+ encodeX(bx),
+ encodeY(cy),
+ encodeX(cx)};
+
+ //quantize the triangle
+ byte[] b = new byte[7 * ShapeField.BYTES];
+ ShapeField.encodeTriangle(b, original[0], original[1], original[2], original[3], original[4], original[5]);
+ int[] encoded = new int[6];
+ ShapeField.decodeTriangle(b, encoded);
+ double[] encodedQuantize = new double[] {
+ decodeY(encoded[0]),
+ decodeX(encoded[1]),
+ decodeY(encoded[2]),
+ decodeX(encoded[3]),
+ decodeY(encoded[4]),
+ decodeX(encoded[5])};
+
+ int orientation = GeoUtils.orient(original[1], original[0], original[3], original[2], original[5], original[4]);
+ //quantize original
+ double[] originalQuantize;
+ //we need to change the orientation if CW
+ if (orientation == -1) {
+ originalQuantize = new double[] {
+ decodeY(original[4]),
+ decodeX(original[5]),
+ decodeY(original[2]),
+ decodeX(original[3]),
+ decodeY(original[0]),
+ decodeX(original[1])};
+ } else {
+ originalQuantize = new double[] {
+ decodeY(original[0]),
+ decodeX(original[1]),
+ decodeY(original[2]),
+ decodeX(original[3]),
+ decodeY(original[4]),
+ decodeX(original[5])};
+ }
+
+ for (int i =0; i < 100; i ++) {
+ Polygon2D polygon2D = createPolygon2D(nextPolygon());
+ PointValues.Relation originalRelation = polygon2D.relateTriangle(originalQuantize[1], originalQuantize[0], originalQuantize[3], originalQuantize[2], originalQuantize[5], originalQuantize[4]);
+ PointValues.Relation encodedRelation = polygon2D.relateTriangle(encodedQuantize[1], encodedQuantize[0], encodedQuantize[3], encodedQuantize[2], encodedQuantize[5], encodedQuantize[4]);
+ assertTrue(originalRelation == encodedRelation);
+ }
+ }
+
+ public void testDegeneratedTriangle() {
+ double ay = 1e-26d;
+ double ax = 0.0d;
+ double by = -1.0d;
+ double bx = 0.0d;
+ double cy = 1.0d;
+ double cx = 0.0d;
+ int ayEnc = encodeY(ay);
+ int axEnc = encodeX(ax);
+ int byEnc = encodeY(by);
+ int bxEnc = encodeX(bx);
+ int cyEnc = encodeY(cy);
+ int cxEnc = encodeX(cx);
+ byte[] b = new byte[7 * ShapeField.BYTES];
+ ShapeField.encodeTriangle(b, ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc);
+ int[] encoded = new int[6];
+ ShapeField.decodeTriangle(b, encoded);
+ assertTrue(encoded[0] == byEnc);
+ assertTrue(encoded[1] == bxEnc);
+ assertTrue(encoded[2] == cyEnc);
+ assertTrue(encoded[3] == cxEnc);
+ assertTrue(encoded[4] == ayEnc);
+ assertTrue(encoded[5] == axEnc);
+ }
+}
diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/BaseShapeTestCase.java b/lucene/sandbox/src/test/org/apache/lucene/document/BaseShapeTestCase.java
new file mode 100644
index 000000000000..1e4e173dcdd4
--- /dev/null
+++ b/lucene/sandbox/src/test/org/apache/lucene/document/BaseShapeTestCase.java
@@ -0,0 +1,581 @@
+/*
+ * 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 java.io.IOException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import com.carrotsearch.randomizedtesting.generators.RandomPicks;
+import org.apache.lucene.document.ShapeField.QueryRelation;
+import org.apache.lucene.geo.Line2D;
+import org.apache.lucene.index.DirectoryReader;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.IndexWriterConfig;
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.index.MultiBits;
+import org.apache.lucene.index.MultiDocValues;
+import org.apache.lucene.index.NumericDocValues;
+import org.apache.lucene.index.SerialMergeScheduler;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.ScoreMode;
+import org.apache.lucene.search.SimpleCollector;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.util.Bits;
+import org.apache.lucene.util.FixedBitSet;
+import org.apache.lucene.util.IOUtils;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.lucene.util.TestUtil;
+
+import static com.carrotsearch.randomizedtesting.RandomizedTest.randomBoolean;
+import static com.carrotsearch.randomizedtesting.RandomizedTest.randomIntBetween;
+
+/**
+ * Base test case for testing spherical and cartesian geometry indexing and search functionality
+ *
+ * This class is implemented by {@link BaseXYShapeTestCase} for testing XY cartesian geometry
+ * and {@link BaseLatLonShapeTestCase} for testing Lat Lon geospatial geometry
+ **/
+public abstract class BaseShapeTestCase extends LuceneTestCase {
+
+ /** name of the LatLonShape indexed field */
+ protected static final String FIELD_NAME = "shape";
+ public final Encoder ENCODER;
+ public final Validator VALIDATOR;
+ protected static final QueryRelation[] POINT_LINE_RELATIONS = {QueryRelation.INTERSECTS, QueryRelation.DISJOINT};
+
+ public BaseShapeTestCase() {
+ ENCODER = getEncoder();
+ VALIDATOR = getValidator();
+ }
+
+ // A particularly tricky adversary for BKD tree:
+ public void testSameShapeManyTimes() throws Exception {
+ int numShapes = atLeast(500);
+
+ // Every doc has 2 points:
+ Object theShape = nextShape();
+
+ Object[] shapes = new Object[numShapes];
+ Arrays.fill(shapes, theShape);
+
+ verify(shapes);
+ }
+
+ // Force low cardinality leaves
+ public void testLowCardinalityShapeManyTimes() throws Exception {
+ int numShapes = atLeast(500);
+ int cardinality = TestUtil.nextInt(random(), 2, 20);
+
+ Object[] diffShapes = new Object[cardinality];
+ for (int i = 0; i < cardinality; i++) {
+ diffShapes[i] = nextShape();
+ }
+
+ Object[] shapes = new Object[numShapes];
+ for (int i = 0; i < numShapes; i++) {
+ shapes[i] = diffShapes[random().nextInt(cardinality)];
+ }
+
+ verify(shapes);
+ }
+
+ public void testRandomTiny() throws Exception {
+ // Make sure single-leaf-node case is OK:
+ doTestRandom(10);
+ }
+
+ @Slow
+ public void testRandomMedium() throws Exception {
+ doTestRandom(1000);
+ }
+
+ @Slow
+ @Nightly
+ public void testRandomBig() throws Exception {
+ doTestRandom(20000);
+ }
+
+ protected void doTestRandom(int count) throws Exception {
+ int numShapes = atLeast(count);
+
+ if (VERBOSE) {
+ System.out.println("TEST: number of " + getShapeType() + " shapes=" + numShapes);
+ }
+
+ Object[] shapes = new Object[numShapes];
+ for (int id = 0; id < numShapes; ++id) {
+ int x = randomIntBetween(0, 20);
+ if (x == 17) {
+ shapes[id] = null;
+ if (VERBOSE) {
+ System.out.println(" id=" + id + " is missing");
+ }
+ } else {
+ // create a new shape
+ shapes[id] = nextShape();
+ }
+ }
+ verify(shapes);
+ }
+
+ protected abstract Object getShapeType();
+ protected abstract Object nextShape();
+
+ protected abstract Encoder getEncoder();
+
+ /** creates the array of LatLonShape.Triangle values that are used to index the shape */
+ protected abstract Field[] createIndexableFields(String field, Object shape);
+
+ /** adds a shape to a provided document */
+ private void addShapeToDoc(String field, Document doc, Object shape) {
+ Field[] fields = createIndexableFields(field, shape);
+ for (Field f : fields) {
+ doc.add(f);
+ }
+ }
+
+ /** return a semi-random line used for queries **/
+ protected abstract Object nextLine();
+
+ protected abstract Object nextPolygon();
+
+ protected abstract Object randomQueryBox();
+
+ protected abstract double rectMinX(Object rect);
+ protected abstract double rectMaxX(Object rect);
+ protected abstract double rectMinY(Object rect);
+ protected abstract double rectMaxY(Object rect);
+ protected abstract boolean rectCrossesDateline(Object rect);
+
+ /**
+ * return a semi-random line used for queries
+ *
+ * note: shapes parameter may be used to ensure some queries intersect indexed shapes
+ **/
+ protected Object randomQueryLine(Object... shapes) {
+ return nextLine();
+ }
+
+ protected Object randomQueryPolygon() {
+ return nextPolygon();
+ }
+
+ /** factory method to create a new bounding box query */
+ protected abstract Query newRectQuery(String field, QueryRelation queryRelation, double minX, double maxX, double minY, double maxY);
+
+ /** factory method to create a new line query */
+ protected abstract Query newLineQuery(String field, QueryRelation queryRelation, Object... lines);
+
+ /** factory method to create a new polygon query */
+ protected abstract Query newPolygonQuery(String field, QueryRelation queryRelation, Object... polygons);
+
+ protected abstract Line2D toLine2D(Object... line);
+
+ protected abstract Object toPolygon2D(Object... polygon);
+
+ private void verify(Object... shapes) throws Exception {
+ IndexWriterConfig iwc = newIndexWriterConfig();
+ iwc.setMergeScheduler(new SerialMergeScheduler());
+ int mbd = iwc.getMaxBufferedDocs();
+ if (mbd != -1 && mbd < shapes.length / 100) {
+ iwc.setMaxBufferedDocs(shapes.length / 100);
+ }
+ Directory dir;
+ if (shapes.length > 1000) {
+ dir = newFSDirectory(createTempDir(getClass().getSimpleName()));
+ } else {
+ dir = newDirectory();
+ }
+ IndexWriter w = new IndexWriter(dir, iwc);
+
+ // index random polygons
+ indexRandomShapes(w, shapes);
+
+ // query testing
+ final IndexReader reader = DirectoryReader.open(w);
+ // test random bbox queries
+ verifyRandomQueries(reader, shapes);
+ IOUtils.close(w, reader, dir);
+ }
+
+
+ protected void indexRandomShapes(IndexWriter w, Object... shapes) throws Exception {
+ Set deleted = new HashSet<>();
+ for (int id = 0; id < shapes.length; ++id) {
+ Document doc = new Document();
+ doc.add(newStringField("id", "" + id, Field.Store.NO));
+ doc.add(new NumericDocValuesField("id", id));
+ if (shapes[id] != null) {
+ addShapeToDoc(FIELD_NAME, doc, shapes[id]);
+ }
+ w.addDocument(doc);
+ if (id > 0 && random().nextInt(100) == 42) {
+ int idToDelete = random().nextInt(id);
+ w.deleteDocuments(new Term("id", ""+idToDelete));
+ deleted.add(idToDelete);
+ if (VERBOSE) {
+ System.out.println(" delete id=" + idToDelete);
+ }
+ }
+ }
+
+ if (randomBoolean()) {
+ w.forceMerge(1);
+ }
+ }
+
+ protected void verifyRandomQueries(IndexReader reader, Object... shapes) throws Exception {
+ // test random bbox queries
+ verifyRandomBBoxQueries(reader, shapes);
+ // test random line queries
+ verifyRandomLineQueries(reader, shapes);
+ // test random polygon queries
+ verifyRandomPolygonQueries(reader, shapes);
+ }
+
+ /** test random generated bounding boxes */
+ protected void verifyRandomBBoxQueries(IndexReader reader, Object... shapes) throws Exception {
+ IndexSearcher s = newSearcher(reader);
+
+ final int iters = scaledIterationCount(shapes.length);
+
+ Bits liveDocs = MultiBits.getLiveDocs(s.getIndexReader());
+ int maxDoc = s.getIndexReader().maxDoc();
+
+ for (int iter = 0; iter < iters; ++iter) {
+ if (VERBOSE) {
+ System.out.println("\nTEST: iter=" + (iter+1) + " of " + iters + " s=" + s);
+ }
+
+ // BBox
+ Object rect = randomQueryBox();
+ QueryRelation queryRelation = RandomPicks.randomFrom(random(), QueryRelation.values());
+ Query query = newRectQuery(FIELD_NAME, queryRelation, rectMinX(rect), rectMaxX(rect), rectMinY(rect), rectMaxY(rect));
+
+ if (VERBOSE) {
+ System.out.println(" query=" + query + ", relation=" + queryRelation);
+ }
+
+ final FixedBitSet hits = new FixedBitSet(maxDoc);
+ s.search(query, new SimpleCollector() {
+
+ private int docBase;
+
+ @Override
+ public ScoreMode scoreMode() {
+ return ScoreMode.COMPLETE_NO_SCORES;
+ }
+
+ @Override
+ protected void doSetNextReader(LeafReaderContext context) throws IOException {
+ docBase = context.docBase;
+ }
+
+ @Override
+ public void collect(int doc) throws IOException {
+ hits.set(docBase+doc);
+ }
+ });
+
+ boolean fail = false;
+ NumericDocValues docIDToID = MultiDocValues.getNumericValues(reader, "id");
+ for (int docID = 0; docID < maxDoc; ++docID) {
+ assertEquals(docID, docIDToID.nextDoc());
+ int id = (int) docIDToID.longValue();
+ boolean expected;
+ double qMinLon = ENCODER.quantizeXCeil(rectMinX(rect));
+ double qMaxLon = ENCODER.quantizeX(rectMaxX(rect));
+ double qMinLat = ENCODER.quantizeYCeil(rectMinY(rect));
+ double qMaxLat = ENCODER.quantizeY(rectMaxY(rect));
+ if (liveDocs != null && liveDocs.get(docID) == false) {
+ // document is deleted
+ expected = false;
+ } else if (shapes[id] == null) {
+ expected = false;
+ } else {
+ // check quantized poly against quantized query
+ if (qMinLon > qMaxLon && rectCrossesDateline(rect) == false) {
+ // if the quantization creates a false dateline crossing (because of encodeCeil):
+ // then do not use encodeCeil
+ qMinLon = ENCODER.quantizeX(rectMinX(rect));
+ }
+
+ if (qMinLat > qMaxLat) {
+ qMinLat = ENCODER.quantizeY(rectMaxY(rect));
+ }
+ expected = VALIDATOR.setRelation(queryRelation).testBBoxQuery(qMinLat, qMaxLat, qMinLon, qMaxLon, shapes[id]);
+ }
+
+ if (hits.get(docID) != expected) {
+ StringBuilder b = new StringBuilder();
+
+ if (expected) {
+ b.append("FAIL: id=" + id + " should match but did not\n");
+ } else {
+ b.append("FAIL: id=" + id + " should not match but did\n");
+ }
+ b.append(" relation=" + queryRelation + "\n");
+ b.append(" query=" + query + " docID=" + docID + "\n");
+ if (shapes[id] instanceof Object[]) {
+ b.append(" shape=" + Arrays.toString((Object[]) shapes[id]) + "\n");
+ } else {
+ b.append(" shape=" + shapes[id] + "\n");
+ }
+ b.append(" deleted?=" + (liveDocs != null && liveDocs.get(docID) == false));
+ b.append(" rect=Rectangle(lat=" + ENCODER.quantizeYCeil(rectMinY(rect)) + " TO " + ENCODER.quantizeY(rectMaxY(rect)) + " lon=" + qMinLon + " TO " + ENCODER.quantizeX(rectMaxX(rect)) + ")\n");
+ if (true) {
+ fail("wrong hit (first of possibly more):\n\n" + b);
+ } else {
+ System.out.println(b.toString());
+ fail = true;
+ }
+ }
+ }
+ if (fail) {
+ fail("some hits were wrong");
+ }
+ }
+ }
+
+ /** test random generated lines */
+ protected void verifyRandomLineQueries(IndexReader reader, Object... shapes) throws Exception {
+ IndexSearcher s = newSearcher(reader);
+
+ final int iters = scaledIterationCount(shapes.length);
+
+ Bits liveDocs = MultiBits.getLiveDocs(s.getIndexReader());
+ int maxDoc = s.getIndexReader().maxDoc();
+
+ for (int iter = 0; iter < iters; ++iter) {
+ if (VERBOSE) {
+ System.out.println("\nTEST: iter=" + (iter + 1) + " of " + iters + " s=" + s);
+ }
+
+ // line
+ Object queryLine = randomQueryLine(shapes);
+ Line2D queryLine2D = toLine2D(queryLine);
+ QueryRelation queryRelation = RandomPicks.randomFrom(random(), POINT_LINE_RELATIONS);
+ Query query = newLineQuery(FIELD_NAME, queryRelation, queryLine);
+
+ if (VERBOSE) {
+ System.out.println(" query=" + query + ", relation=" + queryRelation);
+ }
+
+ final FixedBitSet hits = new FixedBitSet(maxDoc);
+ s.search(query, new SimpleCollector() {
+
+ private int docBase;
+
+ @Override
+ public ScoreMode scoreMode() {
+ return ScoreMode.COMPLETE_NO_SCORES;
+ }
+
+ @Override
+ protected void doSetNextReader(LeafReaderContext context) throws IOException {
+ docBase = context.docBase;
+ }
+
+ @Override
+ public void collect(int doc) throws IOException {
+ hits.set(docBase+doc);
+ }
+ });
+
+ boolean fail = false;
+ NumericDocValues docIDToID = MultiDocValues.getNumericValues(reader, "id");
+ for (int docID = 0; docID < maxDoc; ++docID) {
+ assertEquals(docID, docIDToID.nextDoc());
+ int id = (int) docIDToID.longValue();
+ boolean expected;
+ if (liveDocs != null && liveDocs.get(docID) == false) {
+ // document is deleted
+ expected = false;
+ } else if (shapes[id] == null) {
+ expected = false;
+ } else {
+ expected = VALIDATOR.setRelation(queryRelation).testLineQuery(queryLine2D, shapes[id]);
+ }
+
+ if (hits.get(docID) != expected) {
+ StringBuilder b = new StringBuilder();
+
+ if (expected) {
+ b.append("FAIL: id=" + id + " should match but did not\n");
+ } else {
+ b.append("FAIL: id=" + id + " should not match but did\n");
+ }
+ b.append(" relation=" + queryRelation + "\n");
+ b.append(" query=" + query + " docID=" + docID + "\n");
+ if (shapes[id] instanceof Object[]) {
+ b.append(" shape=" + Arrays.toString((Object[]) shapes[id]) + "\n");
+ } else {
+ b.append(" shape=" + shapes[id] + "\n");
+ }
+ b.append(" deleted?=" + (liveDocs != null && liveDocs.get(docID) == false));
+ b.append(" queryPolygon=" + queryLine);
+ if (true) {
+ fail("wrong hit (first of possibly more):\n\n" + b);
+ } else {
+ System.out.println(b.toString());
+ fail = true;
+ }
+ }
+ }
+ if (fail) {
+ fail("some hits were wrong");
+ }
+ }
+ }
+
+ /** test random generated polygons */
+ protected void verifyRandomPolygonQueries(IndexReader reader, Object... shapes) throws Exception {
+ IndexSearcher s = newSearcher(reader);
+
+ final int iters = scaledIterationCount(shapes.length);
+
+ Bits liveDocs = MultiBits.getLiveDocs(s.getIndexReader());
+ int maxDoc = s.getIndexReader().maxDoc();
+
+ for (int iter = 0; iter < iters; ++iter) {
+ if (VERBOSE) {
+ System.out.println("\nTEST: iter=" + (iter + 1) + " of " + iters + " s=" + s);
+ }
+
+ // Polygon
+ Object queryPolygon = randomQueryPolygon();
+ Object queryPoly2D = toPolygon2D(queryPolygon);
+ QueryRelation queryRelation = RandomPicks.randomFrom(random(), QueryRelation.values());
+ Query query = newPolygonQuery(FIELD_NAME, queryRelation, queryPolygon);
+
+ if (VERBOSE) {
+ System.out.println(" query=" + query + ", relation=" + queryRelation);
+ }
+
+ final FixedBitSet hits = new FixedBitSet(maxDoc);
+ s.search(query, new SimpleCollector() {
+
+ private int docBase;
+
+ @Override
+ public ScoreMode scoreMode() {
+ return ScoreMode.COMPLETE_NO_SCORES;
+ }
+
+ @Override
+ protected void doSetNextReader(LeafReaderContext context) throws IOException {
+ docBase = context.docBase;
+ }
+
+ @Override
+ public void collect(int doc) throws IOException {
+ hits.set(docBase+doc);
+ }
+ });
+
+ boolean fail = false;
+ NumericDocValues docIDToID = MultiDocValues.getNumericValues(reader, "id");
+ for (int docID = 0; docID < maxDoc; ++docID) {
+ assertEquals(docID, docIDToID.nextDoc());
+ int id = (int) docIDToID.longValue();
+ boolean expected;
+ if (liveDocs != null && liveDocs.get(docID) == false) {
+ // document is deleted
+ expected = false;
+ } else if (shapes[id] == null) {
+ expected = false;
+ } else {
+ expected = VALIDATOR.setRelation(queryRelation).testPolygonQuery(queryPoly2D, shapes[id]);
+ }
+
+ if (hits.get(docID) != expected) {
+ StringBuilder b = new StringBuilder();
+
+ if (expected) {
+ b.append("FAIL: id=" + id + " should match but did not\n");
+ } else {
+ b.append("FAIL: id=" + id + " should not match but did\n");
+ }
+ b.append(" relation=" + queryRelation + "\n");
+ b.append(" query=" + query + " docID=" + docID + "\n");
+ if (shapes[id] instanceof Object[]) {
+ b.append(" shape=" + Arrays.toString((Object[]) shapes[id]) + "\n");
+ } else {
+ b.append(" shape=" + shapes[id] + "\n");
+ }
+ b.append(" deleted?=" + (liveDocs != null && liveDocs.get(docID) == false));
+ b.append(" queryPolygon=" + queryPolygon);
+ if (true) {
+ fail("wrong hit (first of possibly more):\n\n" + b);
+ } else {
+ System.out.println(b.toString());
+ fail = true;
+ }
+ }
+ }
+ if (fail) {
+ fail("some hits were wrong");
+ }
+ }
+ }
+
+ protected abstract Validator getValidator();
+
+ protected static abstract class Encoder {
+ abstract double quantizeX(double raw);
+ abstract double quantizeXCeil(double raw);
+ abstract double quantizeY(double raw);
+ abstract double quantizeYCeil(double raw);
+ abstract double[] quantizeTriangle(double ax, double ay, double bx, double by, double cx, double cy);
+ abstract int[] encodeDecodeTriangle(double ax, double ay, double bx, double by, double cx, double cy);
+ }
+
+ private int scaledIterationCount(int shapes) {
+ if (shapes < 500) {
+ return atLeast(50);
+ } else if (shapes < 5000) {
+ return atLeast(25);
+ } else if (shapes < 25000) {
+ return atLeast(5);
+ } else {
+ return atLeast(2);
+ }
+ }
+
+ /** validator class used to test query results against "ground truth" */
+ protected static abstract class Validator {
+ Encoder encoder;
+ Validator(Encoder encoder) {
+ this.encoder = encoder;
+ }
+
+ protected QueryRelation queryRelation = QueryRelation.INTERSECTS;
+ public abstract boolean testBBoxQuery(double minLat, double maxLat, double minLon, double maxLon, Object shape);
+ public abstract boolean testLineQuery(Line2D line2d, Object shape);
+ public abstract boolean testPolygonQuery(Object poly2d, Object shape);
+
+ public Validator setRelation(QueryRelation relation) {
+ this.queryRelation = relation;
+ return this;
+ }
+ }
+}
diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/BaseXYShapeTestCase.java b/lucene/sandbox/src/test/org/apache/lucene/document/BaseXYShapeTestCase.java
new file mode 100644
index 000000000000..b706b5f20cdc
--- /dev/null
+++ b/lucene/sandbox/src/test/org/apache/lucene/document/BaseXYShapeTestCase.java
@@ -0,0 +1,232 @@
+/*
+ * 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 java.util.Arrays;
+
+import com.carrotsearch.randomizedtesting.generators.RandomPicks;
+import org.apache.lucene.document.ShapeField.QueryRelation;
+import org.apache.lucene.geo.Line2D;
+import org.apache.lucene.geo.ShapeTestUtil;
+import org.apache.lucene.geo.XYLine;
+import org.apache.lucene.geo.XYPolygon;
+import org.apache.lucene.geo.XYPolygon2D;
+import org.apache.lucene.geo.XYRectangle;
+import org.apache.lucene.search.Query;
+
+import static org.apache.lucene.geo.XYEncodingUtils.decode;
+import static org.apache.lucene.geo.XYEncodingUtils.encode;
+
+/** Base test case for testing indexing and search functionality of cartesian geometry **/
+public abstract class BaseXYShapeTestCase extends BaseShapeTestCase {
+ protected abstract ShapeType getShapeType();
+
+ protected Object nextShape() {
+ return getShapeType().nextShape();
+ }
+
+ /** factory method to create a new bounding box query */
+ @Override
+ protected Query newRectQuery(String field, QueryRelation queryRelation, double minX, double maxX, double minY, double maxY) {
+ return XYShape.newBoxQuery(field, queryRelation, (float)minX, (float)maxX, (float)minY, (float)maxY);
+ }
+
+ /** factory method to create a new line query */
+ @Override
+ protected Query newLineQuery(String field, QueryRelation queryRelation, Object... lines) {
+ return XYShape.newLineQuery(field, queryRelation, Arrays.stream(lines).toArray(XYLine[]::new));
+ }
+
+ /** factory method to create a new polygon query */
+ @Override
+ protected Query newPolygonQuery(String field, QueryRelation queryRelation, Object... polygons) {
+ return XYShape.newPolygonQuery(field, queryRelation, Arrays.stream(polygons).toArray(XYPolygon[]::new));
+ }
+
+ @Override
+ protected Line2D toLine2D(Object... lines) {
+ return Line2D.create(Arrays.stream(lines).toArray(XYLine[]::new));
+ }
+
+ @Override
+ protected XYPolygon2D toPolygon2D(Object... polygons) {
+ return XYPolygon2D.create(Arrays.stream(polygons).toArray(XYPolygon[]::new));
+ }
+
+ @Override
+ public XYRectangle randomQueryBox() {
+ return ShapeTestUtil.nextBox();
+ }
+
+ @Override
+ protected double rectMinX(Object rect) {
+ return ((XYRectangle)rect).minX;
+ }
+
+ @Override
+ protected double rectMaxX(Object rect) {
+ return ((XYRectangle)rect).maxX;
+ }
+
+ @Override
+ protected double rectMinY(Object rect) {
+ return ((XYRectangle)rect).minY;
+ }
+
+ @Override
+ protected double rectMaxY(Object rect) {
+ return ((XYRectangle)rect).maxY;
+ }
+
+ @Override
+ protected boolean rectCrossesDateline(Object rect) {
+ return false;
+ }
+
+ /** use {@link ShapeTestUtil#nextPolygon()} to create a random line; TODO: move to GeoTestUtil */
+ @Override
+ public XYLine nextLine() {
+ return getNextLine();
+ }
+
+ public static XYLine getNextLine() {
+ XYPolygon poly = ShapeTestUtil.nextPolygon();
+ float[] x = new float[poly.numPoints() - 1];
+ float[] y = new float[x.length];
+ for (int i = 0; i < x.length; ++i) {
+ x[i] = (float) poly.getPolyX(i);
+ y[i] = (float) poly.getPolyY(i);
+ }
+
+ return new XYLine(x, y);
+ }
+
+ @Override
+ protected XYPolygon nextPolygon() {
+ return ShapeTestUtil.nextPolygon();
+ }
+
+ @Override
+ protected Encoder getEncoder() {
+ return new Encoder() {
+ @Override
+ double quantizeX(double raw) {
+ return decode(encode(raw));
+ }
+
+ @Override
+ double quantizeXCeil(double raw) {
+ return decode(encode(raw));
+ }
+
+ @Override
+ double quantizeY(double raw) {
+ return decode(encode(raw));
+ }
+
+ @Override
+ double quantizeYCeil(double raw) {
+ return decode(encode(raw));
+ }
+
+ @Override
+ double[] quantizeTriangle(double ax, double ay, double bx, double by, double cx, double cy) {
+ int[] decoded = encodeDecodeTriangle(ax, ay, bx, by, cx, cy);
+ return new double[]{decode(decoded[0]), decode(decoded[1]), decode(decoded[2]), decode(decoded[3]), decode(decoded[4]), decode(decoded[5])};
+ }
+
+ @Override
+ int[] encodeDecodeTriangle(double ax, double ay, double bx, double by, double cx, double cy) {
+ byte[] encoded = new byte[7 * ShapeField.BYTES];
+ ShapeField.encodeTriangle(encoded, encode(ay), encode(ax), encode(by), encode(bx), encode(cy), encode(cx));
+ int[] decoded = new int[6];
+ ShapeField.decodeTriangle(encoded, decoded);
+ return decoded;
+ }
+ };
+ }
+
+ /** internal shape type for testing different shape types */
+ protected enum ShapeType {
+ POINT() {
+ public Point nextShape() {
+ return new Point((float)random().nextDouble(), (float)random().nextDouble());
+ }
+ },
+ LINE() {
+ public XYLine nextShape() {
+ XYPolygon p = ShapeTestUtil.nextPolygon();
+ float[] x = new float[p.numPoints() - 1];
+ float[] y = new float[x.length];
+ for (int i = 0; i < x.length; ++i) {
+ x[i] = (float)p.getPolyX(i);
+ y[i] = (float)p.getPolyY(i);
+ }
+ return new XYLine(x, y);
+ }
+ },
+ POLYGON() {
+ public XYPolygon nextShape() {
+ return ShapeTestUtil.nextPolygon();
+ }
+ },
+ MIXED() {
+ public Object nextShape() {
+ return RandomPicks.randomFrom(random(), subList).nextShape();
+ }
+ };
+
+ static ShapeType[] subList;
+ static {
+ subList = new ShapeType[] {POINT, LINE, POLYGON};
+ }
+
+ public abstract Object nextShape();
+
+ static ShapeType fromObject(Object shape) {
+ if (shape instanceof Point) {
+ return POINT;
+ } else if (shape instanceof XYLine) {
+ return LINE;
+ } else if (shape instanceof XYPolygon) {
+ return POLYGON;
+ }
+ throw new IllegalArgumentException("invalid shape type from " + shape.toString());
+ }
+ }
+
+ /** internal point class for testing point shapes */
+ protected static class Point {
+ float x;
+ float y;
+
+ public Point(float x, float y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("POINT(");
+ sb.append(x);
+ sb.append(',');
+ sb.append(y);
+ sb.append(')');
+ return sb.toString();
+ }
+ }
+}
diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonLineShapeQueries.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonLineShapeQueries.java
index 7e6d9958ace5..d7ed52946d1d 100644
--- a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonLineShapeQueries.java
+++ b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonLineShapeQueries.java
@@ -18,7 +18,7 @@
import com.carrotsearch.randomizedtesting.generators.RandomNumbers;
-import org.apache.lucene.document.LatLonShape.QueryRelation;
+import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.EdgeTree;
import org.apache.lucene.geo.GeoTestUtil;
import org.apache.lucene.geo.Line;
@@ -28,12 +28,10 @@
import org.apache.lucene.geo.Rectangle2D;
import org.apache.lucene.index.PointValues.Relation;
-/** random bounding box and polygon query tests for random generated {@link Line} types */
+/** random bounding box, line, and polygon query tests for random generated {@link Line} types */
@SuppressWarnings("SimpleText")
public class TestLatLonLineShapeQueries extends BaseLatLonShapeTestCase {
- protected final LineValidator VALIDATOR = new LineValidator();
-
@Override
protected ShapeType getShapeType() {
return ShapeType.LINE;
@@ -71,18 +69,21 @@ protected Field[] createIndexableFields(String field, Object line) {
}
@Override
- protected Validator getValidator(QueryRelation queryRelation) {
- VALIDATOR.setRelation(queryRelation);
- return VALIDATOR;
+ protected Validator getValidator() {
+ return new LineValidator(this.ENCODER);
}
protected static class LineValidator extends Validator {
+ protected LineValidator(Encoder encoder) {
+ super(encoder);
+ }
+
@Override
public boolean testBBoxQuery(double minLat, double maxLat, double minLon, double maxLon, Object shape) {
Line line = (Line)shape;
Rectangle2D rectangle2D = Rectangle2D.create(new Rectangle(minLat, maxLat, minLon, maxLon));
for (int i = 0, j = 1; j < line.numPoints(); ++i, ++j) {
- int[] decoded = encodeDecodeTriangle(line.getLon(i), line.getLat(i), line.getLon(j), line.getLat(j), line.getLon(i), line.getLat(i));
+ int[] decoded = encoder.encodeDecodeTriangle(line.getLon(i), line.getLat(i), line.getLon(j), line.getLat(j), line.getLon(i), line.getLat(i));
if (queryRelation == QueryRelation.WITHIN) {
if (rectangle2D.containsTriangle(decoded[1], decoded[0], decoded[3], decoded[2], decoded[5], decoded[4]) == false) {
return false;
@@ -102,14 +103,14 @@ public boolean testLineQuery(Line2D line2d, Object shape) {
}
@Override
- public boolean testPolygonQuery(Polygon2D poly2d, Object shape) {
- return testLine(poly2d, (Line) shape);
+ public boolean testPolygonQuery(Object poly2d, Object shape) {
+ return testLine((Polygon2D)poly2d, (Line) shape);
}
private boolean testLine(EdgeTree queryPoly, Line line) {
for (int i = 0, j = 1; j < line.numPoints(); ++i, ++j) {
- double[] qTriangle = quantizeTriangle(line.getLon(i), line.getLat(i), line.getLon(j), line.getLat(j), line.getLon(i), line.getLat(i));
+ double[] qTriangle = encoder.quantizeTriangle(line.getLon(i), line.getLat(i), line.getLon(j), line.getLat(j), line.getLon(i), line.getLat(i));
Relation r = queryPoly.relateTriangle(qTriangle[1], qTriangle[0], qTriangle[3], qTriangle[2], qTriangle[5], qTriangle[4]);
if (queryRelation == QueryRelation.DISJOINT) {
if (r != Relation.CELL_OUTSIDE_QUERY) return false;
diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonMultiLineShapeQueries.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonMultiLineShapeQueries.java
index 5c9c42ee330c..fc5bc91247bc 100644
--- a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonMultiLineShapeQueries.java
+++ b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonMultiLineShapeQueries.java
@@ -19,17 +19,13 @@
import java.util.ArrayList;
import java.util.List;
-import org.apache.lucene.document.LatLonShape.QueryRelation;
+import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.Line;
import org.apache.lucene.geo.Line2D;
-import org.apache.lucene.geo.Polygon2D;
-/** random bounding box and polygon query tests for random indexed arrays of {@link Line} types */
+/** random bounding box, line, and polygon query tests for random indexed arrays of {@link Line} types */
public class TestLatLonMultiLineShapeQueries extends BaseLatLonShapeTestCase {
- protected final MultiLineValidator VALIDATOR = new MultiLineValidator();
- protected final TestLatLonLineShapeQueries.LineValidator LINEVALIDATOR = new TestLatLonLineShapeQueries.LineValidator();
-
@Override
protected ShapeType getShapeType() {
return ShapeType.LINE;
@@ -59,13 +55,24 @@ protected Field[] createIndexableFields(String name, Object o) {
}
@Override
- protected Validator getValidator(QueryRelation relation) {
- VALIDATOR.setRelation(relation);
- LINEVALIDATOR.setRelation(relation);
- return VALIDATOR;
+ public Validator getValidator() {
+ return new MultiLineValidator(ENCODER);
}
protected class MultiLineValidator extends Validator {
+ TestLatLonLineShapeQueries.LineValidator LINEVALIDATOR;
+ MultiLineValidator(Encoder encoder) {
+ super(encoder);
+ LINEVALIDATOR = new TestLatLonLineShapeQueries.LineValidator(encoder);
+ }
+
+ @Override
+ public Validator setRelation(QueryRelation relation) {
+ super.setRelation(relation);
+ LINEVALIDATOR.queryRelation = relation;
+ return this;
+ }
+
@Override
public boolean testBBoxQuery(double minLat, double maxLat, double minLon, double maxLon, Object shape) {
Line[] lines = (Line[])shape;
@@ -99,7 +106,7 @@ public boolean testLineQuery(Line2D query, Object shape) {
}
@Override
- public boolean testPolygonQuery(Polygon2D query, Object shape) {
+ public boolean testPolygonQuery(Object query, Object shape) {
Line[] lines = (Line[])shape;
for (Line l : lines) {
boolean b = LINEVALIDATOR.testPolygonQuery(query, l);
diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonMultiPointShapeQueries.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonMultiPointShapeQueries.java
index 44d095d2d1b0..536e9c2178ec 100644
--- a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonMultiPointShapeQueries.java
+++ b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonMultiPointShapeQueries.java
@@ -19,17 +19,12 @@
import java.util.ArrayList;
import java.util.List;
-import org.apache.lucene.document.LatLonShape.QueryRelation;
-import org.apache.lucene.geo.GeoTestUtil;
+import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.Line2D;
-import org.apache.lucene.geo.Polygon2D;
-/** random bounding box and polygon query tests for random indexed arrays of {@code latitude, longitude} points */
+/** random bounding box, line, and polygon query tests for random indexed arrays of {@code latitude, longitude} points */
public class TestLatLonMultiPointShapeQueries extends BaseLatLonShapeTestCase {
- protected final MultiPointValidator VALIDATOR = new MultiPointValidator();
- protected final TestLatLonPointShapeQueries.PointValidator POINTVALIDATOR = new TestLatLonPointShapeQueries.PointValidator();
-
@Override
protected ShapeType getShapeType() {
return ShapeType.POINT;
@@ -40,7 +35,7 @@ protected Point[] nextShape() {
int n = random().nextInt(4) + 1;
Point[] points = new Point[n];
for (int i =0; i < n; i++) {
- points[i] = new Point(GeoTestUtil.nextLatitude(), GeoTestUtil.nextLongitude());
+ points[i] = (Point)ShapeType.POINT.nextShape();
}
return points;
}
@@ -59,13 +54,24 @@ protected Field[] createIndexableFields(String name, Object o) {
}
@Override
- protected Validator getValidator(QueryRelation relation) {
- VALIDATOR.setRelation(relation);
- POINTVALIDATOR.setRelation(relation);
- return VALIDATOR;
+ public Validator getValidator() {
+ return new MultiPointValidator(ENCODER);
}
protected class MultiPointValidator extends Validator {
+ TestLatLonPointShapeQueries.PointValidator POINTVALIDATOR;
+ MultiPointValidator(Encoder encoder) {
+ super(encoder);
+ POINTVALIDATOR = new TestLatLonPointShapeQueries.PointValidator(encoder);
+ }
+
+ @Override
+ public Validator setRelation(QueryRelation relation) {
+ super.setRelation(relation);
+ POINTVALIDATOR.queryRelation = relation;
+ return this;
+ }
+
@Override
public boolean testBBoxQuery(double minLat, double maxLat, double minLon, double maxLon, Object shape) {
Point[] points = (Point[]) shape;
@@ -99,7 +105,7 @@ public boolean testLineQuery(Line2D query, Object shape) {
}
@Override
- public boolean testPolygonQuery(Polygon2D query, Object shape) {
+ public boolean testPolygonQuery(Object query, Object shape) {
Point[] points = (Point[]) shape;
for (Point p : points) {
boolean b = POINTVALIDATOR.testPolygonQuery(query, p);
diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonMultiPolygonShapeQueries.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonMultiPolygonShapeQueries.java
index 3729bbad08b5..bba9f97a5493 100644
--- a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonMultiPolygonShapeQueries.java
+++ b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonMultiPolygonShapeQueries.java
@@ -19,18 +19,14 @@
import java.util.ArrayList;
import java.util.List;
-import org.apache.lucene.document.LatLonShape.QueryRelation;
+import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.Line2D;
import org.apache.lucene.geo.Polygon;
-import org.apache.lucene.geo.Polygon2D;
import org.apache.lucene.geo.Tessellator;
-/** random bounding box and polygon query tests for random indexed arrays of {@link Polygon} types */
+/** random bounding box, line, and polygon query tests for random indexed arrays of {@link Polygon} types */
public class TestLatLonMultiPolygonShapeQueries extends BaseLatLonShapeTestCase {
- protected final MultiPolygonValidator VALIDATOR = new MultiPolygonValidator();
- protected final TestLatLonPolygonShapeQueries.PolygonValidator POLYGONVALIDATOR = new TestLatLonPolygonShapeQueries.PolygonValidator();
-
@Override
protected ShapeType getShapeType() {
return ShapeType.POLYGON;
@@ -71,13 +67,24 @@ protected Field[] createIndexableFields(String name, Object o) {
}
@Override
- protected Validator getValidator(QueryRelation relation) {
- VALIDATOR.setRelation(relation);
- POLYGONVALIDATOR.setRelation(relation);
- return VALIDATOR;
+ protected Validator getValidator() {
+ return new MultiPolygonValidator(ENCODER);
}
protected class MultiPolygonValidator extends Validator {
+ TestLatLonPolygonShapeQueries.PolygonValidator POLYGONVALIDATOR;
+ MultiPolygonValidator(Encoder encoder) {
+ super(encoder);
+ POLYGONVALIDATOR = new TestLatLonPolygonShapeQueries.PolygonValidator(encoder);
+ }
+
+ @Override
+ public Validator setRelation(QueryRelation relation) {
+ super.setRelation(relation);
+ POLYGONVALIDATOR.queryRelation = relation;
+ return this;
+ }
+
@Override
public boolean testBBoxQuery(double minLat, double maxLat, double minLon, double maxLon, Object shape) {
Polygon[] polygons = (Polygon[])shape;
@@ -111,7 +118,7 @@ public boolean testLineQuery(Line2D query, Object shape) {
}
@Override
- public boolean testPolygonQuery(Polygon2D query, Object shape) {
+ public boolean testPolygonQuery(Object query, Object shape) {
Polygon[] polygons = (Polygon[])shape;
for (Polygon p : polygons) {
boolean b = POLYGONVALIDATOR.testPolygonQuery(query, p);
diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPointShapeQueries.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPointShapeQueries.java
index d894aed7c9a7..b3ab59b9b60b 100644
--- a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPointShapeQueries.java
+++ b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPointShapeQueries.java
@@ -17,7 +17,7 @@
package org.apache.lucene.document;
import com.carrotsearch.randomizedtesting.generators.RandomNumbers;
-import org.apache.lucene.document.LatLonShape.QueryRelation;
+import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.EdgeTree;
import org.apache.lucene.geo.GeoTestUtil;
import org.apache.lucene.geo.Line;
@@ -25,11 +25,9 @@
import org.apache.lucene.geo.Polygon2D;
import org.apache.lucene.index.PointValues.Relation;
-/** random bounding box and polygon query tests for random generated {@code latitude, longitude} points */
+/** random bounding box, line, and polygon query tests for random generated {@code latitude, longitude} points */
public class TestLatLonPointShapeQueries extends BaseLatLonShapeTestCase {
- protected final PointValidator VALIDATOR = new PointValidator();
-
@Override
protected ShapeType getShapeType() {
return ShapeType.POINT;
@@ -67,17 +65,20 @@ protected Field[] createIndexableFields(String field, Object point) {
}
@Override
- protected Validator getValidator(QueryRelation relation) {
- VALIDATOR.setRelation(relation);
- return VALIDATOR;
+ protected Validator getValidator() {
+ return new PointValidator(this.ENCODER);
}
protected static class PointValidator extends Validator {
+ protected PointValidator(Encoder encoder) {
+ super(encoder);
+ }
+
@Override
public boolean testBBoxQuery(double minLat, double maxLat, double minLon, double maxLon, Object shape) {
Point p = (Point)shape;
- double lat = quantizeLat(p.lat);
- double lon = quantizeLon(p.lon);
+ double lat = encoder.quantizeY(p.lat);
+ double lon = encoder.quantizeX(p.lon);
boolean isDisjoint = lat < minLat || lat > maxLat;
isDisjoint = isDisjoint || ((minLon > maxLon)
@@ -95,13 +96,13 @@ public boolean testLineQuery(Line2D line2d, Object shape) {
}
@Override
- public boolean testPolygonQuery(Polygon2D poly2d, Object shape) {
- return testPoint(poly2d, (Point) shape);
+ public boolean testPolygonQuery(Object poly2d, Object shape) {
+ return testPoint((Polygon2D)poly2d, (Point) shape);
}
private boolean testPoint(EdgeTree tree, Point p) {
- double lat = quantizeLat(p.lat);
- double lon = quantizeLon(p.lon);
+ double lat = encoder.quantizeY(p.lat);
+ double lon = encoder.quantizeX(p.lon);
// for consistency w/ the query we test the point as a triangle
Relation r = tree.relateTriangle(lon, lat, lon, lat, lon, lat);
if (queryRelation == QueryRelation.WITHIN) {
diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPolygonShapeQueries.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPolygonShapeQueries.java
index a3e060c9ae11..8b3cab4edda5 100644
--- a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPolygonShapeQueries.java
+++ b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPolygonShapeQueries.java
@@ -18,7 +18,7 @@
import java.util.List;
-import org.apache.lucene.document.LatLonShape.QueryRelation;
+import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.EdgeTree;
import org.apache.lucene.geo.Line2D;
import org.apache.lucene.geo.Polygon;
@@ -28,11 +28,9 @@
import org.apache.lucene.geo.Tessellator;
import org.apache.lucene.index.PointValues.Relation;
-/** random bounding box and polygon query tests for random indexed {@link Polygon} types */
+/** random bounding box, line, and polygon query tests for random indexed {@link Polygon} types */
public class TestLatLonPolygonShapeQueries extends BaseLatLonShapeTestCase {
- protected final PolygonValidator VALIDATOR = new PolygonValidator();
-
@Override
protected ShapeType getShapeType() {
return ShapeType.POLYGON;
@@ -59,19 +57,22 @@ protected Field[] createIndexableFields(String field, Object polygon) {
}
@Override
- protected Validator getValidator(QueryRelation relation) {
- VALIDATOR.setRelation(relation);
- return VALIDATOR;
+ protected Validator getValidator() {
+ return new PolygonValidator(this.ENCODER);
}
protected static class PolygonValidator extends Validator {
+ protected PolygonValidator(Encoder encoder) {
+ super(encoder);
+ }
+
@Override
public boolean testBBoxQuery(double minLat, double maxLat, double minLon, double maxLon, Object shape) {
Polygon p = (Polygon)shape;
Rectangle2D rectangle2D = Rectangle2D.create(new Rectangle(minLat, maxLat, minLon, maxLon));
List tessellation = Tessellator.tessellate(p);
for (Tessellator.Triangle t : tessellation) {
- int[] decoded = encodeDecodeTriangle(t.getLon(0), t.getLat(0), t.getLon(1), t.getLat(1), t.getLon(2), t.getLat(2));
+ int[] decoded = encoder.encodeDecodeTriangle(t.getX(0), t.getY(0), t.getX(1), t.getY(1), t.getX(2), t.getY(2));
if (queryRelation == QueryRelation.WITHIN) {
if (rectangle2D.containsTriangle(decoded[1], decoded[0], decoded[3], decoded[2], decoded[5], decoded[4]) == false) {
return false;
@@ -91,14 +92,14 @@ public boolean testLineQuery(Line2D query, Object shape) {
}
@Override
- public boolean testPolygonQuery(Polygon2D query, Object shape) {
- return testPolygon(query, (Polygon) shape);
+ public boolean testPolygonQuery(Object query, Object shape) {
+ return testPolygon((Polygon2D)query, (Polygon) shape);
}
private boolean testPolygon(EdgeTree tree, Polygon shape) {
List tessellation = Tessellator.tessellate(shape);
for (Tessellator.Triangle t : tessellation) {
- double[] qTriangle = quantizeTriangle(t.getLon(0), t.getLat(0), t.getLon(1), t.getLat(1), t.getLon(2), t.getLat(2));
+ double[] qTriangle = encoder.quantizeTriangle(t.getX(0), t.getY(0), t.getX(1), t.getY(1), t.getX(2), t.getY(2));
Relation r = tree.relateTriangle(qTriangle[1], qTriangle[0], qTriangle[3], qTriangle[2], qTriangle[5], qTriangle[4]);
if (queryRelation == QueryRelation.DISJOINT) {
if (r != Relation.CELL_OUTSIDE_QUERY) return false;
diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonShape.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonShape.java
index a325bc760e22..66948a42ebc7 100644
--- a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonShape.java
+++ b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonShape.java
@@ -17,7 +17,7 @@
package org.apache.lucene.document;
import com.carrotsearch.randomizedtesting.generators.RandomNumbers;
-import org.apache.lucene.document.LatLonShape.QueryRelation;
+import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.GeoTestUtil;
import org.apache.lucene.geo.Line;
import org.apache.lucene.geo.Line2D;
@@ -235,11 +235,11 @@ public void testLUCENE8454() throws Exception {
Tessellator.Triangle t = Tessellator.tessellate(poly).get(0);
- byte[] encoded = new byte[7 * LatLonShape.BYTES];
- LatLonShape.encodeTriangle(encoded, encodeLatitude(t.getLat(0)), encodeLongitude(t.getLon(0)),
- encodeLatitude(t.getLat(1)), encodeLongitude(t.getLon(1)), encodeLatitude(t.getLat(2)), encodeLongitude(t.getLon(2)));
+ byte[] encoded = new byte[7 * ShapeField.BYTES];
+ ShapeField.encodeTriangle(encoded, encodeLatitude(t.getY(0)), encodeLongitude(t.getX(0)),
+ encodeLatitude(t.getY(1)), encodeLongitude(t.getX(1)), encodeLatitude(t.getY(2)), encodeLongitude(t.getX(2)));
int[] decoded = new int[6];
- LatLonShape.decodeTriangle(encoded, decoded);
+ ShapeField.decodeTriangle(encoded, decoded);
int expected =rectangle2D.intersectsTriangle(decoded[1], decoded[0], decoded[3], decoded[2], decoded[5], decoded[4]) ? 0 : 1;
diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonShapeEncoding.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonShapeEncoding.java
index e4cd2bc5e537..22700f5ffa33 100644
--- a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonShapeEncoding.java
+++ b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonShapeEncoding.java
@@ -16,546 +16,51 @@
*/
package org.apache.lucene.document;
-import java.util.Arrays;
-
-
import org.apache.lucene.geo.GeoEncodingUtils;
import org.apache.lucene.geo.GeoTestUtil;
-import org.apache.lucene.geo.GeoUtils;
import org.apache.lucene.geo.Polygon;
import org.apache.lucene.geo.Polygon2D;
-import org.apache.lucene.index.PointValues;
-import org.apache.lucene.util.LuceneTestCase;
/** Test case for LatLonShape encoding */
-public class TestLatLonShapeEncoding extends LuceneTestCase {
-
- //One shared point with MBR -> MinLat, MinLon
- public void testPolygonEncodingMinLatMinLon() {
- double alat = 0.0;
- double alon = 0.0;
- double blat = 1.0;
- double blon = 2.0;
- double clat = 2.0;
- double clon = 1.0;
- int alatEnc = GeoEncodingUtils.encodeLatitude(alat);
- int alonEnc = GeoEncodingUtils.encodeLongitude(alon);
- int blatEnc = GeoEncodingUtils.encodeLatitude(blat);
- int blonEnc = GeoEncodingUtils.encodeLongitude(blon);
- int clatEnc = GeoEncodingUtils.encodeLatitude(clat);
- int clonEnc = GeoEncodingUtils.encodeLongitude(clon);
- verifyEncodingPermutations(alatEnc, alonEnc, blatEnc, blonEnc, clatEnc, clonEnc);
- byte[] b = new byte[7 * LatLonShape.BYTES];
- LatLonShape.encodeTriangle(b, alatEnc, alonEnc, blatEnc, blonEnc, clatEnc, clonEnc);
- int[] encoded = new int[6];
- LatLonShape.decodeTriangle(b, encoded);
- assertTrue(encoded[0] == alatEnc);
- assertTrue(encoded[1] == alonEnc);
- assertTrue(encoded[2] == blatEnc);
- assertTrue(encoded[3] == blonEnc);
- assertTrue(encoded[4] == clatEnc);
- assertTrue(encoded[5] == clonEnc);
- }
-
- //One shared point with MBR -> MinLat, MaxLon
- public void testPolygonEncodingMinLatMaxLon() {
- double alat = 1.0;
- double alon = 0.0;
- double blat = 0.0;
- double blon = 2.0;
- double clat = 2.0;
- double clon = 1.0;
- int alatEnc = GeoEncodingUtils.encodeLatitude(alat);
- int alonEnc = GeoEncodingUtils.encodeLongitude(alon);
- int blatEnc = GeoEncodingUtils.encodeLatitude(blat);
- int blonEnc = GeoEncodingUtils.encodeLongitude(blon);
- int clatEnc = GeoEncodingUtils.encodeLatitude(clat);
- int clonEnc = GeoEncodingUtils.encodeLongitude(clon);
- verifyEncodingPermutations(alatEnc, alonEnc, blatEnc, blonEnc, clatEnc, clonEnc);
- byte[] b = new byte[7 * LatLonShape.BYTES];
- LatLonShape.encodeTriangle(b, alatEnc, alonEnc, blatEnc, blonEnc, clatEnc, clonEnc);
- int[] encoded = new int[6];
- LatLonShape.decodeTriangle(b, encoded);
- assertTrue(encoded[0] == alatEnc);
- assertTrue(encoded[1] == alonEnc);
- assertTrue(encoded[2] == blatEnc);
- assertTrue(encoded[3] == blonEnc);
- assertTrue(encoded[4] == clatEnc);
- assertTrue(encoded[5] == clonEnc);
- }
-
- //One shared point with MBR -> MaxLat, MaxLon
- public void testPolygonEncodingMaxLatMaxLon() {
- double alat = 1.0;
- double alon = 0.0;
- double blat = 2.0;
- double blon = 2.0;
- double clat = 0.0;
- double clon = 1.0;
- int alatEnc = GeoEncodingUtils.encodeLatitude(alat);
- int alonEnc = GeoEncodingUtils.encodeLongitude(alon);
- int blatEnc = GeoEncodingUtils.encodeLatitude(clat);
- int blonEnc = GeoEncodingUtils.encodeLongitude(clon);
- int clatEnc = GeoEncodingUtils.encodeLatitude(blat);
- int clonEnc = GeoEncodingUtils.encodeLongitude(blon);
- verifyEncodingPermutations(alatEnc, alonEnc, blatEnc, blonEnc, clatEnc, clonEnc);
- byte[] b = new byte[7 * LatLonShape.BYTES];
- LatLonShape.encodeTriangle(b, alatEnc, alonEnc, blatEnc, blonEnc, clatEnc, clonEnc);
- int[] encoded = new int[6];
- LatLonShape.decodeTriangle(b, encoded);
- assertTrue(encoded[0] == alatEnc);
- assertTrue(encoded[1] == alonEnc);
- assertTrue(encoded[2] == blatEnc);
- assertTrue(encoded[3] == blonEnc);
- assertTrue(encoded[4] == clatEnc);
- assertTrue(encoded[5] == clonEnc);
- }
+public class TestLatLonShapeEncoding extends BaseShapeEncodingTestCase {
- //One shared point with MBR -> MaxLat, MinLon
- public void testPolygonEncodingMaxLatMinLon() {
- double alat = 2.0;
- double alon = 0.0;
- double blat = 1.0;
- double blon = 2.0;
- double clat = 0.0;
- double clon = 1.0;
- int alatEnc = GeoEncodingUtils.encodeLatitude(alat);
- int alonEnc = GeoEncodingUtils.encodeLongitude(alon);
- int blatEnc = GeoEncodingUtils.encodeLatitude(clat);
- int blonEnc = GeoEncodingUtils.encodeLongitude(clon);
- int clatEnc = GeoEncodingUtils.encodeLatitude(blat);
- int clonEnc = GeoEncodingUtils.encodeLongitude(blon);
- verifyEncodingPermutations(alatEnc, alonEnc, blatEnc, blonEnc, clatEnc, clonEnc);
- byte[] b = new byte[7 * LatLonShape.BYTES];
- LatLonShape.encodeTriangle(b, alatEnc, alonEnc, blatEnc, blonEnc, clatEnc, clonEnc);
- int[] encoded = new int[6];
- LatLonShape.decodeTriangle(b, encoded);
- assertTrue(encoded[0] == alatEnc);
- assertTrue(encoded[1] == alonEnc);
- assertTrue(encoded[2] == blatEnc);
- assertTrue(encoded[3] == blonEnc);
- assertTrue(encoded[4] == clatEnc);
- assertTrue(encoded[5] == clonEnc);
+ @Override
+ protected int encodeX(double x) {
+ return GeoEncodingUtils.encodeLongitude(x);
}
- //Two shared point with MBR -> [MinLat, MinLon], [MaxLat, MaxLon], third point below
- public void testPolygonEncodingMinLatMinLonMaxLatMaxLonBelow() {
- double alat = 0.0;
- double alon = 0.0;
- double blat = 0.25;
- double blon = 0.75;
- double clat = 2.0;
- double clon = 2.0;
- int alatEnc = GeoEncodingUtils.encodeLatitude(alat);
- int alonEnc = GeoEncodingUtils.encodeLongitude(alon);
- int blatEnc = GeoEncodingUtils.encodeLatitude(blat);
- int blonEnc = GeoEncodingUtils.encodeLongitude(blon);
- int clatEnc = GeoEncodingUtils.encodeLatitude(clat);
- int clonEnc = GeoEncodingUtils.encodeLongitude(clon);
- verifyEncodingPermutations(alatEnc, alonEnc, blatEnc, blonEnc, clatEnc, clonEnc);
- byte[] b = new byte[7 * LatLonShape.BYTES];
- LatLonShape.encodeTriangle(b, alatEnc, alonEnc, blatEnc, blonEnc, clatEnc, clonEnc);
- int[] encoded = new int[6];
- LatLonShape.decodeTriangle(b, encoded);
- assertTrue(encoded[0] == alatEnc);
- assertTrue(encoded[1] == alonEnc);
- assertTrue(encoded[2] == blatEnc);
- assertTrue(encoded[3] == blonEnc);
- assertTrue(encoded[4] == clatEnc);
- assertTrue(encoded[5] == clonEnc);
+ @Override
+ protected int encodeY(double y) {
+ return GeoEncodingUtils.encodeLatitude(y);
}
- //Two shared point with MBR -> [MinLat, MinLon], [MaxLat, MaxLon], third point above
- public void testPolygonEncodingMinLatMinLonMaxLatMaxLonAbove() {
- double alat = 0.0;
- double alon = 0.0;
- double blat = 2.0;
- double blon = 2.0;
- double clat = 1.75;
- double clon = 1.25;
- int alatEnc = GeoEncodingUtils.encodeLatitude(alat);
- int alonEnc = GeoEncodingUtils.encodeLongitude(alon);
- int blatEnc = GeoEncodingUtils.encodeLatitude(blat);
- int blonEnc = GeoEncodingUtils.encodeLongitude(blon);
- int clatEnc = GeoEncodingUtils.encodeLatitude(clat);
- int clonEnc = GeoEncodingUtils.encodeLongitude(clon);
- verifyEncodingPermutations(alatEnc, alonEnc, blatEnc, blonEnc, clatEnc, clonEnc);
- byte[] b = new byte[7 * LatLonShape.BYTES];
- LatLonShape.encodeTriangle(b, alatEnc, alonEnc, blatEnc, blonEnc, clatEnc, clonEnc);
- int[] encoded = new int[6];
- LatLonShape.decodeTriangle(b, encoded);
- assertTrue(encoded[0] == alatEnc);
- assertTrue(encoded[1] == alonEnc);
- assertTrue(encoded[2] == blatEnc);
- assertTrue(encoded[3] == blonEnc);
- assertTrue(encoded[4] == clatEnc);
- assertTrue(encoded[5] == clonEnc);
+ @Override
+ protected double decodeX(int xEncoded) {
+ return GeoEncodingUtils.decodeLongitude(xEncoded);
}
- //Two shared point with MBR -> [MinLat, MaxLon], [MaxLat, MinLon], third point below
- public void testPolygonEncodingMinLatMaxLonMaxLatMinLonBelow() {
- double alat = 2.0;
- double alon = 0.0;
- double blat = 0.25;
- double blon = 0.75;
- double clat = 0.0;
- double clon = 2.0;
- int alatEnc = GeoEncodingUtils.encodeLatitude(alat);
- int alonEnc = GeoEncodingUtils.encodeLongitude(alon);
- int blatEnc = GeoEncodingUtils.encodeLatitude(blat);
- int blonEnc = GeoEncodingUtils.encodeLongitude(blon);
- int clatEnc = GeoEncodingUtils.encodeLatitude(clat);
- int clonEnc = GeoEncodingUtils.encodeLongitude(clon);
- verifyEncodingPermutations(alatEnc, alonEnc, blatEnc, blonEnc, clatEnc, clonEnc);
- byte[] b = new byte[7 * LatLonShape.BYTES];
- LatLonShape.encodeTriangle(b, alatEnc, alonEnc, blatEnc, blonEnc, clatEnc, clonEnc);
- int[] encoded = new int[6];
- LatLonShape.decodeTriangle(b, encoded);
- assertTrue(encoded[0] == alatEnc);
- assertTrue(encoded[1] == alonEnc);
- assertTrue(encoded[2] == blatEnc);
- assertTrue(encoded[3] == blonEnc);
- assertTrue(encoded[4] == clatEnc);
- assertTrue(encoded[5] == clonEnc);
+ @Override
+ protected double decodeY(int yEncoded) {
+ return GeoEncodingUtils.decodeLatitude(yEncoded);
}
- //Two shared point with MBR -> [MinLat, MaxLon], [MaxLat, MinLon], third point above
- public void testPolygonEncodingMinLatMaxLonMaxLatMinLonAbove() {
- double alat = 2.0;
- double alon = 0.0;
- double blat = 0.0;
- double blon = 2.0;
- double clat = 1.75;
- double clon = 1.25;
- int alatEnc = GeoEncodingUtils.encodeLatitude(alat);
- int alonEnc = GeoEncodingUtils.encodeLongitude(alon);
- int blatEnc = GeoEncodingUtils.encodeLatitude(blat);
- int blonEnc = GeoEncodingUtils.encodeLongitude(blon);
- int clatEnc = GeoEncodingUtils.encodeLatitude(clat);
- int clonEnc = GeoEncodingUtils.encodeLongitude(clon);
- verifyEncodingPermutations(alatEnc, alonEnc, blatEnc, blonEnc, clatEnc, clonEnc);
- byte[] b = new byte[7 * LatLonShape.BYTES];
- LatLonShape.encodeTriangle(b, alatEnc, alonEnc, blatEnc, blonEnc, clatEnc, clonEnc);
- int[] encoded = new int[6];
- LatLonShape.decodeTriangle(b, encoded);
- assertTrue(encoded[0] == alatEnc);
- assertTrue(encoded[1] == alonEnc);
- assertTrue(encoded[2] == blatEnc);
- assertTrue(encoded[3] == blonEnc);
- assertTrue(encoded[4] == clatEnc);
- assertTrue(encoded[5] == clonEnc);
+ @Override
+ protected double nextX() {
+ return GeoTestUtil.nextLongitude();
}
- //all points shared with MBR
- public void testPolygonEncodingAllSharedAbove() {
- double alat = 0.0;
- double alon = 0.0;
- double blat = 0.0;
- double blon = 2.0;
- double clat = 2.0;
- double clon = 2.0;
- int alatEnc = GeoEncodingUtils.encodeLatitude(alat);
- int alonEnc = GeoEncodingUtils.encodeLongitude(alon);
- int blatEnc = GeoEncodingUtils.encodeLatitude(blat);
- int blonEnc = GeoEncodingUtils.encodeLongitude(blon);
- int clatEnc = GeoEncodingUtils.encodeLatitude(clat);
- int clonEnc = GeoEncodingUtils.encodeLongitude(clon);
- verifyEncodingPermutations(alatEnc, alonEnc, blatEnc, blonEnc, clatEnc, clonEnc);
- byte[] b = new byte[7 * LatLonShape.BYTES];
- LatLonShape.encodeTriangle(b, alatEnc, alonEnc, blatEnc, blonEnc, clatEnc, clonEnc);
- int[] encoded = new int[6];
- LatLonShape.decodeTriangle(b, encoded);
- assertTrue(encoded[0] == alatEnc);
- assertTrue(encoded[1] == alonEnc);
- assertTrue(encoded[2] == blatEnc);
- assertTrue(encoded[3] == blonEnc);
- assertTrue(encoded[4] == clatEnc);
- assertTrue(encoded[5] == clonEnc);
+ @Override
+ protected double nextY() {
+ return GeoTestUtil.nextLatitude();
}
- //all points shared with MBR
- public void testPolygonEncodingAllSharedBelow() {
- double alat = 2.0;
- double alon = 0.0;
- double blat = 0.0;
- double blon = 0.0;
- double clat = 2.0;
- double clon = 2.0;
- int alatEnc = GeoEncodingUtils.encodeLatitude(alat);
- int alonEnc = GeoEncodingUtils.encodeLongitude(alon);
- int blatEnc = GeoEncodingUtils.encodeLatitude(blat);
- int blonEnc = GeoEncodingUtils.encodeLongitude(blon);
- int clatEnc = GeoEncodingUtils.encodeLatitude(clat);
- int clonEnc = GeoEncodingUtils.encodeLongitude(clon);
- byte[] b = new byte[7 * LatLonShape.BYTES];
- LatLonShape.encodeTriangle(b, alatEnc, alonEnc, blatEnc, blonEnc, clatEnc, clonEnc);
- int[] encoded = new int[6];
- LatLonShape.decodeTriangle(b, encoded);
- assertTrue(encoded[0] == alatEnc);
- assertTrue(encoded[1] == alonEnc);
- assertTrue(encoded[2] == blatEnc);
- assertTrue(encoded[3] == blonEnc);
- assertTrue(encoded[4] == clatEnc);
- assertTrue(encoded[5] == clonEnc);
- }
-
- //[a,b,c] == [c,a,b] == [b,c,a] == [c,b,a] == [b,a,c] == [a,c,b]
- public void verifyEncodingPermutations(int alatEnc, int alonEnc, int blatEnc, int blonEnc, int clatEnc, int clonEnc) {
- //this is only valid when points are not co-planar
- assertTrue(GeoUtils.orient(alatEnc, alonEnc, blatEnc, blonEnc, clatEnc, clonEnc) != 0);
- byte[] b = new byte[7 * LatLonShape.BYTES];
- //[a,b,c]
- LatLonShape.encodeTriangle(b, alatEnc, alonEnc, blatEnc, blonEnc, clatEnc, clonEnc);
- int[] encodedABC = new int[6];
- LatLonShape.decodeTriangle(b, encodedABC);
- //[c,a,b]
- LatLonShape.encodeTriangle(b, clatEnc, clonEnc, alatEnc, alonEnc, blatEnc, blonEnc);
- int[] encodedCAB = new int[6];
- LatLonShape.decodeTriangle(b, encodedCAB);
- assertTrue(Arrays.equals(encodedABC, encodedCAB));
- //[b,c,a]
- LatLonShape.encodeTriangle(b, blatEnc, blonEnc, clatEnc, clonEnc, alatEnc, alonEnc);
- int[] encodedBCA = new int[6];
- LatLonShape.decodeTriangle(b, encodedBCA);
- assertTrue(Arrays.equals(encodedABC, encodedBCA));
- //[c,b,a]
- LatLonShape.encodeTriangle(b, clatEnc, clonEnc, blatEnc, blonEnc, alatEnc, alonEnc);
- int[] encodedCBA= new int[6];
- LatLonShape.decodeTriangle(b, encodedCBA);
- assertTrue(Arrays.equals(encodedABC, encodedCBA));
- //[b,a,c]
- LatLonShape.encodeTriangle(b, blatEnc, blonEnc, alatEnc, alonEnc, clatEnc, clonEnc);
- int[] encodedBAC= new int[6];
- LatLonShape.decodeTriangle(b, encodedBAC);
- assertTrue(Arrays.equals(encodedABC, encodedBAC));
- //[a,c,b]
- LatLonShape.encodeTriangle(b, alatEnc, alonEnc, clatEnc, clonEnc, blatEnc, blonEnc);
- int[] encodedACB= new int[6];
- LatLonShape.decodeTriangle(b, encodedACB);
- assertTrue(Arrays.equals(encodedABC, encodedACB));
- }
-
- public void testPointEncoding() {
- double lat = 45.0;
- double lon = 45.0;
- int latEnc = GeoEncodingUtils.encodeLatitude(lat);
- int lonEnc = GeoEncodingUtils.encodeLongitude(lon);
- byte[] b = new byte[7 * LatLonShape.BYTES];
- LatLonShape.encodeTriangle(b, latEnc, lonEnc, latEnc, lonEnc, latEnc, lonEnc);
- int[] encoded = new int[6];
- LatLonShape.decodeTriangle(b, encoded);
- assertTrue(encoded[0] == latEnc && encoded[2] == latEnc && encoded[4] == latEnc);
- assertTrue(encoded[1] == lonEnc && encoded[3] == lonEnc && encoded[5] == lonEnc);
- }
-
- public void testLineEncodingSameLat() {
- double lat = 2.0;
- double alon = 0.0;
- double blon = 2.0;
- int latEnc = GeoEncodingUtils.encodeLatitude(lat);
- int alonEnc = GeoEncodingUtils.encodeLongitude(alon);
- int blonEnc = GeoEncodingUtils.encodeLongitude(blon);
- byte[] b = new byte[7 * LatLonShape.BYTES];
- LatLonShape.encodeTriangle(b, latEnc, alonEnc, latEnc, blonEnc, latEnc, alonEnc);
- int[] encoded = new int[6];
- LatLonShape.decodeTriangle(b, encoded);
- assertTrue(encoded[0] == latEnc);
- assertTrue(encoded[1] == alonEnc);
- assertTrue(encoded[2] == latEnc);
- assertTrue(encoded[3] == blonEnc);
- assertTrue(encoded[4] == latEnc);
- assertTrue(encoded[5] == alonEnc);
- LatLonShape.encodeTriangle(b, latEnc, alonEnc, latEnc, alonEnc, latEnc, blonEnc);
- encoded = new int[6];
- LatLonShape.decodeTriangle(b, encoded);
- assertTrue(encoded[0] == latEnc);
- assertTrue(encoded[1] == alonEnc);
- assertTrue(encoded[2] == latEnc);
- assertTrue(encoded[3] == alonEnc);
- assertTrue(encoded[4] == latEnc);
- assertTrue(encoded[5] == blonEnc);
- LatLonShape.encodeTriangle(b, latEnc, blonEnc, latEnc, alonEnc, latEnc, alonEnc);
- encoded = new int[6];
- LatLonShape.decodeTriangle(b, encoded);
- assertTrue(encoded[0] == latEnc);
- assertTrue(encoded[1] == alonEnc);
- assertTrue(encoded[2] == latEnc);
- assertTrue(encoded[3] == blonEnc);
- assertTrue(encoded[4] == latEnc);
- assertTrue(encoded[5] == alonEnc);
- }
-
- public void testLineEncodingSameLon() {
- double alat = 0.0;
- double blat = 2.0;
- double lon = 2.0;
- int alatEnc = GeoEncodingUtils.encodeLatitude(alat);
- int blatEnc = GeoEncodingUtils.encodeLatitude(blat);
- int lonEnc = GeoEncodingUtils.encodeLongitude(lon);
- byte[] b = new byte[7 * LatLonShape.BYTES];
- LatLonShape.encodeTriangle(b, alatEnc, lonEnc, blatEnc, lonEnc, alatEnc, lonEnc);
- int[] encoded = new int[6];
- LatLonShape.decodeTriangle(b, encoded);
- assertTrue(encoded[0] == alatEnc);
- assertTrue(encoded[1] == lonEnc);
- assertTrue(encoded[2] == blatEnc);
- assertTrue(encoded[3] == lonEnc);
- assertTrue(encoded[4] == alatEnc);
- assertTrue(encoded[5] == lonEnc);
- LatLonShape.encodeTriangle(b, alatEnc, lonEnc, alatEnc, lonEnc, blatEnc, lonEnc);
- encoded = new int[6];
- LatLonShape.decodeTriangle(b, encoded);
- assertTrue(encoded[0] == alatEnc);
- assertTrue(encoded[1] == lonEnc);
- assertTrue(encoded[2] == alatEnc);
- assertTrue(encoded[3] == lonEnc);
- assertTrue(encoded[4] == blatEnc);
- assertTrue(encoded[5] == lonEnc);
- LatLonShape.encodeTriangle(b, blatEnc, lonEnc, alatEnc, lonEnc, alatEnc, lonEnc);
- encoded = new int[6];
- LatLonShape.decodeTriangle(b, encoded);
- assertTrue(encoded[0] == alatEnc);
- assertTrue(encoded[1] == lonEnc);
- assertTrue(encoded[2] == blatEnc);
- assertTrue(encoded[3] == lonEnc);
- assertTrue(encoded[4] == alatEnc);
- assertTrue(encoded[5] == lonEnc);
- }
-
- public void testLineEncoding() {
- double alat = 0.0;
- double blat = 2.0;
- double alon = 0.0;
- double blon = 2.0;
- int alatEnc = GeoEncodingUtils.encodeLatitude(alat);
- int blatEnc = GeoEncodingUtils.encodeLatitude(blat);
- int alonEnc = GeoEncodingUtils.encodeLongitude(alon);
- int blonEnc = GeoEncodingUtils.encodeLongitude(blon);
- byte[] b = new byte[7 * LatLonShape.BYTES];
- LatLonShape.encodeTriangle(b, alatEnc, alonEnc, blatEnc, blonEnc, alatEnc, alonEnc);
- int[] encoded = new int[6];
- LatLonShape.decodeTriangle(b, encoded);
- assertTrue(encoded[0] == alatEnc);
- assertTrue(encoded[1] == alonEnc);
- assertTrue(encoded[2] == blatEnc);
- assertTrue(encoded[3] == blonEnc);
- assertTrue(encoded[4] == alatEnc);
- assertTrue(encoded[5] == alonEnc);
- LatLonShape.encodeTriangle(b, alatEnc, alonEnc, alatEnc, alonEnc, blatEnc, blonEnc);
- encoded = new int[6];
- LatLonShape.decodeTriangle(b, encoded);
- assertTrue(encoded[0] == alatEnc);
- assertTrue(encoded[1] == alonEnc);
- assertTrue(encoded[2] == alatEnc);
- assertTrue(encoded[3] == alonEnc);
- assertTrue(encoded[4] == blatEnc);
- assertTrue(encoded[5] == blonEnc);
- LatLonShape.encodeTriangle(b, blatEnc, blonEnc, alatEnc, alonEnc, alatEnc, alonEnc);
- encoded = new int[6];
- LatLonShape.decodeTriangle(b, encoded);
- assertTrue(encoded[0] == alatEnc);
- assertTrue(encoded[1] == alonEnc);
- assertTrue(encoded[2] == blatEnc);
- assertTrue(encoded[3] == blonEnc);
- assertTrue(encoded[4] == alatEnc);
- assertTrue(encoded[5] == alonEnc);
- }
-
- public void testRandomPointEncoding() {
- double alat = GeoTestUtil.nextLatitude();
- double alon = GeoTestUtil.nextLongitude();
- verifyEncoding(alat, alon, alat, alon, alat, alon);
- }
-
- public void testRandomLineEncoding() {
- double alat = GeoTestUtil.nextLatitude();
- double alon = GeoTestUtil.nextLongitude();
- double blat = GeoTestUtil.nextLatitude();
- double blon = GeoTestUtil.nextLongitude();
- verifyEncoding(alat, alon, blat, blon, alat, alon);
- }
-
- public void testRandomPolygonEncoding() {
- double alat = GeoTestUtil.nextLatitude();
- double alon = GeoTestUtil.nextLongitude();
- double blat = GeoTestUtil.nextLatitude();
- double blon = GeoTestUtil.nextLongitude();
- double clat = GeoTestUtil.nextLatitude();
- double clon = GeoTestUtil.nextLongitude();
- verifyEncoding(alat, alon, blat, blon, clat, clon);
- }
-
- private void verifyEncoding(double alat, double alon, double blat, double blon, double clat, double clon) {
- int[] original = new int[]{GeoEncodingUtils.encodeLatitude(alat),
- GeoEncodingUtils.encodeLongitude(alon),
- GeoEncodingUtils.encodeLatitude(blat),
- GeoEncodingUtils.encodeLongitude(blon),
- GeoEncodingUtils.encodeLatitude(clat),
- GeoEncodingUtils.encodeLongitude(clon)};
-
- //quantize the triangle
- byte[] b = new byte[7 * LatLonShape.BYTES];
- LatLonShape.encodeTriangle(b, original[0], original[1], original[2], original[3], original[4], original[5]);
- int[] encoded = new int[6];
- LatLonShape.decodeTriangle(b, encoded);
- double[] encodedQuantize = new double[] {GeoEncodingUtils.decodeLatitude(encoded[0]),
- GeoEncodingUtils.decodeLongitude(encoded[1]),
- GeoEncodingUtils.decodeLatitude(encoded[2]),
- GeoEncodingUtils.decodeLongitude(encoded[3]),
- GeoEncodingUtils.decodeLatitude(encoded[4]),
- GeoEncodingUtils.decodeLongitude(encoded[5])};
-
- int orientation = GeoUtils.orient(original[1], original[0], original[3], original[2], original[5], original[4]);
- //quantize original
- double[] originalQuantize;
- //we need to change the orientation if CW
- if (orientation == -1) {
- originalQuantize = new double[] {GeoEncodingUtils.decodeLatitude(original[4]),
- GeoEncodingUtils.decodeLongitude(original[5]),
- GeoEncodingUtils.decodeLatitude(original[2]),
- GeoEncodingUtils.decodeLongitude(original[3]),
- GeoEncodingUtils.decodeLatitude(original[0]),
- GeoEncodingUtils.decodeLongitude(original[1])};
- } else {
- originalQuantize = new double[] {GeoEncodingUtils.decodeLatitude(original[0]),
- GeoEncodingUtils.decodeLongitude(original[1]),
- GeoEncodingUtils.decodeLatitude(original[2]),
- GeoEncodingUtils.decodeLongitude(original[3]),
- GeoEncodingUtils.decodeLatitude(original[4]),
- GeoEncodingUtils.decodeLongitude(original[5])};
- }
-
- for (int i =0; i < 100; i ++) {
- Polygon polygon = GeoTestUtil.nextPolygon();
- Polygon2D polygon2D = Polygon2D.create(polygon);
- PointValues.Relation originalRelation = polygon2D.relateTriangle(originalQuantize[1], originalQuantize[0], originalQuantize[3], originalQuantize[2], originalQuantize[5], originalQuantize[4]);
- PointValues.Relation encodedRelation = polygon2D.relateTriangle(encodedQuantize[1], encodedQuantize[0], encodedQuantize[3], encodedQuantize[2], encodedQuantize[5], encodedQuantize[4]);
- assertTrue(originalRelation == encodedRelation);
- }
+ @Override
+ protected Polygon nextPolygon() {
+ return GeoTestUtil.nextPolygon();
}
- public void testDegeneratedTriangle() {
- double alat = 1e-26d;
- double alon = 0.0d;
- double blat = -1.0d;
- double blon = 0.0d;
- double clat = 1.0d;
- double clon = 0.0d;
- int alatEnc = GeoEncodingUtils.encodeLatitude(alat);
- int alonEnc = GeoEncodingUtils.encodeLongitude(alon);
- int blatEnc = GeoEncodingUtils.encodeLatitude(blat);
- int blonEnc = GeoEncodingUtils.encodeLongitude(blon);
- int clatEnc = GeoEncodingUtils.encodeLatitude(clat);
- int clonEnc = GeoEncodingUtils.encodeLongitude(clon);
- byte[] b = new byte[7 * LatLonShape.BYTES];
- LatLonShape.encodeTriangle(b, alatEnc, alonEnc, blatEnc, blonEnc, clatEnc, clonEnc);
- int[] encoded = new int[6];
- LatLonShape.decodeTriangle(b, encoded);
- assertTrue(encoded[0] == blatEnc);
- assertTrue(encoded[1] == blonEnc);
- assertTrue(encoded[2] == clatEnc);
- assertTrue(encoded[3] == clonEnc);
- assertTrue(encoded[4] == alatEnc);
- assertTrue(encoded[5] == alonEnc);
+ @Override
+ protected Polygon2D createPolygon2D(Object polygon) {
+ return Polygon2D.create((Polygon)polygon);
}
}
diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestXYLineShapeQueries.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestXYLineShapeQueries.java
new file mode 100644
index 000000000000..c66b9d1e0792
--- /dev/null
+++ b/lucene/sandbox/src/test/org/apache/lucene/document/TestXYLineShapeQueries.java
@@ -0,0 +1,124 @@
+/*
+ * 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 com.carrotsearch.randomizedtesting.generators.RandomNumbers;
+import org.apache.lucene.document.ShapeField.QueryRelation;
+import org.apache.lucene.geo.EdgeTree;
+import org.apache.lucene.geo.Line2D;
+import org.apache.lucene.geo.ShapeTestUtil;
+import org.apache.lucene.geo.XYLine;
+import org.apache.lucene.geo.XYPolygon2D;
+import org.apache.lucene.geo.XYRectangle;
+import org.apache.lucene.geo.XYRectangle2D;
+import org.apache.lucene.index.PointValues.Relation;
+
+/** random cartesian bounding box, line, and polygon query tests for random generated cartesian {@link XYLine} types */
+public class TestXYLineShapeQueries extends BaseXYShapeTestCase {
+
+ @Override
+ protected ShapeType getShapeType() {
+ return ShapeType.LINE;
+ }
+
+ @Override
+ protected XYLine randomQueryLine(Object... shapes) {
+ if (random().nextInt(100) == 42) {
+ // we want to ensure some cross, so randomly generate lines that share vertices with the indexed point set
+ int maxBound = (int)Math.floor(shapes.length * 0.1d);
+ if (maxBound < 2) {
+ maxBound = shapes.length;
+ }
+ float[] x = new float[RandomNumbers.randomIntBetween(random(), 2, maxBound)];
+ float[] y = new float[x.length];
+ for (int i = 0, j = 0; j < x.length && i < shapes.length; ++i, ++j) {
+ XYLine l = (XYLine) (shapes[i]);
+ if (random().nextBoolean() && l != null) {
+ int v = random().nextInt(l.numPoints() - 1);
+ x[j] = (float)l.getX(v);
+ y[j] = (float)l.getY(v);
+ } else {
+ x[j] = (float)ShapeTestUtil.nextDouble();
+ y[j] = (float)ShapeTestUtil.nextDouble();
+ }
+ }
+ return new XYLine(x, y);
+ }
+ return nextLine();
+ }
+
+ @Override
+ protected Field[] createIndexableFields(String field, Object line) {
+ return XYShape.createIndexableFields(field, (XYLine)line);
+ }
+
+ @Override
+ protected Validator getValidator() {
+ return new LineValidator(this.ENCODER);
+ }
+
+ protected static class LineValidator extends Validator {
+ protected LineValidator(Encoder encoder) {
+ super(encoder);
+ }
+
+ @Override
+ public boolean testBBoxQuery(double minY, double maxY, double minX, double maxX, Object shape) {
+ XYLine line = (XYLine)shape;
+ XYRectangle2D rectangle2D = XYRectangle2D.create(new XYRectangle(minX, maxX, minY, maxY));
+ for (int i = 0, j = 1; j < line.numPoints(); ++i, ++j) {
+ int[] decoded = encoder.encodeDecodeTriangle(line.getX(i), line.getY(i), line.getX(j), line.getY(j), line.getX(i), line.getY(i));
+ if (queryRelation == QueryRelation.WITHIN) {
+ if (rectangle2D.containsTriangle(decoded[1], decoded[0], decoded[3], decoded[2], decoded[5], decoded[4]) == false) {
+ return false;
+ }
+ } else {
+ if (rectangle2D.intersectsTriangle(decoded[1], decoded[0], decoded[3], decoded[2], decoded[5], decoded[4]) == true) {
+ return queryRelation == QueryRelation.INTERSECTS;
+ }
+ }
+ }
+ return queryRelation != QueryRelation.INTERSECTS;
+ }
+
+ @Override
+ public boolean testLineQuery(Line2D line2d, Object shape) {
+ return testLine(line2d, (XYLine) shape);
+ }
+
+ @Override
+ public boolean testPolygonQuery(Object poly2d, Object shape) {
+ return testLine((XYPolygon2D)poly2d, (XYLine) shape);
+ }
+
+ private boolean testLine(EdgeTree queryPoly, XYLine line) {
+
+ for (int i = 0, j = 1; j < line.numPoints(); ++i, ++j) {
+ double[] qTriangle = encoder.quantizeTriangle(line.getX(i), line.getY(i), line.getX(j), line.getY(j), line.getX(i), line.getY(i));
+ Relation r = queryPoly.relateTriangle(qTriangle[1], qTriangle[0], qTriangle[3], qTriangle[2], qTriangle[5], qTriangle[4]);
+ if (queryRelation == QueryRelation.DISJOINT) {
+ if (r != Relation.CELL_OUTSIDE_QUERY) return false;
+ } else if (queryRelation == QueryRelation.WITHIN) {
+ if (r != Relation.CELL_INSIDE_QUERY) return false;
+ } else {
+ if (r != Relation.CELL_OUTSIDE_QUERY) return true;
+ }
+ }
+ return queryRelation == QueryRelation.INTERSECTS ? false : true;
+ }
+ }
+}
diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestXYMultiLineShapeQueries.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestXYMultiLineShapeQueries.java
new file mode 100644
index 000000000000..b2978e58da63
--- /dev/null
+++ b/lucene/sandbox/src/test/org/apache/lucene/document/TestXYMultiLineShapeQueries.java
@@ -0,0 +1,130 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.List;
+
+import org.apache.lucene.document.ShapeField.QueryRelation;
+import org.apache.lucene.geo.Line2D;
+import org.apache.lucene.geo.XYLine;
+
+/** random cartesian bounding box, line, and polygon query tests for random indexed arrays of cartesian {@link XYLine} types */
+public class TestXYMultiLineShapeQueries extends BaseXYShapeTestCase {
+ @Override
+ protected ShapeType getShapeType() {
+ return ShapeType.LINE;
+ }
+
+ @Override
+ protected XYLine[] nextShape() {
+ int n = random().nextInt(4) + 1;
+ XYLine[] lines = new XYLine[n];
+ for (int i =0; i < n; i++) {
+ lines[i] = nextLine();
+ }
+ return lines;
+ }
+
+ @Override
+ protected Field[] createIndexableFields(String name, Object o) {
+ XYLine[] lines = (XYLine[]) o;
+ List allFields = new ArrayList<>();
+ for (XYLine line : lines) {
+ Field[] fields = XYShape.createIndexableFields(name, line);
+ for (Field field : fields) {
+ allFields.add(field);
+ }
+ }
+ return allFields.toArray(new Field[allFields.size()]);
+ }
+
+ @Override
+ public Validator getValidator() {
+ return new MultiLineValidator(ENCODER);
+ }
+
+ protected class MultiLineValidator extends Validator {
+ TestXYLineShapeQueries.LineValidator LINEVALIDATOR;
+ MultiLineValidator(Encoder encoder) {
+ super(encoder);
+ LINEVALIDATOR = new TestXYLineShapeQueries.LineValidator(encoder);
+ }
+
+ @Override
+ public Validator setRelation(QueryRelation relation) {
+ super.setRelation(relation);
+ LINEVALIDATOR.queryRelation = relation;
+ return this;
+ }
+
+ @Override
+ public boolean testBBoxQuery(double minLat, double maxLat, double minLon, double maxLon, Object shape) {
+ XYLine[] lines = (XYLine[])shape;
+ for (XYLine l : lines) {
+ boolean b = LINEVALIDATOR.testBBoxQuery(minLat, maxLat, minLon, maxLon, l);
+ if (b == true && queryRelation == ShapeField.QueryRelation.INTERSECTS) {
+ return true;
+ } else if (b == false && queryRelation == ShapeField.QueryRelation.DISJOINT) {
+ return false;
+ } else if (b == false && queryRelation == ShapeField.QueryRelation.WITHIN) {
+ return false;
+ }
+ }
+ return queryRelation != ShapeField.QueryRelation.INTERSECTS;
+ }
+
+ @Override
+ public boolean testLineQuery(Line2D query, Object shape) {
+ XYLine[] lines = (XYLine[])shape;
+ for (XYLine l : lines) {
+ boolean b = LINEVALIDATOR.testLineQuery(query, l);
+ if (b == true && queryRelation == ShapeField.QueryRelation.INTERSECTS) {
+ return true;
+ } else if (b == false && queryRelation == ShapeField.QueryRelation.DISJOINT) {
+ return false;
+ } else if (b == false && queryRelation == ShapeField.QueryRelation.WITHIN) {
+ return false;
+ }
+ }
+ return queryRelation != ShapeField.QueryRelation.INTERSECTS;
+ }
+
+ @Override
+ public boolean testPolygonQuery(Object query, Object shape) {
+ XYLine[] lines = (XYLine[])shape;
+ for (XYLine l : lines) {
+ boolean b = LINEVALIDATOR.testPolygonQuery(query, l);
+ if (b == true && queryRelation == ShapeField.QueryRelation.INTERSECTS) {
+ return true;
+ } else if (b == false && queryRelation == ShapeField.QueryRelation.DISJOINT) {
+ return false;
+ } else if (b == false && queryRelation == ShapeField.QueryRelation.WITHIN) {
+ return false;
+ }
+ }
+ return queryRelation != ShapeField.QueryRelation.INTERSECTS;
+ }
+ }
+
+ @Slow
+ @Nightly
+ @Override
+ public void testRandomBig() throws Exception {
+ doTestRandom(10000);
+ }
+}
diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestXYMultiPointShapeQueries.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestXYMultiPointShapeQueries.java
new file mode 100644
index 000000000000..54de8abd5fab
--- /dev/null
+++ b/lucene/sandbox/src/test/org/apache/lucene/document/TestXYMultiPointShapeQueries.java
@@ -0,0 +1,129 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.List;
+
+import org.apache.lucene.document.ShapeField.QueryRelation;
+import org.apache.lucene.geo.Line2D;
+
+/** random cartesian bounding box, line, and polygon query tests for random indexed arrays of {@code x, y} points */
+public class TestXYMultiPointShapeQueries extends BaseXYShapeTestCase {
+ @Override
+ protected ShapeType getShapeType() {
+ return ShapeType.POINT;
+ }
+
+ @Override
+ protected Point[] nextShape() {
+ int n = random().nextInt(4) + 1;
+ Point[] points = new Point[n];
+ for (int i =0; i < n; i++) {
+ points[i] = (Point)ShapeType.POINT.nextShape();
+ }
+ return points;
+ }
+
+ @Override
+ protected Field[] createIndexableFields(String name, Object o) {
+ Point[] points = (Point[]) o;
+ List allFields = new ArrayList<>();
+ for (Point point : points) {
+ Field[] fields = XYShape.createIndexableFields(name, point.x, point.y);
+ for (Field field : fields) {
+ allFields.add(field);
+ }
+ }
+ return allFields.toArray(new Field[allFields.size()]);
+ }
+
+ @Override
+ public Validator getValidator() {
+ return new MultiPointValidator(ENCODER);
+ }
+
+ protected class MultiPointValidator extends Validator {
+ TestXYPointShapeQueries.PointValidator POINTVALIDATOR;
+ MultiPointValidator(Encoder encoder) {
+ super(encoder);
+ POINTVALIDATOR = new TestXYPointShapeQueries.PointValidator(encoder);
+ }
+
+ @Override
+ public Validator setRelation(QueryRelation relation) {
+ super.setRelation(relation);
+ POINTVALIDATOR.queryRelation = relation;
+ return this;
+ }
+
+ @Override
+ public boolean testBBoxQuery(double minLat, double maxLat, double minLon, double maxLon, Object shape) {
+ Point[] points = (Point[]) shape;
+ for (Point p : points) {
+ boolean b = POINTVALIDATOR.testBBoxQuery(minLat, maxLat, minLon, maxLon, p);
+ if (b == true && queryRelation == QueryRelation.INTERSECTS) {
+ return true;
+ } else if (b == false && queryRelation == QueryRelation.DISJOINT) {
+ return false;
+ } else if (b == false && queryRelation == QueryRelation.WITHIN) {
+ return false;
+ }
+ }
+ return queryRelation != QueryRelation.INTERSECTS;
+ }
+
+ @Override
+ public boolean testLineQuery(Line2D query, Object shape) {
+ Point[] points = (Point[]) shape;
+ for (Point p : points) {
+ boolean b = POINTVALIDATOR.testLineQuery(query, p);
+ if (b == true && queryRelation == QueryRelation.INTERSECTS) {
+ return true;
+ } else if (b == false && queryRelation == QueryRelation.DISJOINT) {
+ return false;
+ } else if (b == false && queryRelation == QueryRelation.WITHIN) {
+ return false;
+ }
+ }
+ return queryRelation != QueryRelation.INTERSECTS;
+ }
+
+ @Override
+ public boolean testPolygonQuery(Object query, Object shape) {
+ Point[] points = (Point[]) shape;
+ for (Point p : points) {
+ boolean b = POINTVALIDATOR.testPolygonQuery(query, p);
+ if (b == true && queryRelation == QueryRelation.INTERSECTS) {
+ return true;
+ } else if (b == false && queryRelation == QueryRelation.DISJOINT) {
+ return false;
+ } else if (b == false && queryRelation == QueryRelation.WITHIN) {
+ return false;
+ }
+ }
+ return queryRelation != QueryRelation.INTERSECTS;
+ }
+ }
+
+ @Slow
+ @Nightly
+ @Override
+ public void testRandomBig() throws Exception {
+ doTestRandom(10000);
+ }
+}
diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestXYMultiPolygonShapeQueries.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestXYMultiPolygonShapeQueries.java
new file mode 100644
index 000000000000..551919661020
--- /dev/null
+++ b/lucene/sandbox/src/test/org/apache/lucene/document/TestXYMultiPolygonShapeQueries.java
@@ -0,0 +1,142 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.List;
+
+import org.apache.lucene.document.ShapeField.QueryRelation;
+import org.apache.lucene.geo.Line2D;
+import org.apache.lucene.geo.Tessellator;
+import org.apache.lucene.geo.XYPolygon;
+
+/** random cartesian bounding box, line, and polygon query tests for random indexed arrays of cartesian {@link XYPolygon} types */
+public class TestXYMultiPolygonShapeQueries extends BaseXYShapeTestCase {
+ @Override
+ protected ShapeType getShapeType() {
+ return ShapeType.POLYGON;
+ }
+
+ @Override
+ protected XYPolygon[] nextShape() {
+
+ int n = random().nextInt(4) + 1;
+ XYPolygon[] polygons = new XYPolygon[n];
+ for (int i =0; i < n; i++) {
+ while (true) {
+ // if we can't tessellate; then random polygon generator created a malformed shape
+ XYPolygon p = (XYPolygon) getShapeType().nextShape();
+ try {
+ Tessellator.tessellate(p);
+ polygons[i] = p;
+ break;
+ } catch (IllegalArgumentException e) {
+ continue;
+ }
+ }
+ }
+ return polygons;
+ }
+
+ @Override
+ protected Field[] createIndexableFields(String name, Object o) {
+ XYPolygon[] polygons = (XYPolygon[]) o;
+ List allFields = new ArrayList<>();
+ for (XYPolygon polygon : polygons) {
+ Field[] fields = XYShape.createIndexableFields(name, polygon);
+ for (Field field : fields) {
+ allFields.add(field);
+ }
+ }
+ return allFields.toArray(new Field[allFields.size()]);
+ }
+
+ @Override
+ protected Validator getValidator() {
+ return new MultiPolygonValidator(ENCODER);
+ }
+
+ protected class MultiPolygonValidator extends Validator {
+ TestXYPolygonShapeQueries.PolygonValidator POLYGONVALIDATOR;
+ MultiPolygonValidator(Encoder encoder) {
+ super(encoder);
+ POLYGONVALIDATOR = new TestXYPolygonShapeQueries.PolygonValidator(encoder);
+ }
+
+ @Override
+ public Validator setRelation(QueryRelation relation) {
+ super.setRelation(relation);
+ POLYGONVALIDATOR.queryRelation = relation;
+ return this;
+ }
+
+ @Override
+ public boolean testBBoxQuery(double minLat, double maxLat, double minLon, double maxLon, Object shape) {
+ XYPolygon[] polygons = (XYPolygon[])shape;
+ for (XYPolygon p : polygons) {
+ boolean b = POLYGONVALIDATOR.testBBoxQuery(minLat, maxLat, minLon, maxLon, p);
+ if (b == true && queryRelation == QueryRelation.INTERSECTS) {
+ return true;
+ } else if (b == false && queryRelation == QueryRelation.DISJOINT) {
+ return false;
+ } else if (b == false && queryRelation == QueryRelation.WITHIN) {
+ return false;
+ }
+ }
+ return queryRelation != QueryRelation.INTERSECTS;
+ }
+
+ @Override
+ public boolean testLineQuery(Line2D query, Object shape) {
+ XYPolygon[] polygons = (XYPolygon[])shape;
+ for (XYPolygon p : polygons) {
+ boolean b = POLYGONVALIDATOR.testLineQuery(query, p);
+ if (b == true && queryRelation == QueryRelation.INTERSECTS) {
+ return true;
+ } else if (b == false && queryRelation == QueryRelation.DISJOINT) {
+ return false;
+ } else if (b == false && queryRelation == QueryRelation.WITHIN) {
+ return false;
+ }
+ }
+ return queryRelation != QueryRelation.INTERSECTS;
+ }
+
+ @Override
+ public boolean testPolygonQuery(Object query, Object shape) {
+ XYPolygon[] polygons = (XYPolygon[])shape;
+ for (XYPolygon p : polygons) {
+ boolean b = POLYGONVALIDATOR.testPolygonQuery(query, p);
+ if (b == true && queryRelation == QueryRelation.INTERSECTS) {
+ return true;
+ } else if (b == false && queryRelation == QueryRelation.DISJOINT) {
+ return false;
+ } else if (b == false && queryRelation == QueryRelation.WITHIN) {
+ return false;
+ }
+ }
+ return queryRelation != QueryRelation.INTERSECTS;
+ }
+ }
+
+ @Slow
+ @Nightly
+ @Override
+ public void testRandomBig() throws Exception {
+ doTestRandom(10000);
+ }
+}
diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestXYPointShapeQueries.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestXYPointShapeQueries.java
new file mode 100644
index 000000000000..edeefa54a59a
--- /dev/null
+++ b/lucene/sandbox/src/test/org/apache/lucene/document/TestXYPointShapeQueries.java
@@ -0,0 +1,116 @@
+/*
+ * 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 com.carrotsearch.randomizedtesting.generators.RandomNumbers;
+import org.apache.lucene.document.ShapeField.QueryRelation;
+import org.apache.lucene.geo.EdgeTree;
+import org.apache.lucene.geo.Line2D;
+import org.apache.lucene.geo.ShapeTestUtil;
+import org.apache.lucene.geo.XYLine;
+import org.apache.lucene.geo.XYPolygon2D;
+import org.apache.lucene.index.PointValues.Relation;
+
+/** random cartesian bounding box, line, and polygon query tests for random generated {@code x, y} points */
+public class TestXYPointShapeQueries extends BaseXYShapeTestCase {
+
+ @Override
+ protected ShapeType getShapeType() {
+ return ShapeType.POINT;
+ }
+
+ @Override
+ protected XYLine randomQueryLine(Object... shapes) {
+ if (random().nextInt(100) == 42) {
+ // we want to ensure some cross, so randomly generate lines that share vertices with the indexed point set
+ int maxBound = (int)Math.floor(shapes.length * 0.1d);
+ if (maxBound < 2) {
+ maxBound = shapes.length;
+ }
+ float[] x = new float[RandomNumbers.randomIntBetween(random(), 2, maxBound)];
+ float[] y = new float[x.length];
+ for (int i = 0, j = 0; j < x.length && i < shapes.length; ++i, ++j) {
+ Point p = (Point) (shapes[i]);
+ if (random().nextBoolean() && p != null) {
+ x[j] = p.x;
+ y[j] = p.y;
+ } else {
+ x[j] = (float)ShapeTestUtil.nextDouble();
+ y[j] = (float)ShapeTestUtil.nextDouble();
+ }
+ }
+ return new XYLine(x, y);
+ }
+ return nextLine();
+ }
+
+ @Override
+ protected Field[] createIndexableFields(String field, Object point) {
+ Point p = (Point)point;
+ return XYShape.createIndexableFields(field, p.x, p.y);
+ }
+
+ @Override
+ protected Validator getValidator() {
+ return new PointValidator(this.ENCODER);
+ }
+
+ protected static class PointValidator extends Validator {
+ protected PointValidator(Encoder encoder) {
+ super(encoder);
+ }
+
+ @Override
+ public boolean testBBoxQuery(double minLat, double maxLat, double minLon, double maxLon, Object shape) {
+ Point p = (Point)shape;
+ double lat = encoder.quantizeY(p.y);
+ double lon = encoder.quantizeX(p.x);
+ boolean isDisjoint = lat < minLat || lat > maxLat;
+
+ isDisjoint = isDisjoint || ((minLon > maxLon)
+ ? lon < minLon && lon > maxLon
+ : lon < minLon || lon > maxLon);
+ if (queryRelation == QueryRelation.DISJOINT) {
+ return isDisjoint;
+ }
+ return isDisjoint == false;
+ }
+
+ @Override
+ public boolean testLineQuery(Line2D line2d, Object shape) {
+ return testPoint(line2d, (Point) shape);
+ }
+
+ @Override
+ public boolean testPolygonQuery(Object poly2d, Object shape) {
+ return testPoint((XYPolygon2D)poly2d, (Point) shape);
+ }
+
+ private boolean testPoint(EdgeTree tree, Point p) {
+ double lat = encoder.quantizeY(p.y);
+ double lon = encoder.quantizeX(p.x);
+ // for consistency w/ the query we test the point as a triangle
+ Relation r = tree.relateTriangle(lon, lat, lon, lat, lon, lat);
+ if (queryRelation == QueryRelation.WITHIN) {
+ return r == Relation.CELL_INSIDE_QUERY;
+ } else if (queryRelation == QueryRelation.DISJOINT) {
+ return r == Relation.CELL_OUTSIDE_QUERY;
+ }
+ return r != Relation.CELL_OUTSIDE_QUERY;
+ }
+ }
+}
diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestXYPolygonShapeQueries.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestXYPolygonShapeQueries.java
new file mode 100644
index 000000000000..82d887aa2582
--- /dev/null
+++ b/lucene/sandbox/src/test/org/apache/lucene/document/TestXYPolygonShapeQueries.java
@@ -0,0 +1,122 @@
+/*
+ * 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 java.util.List;
+
+import org.apache.lucene.document.ShapeField.QueryRelation;
+import org.apache.lucene.geo.EdgeTree;
+import org.apache.lucene.geo.Line2D;
+import org.apache.lucene.geo.Tessellator;
+import org.apache.lucene.geo.XYPolygon;
+import org.apache.lucene.geo.XYPolygon2D;
+import org.apache.lucene.geo.XYRectangle;
+import org.apache.lucene.geo.XYRectangle2D;
+import org.apache.lucene.index.PointValues.Relation;
+
+/** random cartesian bounding box, line, and polygon query tests for random indexed {@link XYPolygon} types */
+public class TestXYPolygonShapeQueries extends BaseXYShapeTestCase {
+
+ @Override
+ protected ShapeType getShapeType() {
+ return ShapeType.POLYGON;
+ }
+
+ @Override
+ protected XYPolygon nextShape() {
+ XYPolygon p;
+ while (true) {
+ // if we can't tessellate; then random polygon generator created a malformed shape
+ p = (XYPolygon)getShapeType().nextShape();
+ try {
+ Tessellator.tessellate(p);
+ return p;
+ } catch (IllegalArgumentException e) {
+ continue;
+ }
+ }
+ }
+
+ @Override
+ protected Field[] createIndexableFields(String field, Object polygon) {
+ return XYShape.createIndexableFields(field, (XYPolygon)polygon);
+ }
+
+ @Override
+ protected Validator getValidator() {
+ return new PolygonValidator(this.ENCODER);
+ }
+
+ protected static class PolygonValidator extends Validator {
+ protected PolygonValidator(Encoder encoder) {
+ super(encoder);
+ }
+
+ @Override
+ public boolean testBBoxQuery(double minY, double maxY, double minX, double maxX, Object shape) {
+ XYPolygon p = (XYPolygon)shape;
+ XYRectangle2D rectangle2D = XYRectangle2D.create(new XYRectangle(minX, maxX, minY, maxY));
+ List tessellation = Tessellator.tessellate(p);
+ for (Tessellator.Triangle t : tessellation) {
+ int[] decoded = encoder.encodeDecodeTriangle(t.getX(0), t.getY(0), t.getX(1), t.getY(1), t.getX(2), t.getY(2));
+ if (queryRelation == QueryRelation.WITHIN) {
+ if (rectangle2D.containsTriangle(decoded[1], decoded[0], decoded[3], decoded[2], decoded[5], decoded[4]) == false) {
+ return false;
+ }
+ } else {
+ if (rectangle2D.intersectsTriangle(decoded[1], decoded[0], decoded[3], decoded[2], decoded[5], decoded[4]) == true) {
+ return queryRelation == QueryRelation.INTERSECTS;
+ }
+ }
+ }
+ return queryRelation != QueryRelation.INTERSECTS;
+ }
+
+ @Override
+ public boolean testLineQuery(Line2D query, Object shape) {
+ return testPolygon(query, (XYPolygon) shape);
+ }
+
+ @Override
+ public boolean testPolygonQuery(Object query, Object shape) {
+ return testPolygon((XYPolygon2D)query, (XYPolygon) shape);
+ }
+
+ private boolean testPolygon(EdgeTree tree, XYPolygon shape) {
+ List tessellation = Tessellator.tessellate(shape);
+ for (Tessellator.Triangle t : tessellation) {
+ double[] qTriangle = encoder.quantizeTriangle(t.getX(0), t.getY(0), t.getX(1), t.getY(1), t.getX(2), t.getY(2));
+ Relation r = tree.relateTriangle(qTriangle[1], qTriangle[0], qTriangle[3], qTriangle[2], qTriangle[5], qTriangle[4]);
+ if (queryRelation == QueryRelation.DISJOINT) {
+ if (r != Relation.CELL_OUTSIDE_QUERY) return false;
+ } else if (queryRelation == QueryRelation.WITHIN) {
+ if (r != Relation.CELL_INSIDE_QUERY) return false;
+ } else {
+ if (r != Relation.CELL_OUTSIDE_QUERY) return true;
+ }
+ }
+ return queryRelation == QueryRelation.INTERSECTS ? false : true;
+ }
+ }
+
+ @Nightly
+ @Override
+ public void testRandomBig() throws Exception {
+ doTestRandom(25000);
+ }
+
+}
diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestXYShape.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestXYShape.java
new file mode 100644
index 000000000000..1fc1a79282eb
--- /dev/null
+++ b/lucene/sandbox/src/test/org/apache/lucene/document/TestXYShape.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.lucene.document;
+
+import org.apache.lucene.document.ShapeField.QueryRelation;
+import org.apache.lucene.geo.ShapeTestUtil;
+import org.apache.lucene.geo.XYLine;
+import org.apache.lucene.geo.XYPolygon;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.RandomIndexWriter;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.util.IOUtils;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.lucene.util.TestUtil;
+
+/** Test case for indexing cartesian shapes and search by bounding box, lines, and polygons */
+public class TestXYShape extends LuceneTestCase {
+
+ protected static String FIELDNAME = "field";
+ protected static void addPolygonsToDoc(String field, Document doc, XYPolygon polygon) {
+ Field[] fields = XYShape.createIndexableFields(field, polygon);
+ for (Field f : fields) {
+ doc.add(f);
+ }
+ }
+
+ protected static void addLineToDoc(String field, Document doc, XYLine line) {
+ Field[] fields = XYShape.createIndexableFields(field, line);
+ for (Field f : fields) {
+ doc.add(f);
+ }
+ }
+
+ protected Query newRectQuery(String field, double minX, double maxX, double minY, double maxY) {
+ return XYShape.newBoxQuery(field, QueryRelation.INTERSECTS, (float)minX, (float)maxX, (float)minY, (float)maxY);
+ }
+
+ /** test we can search for a point with a standard number of vertices*/
+ public void testBasicIntersects() throws Exception {
+ int numVertices = TestUtil.nextInt(random(), 50, 100);
+ Directory dir = newDirectory();
+ RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+
+ // add a random polygon document
+ XYPolygon p = ShapeTestUtil.createRegularPolygon(0, 90, atLeast(1000000), numVertices);
+ Document document = new Document();
+ addPolygonsToDoc(FIELDNAME, document, p);
+ writer.addDocument(document);
+
+ // add a line document
+ document = new Document();
+ // add a line string
+ float x[] = new float[p.numPoints() - 1];
+ float y[] = new float[p.numPoints() - 1];
+ for (int i = 0; i < x.length; ++i) {
+ x[i] = (float)p.getPolyX(i);
+ y[i] = (float)p.getPolyY(i);
+ }
+ XYLine l = new XYLine(x, y);
+ addLineToDoc(FIELDNAME, document, l);
+ writer.addDocument(document);
+
+ ////// search /////
+ // search an intersecting bbox
+ IndexReader reader = writer.getReader();
+ writer.close();
+ IndexSearcher searcher = newSearcher(reader);
+ double minX = Math.min(x[0], x[1]);
+ double minY = Math.min(y[0], y[1]);
+ double maxX = Math.max(x[0], x[1]);
+ double maxY = Math.max(y[0], y[1]);
+ Query q = newRectQuery(FIELDNAME, minX, maxX, minY, maxY);
+ assertEquals(2, searcher.count(q));
+
+ // search a disjoint bbox
+ q = newRectQuery(FIELDNAME, p.minX-1d, p.minX+1, p.minY-1d, p.minY+1d);
+ assertEquals(0, searcher.count(q));
+
+ // search w/ an intersecting polygon
+ q = XYShape.newPolygonQuery(FIELDNAME, QueryRelation.INTERSECTS, new XYPolygon(
+ new float[] {(float)minX, (float)minX, (float)maxX, (float)maxX, (float)minX},
+ new float[] {(float)minY, (float)maxY, (float)maxY, (float)minY, (float)minY}
+ ));
+ assertEquals(2, searcher.count(q));
+
+ // search w/ an intersecting line
+ q = XYShape.newLineQuery(FIELDNAME, QueryRelation.INTERSECTS, new XYLine(
+ new float[] {(float)minX, (float)minX, (float)maxX, (float)maxX},
+ new float[] {(float)minY, (float)maxY, (float)maxY, (float)minY}
+ ));
+ assertEquals(2, searcher.count(q));
+
+ IOUtils.close(reader, dir);
+ }
+}
diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestXYShapeEncoding.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestXYShapeEncoding.java
new file mode 100644
index 000000000000..62d53924e1b9
--- /dev/null
+++ b/lucene/sandbox/src/test/org/apache/lucene/document/TestXYShapeEncoding.java
@@ -0,0 +1,65 @@
+/*
+ * 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.ShapeTestUtil;
+import org.apache.lucene.geo.XYEncodingUtils;
+import org.apache.lucene.geo.XYPolygon;
+import org.apache.lucene.geo.XYPolygon2D;
+
+/** tests XYShape encoding */
+public class TestXYShapeEncoding extends BaseShapeEncodingTestCase {
+ @Override
+ protected int encodeX(double x) {
+ return XYEncodingUtils.encode(x);
+ }
+
+ @Override
+ protected int encodeY(double y) {
+ return XYEncodingUtils.encode(y);
+ }
+
+ @Override
+ protected double decodeX(int xEncoded) {
+ return XYEncodingUtils.decode(xEncoded);
+ }
+
+ @Override
+ protected double decodeY(int yEncoded) {
+ return XYEncodingUtils.decode(yEncoded);
+ }
+
+ @Override
+ protected double nextX() {
+ return ShapeTestUtil.nextDouble();
+ }
+
+ @Override
+ protected double nextY() {
+ return ShapeTestUtil.nextDouble();
+ }
+
+ @Override
+ protected XYPolygon nextPolygon() {
+ return ShapeTestUtil.nextPolygon();
+ }
+
+ @Override
+ protected XYPolygon2D createPolygon2D(Object polygon) {
+ return XYPolygon2D.create((XYPolygon)polygon);
+ }
+}
diff --git a/lucene/sandbox/src/test/org/apache/lucene/geo/ShapeTestUtil.java b/lucene/sandbox/src/test/org/apache/lucene/geo/ShapeTestUtil.java
new file mode 100644
index 000000000000..f7e304bc5200
--- /dev/null
+++ b/lucene/sandbox/src/test/org/apache/lucene/geo/ShapeTestUtil.java
@@ -0,0 +1,208 @@
+/*
+ * 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.Random;
+
+import com.carrotsearch.randomizedtesting.RandomizedContext;
+import com.carrotsearch.randomizedtesting.generators.BiasedNumbers;
+import org.apache.lucene.util.SloppyMath;
+import org.apache.lucene.util.TestUtil;
+
+/** generates random cartesian geometry; heavy reuse of {@link GeoTestUtil} */
+public class ShapeTestUtil {
+
+ /** returns next pseudorandom polygon */
+ public static XYPolygon nextPolygon() {
+ if (random().nextBoolean()) {
+ return surpriseMePolygon();
+ } else if (random().nextInt(10) == 1) {
+ // this poly is slow to create ... only do it 10% of the time:
+ while (true) {
+ int gons = TestUtil.nextInt(random(), 4, 500);
+ // So the poly can cover at most 50% of the earth's surface:
+ double radius = random().nextDouble() * 0.5 * Float.MAX_VALUE + 1.0;
+ try {
+ return createRegularPolygon(nextDouble(), nextDouble(), radius, gons);
+ } catch (IllegalArgumentException iae) {
+ // we tried to cross dateline or pole ... try again
+ }
+ }
+ }
+
+ XYRectangle box = nextBoxInternal();
+ if (random().nextBoolean()) {
+ // box
+ return boxPolygon(box);
+ } else {
+ // triangle
+ return trianglePolygon(box);
+ }
+ }
+
+ private static XYPolygon trianglePolygon(XYRectangle box) {
+ final float[] polyX = new float[4];
+ final float[] polyY = new float[4];
+ polyX[0] = (float)box.minX;
+ polyY[0] = (float)box.minY;
+ polyX[1] = (float)box.minX;
+ polyY[1] = (float)box.minY;
+ polyX[2] = (float)box.minX;
+ polyY[2] = (float)box.minY;
+ polyX[3] = (float)box.minX;
+ polyY[3] = (float)box.minY;
+ return new XYPolygon(polyX, polyY);
+ }
+
+ public static XYRectangle nextBox() {
+ return nextBoxInternal();
+ }
+
+ private static XYRectangle nextBoxInternal() {
+ // prevent lines instead of boxes
+ double x0 = nextDouble();
+ double x1 = nextDouble();
+ while (x0 == x1) {
+ x1 = nextDouble();
+ }
+ // prevent lines instead of boxes
+ double y0 = nextDouble();
+ double y1 = nextDouble();
+ while (y0 == y1) {
+ y1 = nextDouble();
+ }
+
+ if (x1 < x0) {
+ double x = x0;
+ x0 = x1;
+ x1 = x;
+ }
+
+ if (y1 < y0) {
+ double y = y0;
+ y0 = y1;
+ y1 = y;
+ }
+
+ return new XYRectangle(x0, x1, y0, y1);
+ }
+
+ private static XYPolygon boxPolygon(XYRectangle box) {
+ final float[] polyX = new float[5];
+ final float[] polyY = new float[5];
+ polyX[0] = (float)box.minX;
+ polyY[0] = (float)box.minY;
+ polyX[1] = (float)box.minX;
+ polyY[1] = (float)box.minY;
+ polyX[2] = (float)box.minX;
+ polyY[2] = (float)box.minY;
+ polyX[3] = (float)box.minX;
+ polyY[3] = (float)box.minY;
+ polyX[4] = (float)box.minX;
+ polyY[4] = (float)box.minY;
+ return new XYPolygon(polyX, polyY);
+ }
+
+ private static XYPolygon surpriseMePolygon() {
+ // repeat until we get a poly that doesn't cross dateline:
+ while (true) {
+ //System.out.println("\nPOLY ITER");
+ double centerX = nextDouble();
+ double centerY = nextDouble();
+ double radius = 0.1 + 20 * random().nextDouble();
+ double radiusDelta = random().nextDouble();
+
+ ArrayList xList = new ArrayList<>();
+ ArrayList yList = new ArrayList<>();
+ double angle = 0.0;
+ while (true) {
+ angle += random().nextDouble()*40.0;
+ //System.out.println(" angle " + angle);
+ if (angle > 360) {
+ break;
+ }
+ double len = radius * (1.0 - radiusDelta + radiusDelta * random().nextDouble());
+ double maxX = StrictMath.min(StrictMath.abs(Float.MAX_VALUE - centerX), StrictMath.abs(-Float.MAX_VALUE - centerX));
+ double maxY = StrictMath.min(StrictMath.abs(Float.MAX_VALUE - centerY), StrictMath.abs(-Float.MAX_VALUE - centerY));
+
+ len = StrictMath.min(len, StrictMath.min(maxX, maxY));
+
+ //System.out.println(" len=" + len);
+ float x = (float)(centerX + len * Math.cos(SloppyMath.toRadians(angle)));
+ float y = (float)(centerY + len * Math.sin(SloppyMath.toRadians(angle)));
+
+ xList.add(x);
+ yList.add(y);
+
+ //System.out.println(" lat=" + lats.get(lats.size()-1) + " lon=" + lons.get(lons.size()-1));
+ }
+
+ // close it
+ xList.add(xList.get(0));
+ yList.add(yList.get(0));
+
+ float[] xArray = new float[xList.size()];
+ float[] yArray = new float[yList.size()];
+ for(int i=0;i triangles) {
double area = 0;
for (Tessellator.Triangle t : triangles) {
- double[] lats = new double[] {t.getLat(0), t.getLat(1), t.getLat(2), t.getLat(0)};
- double[] lons = new double[] {t.getLon(0), t.getLon(1), t.getLon(2), t.getLon(0)};
+ double[] lats = new double[] {t.getY(0), t.getY(1), t.getY(2), t.getY(0)};
+ double[] lons = new double[] {t.getX(0), t.getX(1), t.getX(2), t.getX(0)};
area += area(new Polygon(lats, lons));
}
return area;