mirror of https://github.com/apache/lucene.git
LUCENE-7137: consolidate many tests across Points and GeoPoint queries/fields
This commit is contained in:
parent
5b3e07a98a
commit
ff70c680a2
|
@ -16,37 +16,11 @@
|
|||
*/
|
||||
package org.apache.lucene.document;
|
||||
|
||||
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.LuceneTestCase;
|
||||
|
||||
/** Simple tests for {@link LatLonPoint} */
|
||||
public class TestLatLonPoint extends LuceneTestCase {
|
||||
|
||||
/** Add a single point and search for it in a box */
|
||||
// NOTE: we don't currently supply an exact search, only ranges, because of the lossiness...
|
||||
public void testBoxQuery() throws Exception {
|
||||
Directory dir = newDirectory();
|
||||
RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
|
||||
|
||||
// add a doc with a point
|
||||
Document document = new Document();
|
||||
document.add(new LatLonPoint("field", 18.313694, -65.227444));
|
||||
writer.addDocument(document);
|
||||
|
||||
// search and verify we found our doc
|
||||
IndexReader reader = writer.getReader();
|
||||
IndexSearcher searcher = newSearcher(reader);
|
||||
assertEquals(1, searcher.count(LatLonPoint.newBoxQuery("field", 18, 19, -66, -65)));
|
||||
|
||||
reader.close();
|
||||
writer.close();
|
||||
dir.close();
|
||||
}
|
||||
|
||||
public void testToString() throws Exception {
|
||||
// looks crazy due to lossiness
|
||||
assertEquals("LatLonPoint <field:18.313693958334625,-65.22744392976165>",(new LatLonPoint("field", 18.313694, -65.227444)).toString());
|
||||
|
@ -61,79 +35,6 @@ public class TestLatLonPoint extends LuceneTestCase {
|
|||
assertEquals("<distance:\"field\" latitude=18.0 longitude=19.0>", LatLonPoint.newDistanceSort("field", 18.0, 19.0).toString());
|
||||
}
|
||||
|
||||
/** Valid values that should not cause exception */
|
||||
public void testExtremeValues() {
|
||||
new LatLonPoint("foo", 90.0, 180.0);
|
||||
new LatLonPoint("foo", 90.0, -180.0);
|
||||
new LatLonPoint("foo", -90.0, 180.0);
|
||||
new LatLonPoint("foo", -90.0, -180.0);
|
||||
}
|
||||
|
||||
/** Invalid values */
|
||||
public void testOutOfRangeValues() {
|
||||
IllegalArgumentException expected;
|
||||
|
||||
expected = expectThrows(IllegalArgumentException.class, () -> {
|
||||
new LatLonPoint("foo", Math.nextUp(90.0), 50.0);
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("invalid latitude"));
|
||||
|
||||
expected = expectThrows(IllegalArgumentException.class, () -> {
|
||||
new LatLonPoint("foo", Math.nextDown(-90.0), 50.0);
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("invalid latitude"));
|
||||
|
||||
expected = expectThrows(IllegalArgumentException.class, () -> {
|
||||
new LatLonPoint("foo", 90.0, Math.nextUp(180.0));
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("invalid longitude"));
|
||||
|
||||
expected = expectThrows(IllegalArgumentException.class, () -> {
|
||||
new LatLonPoint("foo", 90.0, Math.nextDown(-180.0));
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("invalid longitude"));
|
||||
}
|
||||
|
||||
/** NaN: illegal */
|
||||
public void testNaNValues() {
|
||||
IllegalArgumentException expected;
|
||||
|
||||
expected = expectThrows(IllegalArgumentException.class, () -> {
|
||||
new LatLonPoint("foo", Double.NaN, 50.0);
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("invalid latitude"));
|
||||
|
||||
expected = expectThrows(IllegalArgumentException.class, () -> {
|
||||
new LatLonPoint("foo", 50.0, Double.NaN);
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("invalid longitude"));
|
||||
}
|
||||
|
||||
/** Inf: illegal */
|
||||
public void testInfValues() {
|
||||
IllegalArgumentException expected;
|
||||
|
||||
expected = expectThrows(IllegalArgumentException.class, () -> {
|
||||
new LatLonPoint("foo", Double.POSITIVE_INFINITY, 50.0);
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("invalid latitude"));
|
||||
|
||||
expected = expectThrows(IllegalArgumentException.class, () -> {
|
||||
new LatLonPoint("foo", Double.NEGATIVE_INFINITY, 50.0);
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("invalid latitude"));
|
||||
|
||||
expected = expectThrows(IllegalArgumentException.class, () -> {
|
||||
new LatLonPoint("foo", 50.0, Double.POSITIVE_INFINITY);
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("invalid longitude"));
|
||||
|
||||
expected = expectThrows(IllegalArgumentException.class, () -> {
|
||||
new LatLonPoint("foo", 50.0, Double.NEGATIVE_INFINITY);
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("invalid longitude"));
|
||||
}
|
||||
|
||||
public void testEncodeDecode() throws Exception {
|
||||
// just for testing quantization error
|
||||
final double ENCODING_TOLERANCE = 1e-7;
|
||||
|
@ -184,29 +85,4 @@ public class TestLatLonPoint extends LuceneTestCase {
|
|||
assertEquals(lonEnc, lonEnc2, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
public void testQueryEquals() throws Exception {
|
||||
Query q1 = LatLonPoint.newBoxQuery("field", 50, 70, -40, 20);
|
||||
Query q2 = LatLonPoint.newBoxQuery("field", 50, 70, -40, 20);
|
||||
assertEquals(q1, q2);
|
||||
assertEquals(q1.hashCode(), q2.hashCode());
|
||||
assertFalse(q1.equals(LatLonPoint.newBoxQuery("field", 50, 70, -40, 10)));
|
||||
|
||||
q1 = LatLonPoint.newDistanceQuery("field", 50, 70, 10000);
|
||||
q2 = LatLonPoint.newDistanceQuery("field", 50, 70, 10000);
|
||||
assertEquals(q1, q2);
|
||||
assertEquals(q1.hashCode(), q2.hashCode());
|
||||
assertFalse(q1.equals(LatLonPoint.newDistanceQuery("field", 50, 70, 11000)));
|
||||
assertFalse(q1.equals(LatLonPoint.newDistanceQuery("field", 50, 60, 10000)));
|
||||
|
||||
|
||||
double[] polyLats1 = new double[] {30, 40, 40, 30, 30};
|
||||
double[] polyLons1 = new double[] {90, 90, -40, -40, 90};
|
||||
double[] polyLats2 = new double[] {20, 40, 40, 20, 20};
|
||||
q1 = LatLonPoint.newPolygonQuery("field", polyLats1, polyLons1);
|
||||
q2 = LatLonPoint.newPolygonQuery("field", polyLats1, polyLons1);
|
||||
assertEquals(q1, q2);
|
||||
assertEquals(q1.hashCode(), q2.hashCode());
|
||||
assertFalse(q1.equals(LatLonPoint.newPolygonQuery("field", polyLats2, polyLons1)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,82 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.lucene.document;
|
||||
|
||||
import org.apache.lucene.index.IndexReader;
|
||||
import org.apache.lucene.index.RandomIndexWriter;
|
||||
import org.apache.lucene.search.IndexSearcher;
|
||||
import org.apache.lucene.store.Directory;
|
||||
import org.apache.lucene.util.LuceneTestCase;
|
||||
|
||||
/** Simple tests for {@link LatLonPoint#newDistanceQuery} */
|
||||
public class TestLatLonPointDistanceQuery extends LuceneTestCase {
|
||||
|
||||
/** test we can search for a point */
|
||||
public void testBasics() throws Exception {
|
||||
Directory dir = newDirectory();
|
||||
RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
|
||||
|
||||
// add a doc with a location
|
||||
Document document = new Document();
|
||||
document.add(new LatLonPoint("field", 18.313694, -65.227444));
|
||||
writer.addDocument(document);
|
||||
|
||||
// search within 50km and verify we found our doc
|
||||
IndexReader reader = writer.getReader();
|
||||
IndexSearcher searcher = newSearcher(reader);
|
||||
assertEquals(1, searcher.count(LatLonPoint.newDistanceQuery("field", 18, -65, 50_000)));
|
||||
|
||||
reader.close();
|
||||
writer.close();
|
||||
dir.close();
|
||||
}
|
||||
|
||||
/** negative distance queries are not allowed */
|
||||
public void testNegativeRadius() {
|
||||
IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
|
||||
LatLonPoint.newDistanceQuery("field", 18, 19, -1);
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("radiusMeters"));
|
||||
assertTrue(expected.getMessage().contains("is invalid"));
|
||||
}
|
||||
|
||||
/** NaN distance queries are not allowed */
|
||||
public void testNaNRadius() {
|
||||
IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
|
||||
LatLonPoint.newDistanceQuery("field", 18, 19, Double.NaN);
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("radiusMeters"));
|
||||
assertTrue(expected.getMessage().contains("is invalid"));
|
||||
}
|
||||
|
||||
/** Inf distance queries are not allowed */
|
||||
public void testInfRadius() {
|
||||
IllegalArgumentException expected;
|
||||
|
||||
expected = expectThrows(IllegalArgumentException.class, () -> {
|
||||
LatLonPoint.newDistanceQuery("field", 18, 19, Double.POSITIVE_INFINITY);
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("radiusMeters"));
|
||||
assertTrue(expected.getMessage().contains("is invalid"));
|
||||
|
||||
expected = expectThrows(IllegalArgumentException.class, () -> {
|
||||
LatLonPoint.newDistanceQuery("field", 18, 19, Double.NEGATIVE_INFINITY);
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("radiusMeters"));
|
||||
assertTrue(expected.getMessage().contains("is invalid"));
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.lucene.document;
|
||||
|
||||
import org.apache.lucene.index.IndexReader;
|
||||
import org.apache.lucene.index.RandomIndexWriter;
|
||||
import org.apache.lucene.search.IndexSearcher;
|
||||
import org.apache.lucene.store.Directory;
|
||||
import org.apache.lucene.util.LuceneTestCase;
|
||||
|
||||
/** Simple tests for {@link LatLonPoint#newPolygonQuery} */
|
||||
public class TestLatLonPointInPolygonQuery extends LuceneTestCase {
|
||||
|
||||
/** test we can search for a polygon */
|
||||
public void testBasics() throws Exception {
|
||||
Directory dir = newDirectory();
|
||||
RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
|
||||
|
||||
// add a doc with a point
|
||||
Document document = new Document();
|
||||
document.add(new LatLonPoint("field", 18.313694, -65.227444));
|
||||
writer.addDocument(document);
|
||||
|
||||
// search and verify we found our doc
|
||||
IndexReader reader = writer.getReader();
|
||||
IndexSearcher searcher = newSearcher(reader);
|
||||
assertEquals(1, searcher.count(LatLonPoint.newPolygonQuery("field",
|
||||
new double[] { 18, 18, 19, 19, 18 },
|
||||
new double[] { -66, -65, -65, -66, -66 })));
|
||||
|
||||
reader.close();
|
||||
writer.close();
|
||||
dir.close();
|
||||
}
|
||||
}
|
|
@ -19,7 +19,6 @@ package org.apache.lucene.search;
|
|||
import org.apache.lucene.document.Document;
|
||||
import org.apache.lucene.document.LatLonPoint;
|
||||
import org.apache.lucene.spatial.util.BaseGeoPointTestCase;
|
||||
import org.apache.lucene.spatial.util.GeoRect;
|
||||
|
||||
public class TestLatLonPointQueries extends BaseGeoPointTestCase {
|
||||
|
||||
|
@ -29,8 +28,8 @@ public class TestLatLonPointQueries extends BaseGeoPointTestCase {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected Query newRectQuery(String field, GeoRect rect) {
|
||||
return LatLonPoint.newBoxQuery(field, rect.minLat, rect.maxLat, rect.minLon, rect.maxLon);
|
||||
protected Query newRectQuery(String field, double minLat, double maxLat, double minLon, double maxLon) {
|
||||
return LatLonPoint.newBoxQuery(field, minLat, maxLat, minLon, maxLon);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -128,47 +128,47 @@ public final class GeoPointField extends Field {
|
|||
|
||||
/** Creates a stored or un-stored GeoPointField
|
||||
* @param name field name
|
||||
* @param lat latitude double value [-90.0 : 90.0]
|
||||
* @param lon longitude double value [-180.0 : 180.0]
|
||||
* @param latitude latitude double value [-90.0 : 90.0]
|
||||
* @param longitude longitude double value [-180.0 : 180.0]
|
||||
* @param stored Store.YES if the content should also be stored
|
||||
* @throws IllegalArgumentException if the field name is null.
|
||||
*/
|
||||
public GeoPointField(String name, double lat, double lon, Store stored) {
|
||||
this(name, lat, lon, getFieldType(stored));
|
||||
public GeoPointField(String name, double latitude, double longitude, Store stored) {
|
||||
this(name, latitude, longitude, getFieldType(stored));
|
||||
}
|
||||
|
||||
/** Creates a stored or un-stored GeoPointField using the specified {@link TermEncoding} method
|
||||
* @param name field name
|
||||
* @param lat latitude double value [-90.0 : 90.0]
|
||||
* @param lon longitude double value [-180.0 : 180.0]
|
||||
* @param latitude latitude double value [-90.0 : 90.0]
|
||||
* @param longitude longitude double value [-180.0 : 180.0]
|
||||
* @param termEncoding encoding type to use ({@link TermEncoding#NUMERIC} Terms, or {@link TermEncoding#PREFIX} only Terms)
|
||||
* @param stored Store.YES if the content should also be stored
|
||||
* @throws IllegalArgumentException if the field name is null.
|
||||
*/
|
||||
@Deprecated
|
||||
public GeoPointField(String name, double lat, double lon, TermEncoding termEncoding, Store stored) {
|
||||
this(name, lat, lon, getFieldType(termEncoding, stored));
|
||||
public GeoPointField(String name, double latitude, double longitude, TermEncoding termEncoding, Store stored) {
|
||||
this(name, latitude, longitude, getFieldType(termEncoding, stored));
|
||||
}
|
||||
|
||||
/** Expert: allows you to customize the {@link
|
||||
* FieldType}.
|
||||
* @param name field name
|
||||
* @param lat latitude double value [-90.0 : 90.0]
|
||||
* @param lon longitude double value [-180.0 : 180.0]
|
||||
* @param latitude latitude double value [-90.0 : 90.0]
|
||||
* @param longitude longitude double value [-180.0 : 180.0]
|
||||
* @param type customized field type: must have {@link FieldType#numericType()}
|
||||
* of {@link org.apache.lucene.document.FieldType.LegacyNumericType#LONG}.
|
||||
* @throws IllegalArgumentException if the field name or type is null, or
|
||||
* if the field type does not have a LONG numericType()
|
||||
*/
|
||||
public GeoPointField(String name, double lat, double lon, FieldType type) {
|
||||
public GeoPointField(String name, double latitude, double longitude, FieldType type) {
|
||||
super(name, type);
|
||||
|
||||
if (GeoUtils.isValidLat(lat) == false) {
|
||||
throw new IllegalArgumentException("invalid lat=" + lat + " for field \"" + name + "\"");
|
||||
if (GeoUtils.isValidLat(latitude) == false) {
|
||||
throw new IllegalArgumentException("invalid latitude=" + latitude + " for field \"" + name + "\"");
|
||||
}
|
||||
|
||||
if (GeoUtils.isValidLon(lon) == false) {
|
||||
throw new IllegalArgumentException("invalid lon=" + lon + " for field \"" + name + "\"");
|
||||
if (GeoUtils.isValidLon(longitude) == false) {
|
||||
throw new IllegalArgumentException("invalid longitude=" + longitude + " for field \"" + name + "\"");
|
||||
}
|
||||
|
||||
// field must be indexed
|
||||
|
@ -190,7 +190,7 @@ public final class GeoPointField extends Field {
|
|||
}
|
||||
|
||||
// set field data
|
||||
fieldsData = GeoEncodingUtils.mortonHash(lat, lon);
|
||||
fieldsData = GeoEncodingUtils.mortonHash(latitude, longitude);
|
||||
}
|
||||
|
||||
private static FieldType getFieldType(Store stored) {
|
||||
|
|
|
@ -21,7 +21,6 @@ import org.apache.lucene.search.BooleanClause;
|
|||
import org.apache.lucene.search.BooleanQuery;
|
||||
import org.apache.lucene.search.Query;
|
||||
import org.apache.lucene.spatial.geopoint.document.GeoPointField.TermEncoding;
|
||||
import org.apache.lucene.spatial.util.GeoDistanceUtils;
|
||||
import org.apache.lucene.spatial.util.GeoRect;
|
||||
import org.apache.lucene.spatial.util.GeoUtils;
|
||||
|
||||
|
@ -49,12 +48,34 @@ public class GeoPointDistanceQuery extends GeoPointInBBoxQuery {
|
|||
/** distance (in meters) from lat, lon center location */
|
||||
protected final double radiusMeters;
|
||||
|
||||
// we must check these before passing to superclass or circleToBBox, or users can get a strange exception!
|
||||
private static double checkRadius(double radiusMeters) {
|
||||
if (Double.isFinite(radiusMeters) == false || radiusMeters < 0) {
|
||||
throw new IllegalArgumentException("invalid radiusMeters " + radiusMeters);
|
||||
}
|
||||
return radiusMeters;
|
||||
}
|
||||
|
||||
private static double checkLatitude(double centerLat) {
|
||||
if (GeoUtils.isValidLat(centerLat) == false) {
|
||||
throw new IllegalArgumentException("invalid centerLat " + centerLat);
|
||||
}
|
||||
return centerLat;
|
||||
}
|
||||
|
||||
private static double checkLongitude(double centerLon) {
|
||||
if (GeoUtils.isValidLon(centerLon) == false) {
|
||||
throw new IllegalArgumentException("invalid centerLon " + centerLon);
|
||||
}
|
||||
return centerLon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a Query for all {@link org.apache.lucene.spatial.geopoint.document.GeoPointField} types within a
|
||||
* distance (in meters) from a given point
|
||||
**/
|
||||
public GeoPointDistanceQuery(final String field, final double centerLat, final double centerLon, final double radiusMeters) {
|
||||
this(field, TermEncoding.PREFIX, centerLat, centerLon, radiusMeters);
|
||||
this(field, TermEncoding.PREFIX, checkLatitude(centerLat), checkLongitude(centerLon), checkRadius(radiusMeters));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -63,34 +84,12 @@ public class GeoPointDistanceQuery extends GeoPointInBBoxQuery {
|
|||
* {@link org.apache.lucene.spatial.geopoint.document.GeoPointField.TermEncoding} parameter
|
||||
**/
|
||||
public GeoPointDistanceQuery(final String field, final TermEncoding termEncoding, final double centerLat, final double centerLon, final double radiusMeters) {
|
||||
this(field, termEncoding, GeoUtils.circleToBBox(centerLat, centerLon, radiusMeters), centerLat, centerLon, radiusMeters);
|
||||
this(field, termEncoding, GeoUtils.circleToBBox(checkLatitude(centerLat), checkLongitude(centerLon), checkRadius(radiusMeters)), centerLat, centerLon, radiusMeters);
|
||||
}
|
||||
|
||||
private GeoPointDistanceQuery(final String field, final TermEncoding termEncoding, final GeoRect bbox,
|
||||
final double centerLat, final double centerLon, final double radiusMeters) {
|
||||
super(field, termEncoding, bbox.minLat, bbox.maxLat, bbox.minLon, bbox.maxLon);
|
||||
{
|
||||
// check longitudinal overlap (restrict distance to maximum longitudinal radius)
|
||||
// todo this restriction technically shouldn't be needed,
|
||||
// its only purpose is to ensure the bounding box doesn't self overlap.
|
||||
final double maxRadius = GeoDistanceUtils.maxRadialDistanceMeters(centerLat, centerLon);
|
||||
if (radiusMeters > maxRadius) {
|
||||
throw new IllegalArgumentException("radiusMeters " + radiusMeters + " exceeds maxRadius [" + maxRadius
|
||||
+ "] at location [" + centerLat + " " + centerLon + "]");
|
||||
}
|
||||
}
|
||||
|
||||
if (GeoUtils.isValidLat(centerLat) == false) {
|
||||
throw new IllegalArgumentException("invalid centerLat " + centerLat);
|
||||
}
|
||||
|
||||
if (GeoUtils.isValidLon(centerLon) == false) {
|
||||
throw new IllegalArgumentException("invalid centerLon " + centerLon);
|
||||
}
|
||||
|
||||
if (radiusMeters <= 0.0) {
|
||||
throw new IllegalArgumentException("invalid radiusMeters " + radiusMeters);
|
||||
}
|
||||
|
||||
this.centerLat = centerLat;
|
||||
this.centerLon = centerLon;
|
||||
|
|
|
@ -71,6 +71,24 @@ public class GeoPointInBBoxQuery extends Query {
|
|||
* defined bounding box. Accepts optional {@link org.apache.lucene.spatial.geopoint.document.GeoPointField.TermEncoding} parameter
|
||||
*/
|
||||
public GeoPointInBBoxQuery(final String field, final TermEncoding termEncoding, final double minLat, final double maxLat, final double minLon, final double maxLon) {
|
||||
if (field == null) {
|
||||
throw new IllegalArgumentException("field cannot be null");
|
||||
}
|
||||
if (termEncoding == null) {
|
||||
throw new IllegalArgumentException("termEncoding cannot be null");
|
||||
}
|
||||
if (GeoUtils.isValidLat(minLat) == false) {
|
||||
throw new IllegalArgumentException("invalid minimum latitude: " + minLat + ", must be -90 to 90");
|
||||
}
|
||||
if (GeoUtils.isValidLat(maxLat) == false) {
|
||||
throw new IllegalArgumentException("invalid maximum latitude: " + maxLat + ", must be -90 to 90");
|
||||
}
|
||||
if (GeoUtils.isValidLon(minLon) == false) {
|
||||
throw new IllegalArgumentException("invalid minimum longitude: " + minLon + ", must be -180 to 180");
|
||||
}
|
||||
if (GeoUtils.isValidLon(maxLon) == false) {
|
||||
throw new IllegalArgumentException("invalid maximum longitude: " + maxLon + ", must be -180 to 180");
|
||||
}
|
||||
this.field = field;
|
||||
this.minLat = minLat;
|
||||
this.maxLat = maxLat;
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.lucene.spatial.util;
|
||||
|
||||
import org.apache.lucene.util.SloppyMath;
|
||||
|
||||
/**
|
||||
* Reusable geo-spatial distance utility methods.
|
||||
*
|
||||
* @lucene.experimental
|
||||
*/
|
||||
public class GeoDistanceUtils {
|
||||
|
||||
// No instance:
|
||||
private GeoDistanceUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the inverse haversine to determine distance in degrees longitude for provided distance in meters
|
||||
* @param lat latitude to compute delta degrees lon
|
||||
* @param distance distance in meters to convert to degrees lon
|
||||
* @return Sloppy distance in degrees longitude for provided distance in meters
|
||||
*/
|
||||
public static double distanceToDegreesLon(double lat, double distance) {
|
||||
// convert latitude to radians
|
||||
lat = StrictMath.toRadians(lat);
|
||||
|
||||
// get the diameter at the latitude
|
||||
final double diameter = 2 * GeoUtils.SEMIMAJOR_AXIS;
|
||||
|
||||
// compute inverse haversine
|
||||
double a = StrictMath.sin(distance/diameter);
|
||||
double h = StrictMath.min(1, a);
|
||||
h *= h;
|
||||
double cLat = StrictMath.cos(lat);
|
||||
|
||||
return StrictMath.toDegrees(StrictMath.acos(1-((2d*h)/(cLat*cLat))));
|
||||
}
|
||||
|
||||
/** Returns the maximum distance/radius (in meters) from the point 'center' before overlapping */
|
||||
public static double maxRadialDistanceMeters(final double centerLat, final double centerLon) {
|
||||
if (Math.abs(centerLat) == GeoUtils.MAX_LAT_INCL) {
|
||||
return SloppyMath.haversinMeters(centerLat, centerLon, 0, centerLon);
|
||||
}
|
||||
return SloppyMath.haversinMeters(centerLat, centerLon, centerLat, (GeoUtils.MAX_LON_INCL + centerLon) % 360);
|
||||
}
|
||||
}
|
|
@ -1,255 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.lucene.spatial.geopoint.search;
|
||||
|
||||
import org.apache.lucene.analysis.MockAnalyzer;
|
||||
import org.apache.lucene.document.Document;
|
||||
import org.apache.lucene.document.Field;
|
||||
import org.apache.lucene.document.StringField;
|
||||
import org.apache.lucene.index.IndexReader;
|
||||
import org.apache.lucene.index.RandomIndexWriter;
|
||||
import org.apache.lucene.search.IndexSearcher;
|
||||
import org.apache.lucene.search.TopDocs;
|
||||
import org.apache.lucene.spatial.geopoint.document.GeoPointField;
|
||||
import org.apache.lucene.spatial.geopoint.document.GeoPointField.TermEncoding;
|
||||
import org.apache.lucene.spatial.util.GeoRelationUtils;
|
||||
import org.apache.lucene.spatial.util.GeoUtils;
|
||||
import org.apache.lucene.store.Directory;
|
||||
import org.apache.lucene.util.LuceneTestCase;
|
||||
import org.apache.lucene.util.TestUtil;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
|
||||
/** Simple tests for GeoPoint */
|
||||
public class TestGeoPointField extends LuceneTestCase {
|
||||
|
||||
private static Directory directory = null;
|
||||
private static IndexReader reader = null;
|
||||
private static IndexSearcher searcher = null;
|
||||
private static final String FIELD_NAME = "point";
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
directory = newDirectory();
|
||||
|
||||
RandomIndexWriter writer = new RandomIndexWriter(random(), directory,
|
||||
newIndexWriterConfig(new MockAnalyzer(random()))
|
||||
.setMaxBufferedDocs(TestUtil.nextInt(random(), 100, 1000))
|
||||
.setMergePolicy(newLogMergePolicy()));
|
||||
|
||||
// this is a simple systematic test
|
||||
GeoPointField[] pts = new GeoPointField[] {
|
||||
new GeoPointField(FIELD_NAME, 32.763420, -96.774, GeoPointField.PREFIX_TYPE_NOT_STORED),
|
||||
new GeoPointField(FIELD_NAME, 32.7559529921407, -96.7759895324707, GeoPointField.PREFIX_TYPE_NOT_STORED),
|
||||
new GeoPointField(FIELD_NAME, 32.77866942010977, -96.77701950073242, GeoPointField.PREFIX_TYPE_NOT_STORED),
|
||||
new GeoPointField(FIELD_NAME, 32.7756745755423, -96.7706036567688, GeoPointField.PREFIX_TYPE_NOT_STORED),
|
||||
new GeoPointField(FIELD_NAME, 27.703618681345585, -139.73458170890808, GeoPointField.PREFIX_TYPE_NOT_STORED),
|
||||
new GeoPointField(FIELD_NAME, 32.94823588839368, -96.4538113027811, GeoPointField.PREFIX_TYPE_NOT_STORED),
|
||||
new GeoPointField(FIELD_NAME, 33.06047141970814, -96.65084838867188, GeoPointField.PREFIX_TYPE_NOT_STORED),
|
||||
new GeoPointField(FIELD_NAME, 32.778650, -96.7772, GeoPointField.PREFIX_TYPE_NOT_STORED),
|
||||
new GeoPointField(FIELD_NAME, -88.56029371730983, -177.23537676036358, GeoPointField.PREFIX_TYPE_NOT_STORED),
|
||||
new GeoPointField(FIELD_NAME, 33.541429799076354, -26.779373834241003, GeoPointField.PREFIX_TYPE_NOT_STORED),
|
||||
new GeoPointField(FIELD_NAME, 26.774024500421728, -77.35379276106497, GeoPointField.PREFIX_TYPE_NOT_STORED),
|
||||
new GeoPointField(FIELD_NAME, -90.0, -14.796283808944777, GeoPointField.PREFIX_TYPE_NOT_STORED),
|
||||
new GeoPointField(FIELD_NAME, 32.94823588839368, -178.8538113027811, GeoPointField.PREFIX_TYPE_NOT_STORED),
|
||||
new GeoPointField(FIELD_NAME, 32.94823588839368, 178.8538113027811, GeoPointField.PREFIX_TYPE_NOT_STORED),
|
||||
new GeoPointField(FIELD_NAME, 40.720611, -73.998776, GeoPointField.PREFIX_TYPE_NOT_STORED),
|
||||
new GeoPointField(FIELD_NAME, -44.5, -179.5, GeoPointField.PREFIX_TYPE_NOT_STORED)};
|
||||
|
||||
for (GeoPointField p : pts) {
|
||||
Document doc = new Document();
|
||||
doc.add(p);
|
||||
writer.addDocument(doc);
|
||||
}
|
||||
|
||||
// add explicit multi-valued docs
|
||||
for (int i=0; i<pts.length; i+=2) {
|
||||
Document doc = new Document();
|
||||
doc.add(pts[i]);
|
||||
doc.add(pts[i+1]);
|
||||
writer.addDocument(doc);
|
||||
}
|
||||
|
||||
// index random string documents
|
||||
for (int i=0; i<random().nextInt(10); ++i) {
|
||||
Document doc = new Document();
|
||||
doc.add(new StringField("string", Integer.toString(i), Field.Store.NO));
|
||||
writer.addDocument(doc);
|
||||
}
|
||||
|
||||
reader = writer.getReader();
|
||||
searcher = newSearcher(reader);
|
||||
writer.close();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClass() throws Exception {
|
||||
searcher = null;
|
||||
reader.close();
|
||||
reader = null;
|
||||
directory.close();
|
||||
directory = null;
|
||||
}
|
||||
|
||||
private TopDocs bboxQuery(double minLat, double maxLat, double minLon, double maxLon, int limit) throws Exception {
|
||||
GeoPointInBBoxQuery q = new GeoPointInBBoxQuery(FIELD_NAME, TermEncoding.PREFIX, minLat, maxLat, minLon, maxLon);
|
||||
return searcher.search(q, limit);
|
||||
}
|
||||
|
||||
private TopDocs polygonQuery(double[] polyLats, double[] polyLons, int limit) throws Exception {
|
||||
GeoPointInPolygonQuery q = new GeoPointInPolygonQuery(FIELD_NAME, TermEncoding.PREFIX, polyLats, polyLons);
|
||||
return searcher.search(q, limit);
|
||||
}
|
||||
|
||||
private TopDocs geoDistanceQuery(double lat, double lon, double radius, int limit) throws Exception {
|
||||
GeoPointDistanceQuery q = new GeoPointDistanceQuery(FIELD_NAME, TermEncoding.PREFIX, lat, lon, radius);
|
||||
return searcher.search(q, limit);
|
||||
}
|
||||
|
||||
private TopDocs geoDistanceRangeQuery(double lat, double lon, double minRadius, double maxRadius, int limit)
|
||||
throws Exception {
|
||||
GeoPointDistanceRangeQuery q = new GeoPointDistanceRangeQuery(FIELD_NAME, TermEncoding.PREFIX, lat, lon, minRadius, maxRadius);
|
||||
return searcher.search(q, limit);
|
||||
}
|
||||
|
||||
public void testBBoxQuery() throws Exception {
|
||||
TopDocs td = bboxQuery(32.778650, 32.778950, -96.7772, -96.77690000, 5);
|
||||
assertEquals("GeoBoundingBoxQuery failed", 4, td.totalHits);
|
||||
}
|
||||
|
||||
public void testPolyQuery() throws Exception {
|
||||
TopDocs td = polygonQuery(
|
||||
new double[]{33.073130, 32.9942669, 32.938386, 33.0374494,
|
||||
33.1369762, 33.1162747, 33.073130, 33.073130},
|
||||
new double[]{-96.7682647, -96.8280029, -96.6288757, -96.4929199,
|
||||
-96.6041564, -96.7449188, -96.76826477, -96.7682647},
|
||||
5);
|
||||
assertEquals("GeoPolygonQuery failed", 2, td.totalHits);
|
||||
}
|
||||
|
||||
public void testPacManPolyQuery() throws Exception {
|
||||
// pacman
|
||||
double[] px = {0, 10, 10, 0, -8, -10, -8, 0, 10, 10, 0};
|
||||
double[] py = {0, 5, 9, 10, 9, 0, -9, -10, -9, -5, 0};
|
||||
|
||||
// shape bbox
|
||||
double xMinA = -10;
|
||||
double xMaxA = 10;
|
||||
double yMinA = -10;
|
||||
double yMaxA = 10;
|
||||
|
||||
// candidate crosses cell
|
||||
double xMin = 2;//-5;
|
||||
double xMax = 11;//0.000001;
|
||||
double yMin = -1;//0;
|
||||
double yMax = 1;//5;
|
||||
|
||||
// test cell crossing poly
|
||||
assertTrue(GeoRelationUtils.rectCrossesPolyApprox(yMin, yMax, xMin, yMax, py, px, yMinA, yMaxA, xMinA, xMaxA));
|
||||
assertFalse(GeoRelationUtils.rectCrossesPolyApprox(0, 5, -5, 0.000001, py, px, yMin, yMax, xMin, xMax));
|
||||
assertTrue(GeoRelationUtils.rectWithinPolyApprox(0, 5, -5, -2, py, px, yMin, yMax, xMin, xMax));
|
||||
}
|
||||
|
||||
public void testBBoxCrossDateline() throws Exception {
|
||||
TopDocs td = bboxQuery(-45.0, -44.0, 179.0, -179.0, 20);
|
||||
assertEquals("BBoxCrossDateline query failed", 2, td.totalHits);
|
||||
}
|
||||
|
||||
public void testWholeMap() throws Exception {
|
||||
TopDocs td = bboxQuery(GeoUtils.MIN_LAT_INCL, GeoUtils.MAX_LAT_INCL, GeoUtils.MIN_LON_INCL, GeoUtils.MAX_LON_INCL, 20);
|
||||
assertEquals("testWholeMap failed", 24, td.totalHits);
|
||||
td = polygonQuery(new double[] {GeoUtils.MIN_LAT_INCL, GeoUtils.MAX_LAT_INCL, GeoUtils.MAX_LAT_INCL, GeoUtils.MIN_LAT_INCL, GeoUtils.MIN_LAT_INCL},
|
||||
new double[] {GeoUtils.MIN_LON_INCL, GeoUtils.MIN_LON_INCL, GeoUtils.MAX_LON_INCL, GeoUtils.MAX_LON_INCL, GeoUtils.MIN_LON_INCL},
|
||||
20); assertEquals("testWholeMap failed", 24, td.totalHits);
|
||||
}
|
||||
|
||||
public void smallTest() throws Exception {
|
||||
TopDocs td = geoDistanceQuery(40.720611, -73.998776, 1, 20);
|
||||
assertEquals("smallTest failed", 2, td.totalHits);
|
||||
}
|
||||
|
||||
// GeoBoundingBox should not accept invalid lat/lon
|
||||
public void testInvalidBBox() throws Exception {
|
||||
expectThrows(Exception.class, () -> {
|
||||
bboxQuery(-92.0, -91.0, 179.0, 181.0, 20);
|
||||
});
|
||||
}
|
||||
|
||||
public void testGeoDistanceQuery() throws Exception {
|
||||
TopDocs td = geoDistanceQuery(32.94823588839368, -96.4538113027811, 6000, 20);
|
||||
assertEquals("GeoDistanceQuery failed", 2, td.totalHits);
|
||||
}
|
||||
|
||||
/** see https://issues.apache.org/jira/browse/LUCENE-6905 */
|
||||
public void testNonEmptyTermsEnum() throws Exception {
|
||||
TopDocs td = geoDistanceQuery(-88.56029371730983, -177.23537676036358, 7757.999232959935, 20);
|
||||
assertEquals("GeoDistanceQuery failed", 2, td.totalHits);
|
||||
}
|
||||
|
||||
public void testMultiValuedQuery() throws Exception {
|
||||
TopDocs td = bboxQuery(32.7559529921407, 32.7756745755423, -96.4538113027811, -96.7706036567688, 20);
|
||||
// 3 single valued docs + 2 multi-valued docs
|
||||
assertEquals("testMultiValuedQuery failed", 5, td.totalHits);
|
||||
}
|
||||
|
||||
public void testTooBigRadius() throws Exception {
|
||||
IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
|
||||
geoDistanceQuery(85.0, 0.0, 4000000, 20);
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("exceeds maxRadius"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly large
|
||||
*/
|
||||
public void testGeoDistanceQueryHuge() throws Exception {
|
||||
TopDocs td = geoDistanceQuery(32.94823588839368, -96.4538113027811, 6000000, 20);
|
||||
assertEquals("GeoDistanceQuery failed", 16, td.totalHits);
|
||||
}
|
||||
|
||||
public void testGeoDistanceQueryCrossDateline() throws Exception {
|
||||
TopDocs td = geoDistanceQuery(32.94823588839368, -179.9538113027811, 120000, 20);
|
||||
assertEquals("GeoDistanceQuery failed", 3, td.totalHits);
|
||||
}
|
||||
|
||||
// GeoDistanceQuery should not accept invalid lat/lon as origin
|
||||
public void testInvalidGeoDistanceQuery() throws Exception {
|
||||
expectThrows(Exception.class, () -> {
|
||||
geoDistanceQuery(92.0, 181.0, 120000, 20);
|
||||
});
|
||||
}
|
||||
|
||||
public void testMaxDistanceRangeQuery() throws Exception {
|
||||
TopDocs td = geoDistanceRangeQuery(0.0, 0.0, 10, 20000000, 20);
|
||||
assertEquals("GeoDistanceRangeQuery failed", 24, td.totalHits);
|
||||
}
|
||||
|
||||
public void testInvalidLatLon() throws Exception {
|
||||
IllegalArgumentException e;
|
||||
e= expectThrows(IllegalArgumentException.class,
|
||||
() -> {
|
||||
new GeoPointField("field", 180.0, 0.0, Field.Store.NO);
|
||||
});
|
||||
assertEquals("invalid lat=180.0 for field \"field\"", e.getMessage());
|
||||
|
||||
e = expectThrows(IllegalArgumentException.class,
|
||||
() -> {
|
||||
new GeoPointField("field", 0.0, 190.0, Field.Store.NO);
|
||||
});
|
||||
assertEquals("invalid lon=190.0 for field \"field\"", e.getMessage());
|
||||
}
|
||||
}
|
|
@ -22,8 +22,6 @@ import org.apache.lucene.spatial.util.GeoEncodingUtils;
|
|||
import org.apache.lucene.spatial.geopoint.document.GeoPointField;
|
||||
import org.apache.lucene.spatial.geopoint.document.GeoPointField.TermEncoding;
|
||||
import org.apache.lucene.spatial.util.BaseGeoPointTestCase;
|
||||
import org.apache.lucene.spatial.util.GeoDistanceUtils;
|
||||
import org.apache.lucene.spatial.util.GeoRect;
|
||||
|
||||
/**
|
||||
* random testing for GeoPoint query logic
|
||||
|
@ -32,12 +30,6 @@ import org.apache.lucene.spatial.util.GeoRect;
|
|||
*/
|
||||
public class TestGeoPointQuery extends BaseGeoPointTestCase {
|
||||
|
||||
@Override
|
||||
protected double maxRadius(double latitude, double longitude) {
|
||||
// TODO: clean this up
|
||||
return GeoDistanceUtils.maxRadialDistanceMeters(latitude, longitude);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double quantizeLat(double lat) {
|
||||
return GeoEncodingUtils.mortonUnhashLat(GeoEncodingUtils.mortonHash(lat, 0));
|
||||
|
@ -54,8 +46,8 @@ public class TestGeoPointQuery extends BaseGeoPointTestCase {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected Query newRectQuery(String field, GeoRect rect) {
|
||||
return new GeoPointInBBoxQuery(field, TermEncoding.PREFIX, rect.minLat, rect.maxLat, rect.minLon, rect.maxLon);
|
||||
protected Query newRectQuery(String field, double minLat, double maxLat, double minLon, double maxLon) {
|
||||
return new GeoPointInBBoxQuery(field, TermEncoding.PREFIX, minLat, maxLat, minLon, maxLon);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,242 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.lucene.spatial.geopoint.search;
|
||||
|
||||
import org.apache.lucene.analysis.MockAnalyzer;
|
||||
import org.apache.lucene.document.Document;
|
||||
import org.apache.lucene.document.Field;
|
||||
import org.apache.lucene.document.StringField;
|
||||
import org.apache.lucene.index.IndexReader;
|
||||
import org.apache.lucene.index.RandomIndexWriter;
|
||||
import org.apache.lucene.search.IndexSearcher;
|
||||
import org.apache.lucene.search.TopDocs;
|
||||
import org.apache.lucene.spatial.geopoint.document.GeoPointField;
|
||||
import org.apache.lucene.spatial.geopoint.document.GeoPointField.TermEncoding;
|
||||
import org.apache.lucene.spatial.util.GeoRelationUtils;
|
||||
import org.apache.lucene.spatial.util.GeoUtils;
|
||||
import org.apache.lucene.store.Directory;
|
||||
import org.apache.lucene.util.LuceneTestCase;
|
||||
import org.apache.lucene.util.TestUtil;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
|
||||
/** Simple tests for GeoPoint with legacy numeric encoding
|
||||
* @deprecated remove this when TermEncoding.NUMERIC is removed */
|
||||
@Deprecated
|
||||
public class TestLegacyGeoPointField extends LuceneTestCase {
|
||||
|
||||
private static Directory directory = null;
|
||||
private static IndexReader reader = null;
|
||||
private static IndexSearcher searcher = null;
|
||||
private static final String FIELD_NAME = "point";
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
directory = newDirectory();
|
||||
|
||||
RandomIndexWriter writer = new RandomIndexWriter(random(), directory,
|
||||
newIndexWriterConfig(new MockAnalyzer(random()))
|
||||
.setMaxBufferedDocs(TestUtil.nextInt(random(), 100, 1000))
|
||||
.setMergePolicy(newLogMergePolicy()));
|
||||
|
||||
// this is a simple systematic test
|
||||
GeoPointField[] pts = new GeoPointField[] {
|
||||
new GeoPointField(FIELD_NAME, 32.763420, -96.774, GeoPointField.NUMERIC_TYPE_NOT_STORED),
|
||||
new GeoPointField(FIELD_NAME, 32.7559529921407, -96.7759895324707, GeoPointField.NUMERIC_TYPE_NOT_STORED),
|
||||
new GeoPointField(FIELD_NAME, 32.77866942010977, -96.77701950073242, GeoPointField.NUMERIC_TYPE_NOT_STORED),
|
||||
new GeoPointField(FIELD_NAME, 32.7756745755423, -96.7706036567688, GeoPointField.NUMERIC_TYPE_NOT_STORED),
|
||||
new GeoPointField(FIELD_NAME, 27.703618681345585, -139.73458170890808, GeoPointField.NUMERIC_TYPE_NOT_STORED),
|
||||
new GeoPointField(FIELD_NAME, 32.94823588839368, -96.4538113027811, GeoPointField.NUMERIC_TYPE_NOT_STORED),
|
||||
new GeoPointField(FIELD_NAME, 33.06047141970814, -96.65084838867188, GeoPointField.NUMERIC_TYPE_NOT_STORED),
|
||||
new GeoPointField(FIELD_NAME, 32.778650, -96.7772, GeoPointField.NUMERIC_TYPE_NOT_STORED),
|
||||
new GeoPointField(FIELD_NAME, -88.56029371730983, -177.23537676036358, GeoPointField.NUMERIC_TYPE_NOT_STORED),
|
||||
new GeoPointField(FIELD_NAME, 33.541429799076354, -26.779373834241003, GeoPointField.NUMERIC_TYPE_NOT_STORED),
|
||||
new GeoPointField(FIELD_NAME, 26.774024500421728, -77.35379276106497, GeoPointField.NUMERIC_TYPE_NOT_STORED),
|
||||
new GeoPointField(FIELD_NAME, -90.0, -14.796283808944777, GeoPointField.NUMERIC_TYPE_NOT_STORED),
|
||||
new GeoPointField(FIELD_NAME, 32.94823588839368, -178.8538113027811, GeoPointField.NUMERIC_TYPE_NOT_STORED),
|
||||
new GeoPointField(FIELD_NAME, 32.94823588839368, 178.8538113027811, GeoPointField.NUMERIC_TYPE_NOT_STORED),
|
||||
new GeoPointField(FIELD_NAME, 40.720611, -73.998776, GeoPointField.NUMERIC_TYPE_NOT_STORED),
|
||||
new GeoPointField(FIELD_NAME, -44.5, -179.5, GeoPointField.NUMERIC_TYPE_NOT_STORED)};
|
||||
|
||||
for (GeoPointField p : pts) {
|
||||
Document doc = new Document();
|
||||
doc.add(p);
|
||||
writer.addDocument(doc);
|
||||
}
|
||||
|
||||
// add explicit multi-valued docs
|
||||
for (int i=0; i<pts.length; i+=2) {
|
||||
Document doc = new Document();
|
||||
doc.add(pts[i]);
|
||||
doc.add(pts[i+1]);
|
||||
writer.addDocument(doc);
|
||||
}
|
||||
|
||||
// index random string documents
|
||||
for (int i=0; i<random().nextInt(10); ++i) {
|
||||
Document doc = new Document();
|
||||
doc.add(new StringField("string", Integer.toString(i), Field.Store.NO));
|
||||
writer.addDocument(doc);
|
||||
}
|
||||
|
||||
reader = writer.getReader();
|
||||
searcher = newSearcher(reader);
|
||||
writer.close();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClass() throws Exception {
|
||||
searcher = null;
|
||||
reader.close();
|
||||
reader = null;
|
||||
directory.close();
|
||||
directory = null;
|
||||
}
|
||||
|
||||
private TopDocs bboxQuery(double minLat, double maxLat, double minLon, double maxLon, int limit) throws Exception {
|
||||
GeoPointInBBoxQuery q = new GeoPointInBBoxQuery(FIELD_NAME, TermEncoding.NUMERIC, minLat, maxLat, minLon, maxLon);
|
||||
return searcher.search(q, limit);
|
||||
}
|
||||
|
||||
private TopDocs polygonQuery(double[] polyLats, double[] polyLons, int limit) throws Exception {
|
||||
GeoPointInPolygonQuery q = new GeoPointInPolygonQuery(FIELD_NAME, TermEncoding.NUMERIC, polyLats, polyLons);
|
||||
return searcher.search(q, limit);
|
||||
}
|
||||
|
||||
private TopDocs geoDistanceQuery(double lat, double lon, double radius, int limit) throws Exception {
|
||||
GeoPointDistanceQuery q = new GeoPointDistanceQuery(FIELD_NAME, TermEncoding.NUMERIC, lat, lon, radius);
|
||||
return searcher.search(q, limit);
|
||||
}
|
||||
|
||||
private TopDocs geoDistanceRangeQuery(double lat, double lon, double minRadius, double maxRadius, int limit)
|
||||
throws Exception {
|
||||
GeoPointDistanceRangeQuery q = new GeoPointDistanceRangeQuery(FIELD_NAME, TermEncoding.NUMERIC, lat, lon, minRadius, maxRadius);
|
||||
return searcher.search(q, limit);
|
||||
}
|
||||
|
||||
public void testBBoxQuery() throws Exception {
|
||||
TopDocs td = bboxQuery(32.778650, 32.778950, -96.7772, -96.77690000, 5);
|
||||
assertEquals("GeoBoundingBoxQuery failed", 4, td.totalHits);
|
||||
}
|
||||
|
||||
public void testPolyQuery() throws Exception {
|
||||
TopDocs td = polygonQuery(
|
||||
new double[]{33.073130, 32.9942669, 32.938386, 33.0374494,
|
||||
33.1369762, 33.1162747, 33.073130, 33.073130},
|
||||
new double[]{-96.7682647, -96.8280029, -96.6288757, -96.4929199,
|
||||
-96.6041564, -96.7449188, -96.76826477, -96.7682647},
|
||||
5);
|
||||
assertEquals("GeoPolygonQuery failed", 2, td.totalHits);
|
||||
}
|
||||
|
||||
public void testPacManPolyQuery() throws Exception {
|
||||
// pacman
|
||||
double[] px = {0, 10, 10, 0, -8, -10, -8, 0, 10, 10, 0};
|
||||
double[] py = {0, 5, 9, 10, 9, 0, -9, -10, -9, -5, 0};
|
||||
|
||||
// shape bbox
|
||||
double xMinA = -10;
|
||||
double xMaxA = 10;
|
||||
double yMinA = -10;
|
||||
double yMaxA = 10;
|
||||
|
||||
// candidate crosses cell
|
||||
double xMin = 2;//-5;
|
||||
double xMax = 11;//0.000001;
|
||||
double yMin = -1;//0;
|
||||
double yMax = 1;//5;
|
||||
|
||||
// test cell crossing poly
|
||||
assertTrue(GeoRelationUtils.rectCrossesPolyApprox(yMin, yMax, xMin, yMax, py, px, yMinA, yMaxA, xMinA, xMaxA));
|
||||
assertFalse(GeoRelationUtils.rectCrossesPolyApprox(0, 5, -5, 0.000001, py, px, yMin, yMax, xMin, xMax));
|
||||
assertTrue(GeoRelationUtils.rectWithinPolyApprox(0, 5, -5, -2, py, px, yMin, yMax, xMin, xMax));
|
||||
}
|
||||
|
||||
public void testBBoxCrossDateline() throws Exception {
|
||||
TopDocs td = bboxQuery(-45.0, -44.0, 179.0, -179.0, 20);
|
||||
assertEquals("BBoxCrossDateline query failed", 2, td.totalHits);
|
||||
}
|
||||
|
||||
public void testWholeMap() throws Exception {
|
||||
TopDocs td = bboxQuery(GeoUtils.MIN_LAT_INCL, GeoUtils.MAX_LAT_INCL, GeoUtils.MIN_LON_INCL, GeoUtils.MAX_LON_INCL, 20);
|
||||
assertEquals("testWholeMap failed", 24, td.totalHits);
|
||||
td = polygonQuery(new double[] {GeoUtils.MIN_LAT_INCL, GeoUtils.MAX_LAT_INCL, GeoUtils.MAX_LAT_INCL, GeoUtils.MIN_LAT_INCL, GeoUtils.MIN_LAT_INCL},
|
||||
new double[] {GeoUtils.MIN_LON_INCL, GeoUtils.MIN_LON_INCL, GeoUtils.MAX_LON_INCL, GeoUtils.MAX_LON_INCL, GeoUtils.MIN_LON_INCL},
|
||||
20); assertEquals("testWholeMap failed", 24, td.totalHits);
|
||||
}
|
||||
|
||||
public void smallTest() throws Exception {
|
||||
TopDocs td = geoDistanceQuery(40.720611, -73.998776, 1, 20);
|
||||
assertEquals("smallTest failed", 2, td.totalHits);
|
||||
}
|
||||
|
||||
// GeoBoundingBox should not accept invalid lat/lon
|
||||
public void testInvalidBBox() throws Exception {
|
||||
expectThrows(Exception.class, () -> {
|
||||
bboxQuery(-92.0, -91.0, 179.0, 181.0, 20);
|
||||
});
|
||||
}
|
||||
|
||||
public void testGeoDistanceQuery() throws Exception {
|
||||
TopDocs td = geoDistanceQuery(32.94823588839368, -96.4538113027811, 6000, 20);
|
||||
assertEquals("GeoDistanceQuery failed", 2, td.totalHits);
|
||||
}
|
||||
|
||||
/** see https://issues.apache.org/jira/browse/LUCENE-6905 */
|
||||
public void testNonEmptyTermsEnum() throws Exception {
|
||||
TopDocs td = geoDistanceQuery(-88.56029371730983, -177.23537676036358, 7757.999232959935, 20);
|
||||
assertEquals("GeoDistanceQuery failed", 2, td.totalHits);
|
||||
}
|
||||
|
||||
public void testMultiValuedQuery() throws Exception {
|
||||
TopDocs td = bboxQuery(32.7559529921407, 32.7756745755423, -96.4538113027811, -96.7706036567688, 20);
|
||||
// 3 single valued docs + 2 multi-valued docs
|
||||
assertEquals("testMultiValuedQuery failed", 5, td.totalHits);
|
||||
}
|
||||
|
||||
public void testTooBigRadius() throws Exception {
|
||||
IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
|
||||
geoDistanceQuery(85.0, 0.0, 4000000, 20);
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("exceeds maxRadius"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly large
|
||||
*/
|
||||
public void testGeoDistanceQueryHuge() throws Exception {
|
||||
TopDocs td = geoDistanceQuery(32.94823588839368, -96.4538113027811, 6000000, 20);
|
||||
assertEquals("GeoDistanceQuery failed", 16, td.totalHits);
|
||||
}
|
||||
|
||||
public void testGeoDistanceQueryCrossDateline() throws Exception {
|
||||
TopDocs td = geoDistanceQuery(32.94823588839368, -179.9538113027811, 120000, 20);
|
||||
assertEquals("GeoDistanceQuery failed", 3, td.totalHits);
|
||||
}
|
||||
|
||||
// GeoDistanceQuery should not accept invalid lat/lon as origin
|
||||
public void testInvalidGeoDistanceQuery() throws Exception {
|
||||
expectThrows(Exception.class, () -> {
|
||||
geoDistanceQuery(92.0, 181.0, 120000, 20);
|
||||
});
|
||||
}
|
||||
|
||||
public void testMaxDistanceRangeQuery() throws Exception {
|
||||
TopDocs td = geoDistanceRangeQuery(0.0, 0.0, 10, 20000000, 20);
|
||||
assertEquals("GeoDistanceRangeQuery failed", 24, td.totalHits);
|
||||
}
|
||||
}
|
|
@ -22,8 +22,6 @@ import org.apache.lucene.spatial.util.GeoEncodingUtils;
|
|||
import org.apache.lucene.spatial.geopoint.document.GeoPointField;
|
||||
import org.apache.lucene.spatial.geopoint.document.GeoPointField.TermEncoding;
|
||||
import org.apache.lucene.spatial.util.BaseGeoPointTestCase;
|
||||
import org.apache.lucene.spatial.util.GeoDistanceUtils;
|
||||
import org.apache.lucene.spatial.util.GeoRect;
|
||||
|
||||
/**
|
||||
* random testing for GeoPoint query logic (with deprecated numeric encoding)
|
||||
|
@ -32,12 +30,6 @@ import org.apache.lucene.spatial.util.GeoRect;
|
|||
@Deprecated
|
||||
public class TestLegacyGeoPointQuery extends BaseGeoPointTestCase {
|
||||
|
||||
@Override
|
||||
protected double maxRadius(double latitude, double longitude) {
|
||||
// TODO: clean this up
|
||||
return GeoDistanceUtils.maxRadialDistanceMeters(latitude, longitude);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double quantizeLat(double lat) {
|
||||
return GeoEncodingUtils.mortonUnhashLat(GeoEncodingUtils.mortonHash(lat, 0));
|
||||
|
@ -54,8 +46,8 @@ public class TestLegacyGeoPointQuery extends BaseGeoPointTestCase {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected Query newRectQuery(String field, GeoRect rect) {
|
||||
return new GeoPointInBBoxQuery(field, TermEncoding.NUMERIC, rect.minLat, rect.maxLat, rect.minLon, rect.maxLon);
|
||||
protected Query newRectQuery(String field, double minLat, double maxLat, double minLon, double maxLon) {
|
||||
return new GeoPointInBBoxQuery(field, TermEncoding.NUMERIC, minLat, maxLat, minLon, maxLon);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -29,6 +29,7 @@ import java.util.Set;
|
|||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.apache.lucene.analysis.MockAnalyzer;
|
||||
import org.apache.lucene.codecs.FilterCodec;
|
||||
import org.apache.lucene.codecs.PointsFormat;
|
||||
import org.apache.lucene.codecs.PointsReader;
|
||||
|
@ -39,6 +40,7 @@ import org.apache.lucene.document.Document;
|
|||
import org.apache.lucene.document.Field;
|
||||
import org.apache.lucene.document.NumericDocValuesField;
|
||||
import org.apache.lucene.document.StoredField;
|
||||
import org.apache.lucene.document.StringField;
|
||||
import org.apache.lucene.index.DirectoryReader;
|
||||
import org.apache.lucene.index.IndexReader;
|
||||
import org.apache.lucene.index.IndexWriter;
|
||||
|
@ -65,8 +67,17 @@ import org.apache.lucene.util.TestUtil;
|
|||
import org.apache.lucene.util.bkd.BKDWriter;
|
||||
import org.junit.BeforeClass;
|
||||
|
||||
// TODO: cutover TestGeoUtils too?
|
||||
|
||||
/**
|
||||
* Abstract class to do basic tests for a geospatial impl (high level
|
||||
* fields and queries)
|
||||
* NOTE: This test focuses on geospatial (distance queries, polygon
|
||||
* queries, etc) indexing and search, not any underlying storage
|
||||
* format or encoding: it merely supplies two hooks for the encoding
|
||||
* so that tests can be exact. The [stretch] goal is for this test to be
|
||||
* so thorough in testing a new geo impl that if this
|
||||
* test passes, then all Lucene/Solr tests should also pass. Ie,
|
||||
* if there is some bug in a given geo impl that this
|
||||
* test fails to catch then this test needs to be improved! */
|
||||
public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
||||
|
||||
protected static final String FIELD_NAME = "point";
|
||||
|
@ -110,6 +121,195 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
return (off <= 180 ? off : 360-off) - 90;
|
||||
}
|
||||
|
||||
/** Valid values that should not cause exception */
|
||||
public void testIndexExtremeValues() {
|
||||
Document document = new Document();
|
||||
addPointToDoc("foo", document, 90.0, 180.0);
|
||||
addPointToDoc("foo", document, 90.0, -180.0);
|
||||
addPointToDoc("foo", document, -90.0, 180.0);
|
||||
addPointToDoc("foo", document, -90.0, -180.0);
|
||||
}
|
||||
|
||||
/** Invalid values */
|
||||
public void testIndexOutOfRangeValues() {
|
||||
Document document = new Document();
|
||||
IllegalArgumentException expected;
|
||||
|
||||
expected = expectThrows(IllegalArgumentException.class, () -> {
|
||||
addPointToDoc("foo", document, Math.nextUp(90.0), 50.0);
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("invalid latitude"));
|
||||
|
||||
expected = expectThrows(IllegalArgumentException.class, () -> {
|
||||
addPointToDoc("foo", document, Math.nextDown(-90.0), 50.0);
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("invalid latitude"));
|
||||
|
||||
expected = expectThrows(IllegalArgumentException.class, () -> {
|
||||
addPointToDoc("foo", document, 90.0, Math.nextUp(180.0));
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("invalid longitude"));
|
||||
|
||||
expected = expectThrows(IllegalArgumentException.class, () -> {
|
||||
addPointToDoc("foo", document, 90.0, Math.nextDown(-180.0));
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("invalid longitude"));
|
||||
}
|
||||
|
||||
/** NaN: illegal */
|
||||
public void testIndexNaNValues() {
|
||||
Document document = new Document();
|
||||
IllegalArgumentException expected;
|
||||
|
||||
expected = expectThrows(IllegalArgumentException.class, () -> {
|
||||
addPointToDoc("foo", document, Double.NaN, 50.0);
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("invalid latitude"));
|
||||
|
||||
expected = expectThrows(IllegalArgumentException.class, () -> {
|
||||
addPointToDoc("foo", document, 50.0, Double.NaN);
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("invalid longitude"));
|
||||
}
|
||||
|
||||
/** Inf: illegal */
|
||||
public void testIndexInfValues() {
|
||||
Document document = new Document();
|
||||
IllegalArgumentException expected;
|
||||
|
||||
expected = expectThrows(IllegalArgumentException.class, () -> {
|
||||
addPointToDoc("foo", document, Double.POSITIVE_INFINITY, 50.0);
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("invalid latitude"));
|
||||
|
||||
expected = expectThrows(IllegalArgumentException.class, () -> {
|
||||
addPointToDoc("foo", document, Double.NEGATIVE_INFINITY, 50.0);
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("invalid latitude"));
|
||||
|
||||
expected = expectThrows(IllegalArgumentException.class, () -> {
|
||||
addPointToDoc("foo", document, 50.0, Double.POSITIVE_INFINITY);
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("invalid longitude"));
|
||||
|
||||
expected = expectThrows(IllegalArgumentException.class, () -> {
|
||||
addPointToDoc("foo", document, 50.0, Double.NEGATIVE_INFINITY);
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("invalid longitude"));
|
||||
}
|
||||
|
||||
/** Add a single point and search for it in a box */
|
||||
// NOTE: we don't currently supply an exact search, only ranges, because of the lossiness...
|
||||
public void testBoxBasics() throws Exception {
|
||||
Directory dir = newDirectory();
|
||||
RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
|
||||
|
||||
// add a doc with a point
|
||||
Document document = new Document();
|
||||
addPointToDoc("field", document, 18.313694, -65.227444);
|
||||
writer.addDocument(document);
|
||||
|
||||
// search and verify we found our doc
|
||||
IndexReader reader = writer.getReader();
|
||||
IndexSearcher searcher = newSearcher(reader);
|
||||
assertEquals(1, searcher.count(newRectQuery("field", 18, 19, -66, -65)));
|
||||
|
||||
reader.close();
|
||||
writer.close();
|
||||
dir.close();
|
||||
}
|
||||
|
||||
|
||||
// box should not accept invalid lat/lon
|
||||
public void testBoxInvalidCoordinates() throws Exception {
|
||||
expectThrows(Exception.class, () -> {
|
||||
newRectQuery("field", -92.0, -91.0, 179.0, 181.0);
|
||||
});
|
||||
}
|
||||
|
||||
/** test we can search for a point */
|
||||
public void testDistanceBasics() throws Exception {
|
||||
Directory dir = newDirectory();
|
||||
RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
|
||||
|
||||
// add a doc with a location
|
||||
Document document = new Document();
|
||||
addPointToDoc("field", document, 18.313694, -65.227444);
|
||||
writer.addDocument(document);
|
||||
|
||||
// search within 50km and verify we found our doc
|
||||
IndexReader reader = writer.getReader();
|
||||
IndexSearcher searcher = newSearcher(reader);
|
||||
assertEquals(1, searcher.count(newDistanceQuery("field", 18, -65, 50_000)));
|
||||
|
||||
reader.close();
|
||||
writer.close();
|
||||
dir.close();
|
||||
}
|
||||
|
||||
/** distance query should not accept invalid lat/lon as origin */
|
||||
public void testDistanceIllegal() throws Exception {
|
||||
expectThrows(Exception.class, () -> {
|
||||
newDistanceQuery("field", 92.0, 181.0, 120000);
|
||||
});
|
||||
}
|
||||
/** negative distance queries are not allowed */
|
||||
public void testDistanceNegative() {
|
||||
IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
|
||||
newDistanceQuery("field", 18, 19, -1);
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("radiusMeters"));
|
||||
assertTrue(expected.getMessage().contains("invalid"));
|
||||
}
|
||||
|
||||
/** NaN distance queries are not allowed */
|
||||
public void testDistanceNaN() {
|
||||
IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
|
||||
newDistanceQuery("field", 18, 19, Double.NaN);
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("radiusMeters"));
|
||||
assertTrue(expected.getMessage().contains("invalid"));
|
||||
}
|
||||
|
||||
/** Inf distance queries are not allowed */
|
||||
public void testDistanceInf() {
|
||||
IllegalArgumentException expected;
|
||||
|
||||
expected = expectThrows(IllegalArgumentException.class, () -> {
|
||||
newDistanceQuery("field", 18, 19, Double.POSITIVE_INFINITY);
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("radiusMeters"));
|
||||
assertTrue(expected.getMessage().contains("invalid"));
|
||||
|
||||
expected = expectThrows(IllegalArgumentException.class, () -> {
|
||||
newDistanceQuery("field", 18, 19, Double.NEGATIVE_INFINITY);
|
||||
});
|
||||
assertTrue(expected.getMessage(), expected.getMessage().contains("radiusMeters"));
|
||||
assertTrue(expected.getMessage().contains("invalid"));
|
||||
}
|
||||
|
||||
/** test we can search for a polygon */
|
||||
public void testPolygonBasics() throws Exception {
|
||||
Directory dir = newDirectory();
|
||||
RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
|
||||
|
||||
// add a doc with a point
|
||||
Document document = new Document();
|
||||
addPointToDoc("field", document, 18.313694, -65.227444);
|
||||
writer.addDocument(document);
|
||||
|
||||
// search and verify we found our doc
|
||||
IndexReader reader = writer.getReader();
|
||||
IndexSearcher searcher = newSearcher(reader);
|
||||
assertEquals(1, searcher.count(newPolygonQuery("field",
|
||||
new double[] { 18, 18, 19, 19, 18 },
|
||||
new double[] { -66, -65, -65, -66, -66 })));
|
||||
|
||||
reader.close();
|
||||
writer.close();
|
||||
dir.close();
|
||||
}
|
||||
|
||||
// A particularly tricky adversary for BKD tree:
|
||||
public void testSamePointManyTimes() throws Exception {
|
||||
int numPoints = atLeast(1000);
|
||||
|
@ -275,7 +475,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
System.out.println("\nTEST: iter=" + iter + " rect=" + rect);
|
||||
}
|
||||
|
||||
Query query = newRectQuery(FIELD_NAME, rect);
|
||||
Query query = newRectQuery(FIELD_NAME, rect.minLat, rect.maxLat, rect.minLon, rect.maxLon);
|
||||
|
||||
final FixedBitSet hits = new FixedBitSet(r.maxDoc());
|
||||
s.search(query, new SimpleCollector() {
|
||||
|
@ -456,10 +656,6 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
return lon;
|
||||
}
|
||||
|
||||
protected double maxRadius(double latitude, double longitude) {
|
||||
return 50000000D; // bigger than earth, shouldnt matter
|
||||
}
|
||||
|
||||
protected GeoRect randomRect(boolean small, boolean canCrossDateLine) {
|
||||
double lat0 = randomLat(small);
|
||||
double lat1 = randomLat(small);
|
||||
|
@ -486,7 +682,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
|
||||
protected abstract void addPointToDoc(String field, Document doc, double lat, double lon);
|
||||
|
||||
protected abstract Query newRectQuery(String field, GeoRect bbox);
|
||||
protected abstract Query newRectQuery(String field, double minLat, double maxLat, double minLon, double maxLon);
|
||||
|
||||
protected abstract Query newDistanceQuery(String field, double centerLat, double centerLon, double radiusMeters);
|
||||
|
||||
|
@ -708,7 +904,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
// Rect: don't allow dateline crossing when testing small:
|
||||
final GeoRect rect = randomRect(small, small == false);
|
||||
|
||||
query = newRectQuery(FIELD_NAME, rect);
|
||||
query = newRectQuery(FIELD_NAME, rect.minLat, rect.maxLat, rect.minLon, rect.maxLon);
|
||||
|
||||
verifyHits = new VerifyHits() {
|
||||
@Override
|
||||
|
@ -875,7 +1071,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
}
|
||||
IndexReader r = w.getReader();
|
||||
IndexSearcher s = newSearcher(r, false);
|
||||
assertEquals(8, s.count(newRectQuery(FIELD_NAME, rect)));
|
||||
assertEquals(8, s.count(newRectQuery(FIELD_NAME, rect.minLat, rect.maxLat, rect.minLon, rect.maxLon)));
|
||||
r.close();
|
||||
w.close();
|
||||
dir.close();
|
||||
|
@ -936,7 +1132,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
for (int i = 0; i < numQueries; i++) {
|
||||
double lat = -90 + 180.0 * random().nextDouble();
|
||||
double lon = -180 + 360.0 * random().nextDouble();
|
||||
double radius = maxRadius(lat, lon) * random().nextDouble();
|
||||
double radius = 50000000D * random().nextDouble();
|
||||
|
||||
BitSet expected = new BitSet();
|
||||
for (int doc = 0; doc < reader.maxDoc(); doc++) {
|
||||
|
@ -977,10 +1173,10 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
|
||||
GeoRect rect = randomRect(false, true);
|
||||
|
||||
q1 = newRectQuery("field", rect);
|
||||
q2 = newRectQuery("field", rect);
|
||||
q1 = newRectQuery("field", rect.minLat, rect.maxLat, rect.minLon, rect.maxLon);
|
||||
q2 = newRectQuery("field", rect.minLat, rect.maxLat, rect.minLon, rect.maxLon);
|
||||
assertEquals(q1, q2);
|
||||
assertFalse(q1.equals(newRectQuery("field2", rect)));
|
||||
assertFalse(q1.equals(newRectQuery("field2", rect.minLat, rect.maxLat, rect.minLon, rect.maxLon)));
|
||||
|
||||
double lat = randomLat(false);
|
||||
double lon = randomLon(false);
|
||||
|
@ -1014,4 +1210,132 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
assertEquals(q1, q2);
|
||||
assertFalse(q1.equals(newPolygonQuery("field2", lats, lons)));
|
||||
}
|
||||
|
||||
/** return topdocs over a small set of points in field "point" */
|
||||
private TopDocs searchSmallSet(Query query, int size) throws Exception {
|
||||
// this is a simple systematic test, indexing these points
|
||||
double[][] pts = new double[][] {
|
||||
{ 32.763420, -96.774 },
|
||||
{ 32.7559529921407, -96.7759895324707 },
|
||||
{ 32.77866942010977, -96.77701950073242 },
|
||||
{ 32.7756745755423, -96.7706036567688 },
|
||||
{ 27.703618681345585, -139.73458170890808 },
|
||||
{ 32.94823588839368, -96.4538113027811 },
|
||||
{ 33.06047141970814, -96.65084838867188 },
|
||||
{ 32.778650, -96.7772 },
|
||||
{ -88.56029371730983, -177.23537676036358 },
|
||||
{ 33.541429799076354, -26.779373834241003 },
|
||||
{ 26.774024500421728, -77.35379276106497 },
|
||||
{ -90.0, -14.796283808944777 },
|
||||
{ 32.94823588839368, -178.8538113027811 },
|
||||
{ 32.94823588839368, 178.8538113027811 },
|
||||
{ 40.720611, -73.998776 },
|
||||
{ -44.5, -179.5 }
|
||||
};
|
||||
|
||||
Directory directory = newDirectory();
|
||||
|
||||
RandomIndexWriter writer = new RandomIndexWriter(random(), directory,
|
||||
newIndexWriterConfig(new MockAnalyzer(random()))
|
||||
.setMaxBufferedDocs(TestUtil.nextInt(random(), 100, 1000))
|
||||
.setMergePolicy(newLogMergePolicy()));
|
||||
|
||||
for (double p[] : pts) {
|
||||
Document doc = new Document();
|
||||
addPointToDoc("point", doc, p[0], p[1]);
|
||||
writer.addDocument(doc);
|
||||
}
|
||||
|
||||
// add explicit multi-valued docs
|
||||
for (int i=0; i<pts.length; i+=2) {
|
||||
Document doc = new Document();
|
||||
addPointToDoc("point", doc, pts[i][0], pts[i][1]);
|
||||
addPointToDoc("point", doc, pts[i+1][0], pts[i+1][1]);
|
||||
writer.addDocument(doc);
|
||||
}
|
||||
|
||||
// index random string documents
|
||||
for (int i=0; i<random().nextInt(10); ++i) {
|
||||
Document doc = new Document();
|
||||
doc.add(new StringField("string", Integer.toString(i), Field.Store.NO));
|
||||
writer.addDocument(doc);
|
||||
}
|
||||
|
||||
IndexReader reader = writer.getReader();
|
||||
writer.close();
|
||||
|
||||
IndexSearcher searcher = newSearcher(reader);
|
||||
TopDocs topDocs = searcher.search(query, size);
|
||||
reader.close();
|
||||
directory.close();
|
||||
return topDocs;
|
||||
}
|
||||
|
||||
public void testSmallSetRect() throws Exception {
|
||||
TopDocs td = searchSmallSet(newRectQuery("point", 32.778650, 32.778950, -96.7772, -96.77690000), 5);
|
||||
assertEquals(4, td.totalHits);
|
||||
}
|
||||
|
||||
public void testSmallSetDateline() throws Exception {
|
||||
TopDocs td = searchSmallSet(newRectQuery("point", -45.0, -44.0, 179.0, -179.0), 20);
|
||||
assertEquals(2, td.totalHits);
|
||||
}
|
||||
|
||||
public void testSmallSetMultiValued() throws Exception {
|
||||
TopDocs td = searchSmallSet(newRectQuery("point", 32.7559529921407, 32.7756745755423, -96.4538113027811, -96.7706036567688), 20);
|
||||
// 3 single valued docs + 2 multi-valued docs
|
||||
assertEquals(5, td.totalHits);
|
||||
}
|
||||
|
||||
public void testSmallSetWholeMap() throws Exception {
|
||||
TopDocs td = searchSmallSet(newRectQuery("point", GeoUtils.MIN_LAT_INCL, GeoUtils.MAX_LAT_INCL, GeoUtils.MIN_LON_INCL, GeoUtils.MAX_LON_INCL), 20);
|
||||
assertEquals(24, td.totalHits);
|
||||
}
|
||||
|
||||
public void testSmallSetPoly() throws Exception {
|
||||
TopDocs td = searchSmallSet(newPolygonQuery("point",
|
||||
new double[]{33.073130, 32.9942669, 32.938386, 33.0374494,
|
||||
33.1369762, 33.1162747, 33.073130, 33.073130},
|
||||
new double[]{-96.7682647, -96.8280029, -96.6288757, -96.4929199,
|
||||
-96.6041564, -96.7449188, -96.76826477, -96.7682647}),
|
||||
5);
|
||||
assertEquals(2, td.totalHits);
|
||||
}
|
||||
|
||||
public void testSmallSetPolyWholeMap() throws Exception {
|
||||
TopDocs td = searchSmallSet(newPolygonQuery("point",
|
||||
new double[] {GeoUtils.MIN_LAT_INCL, GeoUtils.MAX_LAT_INCL, GeoUtils.MAX_LAT_INCL, GeoUtils.MIN_LAT_INCL, GeoUtils.MIN_LAT_INCL},
|
||||
new double[] {GeoUtils.MIN_LON_INCL, GeoUtils.MIN_LON_INCL, GeoUtils.MAX_LON_INCL, GeoUtils.MAX_LON_INCL, GeoUtils.MIN_LON_INCL}),
|
||||
20);
|
||||
assertEquals("testWholeMap failed", 24, td.totalHits);
|
||||
}
|
||||
|
||||
public void testSmallSetDistance() throws Exception {
|
||||
TopDocs td = searchSmallSet(newDistanceQuery("point", 32.94823588839368, -96.4538113027811, 6000), 20);
|
||||
assertEquals(2, td.totalHits);
|
||||
}
|
||||
|
||||
public void testSmallSetTinyDistance() throws Exception {
|
||||
TopDocs td = searchSmallSet(newDistanceQuery("point", 40.720611, -73.998776, 1), 20);
|
||||
assertEquals(2, td.totalHits);
|
||||
}
|
||||
|
||||
/** see https://issues.apache.org/jira/browse/LUCENE-6905 */
|
||||
public void testSmallSetDistanceNotEmpty() throws Exception {
|
||||
TopDocs td = searchSmallSet(newDistanceQuery("point", -88.56029371730983, -177.23537676036358, 7757.999232959935), 20);
|
||||
assertEquals(2, td.totalHits);
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly large
|
||||
*/
|
||||
public void testSmallSetHugeDistance() throws Exception {
|
||||
TopDocs td = searchSmallSet(newDistanceQuery("point", 32.94823588839368, -96.4538113027811, 6000000), 20);
|
||||
assertEquals(16, td.totalHits);
|
||||
}
|
||||
|
||||
public void testSmallSetDistanceDateline() throws Exception {
|
||||
TopDocs td = searchSmallSet(newDistanceQuery("point", 32.94823588839368, -179.9538113027811, 120000), 20);
|
||||
assertEquals(3, td.totalHits);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -298,4 +298,27 @@ public class TestGeoUtils extends LuceneTestCase {
|
|||
assertFalse(rect.crossesDateline());
|
||||
}
|
||||
}
|
||||
|
||||
public void testPacManPolyQuery() throws Exception {
|
||||
// pacman
|
||||
double[] px = {0, 10, 10, 0, -8, -10, -8, 0, 10, 10, 0};
|
||||
double[] py = {0, 5, 9, 10, 9, 0, -9, -10, -9, -5, 0};
|
||||
|
||||
// shape bbox
|
||||
double xMinA = -10;
|
||||
double xMaxA = 10;
|
||||
double yMinA = -10;
|
||||
double yMaxA = 10;
|
||||
|
||||
// candidate crosses cell
|
||||
double xMin = 2;//-5;
|
||||
double xMax = 11;//0.000001;
|
||||
double yMin = -1;//0;
|
||||
double yMax = 1;//5;
|
||||
|
||||
// test cell crossing poly
|
||||
assertTrue(GeoRelationUtils.rectCrossesPolyApprox(yMin, yMax, xMin, yMax, py, px, yMinA, yMaxA, xMinA, xMaxA));
|
||||
assertFalse(GeoRelationUtils.rectCrossesPolyApprox(0, 5, -5, 0.000001, py, px, yMin, yMax, xMin, xMax));
|
||||
assertTrue(GeoRelationUtils.rectWithinPolyApprox(0, 5, -5, -2, py, px, yMin, yMax, xMin, xMax));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue