You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by nk...@apache.org on 2018/08/02 23:06:28 UTC
lucene-solr:branch_7x: LUCENE-8440: Add support for indexing and
searching Line and Point shapes using LatLonShape encoding
Repository: lucene-solr
Updated Branches:
refs/heads/branch_7x 724a65a60 -> b2b9ecb4f
LUCENE-8440: Add support for indexing and searching Line and Point shapes using LatLonShape encoding
Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/b2b9ecb4
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/b2b9ecb4
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/b2b9ecb4
Branch: refs/heads/branch_7x
Commit: b2b9ecb4f090c4a345fdafbe395c72ff0c922591
Parents: 724a65a
Author: Nicholas Knize <nk...@gmail.com>
Authored: Tue Jul 31 17:45:12 2018 -0500
Committer: Nicholas Knize <nk...@gmail.com>
Committed: Thu Aug 2 17:54:55 2018 -0500
----------------------------------------------------------------------
lucene/CHANGES.txt | 2 +
.../src/java/org/apache/lucene/geo/Polygon.java | 2 +-
.../org/apache/lucene/document/LatLonShape.java | 67 ++-
.../src/java/org/apache/lucene/geo/Line.java | 139 ++++++
.../document/BaseLatLonShapeTestCase.java | 457 +++++++++++++++++++
.../document/TestLatLonLineShapeQueries.java | 94 ++++
.../document/TestLatLonPointShapeQueries.java | 66 +++
.../document/TestLatLonPolygonShapeQueries.java | 385 ++--------------
.../apache/lucene/document/TestLatLonShape.java | 31 +-
9 files changed, 885 insertions(+), 358 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b2b9ecb4/lucene/CHANGES.txt
----------------------------------------------------------------------
diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index 745d996..f58c993 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -84,6 +84,8 @@ Changes in Runtime Behavior:
Improvements
+* LUCENE-8440: Add support for indexing and searching Line and Point shapes using LatLonShape encoding (Nick Knize)
+
* LUCENE-8435: Add new LatLonShapePolygonQuery for querying indexed LatLonShape fields by arbitrary polygons (Nick Knize)
* LUCENE-8367: Make per-dimension drill down optional for each facet dimension (Mike McCandless)
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b2b9ecb4/lucene/core/src/java/org/apache/lucene/geo/Polygon.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/geo/Polygon.java b/lucene/core/src/java/org/apache/lucene/geo/Polygon.java
index 5e14286..a6d7e9d 100644
--- a/lucene/core/src/java/org/apache/lucene/geo/Polygon.java
+++ b/lucene/core/src/java/org/apache/lucene/geo/Polygon.java
@@ -202,7 +202,7 @@ public final class Polygon {
return sb.toString();
}
- private String verticesToGeoJSON(final double[] lats, final double[] lons) {
+ public static String verticesToGeoJSON(final double[] lats, final double[] lons) {
StringBuilder sb = new StringBuilder();
sb.append('[');
for (int i = 0; i < lats.length; i++) {
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b2b9ecb4/lucene/sandbox/src/java/org/apache/lucene/document/LatLonShape.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonShape.java b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonShape.java
index 28c95e4..01a31ad 100644
--- a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonShape.java
+++ b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonShape.java
@@ -19,6 +19,7 @@ package org.apache.lucene.document;
import java.util.ArrayList;
import java.util.List;
+import org.apache.lucene.geo.Line;
import org.apache.lucene.geo.Polygon;
import org.apache.lucene.geo.Tessellator;
import org.apache.lucene.geo.Tessellator.Triangle;
@@ -27,6 +28,9 @@ import org.apache.lucene.search.Query;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.NumericUtils;
+import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitude;
+import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude;
+
/**
* An indexed shape utility class.
* <p>
@@ -62,16 +66,67 @@ public class LatLonShape {
private LatLonShape() {
}
- /** the lionshare of the indexing is done by the tessellator */
+ /** create indexable fields for polygon geometry */
public static Field[] createIndexableFields(String fieldName, Polygon polygon) {
+ // the lionshare of the indexing is done by the tessellator
List<Triangle> tessellation = Tessellator.tessellate(polygon);
List<LatLonTriangle> fields = new ArrayList<>();
- for (int i = 0; i < tessellation.size(); ++i) {
- fields.add(new LatLonTriangle(fieldName, tessellation.get(i)));
+ for (Triangle t : tessellation) {
+ fields.add(new LatLonTriangle(fieldName, t.getEncodedX(0), t.getEncodedY(0),
+ t.getEncodedX(1), t.getEncodedY(1), t.getEncodedX(2), t.getEncodedY(2)));
}
return fields.toArray(new Field[fields.size()]);
}
+ /** create indexable fields for line geometry */
+ public static Field[] createIndexableFields(String fieldName, Line line) {
+ int numPoints = line.numPoints();
+ List<LatLonTriangle> fields = new ArrayList<>(numPoints - 1);
+
+ // encode the line vertices
+ int[] encodedLats = new int[numPoints];
+ int[] encodedLons = new int[numPoints];
+ for (int i = 0; i < numPoints; ++i) {
+ encodedLats[i] = encodeLatitude(line.getLat(i));
+ encodedLons[i] = encodeLongitude(line.getLon(i));
+ }
+
+ // create "flat" triangles
+ int aLat, bLat, aLon, bLon, temp;
+ for (int i = 0, j = 1; j < numPoints; ++i, ++j) {
+ aLat = encodedLats[i];
+ aLon = encodedLons[i];
+ bLat = encodedLats[j];
+ bLon = encodedLons[j];
+ if (aLat > bLat) {
+ temp = aLat;
+ aLat = bLat;
+ bLat = temp;
+ temp = aLon;
+ aLon = bLon;
+ bLon = temp;
+ } else if (aLat == bLat) {
+ if (aLon > bLon) {
+ temp = aLat;
+ aLat = bLat;
+ bLat = temp;
+ temp = aLon;
+ aLon = bLon;
+ bLon = temp;
+ }
+ }
+ fields.add(new LatLonTriangle(fieldName, aLon, aLat, bLon, bLat, aLon, aLat));
+ }
+ return fields.toArray(new Field[fields.size()]);
+ }
+
+ /** create indexable fields for point geometry */
+ public static Field[] createIndexableFields(String fieldName, double lat, double lon) {
+ final int encodedLat = encodeLatitude(lat);
+ final int encodedLon = encodeLongitude(lon);
+ return new Field[] {new LatLonTriangle(fieldName, encodedLon, encodedLat, encodedLon, encodedLat, encodedLon, encodedLat)};
+ }
+
/** create a query to find all polygons that intersect a defined bounding box
* note: does not currently support dateline crossing boxes
* todo split dateline crossing boxes into two queries like {@link LatLonPoint#newBoxQuery}
@@ -89,11 +144,9 @@ public class LatLonShape {
*/
private static class LatLonTriangle extends Field {
- public LatLonTriangle(String name, Triangle t) {
+ LatLonTriangle(String name, int ax, int ay, int bx, int by, int cx, int cy) {
super(name, TYPE);
- setTriangleValue(t.getEncodedX(0), t.getEncodedY(0),
- t.getEncodedX(1), t.getEncodedY(1),
- t.getEncodedX(2), t.getEncodedY(2));
+ setTriangleValue(ax, ay, bx, by, cx, cy);
}
public void setTriangleValue(int aX, int aY, int bX, int bY, int cX, int cY) {
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b2b9ecb4/lucene/sandbox/src/java/org/apache/lucene/geo/Line.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/geo/Line.java b/lucene/sandbox/src/java/org/apache/lucene/geo/Line.java
new file mode 100644
index 0000000..c7e626d
--- /dev/null
+++ b/lucene/sandbox/src/java/org/apache/lucene/geo/Line.java
@@ -0,0 +1,139 @@
+/*
+ * 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 on the earth's surface. You can construct the Line directly with {@code double[]}
+ * coordinates.
+ * <p>
+ * NOTES:
+ * <ol>
+ * <li>All latitude/longitude values must be in decimal degrees.
+ * <li>For more advanced GeoSpatial indexing and query operations see the {@code spatial-extras} module
+ * </ol>
+ * @lucene.experimental
+ */
+public class Line {
+ /** array of latitude coordinates */
+ private final double[] lats;
+ /** array of longitude coordinates */
+ private final double[] lons;
+
+ /** minimum latitude of this line's bounding box */
+ public final double minLat;
+ /** maximum latitude of this line's bounding box */
+ public final double maxLat;
+ /** minimum longitude of this line's bounding box */
+ public final double minLon;
+ /** maximum longitude of this line's bounding box */
+ public final double maxLon;
+
+ /**
+ * Creates a new Line from the supplied latitude/longitude array.
+ */
+ public Line(double[] lats, double[] lons) {
+ if (lats == null) {
+ throw new IllegalArgumentException("lats must not be null");
+ }
+ if (lons == null) {
+ throw new IllegalArgumentException("lons must not be null");
+ }
+ if (lats.length != lons.length) {
+ throw new IllegalArgumentException("lats and lons must be equal length");
+ }
+ if (lats.length < 2) {
+ throw new IllegalArgumentException("at least 2 line points required");
+ }
+
+ // compute bounding box
+ double minLat = lats[0];
+ double minLon = lons[0];
+ double maxLat = lats[0];
+ double maxLon = lons[0];
+ for (int i = 0; i < lats.length; ++i) {
+ GeoUtils.checkLatitude(lats[i]);
+ GeoUtils.checkLongitude(lons[i]);
+ minLat = Math.min(lats[i], minLat);
+ minLon = Math.min(lons[i], minLon);
+ maxLat = Math.max(lats[i], maxLat);
+ maxLon = Math.max(lons[i], maxLon);
+ }
+
+ this.lats = lats.clone();
+ this.lons = lons.clone();
+ this.minLat = minLat;
+ this.maxLat = maxLat;
+ this.minLon = minLon;
+ this.maxLon = maxLon;
+ }
+
+ /** returns the number of vertex points */
+ public int numPoints() {
+ return lats.length;
+ }
+
+ /** Returns latitude value at given index */
+ public double getLat(int vertex) {
+ return lats[vertex];
+ }
+
+ /** Returns longitude value at given index */
+ public double getLon(int vertex) {
+ return lons[vertex];
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof Line)) return false;
+ Line line = (Line) o;
+ return Arrays.equals(lats, line.lats) && Arrays.equals(lons, line.lons);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Arrays.hashCode(lats);
+ result = 31 * result + Arrays.hashCode(lons);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("LINE(");
+ for (int i = 0; i < lats.length; i++) {
+ sb.append("[")
+ .append(lats[i])
+ .append(", ")
+ .append(lons[i])
+ .append("]");
+ }
+ sb.append(')');
+ return sb.toString();
+ }
+
+ /** prints polygons as geojson */
+ public String toGeoJSON() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("[");
+ sb.append(Polygon.verticesToGeoJSON(lats, lons));
+ sb.append("]");
+ return sb.toString();
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b2b9ecb4/lucene/sandbox/src/test/org/apache/lucene/document/BaseLatLonShapeTestCase.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/BaseLatLonShapeTestCase.java b/lucene/sandbox/src/test/org/apache/lucene/document/BaseLatLonShapeTestCase.java
new file mode 100644
index 0000000..1ca505e
--- /dev/null
+++ b/lucene/sandbox/src/test/org/apache/lucene/document/BaseLatLonShapeTestCase.java
@@ -0,0 +1,457 @@
+/*
+ * 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.HashSet;
+import java.util.Set;
+
+import com.carrotsearch.randomizedtesting.generators.RandomPicks;
+import org.apache.lucene.geo.GeoTestUtil;
+import org.apache.lucene.geo.Line;
+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.MultiDocValues;
+import org.apache.lucene.index.MultiFields;
+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.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 static com.carrotsearch.randomizedtesting.RandomizedTest.randomBoolean;
+import static com.carrotsearch.randomizedtesting.RandomizedTest.randomInt;
+import static org.apache.lucene.geo.GeoEncodingUtils.decodeLatitude;
+import static org.apache.lucene.geo.GeoEncodingUtils.decodeLongitude;
+import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitude;
+import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitudeCeil;
+import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude;
+import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitudeCeil;
+import static org.apache.lucene.geo.GeoTestUtil.nextLatitude;
+import static org.apache.lucene.geo.GeoTestUtil.nextLongitude;
+
+public abstract class BaseLatLonShapeTestCase extends LuceneTestCase {
+
+ protected static final String FIELD_NAME = "shape";
+
+ protected abstract ShapeType getShapeType();
+
+ protected Object nextShape() {
+ return getShapeType().nextShape();
+ }
+
+ protected double quantizeLat(double rawLat) {
+ return decodeLatitude(encodeLatitude(rawLat));
+ }
+
+ protected double quantizeLatCeil(double rawLat) {
+ return decodeLatitude(encodeLatitudeCeil(rawLat));
+ }
+
+ protected double quantizeLon(double rawLon) {
+ return decodeLongitude(encodeLongitude(rawLon));
+ }
+
+ protected double quantizeLonCeil(double rawLon) {
+ return decodeLongitude(encodeLongitudeCeil(rawLon));
+ }
+
+ protected Polygon quantizePolygon(Polygon polygon) {
+ double[] lats = new double[polygon.numPoints()];
+ double[] lons = new double[polygon.numPoints()];
+ for (int i = 0; i < lats.length; ++i) {
+ lats[i] = quantizeLat(polygon.getPolyLat(i));
+ lons[i] = quantizeLon(polygon.getPolyLon(i));
+ }
+ return new Polygon(lats, lons);
+ }
+
+ protected abstract Field[] createIndexableFields(String field, Object shape);
+
+ private void addShapeToDoc(String field, Document doc, Object shape) {
+ Field[] fields = createIndexableFields(field, shape);
+ for (Field f : fields) {
+ doc.add(f);
+ }
+ }
+
+ protected Query newRectQuery(String field, double minLat, double maxLat, double minLon, double maxLon) {
+ return LatLonShape.newBoxQuery(field, minLat, maxLat, minLon, maxLon);
+ }
+
+ protected Query newPolygonQuery(String field, Polygon... polygons) {
+ return LatLonShape.newPolygonQuery(field, polygons);
+ }
+
+ public void testRandomTiny() throws Exception {
+ // Make sure single-leaf-node case is OK:
+ doTestRandom(10);
+ }
+
+ public void testRandomMedium() throws Exception {
+ doTestRandom(10000);
+ }
+
+ @Nightly
+ public void testRandomBig() throws Exception {
+ doTestRandom(50000);
+ }
+
+ private 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);
+ }
+
+ Object[] shapes = new Object[numShapes];
+ for (int id = 0; id < numShapes; ++id) {
+ int x = randomInt(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);
+ }
+
+ 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 polygon queires
+ verifyRandomPolygonQueries(reader, shapes);
+
+ IOUtils.close(w, reader, dir);
+ }
+
+ protected void indexRandomShapes(IndexWriter w, Object... shapes) throws Exception {
+ Set<Integer> 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 && randomInt(100) == 42) {
+ int idToDelete = randomInt(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 verifyRandomBBoxQueries(IndexReader reader, Object... shapes) throws Exception {
+ IndexSearcher s = newSearcher(reader);
+
+ final int iters = atLeast(75);
+
+ Bits liveDocs = MultiFields.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
+ Rectangle rect;
+ // quantizing the bbox may end up w/ bounding boxes crossing dateline...
+ // todo add support for bounding boxes crossing dateline
+ while (true) {
+ rect = GeoTestUtil.nextBoxNotCrossingDateline();
+ if (decodeLongitude(encodeLongitudeCeil(rect.minLon)) <= decodeLongitude(encodeLongitude(rect.maxLon)) &&
+ decodeLatitude(encodeLatitudeCeil(rect.minLat)) <= decodeLatitude(encodeLatitude(rect.maxLat))) {
+ break;
+ }
+ }
+ Query query = newRectQuery(FIELD_NAME, rect.minLat, rect.maxLat, rect.minLon, rect.maxLon);
+
+ if (VERBOSE) {
+ System.out.println(" query=" + query);
+ }
+
+ final FixedBitSet hits = new FixedBitSet(maxDoc);
+ s.search(query, new SimpleCollector() {
+
+ private int docBase;
+
+ @Override
+ public boolean needsScores() {
+ return false;
+ }
+
+ @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 {
+ // check quantized poly against quantized query
+ expected = getValidator().testBBoxQuery(quantizeLatCeil(rect.minLat), quantizeLat(rect.maxLat),
+ quantizeLonCeil(rect.minLon), quantizeLon(rect.maxLon), 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(" query=" + query + " docID=" + docID + "\n");
+ b.append(" shape=" + shapes[id] + "\n");
+ b.append(" deleted?=" + (liveDocs != null && liveDocs.get(docID) == false));
+ b.append(" rect=Rectangle(" + quantizeLatCeil(rect.minLat) + " TO " + quantizeLat(rect.maxLat) + " lon=" + quantizeLonCeil(rect.minLon) + " TO " + quantizeLon(rect.maxLon) + ")");
+ 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 void verifyRandomPolygonQueries(IndexReader reader, Object... shapes) throws Exception {
+ IndexSearcher s = newSearcher(reader);
+
+ final int iters = atLeast(75);
+
+ Bits liveDocs = MultiFields.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
+ Polygon queryPolygon = GeoTestUtil.nextPolygon();
+ Polygon2D queryPoly2D = Polygon2D.create(queryPolygon);
+ Query query = newPolygonQuery(FIELD_NAME, queryPolygon);
+
+ if (VERBOSE) {
+ System.out.println(" query=" + query);
+ }
+
+ final FixedBitSet hits = new FixedBitSet(maxDoc);
+ s.search(query, new SimpleCollector() {
+
+ private int docBase;
+
+ @Override
+ public boolean needsScores() {
+ return false;
+ }
+
+ @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().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(" query=" + query + " docID=" + docID + "\n");
+ 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;
+ }
+ }
+ }
+ if (fail) {
+ fail("some hits were wrong");
+ }
+ }
+ }
+
+ protected abstract Validator getValidator();
+
+ /** 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);
+ return sb.toString();
+ }
+ }
+
+ /** internal shape type for testing different shape types */
+ protected enum ShapeType {
+ POINT() {
+ public Point nextShape() {
+ return new Point(nextLatitude(), nextLongitude());
+ }
+ },
+ LINE() {
+ public Line nextShape() {
+ Polygon p = GeoTestUtil.nextPolygon();
+ double[] lats = new double[p.numPoints() - 1];
+ double[] lons = new double[lats.length];
+ for (int i = 0; i < lats.length; ++i) {
+ lats[i] = p.getPolyLat(i);
+ lons[i] = p.getPolyLon(i);
+ }
+ return new Line(lats, lons);
+ }
+ },
+ POLYGON() {
+ public Polygon nextShape() {
+ return GeoTestUtil.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 Line) {
+ return LINE;
+ } else if (shape instanceof Polygon) {
+ return POLYGON;
+ }
+ throw new IllegalArgumentException("invalid shape type from " + shape.toString());
+ }
+ }
+
+ protected interface Validator {
+ boolean testBBoxQuery(double minLat, double maxLat, double minLon, double maxLon, Object shape);
+ boolean testPolygonQuery(Polygon2D poly2d, Object shape);
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b2b9ecb4/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonLineShapeQueries.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonLineShapeQueries.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonLineShapeQueries.java
new file mode 100644
index 0000000..21367dc
--- /dev/null
+++ b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonLineShapeQueries.java
@@ -0,0 +1,94 @@
+/*
+ * 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.Line;
+import org.apache.lucene.geo.Polygon;
+import org.apache.lucene.geo.Polygon2D;
+import org.apache.lucene.index.PointValues.Relation;
+
+import static org.apache.lucene.geo.GeoEncodingUtils.decodeLatitude;
+import static org.apache.lucene.geo.GeoEncodingUtils.decodeLongitude;
+import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitude;
+import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude;
+
+/** random bounding box and polygon query tests for random generated {@link Line} types */
+public class TestLatLonLineShapeQueries extends BaseLatLonShapeTestCase {
+
+ protected final LineValidator VALIDATOR = new LineValidator();
+
+ @Override
+ protected ShapeType getShapeType() {
+ return ShapeType.LINE;
+ }
+
+ @Override
+ protected Field[] createIndexableFields(String field, Object line) {
+ return LatLonShape.createIndexableFields(field, (Line)line);
+ }
+
+ @Override
+ protected Validator getValidator() {
+ return VALIDATOR;
+ }
+
+ protected class LineValidator implements Validator {
+ @Override
+ public boolean testBBoxQuery(double minLat, double maxLat, double minLon, double maxLon, Object shape) {
+ // to keep it simple we convert the bbox into a polygon and use poly2d
+ Polygon2D p = Polygon2D.create(new Polygon[] {new Polygon(new double[] {minLat, minLat, maxLat, maxLat, minLat},
+ new double[] {minLon, maxLon, maxLon, minLon, minLon})});
+ return testLine(p, (Line)shape);
+ }
+
+ @Override
+ public boolean testPolygonQuery(Polygon2D poly2d, Object shape) {
+ return testLine(poly2d, (Line) shape);
+ }
+
+ private boolean testLine(Polygon2D queryPoly, Line line) {
+ double ax, ay, bx, by, temp;
+ for (int i = 0, j = 1; j < line.numPoints(); ++i, ++j) {
+ ay = decodeLatitude(encodeLatitude(line.getLat(i)));
+ ax = decodeLongitude(encodeLongitude(line.getLon(i)));
+ by = decodeLatitude(encodeLatitude(line.getLat(j)));
+ bx = decodeLongitude(encodeLongitude(line.getLon(j)));
+ if (ay > by) {
+ temp = ay;
+ ay = by;
+ by = temp;
+ temp = ax;
+ ax = bx;
+ bx = temp;
+ } else if (ay == by) {
+ if (ax > bx) {
+ temp = ay;
+ ay = by;
+ by = temp;
+ temp = ax;
+ ax = bx;
+ bx = temp;
+ }
+ }
+ if (queryPoly.relateTriangle(ax, ay, bx, by, ax, ay) != Relation.CELL_OUTSIDE_QUERY) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b2b9ecb4/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPointShapeQueries.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPointShapeQueries.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPointShapeQueries.java
new file mode 100644
index 0000000..e98cb73
--- /dev/null
+++ b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPointShapeQueries.java
@@ -0,0 +1,66 @@
+/*
+ * 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.Polygon2D;
+import org.apache.lucene.index.PointValues.Relation;
+
+import static org.apache.lucene.geo.GeoEncodingUtils.decodeLatitude;
+import static org.apache.lucene.geo.GeoEncodingUtils.decodeLongitude;
+import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitude;
+import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude;
+
+/** random bounding box 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;
+ }
+
+ @Override
+ protected Field[] createIndexableFields(String field, Object point) {
+ Point p = (Point)point;
+ return LatLonShape.createIndexableFields(field, p.lat, p.lon);
+ }
+
+ @Override
+ protected Validator getValidator() {
+ return VALIDATOR;
+ }
+
+ protected class PointValidator implements Validator {
+ @Override
+ public boolean testBBoxQuery(double minLat, double maxLat, double minLon, double maxLon, Object shape) {
+ Point p = (Point)shape;
+ double lat = decodeLatitude(encodeLatitude(p.lat));
+ double lon = decodeLongitude(encodeLongitude(p.lon));
+ return (lat < minLat || lat > maxLat || lon < minLon || lon > maxLon) == false;
+ }
+
+ @Override
+ public boolean testPolygonQuery(Polygon2D poly2d, Object shape) {
+ Point p = (Point) shape;
+ double lat = decodeLatitude(encodeLatitude(p.lat));
+ double lon = decodeLongitude(encodeLongitude(p.lon));
+ // for consistency w/ the query we test the point as a triangle
+ return poly2d.relateTriangle(lon, lat, lon, lat, lon, lat) != Relation.CELL_OUTSIDE_QUERY;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b2b9ecb4/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPolygonShapeQueries.java
----------------------------------------------------------------------
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 e6bd907..de9fd4f 100644
--- a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPolygonShapeQueries.java
+++ b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPolygonShapeQueries.java
@@ -16,377 +16,68 @@
*/
package org.apache.lucene.document;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashSet;
import java.util.List;
-import java.util.Set;
-import org.apache.lucene.geo.GeoTestUtil;
import org.apache.lucene.geo.Polygon;
import org.apache.lucene.geo.Polygon2D;
-import org.apache.lucene.geo.Rectangle;
import org.apache.lucene.geo.Tessellator;
-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.MultiDocValues;
-import org.apache.lucene.index.MultiFields;
-import org.apache.lucene.index.NumericDocValues;
import org.apache.lucene.index.PointValues.Relation;
-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.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 static org.apache.lucene.geo.GeoEncodingUtils.decodeLatitude;
-import static org.apache.lucene.geo.GeoEncodingUtils.decodeLongitude;
-import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitude;
-import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitudeCeil;
-import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude;
-import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitudeCeil;
+/** random bounding box and polygon query tests for random generated {@link Polygon} types */
+public class TestLatLonPolygonShapeQueries extends BaseLatLonShapeTestCase {
-/** base Test case for {@link LatLonShape} indexing and search */
-public class TestLatLonPolygonShapeQueries extends LuceneTestCase {
- protected static final String FIELD_NAME = "shape";
+ protected final PolygonValidator VALIDATOR = new PolygonValidator();
- private Polygon quantizePolygon(Polygon polygon) {
- double[] lats = new double[polygon.numPoints()];
- double[] lons = new double[polygon.numPoints()];
- for (int i = 0; i < lats.length; ++i) {
- lats[i] = quantizeLat(polygon.getPolyLat(i));
- lons[i] = quantizeLon(polygon.getPolyLon(i));
- }
- return new Polygon(lats, lons);
- }
-
- protected double quantizeLat(double rawLat) {
- return decodeLatitude(encodeLatitude(rawLat));
- }
-
- protected double quantizeLatCeil(double rawLat) {
- return decodeLatitude(encodeLatitudeCeil(rawLat));
- }
-
- protected double quantizeLon(double rawLon) {
- return decodeLongitude(encodeLongitude(rawLon));
- }
-
- protected double quantizeLonCeil(double rawLon) {
- return decodeLongitude(encodeLongitudeCeil(rawLon));
- }
-
- protected void addPolygonsToDoc(String field, Document doc, Polygon polygon) {
- Field[] fields = LatLonShape.createIndexableFields(field, polygon);
- for (Field f : fields) {
- doc.add(f);
- }
- }
-
- protected Query newRectQuery(String field, double minLat, double maxLat, double minLon, double maxLon) {
- return LatLonShape.newBoxQuery(field, minLat, maxLat, minLon, maxLon);
- }
-
- protected Query newPolygonQuery(String field, Polygon... polygons) {
- return LatLonShape.newPolygonQuery(field, polygons);
- }
-
- public void testRandomTiny() throws Exception {
- // Make sure single-leaf-node case is OK:
- doTestRandom(10);
- }
-
- public void testRandomMedium() throws Exception {
- doTestRandom(10000);
+ @Override
+ protected ShapeType getShapeType() {
+ return ShapeType.POLYGON;
}
- @Nightly
- public void testRandomBig() throws Exception {
- doTestRandom(50000);
- }
-
- private void doTestRandom(int count) throws Exception {
- int numPolygons = atLeast(count);
-
- if (VERBOSE) {
- System.out.println("TEST: numPolygons=" + numPolygons);
- }
-
- Polygon[] polygons = new Polygon[numPolygons];
- for (int id = 0; id < numPolygons; ++id) {
- int x = random().nextInt(20);
- if (x == 17) {
- polygons[id] = null;
- if (VERBOSE) {
- System.out.println(" id=" + id + " is missing");
- }
- } else {
- // create a polygon that does not cross the dateline
- polygons[id] = GeoTestUtil.nextPolygon();
+ @Override
+ protected Polygon nextShape() {
+ Polygon p;
+ while (true) {
+ // if we can't tessellate; then random polygon generator created a malformed shape
+ p = (Polygon)getShapeType().nextShape();
+ try {
+ Tessellator.tessellate(p);
+ return p;
+ } catch (IllegalArgumentException e) {
+ continue;
}
}
- verify(polygons);
}
- private void verify(Polygon... polygons) throws Exception {
- ArrayList<Polygon2D> poly2d = new ArrayList<>();
- poly2d.ensureCapacity(polygons.length);
- // index random polygons; poly2d will contain the Polygon2D objects needed for verification
- IndexWriter w = indexRandomPolygons(poly2d, polygons);
- Directory dir = w.getDirectory();
- final IndexReader reader = DirectoryReader.open(w);
- // test random bbox queries
- verifyRandomBBoxQueries(reader, poly2d, polygons);
- // test random polygon queires
- verifyRandomPolygonQueries(reader, poly2d, polygons);
- IOUtils.close(w, reader, dir);
+ @Override
+ protected Field[] createIndexableFields(String field, Object polygon) {
+ return LatLonShape.createIndexableFields(field, (Polygon)polygon);
}
- protected IndexWriter indexRandomPolygons(List<Polygon2D> poly2d, Polygon... polygons) throws Exception {
- IndexWriterConfig iwc = newIndexWriterConfig();
- iwc.setMergeScheduler(new SerialMergeScheduler());
- int mbd = iwc.getMaxBufferedDocs();
- if (mbd != -1 && mbd < polygons.length / 100) {
- iwc.setMaxBufferedDocs(polygons.length / 100);
- }
- Directory dir;
- if (polygons.length > 1000) {
- dir = newFSDirectory(createTempDir(getClass().getSimpleName()));
- } else {
- dir = newDirectory();
- }
-
- Set<Integer> deleted = new HashSet<>();
- IndexWriter w = new IndexWriter(dir, iwc);
- for (int id = 0; id < polygons.length; ++id) {
- Document doc = new Document();
- doc.add(newStringField("id", "" + id, Field.Store.NO));
- doc.add(new NumericDocValuesField("id", id));
- if (polygons[id] != null) {
- try {
- addPolygonsToDoc(FIELD_NAME, doc, polygons[id]);
- } catch (IllegalArgumentException e) {
- // GeoTestUtil will occassionally create invalid polygons
- // invalid polygons will not tessellate
- // we skip those polygons that will not tessellate, relying on the TestTessellator class
- // to ensure the Tessellator correctly identified a malformed shape and its not a bug
- if (VERBOSE) {
- System.out.println(" id=" + id + " could not tessellate. Malformed shape " + polygons[id] + " detected");
- }
- // remove and skip the malformed shape
- polygons[id] = null;
- poly2d.add(id, null);
- continue;
- }
- poly2d.add(id, Polygon2D.create(quantizePolygon(polygons[id])));
- } else {
- poly2d.add(id, null);
- }
- 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 (random().nextBoolean()) {
- w.forceMerge(1);
- }
-
- return w;
+ @Override
+ protected Validator getValidator() {
+ return VALIDATOR;
}
- protected void verifyRandomBBoxQueries(IndexReader reader, List<Polygon2D> poly2d, Polygon... polygons) throws Exception {
- IndexSearcher s = newSearcher(reader);
-
- final int iters = atLeast(75);
-
- Bits liveDocs = MultiFields.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
- Rectangle rect = GeoTestUtil.nextBoxNotCrossingDateline();
- Query query = newRectQuery(FIELD_NAME, rect.minLat, rect.maxLat, rect.minLon, rect.maxLon);
-
- if (VERBOSE) {
- System.out.println(" query=" + query);
- }
-
- final FixedBitSet hits = new FixedBitSet(maxDoc);
- s.search(query, new SimpleCollector() {
-
- private int docBase;
-
- @Override
- public boolean needsScores() {
- return false;
- }
-
- @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 (polygons[id] == null) {
- expected = false;
- } else {
- // check quantized poly against quantized query
- expected = poly2d.get(id).relate(quantizeLatCeil(rect.minLat), quantizeLat(rect.maxLat),
- quantizeLonCeil(rect.minLon), quantizeLon(rect.maxLon)) != Relation.CELL_OUTSIDE_QUERY;
- }
-
- 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(" query=" + query + " docID=" + docID + "\n");
- b.append(" polygon=" + quantizePolygon(polygons[id]) + "\n");
- b.append(" deleted?=" + (liveDocs != null && liveDocs.get(docID) == false));
- b.append(" rect=Rectangle(" + quantizeLatCeil(rect.minLat) + " TO " + quantizeLat(rect.maxLat) + " lon=" + quantizeLonCeil(rect.minLon) + " TO " + quantizeLon(rect.maxLon) + ")");
- 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 class PolygonValidator implements Validator {
+ @Override
+ public boolean testBBoxQuery(double minLat, double maxLat, double minLon, double maxLon, Object shape) {
+ Polygon2D poly = Polygon2D.create(quantizePolygon((Polygon)shape));
+ return poly.relate(minLat, maxLat, minLon, maxLon) != Relation.CELL_OUTSIDE_QUERY;
}
- }
- protected void verifyRandomPolygonQueries(IndexReader reader, List<Polygon2D> poly2d, Polygon... polygons) throws Exception {
- IndexSearcher s = newSearcher(reader);
+ @Override
+ public boolean testPolygonQuery(Polygon2D query, Object shape) {
- final int iters = atLeast(75);
-
- Bits liveDocs = MultiFields.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
- Polygon queryPolygon = GeoTestUtil.nextPolygon();
- Polygon2D queryPoly2D = Polygon2D.create(queryPolygon);
- Query query = newPolygonQuery(FIELD_NAME, queryPolygon);
-
- if (VERBOSE) {
- System.out.println(" query=" + query);
- }
-
- final FixedBitSet hits = new FixedBitSet(maxDoc);
- s.search(query, new SimpleCollector() {
-
- private int docBase;
-
- @Override
- public boolean needsScores() {
- return false;
+ List<Tessellator.Triangle> tessellation = Tessellator.tessellate((Polygon) shape);
+ for (Tessellator.Triangle t : tessellation) {
+ // we quantize the triangle for consistency with the index
+ if (query.relateTriangle(quantizeLon(t.getLon(0)), quantizeLat(t.getLat(0)),
+ quantizeLon(t.getLon(1)), quantizeLat(t.getLat(1)),
+ quantizeLon(t.getLon(2)), quantizeLat(t.getLat(2))) != Relation.CELL_OUTSIDE_QUERY) {
+ return true;
}
-
- @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 (polygons[id] == null) {
- expected = false;
- } else {
- expected = false;
- try {
- // check poly (quantized the same way as indexed) against query polygon
- List<Tessellator.Triangle> tesselation = Tessellator.tessellate(quantizePolygon(polygons[id]));
- for (Tessellator.Triangle t : tesselation) {
- if (queryPoly2D.relateTriangle(t.getLon(0), t.getLat(0),
- t.getLon(1), t.getLat(1), t.getLon(2), t.getLat(2)) != Relation.CELL_OUTSIDE_QUERY) {
- expected = true;
- break;
- }
- }
- } catch (IllegalArgumentException e) {
- continue;
- }
- }
-
- 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(" query=" + query + " docID=" + docID + "\n");
- b.append(" polygon=" + quantizePolygon(polygons[id]).toGeoJSON() + "\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;
- }
- }
- }
- if (fail) {
- fail("some hits were wrong");
}
+ return false;
}
}
}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b2b9ecb4/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonShape.java
----------------------------------------------------------------------
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 f673d0a..3aa5ace 100644
--- a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonShape.java
+++ b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonShape.java
@@ -18,6 +18,7 @@ package org.apache.lucene.document;
import com.carrotsearch.randomizedtesting.generators.RandomNumbers;
import org.apache.lucene.geo.GeoTestUtil;
+import org.apache.lucene.geo.Line;
import org.apache.lucene.geo.Polygon;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
@@ -43,6 +44,13 @@ public class TestLatLonShape extends LuceneTestCase {
}
}
+ protected void addLineToDoc(String field, Document doc, Line line) {
+ Field[] fields = LatLonShape.createIndexableFields(field, line);
+ for (Field f : fields) {
+ doc.add(f);
+ }
+ }
+
protected Query newRectQuery(String field, double minLat, double maxLat, double minLon, double maxLon) {
return LatLonShape.newBoxQuery(field, minLat, maxLat, minLon, maxLon);
}
@@ -81,19 +89,36 @@ public class TestLatLonShape extends LuceneTestCase {
Directory dir = newDirectory();
RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
- // add a random polygon
+ // add a random polygon document
Polygon p = GeoTestUtil.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
+ double lats[] = new double[p.numPoints() - 1];
+ double lons[] = new double[p.numPoints() - 1];
+ for (int i = 0; i < lats.length; ++i) {
+ lats[i] = p.getPolyLat(i);
+ lons[i] = p.getPolyLon(i);
+ }
+ Line l = new Line(lats, lons);
+ addLineToDoc(FIELDNAME, document, l);
+ writer.addDocument(document);
+
////// search /////
// search an intersecting bbox
IndexReader reader = writer.getReader();
writer.close();
IndexSearcher searcher = newSearcher(reader);
- Query q = newRectQuery(FIELDNAME, -1d, 1d, p.minLon, p.maxLon);
- assertEquals(1, searcher.count(q));
+ double minLat = Math.min(lats[0], lats[1]);
+ double minLon = Math.min(lons[0], lons[1]);
+ double maxLat = Math.max(lats[0], lats[1]);
+ double maxLon = Math.max(lons[0], lons[1]);
+ Query q = newRectQuery(FIELDNAME, minLat, maxLat, minLon, maxLon);
+ assertEquals(2, searcher.count(q));
// search a disjoint bbox
q = newRectQuery(FIELDNAME, p.minLat-1d, p.minLat+1, p.minLon-1d, p.minLon+1d);