diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPoint.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPoint.java index 1b6a6d23db1..55c8062a31d 100644 --- a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPoint.java +++ b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPoint.java @@ -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 ",(new LatLonPoint("field", 18.313694, -65.227444)).toString()); @@ -60,79 +34,6 @@ public class TestLatLonPoint extends LuceneTestCase { // sort field assertEquals("", 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 @@ -183,30 +84,5 @@ public class TestLatLonPoint extends LuceneTestCase { assertEquals(latEnc, latEnc2, 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))); - } + } } diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPointDistanceQuery.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPointDistanceQuery.java deleted file mode 100644 index f015780be2c..00000000000 --- a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPointDistanceQuery.java +++ /dev/null @@ -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")); - } -} diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPointInPolygonQuery.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPointInPolygonQuery.java deleted file mode 100644 index de870277a33..00000000000 --- a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPointInPolygonQuery.java +++ /dev/null @@ -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(); - } -} diff --git a/lucene/sandbox/src/test/org/apache/lucene/search/TestLatLonPointQueries.java b/lucene/sandbox/src/test/org/apache/lucene/search/TestLatLonPointQueries.java index b1367768993..4f2c2d7a23c 100644 --- a/lucene/sandbox/src/test/org/apache/lucene/search/TestLatLonPointQueries.java +++ b/lucene/sandbox/src/test/org/apache/lucene/search/TestLatLonPointQueries.java @@ -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 diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/document/GeoPointField.java b/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/document/GeoPointField.java index 00db23a8d0f..a477fa90ae2 100644 --- a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/document/GeoPointField.java +++ b/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/document/GeoPointField.java @@ -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) { diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointDistanceQuery.java b/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointDistanceQuery.java index 2486852f87d..98bb2cab2c9 100644 --- a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointDistanceQuery.java +++ b/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointDistanceQuery.java @@ -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; diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointInBBoxQuery.java b/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointInBBoxQuery.java index f81f37ed929..e9486d37c62 100644 --- a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointInBBoxQuery.java +++ b/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointInBBoxQuery.java @@ -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; diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoDistanceUtils.java b/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoDistanceUtils.java deleted file mode 100644 index 1a412aca03e..00000000000 --- a/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoDistanceUtils.java +++ /dev/null @@ -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); - } -} diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestGeoPointField.java b/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestGeoPointField.java deleted file mode 100644 index 123769ee984..00000000000 --- a/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestGeoPointField.java +++ /dev/null @@ -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 { - 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()); - } -} diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestGeoPointQuery.java b/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestGeoPointQuery.java index 747cd1fdc2c..1a739a3e3fd 100644 --- a/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestGeoPointQuery.java +++ b/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestGeoPointQuery.java @@ -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 @@ -31,12 +29,6 @@ import org.apache.lucene.spatial.util.GeoRect; * @lucene.experimental */ 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) { @@ -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 diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestLegacyGeoPointField.java b/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestLegacyGeoPointField.java deleted file mode 100644 index beab5b97477..00000000000 --- a/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestLegacyGeoPointField.java +++ /dev/null @@ -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 { - 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); - } -} diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestLegacyGeoPointQuery.java b/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestLegacyGeoPointQuery.java index 73b30826cfc..c2f74f16deb 100644 --- a/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestLegacyGeoPointQuery.java +++ b/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestLegacyGeoPointQuery.java @@ -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) @@ -31,12 +29,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) { @@ -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 diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/util/BaseGeoPointTestCase.java b/lucene/spatial/src/test/org/apache/lucene/spatial/util/BaseGeoPointTestCase.java index 3943a92039f..4d25451c704 100644 --- a/lucene/spatial/src/test/org/apache/lucene/spatial/util/BaseGeoPointTestCase.java +++ b/lucene/spatial/src/test/org/apache/lucene/spatial/util/BaseGeoPointTestCase.java @@ -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"; @@ -109,6 +120,195 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase { double off = Math.abs((lat_deg + 90) % 360); 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 { @@ -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() { @@ -455,10 +655,6 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase { protected double quantizeLon(double lon) { 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); @@ -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