blob: 2bf27fe3131ded84c799d180edf027c01b3d323a [file] [log] [blame]
/*
* 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.Random;
import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.Component2D;
import org.apache.lucene.geo.ShapeTestUtil;
import org.apache.lucene.geo.Tessellator;
import org.apache.lucene.geo.XYCircle;
import org.apache.lucene.geo.XYGeometry;
import org.apache.lucene.geo.XYLine;
import org.apache.lucene.geo.XYPoint;
import org.apache.lucene.geo.XYPolygon;
import org.apache.lucene.geo.XYRectangle;
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.search.TopDocs;
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, float minX, float maxX, float minY, float maxY) {
return XYShape.newBoxQuery(field, QueryRelation.INTERSECTS, minX, maxX, minY, 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] = p.getPolyX(i);
y[i] = 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);
float minX = Math.min(x[0], x[1]);
float minY = Math.min(y[0], y[1]);
float maxX = Math.max(x[0], x[1]);
float 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-1f, p.minX + 1f, p.minY - 1f, p.minY + 1f);
assertEquals(0, searcher.count(q));
// search w/ an intersecting polygon
q = XYShape.newPolygonQuery(FIELDNAME, QueryRelation.INTERSECTS, new XYPolygon(
new float[] {minX, minX, maxX, maxX, minX},
new float[] {minY, maxY, maxY, minY, minY}
));
assertEquals(2, searcher.count(q));
// search w/ an intersecting line
q = XYShape.newLineQuery(FIELDNAME, QueryRelation.INTERSECTS, new XYLine(
new float[] {minX, minX, maxX, maxX},
new float[] {minY, maxY, maxY, minY}
));
assertEquals(2, searcher.count(q));
IOUtils.close(reader, dir);
}
public void testBoundingBoxQueries() throws Exception {
Random random = random();
XYRectangle r1 = ShapeTestUtil.nextBox(random);
XYRectangle r2 = ShapeTestUtil.nextBox(random);
XYPolygon p;
//find two boxes so that r1 contains r2
while (true) {
// TODO: Should XYRectangle hold values as float?
if (areBoxDisjoint(r1, r2)) {
p = toPolygon(r2);
try {
Tessellator.tessellate(p);
break;
} catch (Exception e) {
// ignore, try other combination
}
}
r1 = ShapeTestUtil.nextBox(random);
r2 = ShapeTestUtil.nextBox(random);
}
Directory dir = newDirectory();
RandomIndexWriter writer = new RandomIndexWriter(random, dir);
// add the polygon to the index
Document document = new Document();
addPolygonsToDoc(FIELDNAME, document, p);
writer.addDocument(document);
////// search /////
IndexReader reader = writer.getReader();
writer.close();
IndexSearcher searcher = newSearcher(reader);
// Query by itself should match
Query q = newRectQuery(FIELDNAME, r2.minX, r2.maxX, r2.minY, r2.maxY);
assertEquals(1, searcher.count(q));
// r1 contains r2, intersects should match
q = newRectQuery(FIELDNAME, r1.minX, r1.maxX, r1.minY, r1.maxY);
assertEquals(1, searcher.count(q));
// r1 contains r2, WITHIN should match
q = XYShape.newBoxQuery(FIELDNAME, QueryRelation.WITHIN, r1.minX, r1.maxX, r1.minY, r1.maxY);
assertEquals(1, searcher.count(q));
IOUtils.close(reader, dir);
}
public void testPointIndexAndDistanceQuery() throws Exception {
Directory dir = newDirectory();
RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
Document document = new Document();
float pX = ShapeTestUtil.nextFloat(random());
float py = ShapeTestUtil.nextFloat(random());
Field[] fields = XYShape.createIndexableFields(FIELDNAME, pX, py);
for (Field f : fields) {
document.add(f);
}
writer.addDocument(document);
//// search
IndexReader r = writer.getReader();
writer.close();
IndexSearcher s = newSearcher(r);
XYCircle circle = ShapeTestUtil.nextCircle();
Component2D circle2D = XYGeometry.create(circle);
int expected;
int expectedDisjoint;
if (circle2D.contains(pX, py)) {
expected = 1;
expectedDisjoint = 0;
} else {
expected = 0;
expectedDisjoint = 1;
}
Query q = XYShape.newDistanceQuery(FIELDNAME, QueryRelation.INTERSECTS, circle);
assertEquals(expected, s.count(q));
q = XYShape.newDistanceQuery(FIELDNAME, QueryRelation.WITHIN, circle);
assertEquals(expected, s.count(q));
q = XYShape.newDistanceQuery(FIELDNAME, QueryRelation.DISJOINT, circle);
assertEquals(expectedDisjoint, s.count(q));
IOUtils.close(r, dir);
}
public void testContainsWrappingBooleanQuery() throws Exception {
float[] ys = new float[] {-30, -30, 30, 30, -30};
float[] xs = new float[] {-30, 30, 30, -30, -30};
XYPolygon polygon = new XYPolygon(xs, ys);
Directory dir = newDirectory();
RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
Document document = new Document();
addPolygonsToDoc(FIELDNAME, document, polygon);
writer.addDocument(document);
//// search
IndexReader r = writer.getReader();
writer.close();
IndexSearcher s = newSearcher(r);
XYGeometry[] geometries = new XYGeometry[] { new XYRectangle(0, 1, 0, 1), new XYPoint(4, 4) };
// geometries within the polygon
Query q = XYShape.newGeometryQuery(FIELDNAME, QueryRelation.CONTAINS, geometries);
TopDocs topDocs = s.search(q, 1);
assertEquals(1, topDocs.scoreDocs.length);
assertEquals(1.0, topDocs.scoreDocs[0].score, 0.0);
IOUtils.close(r, dir);
}
public void testContainsIndexedGeometryCollection() throws Exception {
Directory dir = newDirectory();
RandomIndexWriter w = new RandomIndexWriter(random(), dir);
XYPolygon polygon = new XYPolygon(new float[] {-132, 132, 132, -132, -132}, new float[] {-64, -64, 64, 64, -64});
Field[] polygonFields = XYShape.createIndexableFields(FIELDNAME, polygon);
// POINT(5, 5) inside the indexed polygon
Field[] pointFields = XYShape.createIndexableFields(FIELDNAME, 5, 5);
int numDocs = random().nextInt(1000);
// index the same multi geometry many times
for (int i = 0; i < numDocs; i++) {
Document doc = new Document();
for (Field f : polygonFields) {
doc.add(f);
}
for(int j = 0; j < 10; j++) {
for (Field f : pointFields) {
doc.add(f);
}
}
w.addDocument(doc);
}
w.forceMerge(1);
///// search //////
IndexReader reader = w.getReader();
w.close();
IndexSearcher searcher = newSearcher(reader);
// Contains is only true if the query geometry is inside a geometry and does not intersect with any other geometry
// belonging to the same document. In this case the query geometry contains the indexed polygon but the point is
// inside the query as well, hence the result is 0.
XYPolygon polygonQuery = new XYPolygon(new float[] {4, 6, 6, 4, 4}, new float[] {4, 4, 6, 6, 4});
Query query = XYShape.newGeometryQuery(FIELDNAME, QueryRelation.CONTAINS, polygonQuery);
assertEquals(0, searcher.count(query));
XYRectangle rectangle = new XYRectangle(4, 6, 4, 6);
query = XYShape.newGeometryQuery(FIELDNAME, QueryRelation.CONTAINS, rectangle);
assertEquals(0, searcher.count(query));
XYCircle circle = new XYCircle(5, 5, 1);
query = XYShape.newGeometryQuery(FIELDNAME, QueryRelation.CONTAINS, circle);
assertEquals(0, searcher.count(query));
IOUtils.close(w, reader, dir);
}
private static boolean areBoxDisjoint(XYRectangle r1, XYRectangle r2) {
return ( r1.minX <= r2.minX && r1.minY <= r2.minY && r1.maxX >= r2.maxX && r1.maxY >= r2.maxY);
}
private static XYPolygon toPolygon(XYRectangle r) {
return new XYPolygon(new float[]{ r.minX, r.maxX, r.maxX, r.minX, r.minX},
new float[]{ r.minY, r.minY, r.maxY, r.maxY, r.minY});
}
}