LUCENE-7137: consolidate many tests across Points and GeoPoint queries/fields

This commit is contained in:
Robert Muir 2016-03-24 12:18:40 -04:00
parent 5b3e07a98a
commit ff70c680a2
14 changed files with 426 additions and 892 deletions

View File

@ -16,37 +16,11 @@
*/ */
package org.apache.lucene.document; 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; import org.apache.lucene.util.LuceneTestCase;
/** Simple tests for {@link LatLonPoint} */ /** Simple tests for {@link LatLonPoint} */
public class TestLatLonPoint extends LuceneTestCase { 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 { public void testToString() throws Exception {
// looks crazy due to lossiness // looks crazy due to lossiness
assertEquals("LatLonPoint <field:18.313693958334625,-65.22744392976165>",(new LatLonPoint("field", 18.313694, -65.227444)).toString()); 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()); 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 { public void testEncodeDecode() throws Exception {
// just for testing quantization error // just for testing quantization error
final double ENCODING_TOLERANCE = 1e-7; final double ENCODING_TOLERANCE = 1e-7;
@ -184,29 +85,4 @@ public class TestLatLonPoint extends LuceneTestCase {
assertEquals(lonEnc, lonEnc2, 0.0); 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)));
}
} }

View File

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

View File

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

View File

@ -19,7 +19,6 @@ package org.apache.lucene.search;
import org.apache.lucene.document.Document; import org.apache.lucene.document.Document;
import org.apache.lucene.document.LatLonPoint; import org.apache.lucene.document.LatLonPoint;
import org.apache.lucene.spatial.util.BaseGeoPointTestCase; import org.apache.lucene.spatial.util.BaseGeoPointTestCase;
import org.apache.lucene.spatial.util.GeoRect;
public class TestLatLonPointQueries extends BaseGeoPointTestCase { public class TestLatLonPointQueries extends BaseGeoPointTestCase {
@ -29,8 +28,8 @@ public class TestLatLonPointQueries extends BaseGeoPointTestCase {
} }
@Override @Override
protected Query newRectQuery(String field, GeoRect rect) { protected Query newRectQuery(String field, double minLat, double maxLat, double minLon, double maxLon) {
return LatLonPoint.newBoxQuery(field, rect.minLat, rect.maxLat, rect.minLon, rect.maxLon); return LatLonPoint.newBoxQuery(field, minLat, maxLat, minLon, maxLon);
} }
@Override @Override

View File

@ -128,47 +128,47 @@ public final class GeoPointField extends Field {
/** Creates a stored or un-stored GeoPointField /** Creates a stored or un-stored GeoPointField
* @param name field name * @param name field name
* @param lat latitude double value [-90.0 : 90.0] * @param latitude latitude double value [-90.0 : 90.0]
* @param lon longitude double value [-180.0 : 180.0] * @param longitude longitude double value [-180.0 : 180.0]
* @param stored Store.YES if the content should also be stored * @param stored Store.YES if the content should also be stored
* @throws IllegalArgumentException if the field name is null. * @throws IllegalArgumentException if the field name is null.
*/ */
public GeoPointField(String name, double lat, double lon, Store stored) { public GeoPointField(String name, double latitude, double longitude, Store stored) {
this(name, lat, lon, getFieldType(stored)); this(name, latitude, longitude, getFieldType(stored));
} }
/** Creates a stored or un-stored GeoPointField using the specified {@link TermEncoding} method /** Creates a stored or un-stored GeoPointField using the specified {@link TermEncoding} method
* @param name field name * @param name field name
* @param lat latitude double value [-90.0 : 90.0] * @param latitude latitude double value [-90.0 : 90.0]
* @param lon longitude double value [-180.0 : 180.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 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 * @param stored Store.YES if the content should also be stored
* @throws IllegalArgumentException if the field name is null. * @throws IllegalArgumentException if the field name is null.
*/ */
@Deprecated @Deprecated
public GeoPointField(String name, double lat, double lon, TermEncoding termEncoding, Store stored) { public GeoPointField(String name, double latitude, double longitude, TermEncoding termEncoding, Store stored) {
this(name, lat, lon, getFieldType(termEncoding, stored)); this(name, latitude, longitude, getFieldType(termEncoding, stored));
} }
/** Expert: allows you to customize the {@link /** Expert: allows you to customize the {@link
* FieldType}. * FieldType}.
* @param name field name * @param name field name
* @param lat latitude double value [-90.0 : 90.0] * @param latitude latitude double value [-90.0 : 90.0]
* @param lon longitude double value [-180.0 : 180.0] * @param longitude longitude double value [-180.0 : 180.0]
* @param type customized field type: must have {@link FieldType#numericType()} * @param type customized field type: must have {@link FieldType#numericType()}
* of {@link org.apache.lucene.document.FieldType.LegacyNumericType#LONG}. * of {@link org.apache.lucene.document.FieldType.LegacyNumericType#LONG}.
* @throws IllegalArgumentException if the field name or type is null, or * @throws IllegalArgumentException if the field name or type is null, or
* if the field type does not have a LONG numericType() * 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); super(name, type);
if (GeoUtils.isValidLat(lat) == false) { if (GeoUtils.isValidLat(latitude) == false) {
throw new IllegalArgumentException("invalid lat=" + lat + " for field \"" + name + "\""); throw new IllegalArgumentException("invalid latitude=" + latitude + " for field \"" + name + "\"");
} }
if (GeoUtils.isValidLon(lon) == false) { if (GeoUtils.isValidLon(longitude) == false) {
throw new IllegalArgumentException("invalid lon=" + lon + " for field \"" + name + "\""); throw new IllegalArgumentException("invalid longitude=" + longitude + " for field \"" + name + "\"");
} }
// field must be indexed // field must be indexed
@ -190,7 +190,7 @@ public final class GeoPointField extends Field {
} }
// set field data // set field data
fieldsData = GeoEncodingUtils.mortonHash(lat, lon); fieldsData = GeoEncodingUtils.mortonHash(latitude, longitude);
} }
private static FieldType getFieldType(Store stored) { private static FieldType getFieldType(Store stored) {

View File

@ -21,7 +21,6 @@ import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Query; import org.apache.lucene.search.Query;
import org.apache.lucene.spatial.geopoint.document.GeoPointField.TermEncoding; 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.GeoRect;
import org.apache.lucene.spatial.util.GeoUtils; import org.apache.lucene.spatial.util.GeoUtils;
@ -49,12 +48,34 @@ public class GeoPointDistanceQuery extends GeoPointInBBoxQuery {
/** distance (in meters) from lat, lon center location */ /** distance (in meters) from lat, lon center location */
protected final double radiusMeters; 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 * Constructs a Query for all {@link org.apache.lucene.spatial.geopoint.document.GeoPointField} types within a
* distance (in meters) from a given point * distance (in meters) from a given point
**/ **/
public GeoPointDistanceQuery(final String field, final double centerLat, final double centerLon, final double radiusMeters) { 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 * {@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) { 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, private GeoPointDistanceQuery(final String field, final TermEncoding termEncoding, final GeoRect bbox,
final double centerLat, final double centerLon, final double radiusMeters) { final double centerLat, final double centerLon, final double radiusMeters) {
super(field, termEncoding, bbox.minLat, bbox.maxLat, bbox.minLon, bbox.maxLon); 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.centerLat = centerLat;
this.centerLon = centerLon; this.centerLon = centerLon;

View File

@ -71,6 +71,24 @@ public class GeoPointInBBoxQuery extends Query {
* defined bounding box. Accepts optional {@link org.apache.lucene.spatial.geopoint.document.GeoPointField.TermEncoding} parameter * 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) { 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.field = field;
this.minLat = minLat; this.minLat = minLat;
this.maxLat = maxLat; this.maxLat = maxLat;

View File

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

View File

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

View File

@ -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;
import org.apache.lucene.spatial.geopoint.document.GeoPointField.TermEncoding; import org.apache.lucene.spatial.geopoint.document.GeoPointField.TermEncoding;
import org.apache.lucene.spatial.util.BaseGeoPointTestCase; 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 * random testing for GeoPoint query logic
@ -32,12 +30,6 @@ import org.apache.lucene.spatial.util.GeoRect;
*/ */
public class TestGeoPointQuery extends BaseGeoPointTestCase { public class TestGeoPointQuery extends BaseGeoPointTestCase {
@Override
protected double maxRadius(double latitude, double longitude) {
// TODO: clean this up
return GeoDistanceUtils.maxRadialDistanceMeters(latitude, longitude);
}
@Override @Override
protected double quantizeLat(double lat) { protected double quantizeLat(double lat) {
return GeoEncodingUtils.mortonUnhashLat(GeoEncodingUtils.mortonHash(lat, 0)); return GeoEncodingUtils.mortonUnhashLat(GeoEncodingUtils.mortonHash(lat, 0));
@ -54,8 +46,8 @@ public class TestGeoPointQuery extends BaseGeoPointTestCase {
} }
@Override @Override
protected Query newRectQuery(String field, GeoRect rect) { protected Query newRectQuery(String field, double minLat, double maxLat, double minLon, double maxLon) {
return new GeoPointInBBoxQuery(field, TermEncoding.PREFIX, rect.minLat, rect.maxLat, rect.minLon, rect.maxLon); return new GeoPointInBBoxQuery(field, TermEncoding.PREFIX, minLat, maxLat, minLon, maxLon);
} }
@Override @Override

View File

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

View File

@ -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;
import org.apache.lucene.spatial.geopoint.document.GeoPointField.TermEncoding; import org.apache.lucene.spatial.geopoint.document.GeoPointField.TermEncoding;
import org.apache.lucene.spatial.util.BaseGeoPointTestCase; 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) * random testing for GeoPoint query logic (with deprecated numeric encoding)
@ -32,12 +30,6 @@ import org.apache.lucene.spatial.util.GeoRect;
@Deprecated @Deprecated
public class TestLegacyGeoPointQuery extends BaseGeoPointTestCase { public class TestLegacyGeoPointQuery extends BaseGeoPointTestCase {
@Override
protected double maxRadius(double latitude, double longitude) {
// TODO: clean this up
return GeoDistanceUtils.maxRadialDistanceMeters(latitude, longitude);
}
@Override @Override
protected double quantizeLat(double lat) { protected double quantizeLat(double lat) {
return GeoEncodingUtils.mortonUnhashLat(GeoEncodingUtils.mortonHash(lat, 0)); return GeoEncodingUtils.mortonUnhashLat(GeoEncodingUtils.mortonHash(lat, 0));
@ -54,8 +46,8 @@ public class TestLegacyGeoPointQuery extends BaseGeoPointTestCase {
} }
@Override @Override
protected Query newRectQuery(String field, GeoRect rect) { protected Query newRectQuery(String field, double minLat, double maxLat, double minLon, double maxLon) {
return new GeoPointInBBoxQuery(field, TermEncoding.NUMERIC, rect.minLat, rect.maxLat, rect.minLon, rect.maxLon); return new GeoPointInBBoxQuery(field, TermEncoding.NUMERIC, minLat, maxLat, minLon, maxLon);
} }
@Override @Override

View File

@ -29,6 +29,7 @@ import java.util.Set;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.lucene.analysis.MockAnalyzer;
import org.apache.lucene.codecs.FilterCodec; import org.apache.lucene.codecs.FilterCodec;
import org.apache.lucene.codecs.PointsFormat; import org.apache.lucene.codecs.PointsFormat;
import org.apache.lucene.codecs.PointsReader; 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.Field;
import org.apache.lucene.document.NumericDocValuesField; import org.apache.lucene.document.NumericDocValuesField;
import org.apache.lucene.document.StoredField; import org.apache.lucene.document.StoredField;
import org.apache.lucene.document.StringField;
import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter; 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.apache.lucene.util.bkd.BKDWriter;
import org.junit.BeforeClass; 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 { public abstract class BaseGeoPointTestCase extends LuceneTestCase {
protected static final String FIELD_NAME = "point"; protected static final String FIELD_NAME = "point";
@ -110,6 +121,195 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
return (off <= 180 ? off : 360-off) - 90; 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: // A particularly tricky adversary for BKD tree:
public void testSamePointManyTimes() throws Exception { public void testSamePointManyTimes() throws Exception {
int numPoints = atLeast(1000); int numPoints = atLeast(1000);
@ -275,7 +475,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
System.out.println("\nTEST: iter=" + iter + " rect=" + rect); 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()); final FixedBitSet hits = new FixedBitSet(r.maxDoc());
s.search(query, new SimpleCollector() { s.search(query, new SimpleCollector() {
@ -456,10 +656,6 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
return lon; return lon;
} }
protected double maxRadius(double latitude, double longitude) {
return 50000000D; // bigger than earth, shouldnt matter
}
protected GeoRect randomRect(boolean small, boolean canCrossDateLine) { protected GeoRect randomRect(boolean small, boolean canCrossDateLine) {
double lat0 = randomLat(small); double lat0 = randomLat(small);
double lat1 = 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 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); 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: // Rect: don't allow dateline crossing when testing small:
final GeoRect rect = randomRect(small, small == false); 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() { verifyHits = new VerifyHits() {
@Override @Override
@ -875,7 +1071,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
} }
IndexReader r = w.getReader(); IndexReader r = w.getReader();
IndexSearcher s = newSearcher(r, false); 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(); r.close();
w.close(); w.close();
dir.close(); dir.close();
@ -936,7 +1132,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
for (int i = 0; i < numQueries; i++) { for (int i = 0; i < numQueries; i++) {
double lat = -90 + 180.0 * random().nextDouble(); double lat = -90 + 180.0 * random().nextDouble();
double lon = -180 + 360.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(); BitSet expected = new BitSet();
for (int doc = 0; doc < reader.maxDoc(); doc++) { for (int doc = 0; doc < reader.maxDoc(); doc++) {
@ -977,10 +1173,10 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
GeoRect rect = randomRect(false, true); GeoRect rect = randomRect(false, true);
q1 = newRectQuery("field", rect); q1 = newRectQuery("field", rect.minLat, rect.maxLat, rect.minLon, rect.maxLon);
q2 = newRectQuery("field", rect); q2 = newRectQuery("field", rect.minLat, rect.maxLat, rect.minLon, rect.maxLon);
assertEquals(q1, q2); 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 lat = randomLat(false);
double lon = randomLon(false); double lon = randomLon(false);
@ -1014,4 +1210,132 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
assertEquals(q1, q2); assertEquals(q1, q2);
assertFalse(q1.equals(newPolygonQuery("field2", lats, lons))); 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);
}
} }

View File

@ -298,4 +298,27 @@ public class TestGeoUtils extends LuceneTestCase {
assertFalse(rect.crossesDateline()); 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));
}
} }